So übergeben Sie einen Tabellentyp mit einem Arrayfeld an eine Funktion in postgresql

8

Ich habe einen Tisch namens Buch

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

und eine Funktion 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;

jetzt, wenn ich die Funktion aufrufe

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

Ich bekomme den Fehler

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

Ich verstehe nicht, weil ich keinen Fehler im Format des Arrays sehe, irgendeine Hilfe?

Indago
quelle
Ihre Funktion sieht für mich etwas überkompliziert aus, aber trotzdem. Ein Array-Literal muss in einfache Anführungszeichen eingeschlossen werden. Versuchen Sie daher Folgendes : ave_book((179,the art of war,fiction,'{190,220}')::book. Die konstruierte Zeile benötigt keine Anführungszeichen.
Dekso
Wenn ich renne, bekomme ich den Fehler ERROR: syntax error at or near "art"
Indago
1
Einzelne Zeichenfolgenliterale müssen weiterhin in Anführungszeichen gesetzt werden.
Andriy M
Entschuldigung, das Richtige ist ave_book((179, 'the art of war', 'fiction', '{190,220}')::book, genau wie Andriy gesagt hat.
Dekso
@dezso: Scheint mir eine Antwort zu sein. :)
Andriy M

Antworten:

7

So etwas wird kompliziert. Ich arbeite gerade an einigen verwandten Projekten. Die grundlegende Optimierung besteht darin, dass PostgreSQL ein Format verwendet, das intern doppelte Anführungszeichen in der Tupeldarstellung verwendet, um Literalwerte darzustellen.

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

sollte arbeiten. Im Wesentlichen besteht ein netter Trick darin, eine CSV zu erstellen und Tupel- oder Array-IDs einzuschließen. Das große Problem ist, dass Sie sich mit dem Entkommen auseinandersetzen müssen (Anführungszeichen auf jeder Ebene nach Bedarf verdoppeln). Das Folgende ist also genau gleichbedeutend:

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

Der zweite Ansatz besteht darin, einen Zeilenkonstruktor zu verwenden:

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

Die erste Lösung hat den offensichtlichen Vorteil, dass vorhandene Programmierframeworks für die CSV-Generierung und das Entkommen genutzt werden können. Der zweite ist in SQL am saubersten. Sie können gemischt und angepasst werden.

Chris Travers
quelle
+1 für die Verwendung eines Array-Konstruktors, Flucht kann ein Albtraum sein
Jack sagt, versuchen Sie topanswers.xyz
@ Chris, die erste Lösung scheint gut zu sein und für mich ist es die beste!
Indago
@JackDouglas, eine Sache, die wir tun wollen, ist, den ersten Weg in LSMB zu gehen, speziell weil wir CSV-Frameworks verwenden können, die bereits funktionieren.
Chris Travers
6

Wenn Sie sich jemals über die richtige Syntax für einen Zeilentyp Gedanken machen, fragen Sie Postgres. Es sollte wissen:

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

Dadurch wird eine Textdarstellung Ihrer Zeile im gültigen Format zurückgegeben:

(179,"the art of war",fiction,"{190,220}")
  • Spaltenwerte werden als nicht in Anführungszeichen stehende, durch Kommas getrennte Liste dargestellt, die in Parethesen eingeschlossen ist.

  • Doppelte Anführungszeichen werden um Werte verwendet, wenn Mehrdeutigkeiten auftreten können - einschließlich Text mit Leerzeichen. Während in diesem speziellen Fall die doppelten Anführungszeichen "the art of war"optional sind, sind die doppelten Anführungszeichen "{190,220}"für ein Array erforderlich.

Schließen Sie die Zeichenfolge in einfache Anführungszeichen ein, ändern und testen Sie:

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

Funktion überprüft

Überlegen Sie, was wir unter der entsprechenden vorhergehenden Frage besprochen haben:
Problem mit dem zusammengesetzten Typ in einer UPSERT-Funktion

Ein separater Block ( BEGIN .. END;) ist nur dann sinnvoll , wenn Sie die fangen wollen EXCEPTIONeine INSERTMacht erhöhen. Da ein Block mit Ausnahme einen gewissen Overhead mit sich bringt, ist es sinnvoll, einen separaten Block zu haben, der möglicherweise nie eingegeben wird:

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

Sonst vereinfachen :

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

Ich habe auch Ihre INSERTAussage vereinfacht . Unter den gegebenen Umständen ist es sicher, die Spaltenliste in INSERT wegzulassen.

Erwin Brandstetter
quelle
3

Obwohl ich den wirklichen Vorteil Ihrer Lösung nicht sehe, meine ich, eine Zeile an die Funktion zu übergeben, anstatt die einzelnen Werte wie in zu übergeben

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

Auf jeden Fall funktioniert Ihre Lösung auch, wenn Sie die Funktion korrekt aufrufen:

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

Das heißt, der Datensatzausdruck muss nicht in Anführungszeichen gesetzt werden, während dies bei den Textwerten und dem Array-Literal der Fall ist.

dezso
quelle
Ich habe diese Funktion entwickelt, um alle Einfügungen und Aktualisierungen zu handhaben, die die Anwendung vor dem direkten Umgang mit den Tabellen schützen. Glauben Sie, dass diese Funktion keinen Vorteil bringt?
Indago
Meine $ 0,02 für die Designentscheidung. Ich denke, es hat einen großen Vorteil, vorausgesetzt, Sie haben Anwendungscode, um die Struktur des betreffenden Typs nachzuschlagen und das Argument für Sie zu konstruieren. Dies gibt Ihnen eine erkennbare API, die sehr hilfreich ist. Ohne dies bedeutet dies jedoch, dass Sie bei Änderungen Ihrer Tabellenstruktur zusätzliche Stellen haben, an denen Sie Änderungen vornehmen können, die definitiv nicht gut sind. Ich sage, dies ist einer, der meine Entwicklung in die Richtung bewegt, in die Sie gehen, und im Großen und Ganzen halte ich es für eine gute Idee. Es birgt jedoch Gefahren.
Chris Travers