web-dev-qa-db-fra.com

Comment passer un type de table avec un champ de tableau vers une fonction de PostgreSQL

j'ai une table appelée livre

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

et une fonction 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;

maintenant quand j'appelle la fonction

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

je reçois l'erreur

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

je ne comprends pas parce que je ne vois aucune erreur dans le format de la matrice, aucune aide?

8
indago

Ce genre de chose est compliqué. Je travaille actuellement sur certains projets connexes. Le Tweak de base est que PostgreSQL utilise un format qui utilise des guillemets doubles en interne dans la représentation des tuples pour représenter des valeurs littérales, de sorte que:

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

devrait marcher. En substance, une astuce soignée crée un CSV et entourer des identificateurs de tuple ou de matrice. Le gros problème est que vous devez faire face à l'échappement (doubler des citations à tous les niveaux selon les besoins). Voici donc exactement l'équivalent:

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

La deuxième approche consiste à utiliser un constructeur de ligne:

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

La première solution a l'avantage évident d'être capable de tirer parti des cadres de programmation existants pour la génération de CSV et de l'échappement. La seconde est la plus propre dans SQL. Ils peuvent être mélangés et assortis.

7
Chris Travers

Si vous vous demandez déjà sur la syntaxe correcte pour un type de ligne, posez Postgres. Cela devrait savoir:

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

Qui retournera une représentation de texte de votre ligne au format valide:

(179,"the art of war",fiction,"{190,220}")
  • Les valeurs des colonnes sont représentées sous la forme d'une liste non cochée et séparée par des virgules, enfermée dans des paréthèses.

  • Les citations doubles sont utilisées autour des valeurs, s'il peut y avoir une ambiguïté - y compris du texte avec de l'espace blanc. Pendant ce cas particulier, les doubles citations autour de "the art of war" sont facultatifs, les deux citations autour de "{190,220}" sont nécessaires pour un tableau.

Joignez la chaîne en guillemets simples, modifiez et testez:

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

Fonction examiné

Considérez ce que nous avons discuté sous la question correspondante et précédente:
[.____] problème avec type composite dans une fonction upsert

Un - bloc (BEGIN .. END;) n'est utile que si vous voulez attraper le EXCEPTION an INSERT pourrait soulever. Depuis un bloc avec une exception porte quelques frais généraux, il est logique d'avoir un bloc distinct qui pourrait ne jamais être entré:

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

Sinon, simplifier :

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

J'ai également simplifié votre déclaration INSERT. Il est prudent d'omettre la liste des colonnes à partir d'insérer dans les circonstances données.

6
Erwin Brandstetter

Pendant que je ne vois pas l'avantage réel de votre solution, je veux dire passer une ligne à la fonction au lieu de passer les valeurs individuelles comme dans

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

Quoi qu'il en soit, votre solution fonctionne également si vous appelez correctement la fonction:

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

C'est-à-dire que l'expression record n'a pas besoin de citer, tandis que les valeurs de texte et le tableau littéral.

3
dezso