Come passare un tipo di tabella con un campo array a una funzione in postgresql


8

ho un tavolo chiamato libro

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

e una funzione 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;

ora quando chiamo la funzione

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

ottengo l'errore

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

non capisco perché non vedo alcun errore nel formato dell'array, qualche aiuto?


La tua funzione sembra un po 'troppo complicata per me, ma sempre. Un letterale array deve essere racchiuso tra virgolette singole, in modo da provare quanto segue: ave_book((179,the art of war,fiction,'{190,220}')::book. La riga costruita non ha bisogno delle virgolette.
dezso,

quando corro, ricevo l'errore ERROR: syntax error at or near "art"
indago,

1
I singoli letterali di stringa devono ancora essere racchiusi tra virgolette.
Andriy M,

Mi dispiace, quello corretto è ave_book((179, 'the art of war', 'fiction', '{190,220}')::book, proprio come ha detto Andriy.
dezso

@dezso: mi sembra una risposta. :)
Andriy M,

Risposte:


7

Questo genere di cose diventa complicato. Sto lavorando ad alcuni progetti correlati in questo momento. Il tweak di base è che PostgreSQL utilizza un formato che utilizza internamente doppie virgolette nella rappresentazione tupla per rappresentare valori letterali, quindi:

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

dovrebbe funzionare. In sostanza, un trucco accurato sta creando un csv e racchiudendolo in identificatori di tuple o array. Il grosso problema è che devi affrontare la fuga (raddoppiando le virgolette ad ogni livello, se necessario). Quindi il seguente è esattamente equivalente:

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

Il secondo approccio consiste nell'utilizzare un costruttore di righe:

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

La prima soluzione ha l'ovvio vantaggio di poter sfruttare i quadri di programmazione esistenti per la generazione e la fuga di CSV. Il secondo è il più pulito in SQL. Possono essere mescolati e abbinati.


+1 per l'utilizzo di un costruttore di array, la fuga può essere un incubo
Jack dice di provare topanswers.xyz il

@ Chris, la prima soluzione sembra buona e per me penso sia la migliore!
indago,

@JackDouglas, una cosa che stiamo cercando di fare è seguire la prima strada in LSMB proprio perché possiamo usare framework CSV che stanno già funzionando.
Chris Travers,

6

Se ti chiedi mai la sintassi corretta per un tipo di riga, chiedi a Postgres. Dovrebbe sapere:

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

Che restituirà una rappresentazione testuale della tua riga in formato valido:

(179,"the art of war",fiction,"{190,220}")
  • I valori delle colonne sono rappresentati come un elenco non quotato, separato da virgole, racchiuso tra parentesi.

  • Le virgolette doppie sono usate attorno ai valori, se può esserci ambiguità, incluso il testo con spazi bianchi. Mentre in questo caso particolare le doppie virgolette "the art of war"sono facoltative, le doppie virgolette "{190,220}"sono necessarie per un array.

Racchiudere la stringa tra virgolette singole, modificare e testare:

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

Funzione rivista

Considera ciò di cui abbiamo discusso nella relativa domanda precedente:
Problema con il tipo composito in una funzione UPSERT

Un blocco separato ( BEGIN .. END;) è utile solo se si desidera catturare EXCEPTIONun INSERTpotrebbe sollevare. Poiché un blocco con eccezione comporta un certo sovraccarico, ha senso avere un blocco separato che potrebbe non essere mai inserito:

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

Altrimenti, semplifica :

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

Ho anche semplificato la tua INSERTaffermazione. È sicuro omettere l'elenco di colonne da INSERT nelle circostanze indicate.


3

Anche se non vedo il vero vantaggio della tua soluzione, intendo passare una riga alla funzione invece di passare i singoli valori come in

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

Comunque, la tua soluzione funziona anche se chiami correttamente la funzione:

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

Cioè, l'espressione record non ha bisogno di virgolette, mentre i valori di testo e l'array letterale lo fanno.


Ho fatto quella funzione per gestire tutti gli inserti e gli aggiornamenti proteggendo l'applicazione dal gestire direttamente le tabelle, pensi che avere quella funzione non aggiunga alcun vantaggio?
indago,

I miei $ 0,02 sulla decisione di progettazione. Penso che abbia un grande vantaggio a condizione che tu abbia il codice dell'applicazione per cercare la struttura del tipo coinvolto e costruire l'argomento per te. Questo ti dà un'API rilevabile che è molto utile. Senza questo, tuttavia, significa che se la struttura della tabella cambia, hai posti aggiuntivi per apportare una modifica che sicuramente non va bene. Dico che questo è uno che sta spostando il mio sviluppo nella direzione in cui stai andando, e nel complesso penso che sia una buona idea. Tuttavia ha dei pericoli.
Chris Travers,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.