Como passar um tipo de tabela com um campo de matriz para uma função no postgresql

8

eu tenho uma mesa chamada livro

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

e uma função 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;

agora quando eu chamo a função

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

eu recebo o erro

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

Eu não entendo porque não vejo nenhum erro no formato da matriz, alguma ajuda?

indago
fonte
Sua função parece um pouco complicada para mim, mas de qualquer maneira. Um literal matriz deve ser delimitada por aspas simples, por isso, tente o seguinte: ave_book((179,the art of war,fiction,'{190,220}')::book. A linha construída não precisa de aspas.
dezso 27/02
quando eu corro que eu recebo o erro # ERROR: syntax error at or near "art"
214 indago
11
Literais de sequência individuais ainda devem estar entre aspas.
precisa
Desculpe, o correto é ave_book((179, 'the art of war', 'fiction', '{190,220}')::book, como Andriy disse.
Dezso
@dezso: Parece uma resposta para mim. :)
Andriy M

Respostas:

7

Esse tipo de coisa fica complicado. Estou trabalhando em alguns projetos relacionados no momento. O ajuste básico é que o PostgreSQL usa um formato que usa aspas duplas internamente na representação de tupla para representar valores literais, portanto:

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

Deveria trabalhar. Em essência, um truque interessante é criar um csv e incluir identificadores de tupla ou matriz. O grande problema é que você precisa lidar com a fuga (duplicando aspas em todos os níveis, conforme necessário). Portanto, o seguinte é exatamente equivalente:

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

A segunda abordagem é usar um construtor de linha:

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

A primeira solução tem a vantagem óbvia de poder tirar proveito das estruturas de programação existentes para geração e escape de CSV. O segundo é o mais limpo no SQL. Eles podem ser misturados e combinados.

Chris Travers
fonte
+1 para usar um construtor de matriz, escapando pode ser um pesadelo
Jack diz tentar topanswers.xyz
@ Chris, a primeira solução parece boa e para mim eu acho que é a melhor!
Indago
@JackDouglas, uma coisa que estamos procurando fazer é seguir a primeira rota no LSMB especificamente porque podemos usar estruturas CSV que já estão funcionando.
27413 Chris Travers
6

Se você já se perguntou sobre a sintaxe correta para um tipo de linha, pergunte ao Postgres. Deve saber:

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

O que retornará uma representação de texto da sua linha em formato válido:

(179,"the art of war",fiction,"{190,220}")
  • Os valores das colunas são representados como uma lista sem aspas, separada por vírgulas, entre parênteses.

  • Aspas duplas são usadas em torno de valores, se houver ambiguidade - incluindo texto com espaço em branco. Enquanto nesse caso específico as aspas duplas "the art of war"são opcionais, as aspas duplas "{190,220}"são necessárias para uma matriz.

Coloque a sequência entre aspas simples, modifique e teste:

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

Função revisada

Considere o que discutimos na pergunta anterior relacionada:
Problema com o tipo composto em uma função UPSERT

Um separado bloco ( BEGIN .. END;) só é útil se você quiser pegar a EXCEPTIONum INSERTaumento de força. Como um bloco com exceção carrega alguma sobrecarga, faz sentido ter um bloco separado que talvez nunca seja inserido:

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

Senão, simplifique :

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

Eu também simplifiquei sua INSERTdeclaração. É seguro omitir a lista de colunas do INSERT sob as circunstâncias especificadas.

Erwin Brandstetter
fonte
3

Embora eu não veja a real vantagem da sua solução, quero dizer passar uma linha para a função em vez de passar os valores individuais como em

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

De qualquer forma, sua solução também funcionará se você chamar a função corretamente:

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

Ou seja, a expressão de registro não precisa ser citada, enquanto os valores de texto e a matriz literal precisam.

dezso
fonte
Eu criei essa função para lidar com todas as inserções e atualizações que impedem o aplicativo de lidar diretamente com as tabelas. Você acha que ter essa função não traz nenhuma vantagem?
indago 27/02
Meus US $ 0,02 na decisão de design. Eu acho que tem uma grande vantagem, desde que você tenha um código de aplicativo para procurar a estrutura do tipo envolvido e construir o argumento para você. Isso fornece uma API detectável, que é muito útil. Sem isso, no entanto, isso significa que, se a estrutura da sua tabela mudar, você terá mais lugares para fazer uma alteração que definitivamente não é boa. Eu digo que este é aquele que está movendo meu desenvolvimento na direção que você está seguindo, e no geral acho que é uma boa ideia. Mas tem perigos.
Chris Travers