配列フィールドを持つテーブルタイプを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 2013

実行するとエラーが発生する ERROR: syntax error at or near "art"
インダゴ2013

1
個々の文字列リテラルは、引用符で囲む必要があります。
Andriy M

ave_book((179, 'the art of war', 'fiction', '{190,220}')::bookAndriyが言ったように、申し訳ありませんが、正しいのはです。
dezso 2013

@dezso:私への答えのようです。:)
Andriy M

回答:


7

この種のものは複雑になります。現在、いくつかの関連プロジェクトに取り組んでいます。基本的な調整は、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で最もクリーンです。それらを混合して一致させることができます。


配列コンストラクターを使用する場合の+1、エスケープは悪夢になる可能性があります
ジャックはtopanswers.xyzを

@クリス、最初の解決策はいいようで、私にとっては最高だと思います!
インダゴ2013

@JackDouglas、私たちが現在検討していることの1つは、LSMBの最初のルートに行くことです。これは、既に機能しているCSVフレームワークを使用できるためです。
Chris Travers

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

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


すべての挿入と更新を処理するようにその関数を作成しました。アプリケーションがテーブルを直接処理するのを防ぎますが、その関数を使用しても何の利点もないと思いますか?
インダゴ2013

設計上の決定に対する私の$ 0.02。関係する型の構造を調べて引数を作成するアプリケーションコードがあれば、大きな利点があると思います。これは非常に役立つ発見可能なAPIを提供します。ただし、それがなければ、テーブル構造が変更された場合、変更を行うための余分な場所があり、これは間違いなく好ましくありません。これは私の発展をあなたの進む方向に動かしている一人であると私は言います、そして概してそれは良い考えだと思います。しかし、危険は伴います。
Chris Travers
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.