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