web-dev-qa-db-ja.com

配列フィールドを持つテーブルタイプをpostgresqlの関数に渡す方法

本というテーブルがあります

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

配列の形式にエラーが表示されないため、理解できません。ヘルプはありますか?

8
indago

この種のものは複雑になります。現在、いくつかの関連プロジェクトに取り組んでいます。基本的なTweakは、PostgreSQLがタプル表現で内部的に二重引用符を使用してリテラル値を表す形式を使用しているためです。

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

うまくいくはずです。本質的には、巧妙なトリックはcsvを作成し、タプルまたは配列識別子で囲むことです。大きな問題は、エスケープに対処する必要があることです(必要に応じて、すべてのレベルで引用符を2倍にします)。したがって、以下はまったく同じです。

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

2番目のアプローチは、行コンストラクターを使用することです。

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

最初のソリューションには、CSVの生成とエスケープのために既存のプログラミングフレームワークを利用できるという明らかな利点があります。 2番目はSQLで最もクリーンです。それらを混合して一致させることができます。

7
Chris Travers

行型の正しい構文について疑問がある場合は、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

機能の見直し

関連する前述の質問の下で私たちが議論したことを考慮してください:
PSERT関数の複合型の問題

別の blockBEGIN .. 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

それ以外の場合、simplify

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から省略しても安全です。

6

私はあなたのソリューションの本当の利点を見ていませんが、次のように個々の値を渡すのではなく、関数に行を渡すことを意味します

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);

つまり、レコード式は引用符で囲む必要はありませんが、テキスト値と配列リテラルは引用します。

3
dezso