PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

PostgreSQL:ERRO:42601:uma lista de definição de coluna é necessária para funções que retornam registro

Retornar colunas selecionadas

CREATE OR REPLACE FUNCTION get_user_by_username(_username text
                                              , _online bool DEFAULT false)
  RETURNS TABLE (
    user_id int
  , user_name varchar
  , last_activity timestamptz
  )
  LANGUAGE plpgsql AS
$func$
BEGIN
   IF _online THEN
      RETURN QUERY
      UPDATE users u 
      SET    last_activity = current_timestamp  -- ts with time zone
      WHERE  u.user_name = _username
      RETURNING u.user_id
              , u.user_name
              , u.last_activity;
   ELSE
      RETURN QUERY
      SELECT u.user_id
           , u.user_name
           , u.last_activity
      FROM   users u
      WHERE  u.user_name = _username;
   END IF;
END
$func$;

Ligar:
SELECT * FROM get_user_by_username('myuser', true);

Você tinha DECLARE result record; mas não usei a variável. Eu deletei o cruft.

Você pode retornar o registro diretamente do UPDATE , que é muito mais rápido do que chamar um SELECT adicional demonstração. Use RETURN QUERY e UPDATE com um RETURNING cláusula.

Se o usuário não estiver _online , padrão para um simples SELECT . Este também é o padrão (seguro) se o segundo parâmetro for omitido - o que só é possível depois de fornecer esse padrão com DEFAULT false na definição da função.

Se você não qualificar os nomes das colunas (tablename.columnname ) em consultas dentro da função, tenha cuidado com conflitos de nomenclatura entre nomes de colunas e parâmetros nomeados, que são visíveis (a maioria) em todos os lugares dentro de uma função.
Você também pode evitar esses conflitos usando referências posicionais ($n ) para parâmetros. Ou use um prefixo que você nunca use para nomes de colunas:como um sublinhado (_username ).

Se users.username é definido como único na sua tabela, então LIMIT 1 na segunda consulta é apenas grosseiro. Se for não , então o UPDATE pode atualizar várias linhas, o que provavelmente está errado . Presumo um username exclusivo e reduza o ruído.

Defina o tipo de retorno da função (como @ertx demonstrado) ou você precisa fornecer uma lista de definição de coluna com cada chamada de função, o que é estranho.

Criar um tipo para esse propósito (como @ertx proposto) é uma abordagem válida, mas provavelmente um exagero para uma única função. Esse era o caminho a seguir nas versões antigas do Postgres antes de termos RETURNS TABLE para esse fim - como demonstrado acima.

Você não precisa de um loop para esta função simples.

Cada função precisa de uma declaração de linguagem. LANGUAGE plpgsql nesse caso.

Eu uso timestamptz (timestamp with time zone ) em vez de timestamp (timestamp without time zone ), que é o padrão sensato. Ver:
  • Ignorando completamente os fusos horários no Rails e no PostgreSQL

Retorna (conjunto de) linhas inteiras


Para retornar todas as colunas da tabela existente users , existe uma maneira mais simples. O Postgres define automaticamente um tipo composto de mesmo nome para cada tabela . Basta usar RETURNS SETOF users para simplificar bastante a consulta:
CREATE OR REPLACE FUNCTION get_user_by_username(_username text
                                              , _online bool DEFAULT false)
  RETURNS SETOF users
  LANGUAGE plpgsql AS
$func$
BEGIN
   IF _online THEN
      RETURN QUERY
      UPDATE users u 
      SET    last_activity = current_timestamp
      WHERE  u.user_name = _username
      RETURNING u.*;
   ELSE
      RETURN QUERY
      SELECT *
      FROM   users u
      WHERE  u.user_name = _username;
   END IF;
END
$func$;

Retornar linha inteira mais adição personalizada


Para abordar a questão adicionada por TheRealChx101 em um comentário abaixo:

E se você também tiver um valor calculado além de uma tabela inteira? 😑

Não tão simples, mas factível. Podemos enviar todo o tipo de linha como um campo e adicione mais:
CREATE OR REPLACE FUNCTION get_user_by_username3(_username text
                                               , _online bool DEFAULT false)
  RETURNS TABLE (
    users_row users
  , custom_addition text
  )
  LANGUAGE plpgsql AS
$func$
BEGIN
   IF _online THEN
      RETURN QUERY
      UPDATE users u 
      SET    last_activity = current_timestamp  -- ts with time zone
      WHERE  u.user_name = _username
      RETURNING u  -- whole row
              , u.user_name || u.user_id;
   ELSE
      RETURN QUERY
      SELECT u, u.user_name || u.user_id
      FROM   users u
      WHERE  u.user_name = _username;
   END IF;
END
$func$;

A "mágica" está na chamada da função, onde (opcionalmente) decompomos o tipo de linha:
SELECT (users_row).*, custom_addition FROM get_user_by_username('foo', true);

db<>mexa aqui (mostrando tudo)

Se você precisa de algo mais "dinâmico", considere:
  • Refatorar uma função PL/pgSQL para retornar a saída de várias consultas SELECT