如何将带有数组字段的表类型传递给Postgresql中的函数


8

我有一张桌子叫书

CREATE TABLE book
(
  id smallint NOT NULL DEFAULT 0,       
  bname text,       
  btype text,
  bprices numeric(11,2)[],
  CONSTRAINT key PRIMARY KEY (id )
)

和一个功能save_book

CREATE OR REPLACE FUNCTION save_book(thebook book)
  RETURNS text AS
$BODY$
DECLARE 
myoutput text :='Nothing has occured';
BEGIN

    update book set 
    bname=thebook.bname,
    btype=thebook.btype,bprices=thebook.bprices  WHERE id=thebook.id;

    IF FOUND THEN
        myoutput:= 'Record with PK[' || thebook.id || '] successfully updated';
        RETURN myoutput;
    END IF;

    BEGIN
        INSERT INTO book values(thebook.id,thebook.bname,thebook.btype,
        thebook.bprices);
        myoutput:= 'Record successfully added';           
    END;
 RETURN myoutput;

    END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

现在当我调用函数

SELECT save_book('(179,the art of war,fiction,{190,220})'::book);

我得到了错误

ERROR: malformed array literal: "{190"
SQL state: 22P02
Character: 18

我不明白,因为我没有看到阵列格式的任何错误,没有任何帮助吗?


对于我来说,您的功能看起来有些复杂,但是有点麻烦。数组文字必须用单引号引起来,因此请尝试以下操作:ave_book((179,the art of war,fiction,'{190,220}')::book。构造的行不需要引号。
dezso

当我运行时,我得到了错误 ERROR: syntax error at or near "art"
indago

1
各个字符串文字仍必须用引号引起来。
Andriy M

抱歉,正确的ave_book((179, 'the art of war', 'fiction', '{190,220}')::book答案是,就像安德里(Andriy)所说的那样。
dezso

@dezso:似乎是我的答案。:)
Andriy M

Answers:


7

这种事情变得复杂。我现在正在从事一些相关项目。基本的调整是PostgreSQL使用一种格式,该格式在内部以元组表示形式使用双引号来表示文字值,因此:

SELECT save_book('(179,the art of war,fiction,"{190,220}")'::book);

应该管用。本质上,一个巧妙的技巧是创建一个csv并将其包含在元组或数组标识符中。最大的问题是您必须处理转义(根据需要在每个级别加倍引用)。因此,以下内容完全相同:

SELECT save_book('(179,"the art of war","fiction","{""190"",""220""}")'::book);

第二种方法是使用行构造器:

SELECT save_book(row(179,'the art of war','fiction', array[190,220])::book);

第一个解决方案的明显优势是能够利用现有的编程框架进行CSV生成和转义。第二个在SQL中是最干净的。它们可以混合和匹配。


对于使用数组构造函数的+1,转义可能是一场噩梦
杰克说请尝试topanswers.xyz 2013年

@Chris,第一个解决方案似乎很好,对我来说,我认为这是最好的!
indago

@JackDouglas,我们正在考虑做的一件事是走LSMB中的第一条路线,特别是因为我们可以使用已经工作的CSV框架。
克里斯·特拉弗斯

6

如果您想知道行类型的正确语法,请询问Postgres。它应该知道:

SELECT b FROM book b LIMIT 1;  -- or: WHERE id = 179;

它将以有效格式返回行的文本表示形式:

(179,"the art of war",fiction,"{190,220}")
  • 列的值表示为用括号括起来的不带引号的逗号分隔列表。

  • 如果可能存在歧义,请在值周围使用双引号-包括带有空格的文本。在这种特殊情况下,双引号"the art of war"是可选的,而双引号"{190,220}"对于数组是必需的。

将字符串括在单引号中,进行修改和测试:

SELECT '(333,the art of war,fiction,"{191,220,235}")'::book

功能审查

考虑我们在前面的相关问题下讨论的内容:UPSERT函数中的复合类型问题

单独的BEGIN .. END;)仅在您想抓住EXCEPTION一个INSERT可能引起的筹码时才有用。由于具有异常块会带来一些开销,因此有意义的是有一个单独的块可能永远不会输入:

CREATE OR REPLACE FUNCTION save_book(thebook book)
  RETURNS text AS
$BODY$
BEGIN
   UPDATE book
   SET    bname =   thebook.bname
         ,btype =   thebook.btype
         ,bprices = thebook.bprices
   WHERE  id = thebook.id;

   IF FOUND THEN
      RETURN format('Record with PK[%s] successfully updated', thebook.id);
   END IF;

   BEGIN
      INSERT INTO book SELECT (thebook).*;
      RETURN format('Record with PK[%s] successfully inserted', thebook.id);

   EXCEPTION WHEN unique_violation THEN
      UPDATE book
      SET    bname =   thebook.bname
            ,btype =   thebook.btype
            ,bprices = thebook.bprices
      WHERE  id = thebook.id;
   END;

   RETURN format('Record with PK[%s] successfully updated', thebook.id);
END
$BODY$  LANGUAGE plpgsql

否则,简化

CREATE OR REPLACE FUNCTION save_book(thebook book)
  RETURNS text AS
$BODY$
BEGIN
   UPDATE book
   SET    bname =   thebook.bname
         ,btype =   thebook.btype
         ,bprices = thebook.bprices
   WHERE  id = thebook.id;

   IF FOUND THEN
      RETURN format('Record with PK[%s] successfully updated', thebook.id);
   END IF;

   INSERT INTO book SELECT (thebook).*;
   RETURN format('Record with PK[%s] successfully inserted', thebook.id);
END
$BODY$  LANGUAGE plpgsql

我还简化了您的INSERT发言。在给定的情况下,可以安全地从INSERT中删除列列表。


3

虽然我没有看到解决方案的真正优势,但我的意思是将行传递给函数,而不是像下面那样传递单个值

CREATE OR REPLACE FUNCTION save_book2(
      integer
    , text
    , text
    , integer[]
)
RETURNS text AS
...

无论如何,如果正确调用该函数,您的解决方案也可以正常工作:

SELECT ave_book((179, 'the art of war', 'fiction', '{190,220}')::book);

也就是说,记录表达式不需要引用,而文本值和数组文字则需要引用。


我使用该函数来处理所有插入和更新操作,从而使应用程序无法直接处理表,您认为拥有该功能不会增加任何优势吗?
indago

我的0.02美元的设计决策费用。我认为它具有很大的优势,只要您有应用程序代码来查找所涉及类型的结构并为您构造参数即可。这为您提供了一个可发现的API,非常有用。但是,如果没有此功能,则意味着如果表结构发生更改,您将有额外的位置进行更改,这绝对是不好的。我说,这是一个使我的发展朝着您的方向发展的人,总的来说,我认为这是一个好主意。它确实有危险。
克里斯·特拉弗斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.