Comment passer un type de table avec un champ de tableau à une fonction dans postgresql

8

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

j'ai l'erreur

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

je ne comprends pas car je ne vois aucune erreur dans le format du tableau, une aide?

indago
la source
Votre fonction me semble un peu trop compliquée, mais toujours. Un littéral de tableau doit être entouré de guillemets simples, alors essayez ce qui suit: ave_book((179,the art of war,fiction,'{190,220}')::book. La ligne construite n'a pas besoin des guillemets.
dezso
quand je cours que j'obtiens l'erreur ERROR: syntax error at or near "art"
indago
1
Les littéraux de chaîne individuels doivent toujours être placés entre guillemets.
Andriy M
Désolé, la bonne est ave_book((179, 'the art of war', 'fiction', '{190,220}')::book, comme l'a dit Andriy.
dezso
@dezso: Semble une réponse pour moi. :)
Andriy M

Réponses:

7

Ce genre de chose se complique. Je travaille actuellement sur des projets connexes. Le tweak de base est que PostgreSQL utilise un format qui utilise des guillemets internes en représentation de tuple pour représenter les valeurs littérales, donc:

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

devrait marcher. En substance, une astuce consiste à créer un csv et à l'enfermer dans des identificateurs de tuple ou de tableau. Le gros problème est que vous devez gérer l'évasion (doubler les devis à chaque niveau selon les besoins). Donc, ce qui suit est exactement équivalent:

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

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

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

La première solution présente l'avantage évident de pouvoir tirer parti des cadres de programmation existants pour la génération et l'échappement CSV. Le second est le plus propre en SQL. Ils peuvent être mélangés et assortis.

Chris Travers
la source
+1 pour l'utilisation d'un constructeur de tableau, s'échapper peut être un cauchemar
Jack dit d'essayer topanswers.xyz
@Chris, La première solution semble bonne et pour moi je pense que c'est la meilleure!
indago
@JackDouglas, une chose que nous envisageons de faire est de suivre la première voie dans LSMB spécifiquement parce que nous pouvons utiliser des frameworks CSV qui fonctionnent déjà.
Chris Travers
6

Si vous vous posez des questions sur la syntaxe correcte pour un type de ligne, demandez à Postgres. Il faut savoir:

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

Qui renverra une représentation textuelle de votre ligne dans un 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 citée, séparée par des virgules, entourée de paréthèses.

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

Mettez la chaîne entre guillemets simples, modifiez et testez:

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

Fonction révisée

Considérez ce que nous avons discuté sous la question précédente connexe:
Problème avec le type composite dans une fonction UPSERT

Un bloc séparé ( BEGIN .. END;) n'est utile que si vous voulez attraper l' EXCEPTIONan INSERTpourrait augmenter. Puisqu'un bloc avec exception comporte une surcharge, il est logique d'avoir un bloc séparé 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, simplifiez :

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 INSERTdéclaration. Il est sûr d'omettre la liste des colonnes de INSERT dans les circonstances données.

Erwin Brandstetter
la source
3

Bien que je ne vois pas le véritable avantage 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);

Autrement dit, l'expression d'enregistrement n'a pas besoin de guillemet, contrairement aux valeurs de texte et au littéral de tableau.

dezso
la source
J'ai fait cette fonction pour gérer toutes les insertions et mises à jour protégeant l'application de traiter directement avec les tables, pensez-vous que cette fonction n'ajoute aucun avantage?
indago
Mon 0,02 $ sur la décision de conception. Je pense que cela a un grand avantage à condition que vous ayez du code d'application pour rechercher la structure du type impliqué et construire l'argument pour vous. Cela vous donne une API détectable qui est très utile. Sans cela, cependant, cela signifie que si la structure de votre table change, vous avez des emplacements supplémentaires pour effectuer un changement qui n'est certainement pas bon. Je dis que c'est celui qui fait avancer mon développement dans la direction que vous allez, et dans l'ensemble, je pense que c'est une bonne idée. Il a cependant des dangers.
Chris Travers