Renvoyer les colonnes sélectionnées
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$;
Appel :
SELECT * FROM get_user_by_username('myuser', true);
Vous aviez DECLARE result record;
mais n'a pas utilisé la variable. J'ai supprimé l'essentiel.
Vous pouvez retourner l'enregistrement directement depuis le UPDATE
, ce qui est beaucoup plus rapide que d'appeler un SELECT
supplémentaire déclaration. Utilisez RETURN QUERY
et UPDATE
avec un RETURNING
clause.
Si l'utilisateur n'est pas _online
, par défaut à un simple SELECT
. C'est également la valeur par défaut (sûre) si le deuxième paramètre est omis - ce qui n'est possible qu'après avoir fourni cette valeur par défaut avec DEFAULT false
dans la définition de la fonction.
Si vous ne qualifiez pas les noms de colonne de table (tablename.columnname
) dans les requêtes à l'intérieur de la fonction, méfiez-vous des conflits de noms entre les noms de colonnes et les paramètres nommés, qui sont visibles (presque) partout dans une fonction.
Vous pouvez également éviter de tels conflits en utilisant des références de position ($n
) pour les paramètres. Ou utilisez un préfixe que vous jamais utiliser pour les noms de colonnes :comme un trait de soulignement (_username
).
Si users.username
est défini unique dans votre tableau, puis LIMIT 1
dans la deuxième requête est juste cru. Si ce n'est pas , puis le UPDATE
peut mettre à jour plusieurs lignes, ce qui est probablement incorrect . Je suppose un username
unique et coupez le bruit.
Définir le type de retour de la fonction (comme @ertx démontré) ou vous devez fournir une liste de définition de colonne avec chaque appel de fonction, ce qui est gênant.
Créer un type à cette fin (comme @ertx proposé) est une approche valable, mais probablement exagérée pour une seule fonction. C'était la voie à suivre dans les anciennes versions de Postgres avant que nous ayons RETURNS TABLE
à cette fin - comme démontré ci-dessus.
Vous n'avez pas besoin d'une boucle pour cette fonction simple.
Chaque fonction a besoin d'une déclaration de langage. LANGUAGE plpgsql
dans ce cas.
J'utilise timestamptz
(timestamp with time zone
) au lieu de timestamp
(timestamp without time zone
), qui est la valeur par défaut. Voir :
- Ignorer complètement les fuseaux horaires dans Rails et PostgreSQL
Renvoyer (ensemble de) ligne(s) entière(s)
Pour renvoyer toutes les colonnes de la table existante users
, il existe un moyen plus simple. Postgres définit automatiquement un type composite du même nom pour chaque table . Utilisez simplement RETURNS SETOF users
pour simplifier grandement la requête :
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$;
Renvoyer la ligne entière plus l'ajout personnalisé
Pour répondre à la question ajoutée par TheRealChx101 dans un commentaire ci-dessous :
Que se passe-t-il si vous avez également une valeur calculée en plus d'un tableau entier ? 😑
Pas aussi simple, mais faisable. Nous pouvons envoyer le type de ligne entier comme un champ, et ajoutez-en d'autres :
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$;
La "magie" est dans l'appel de fonction, où nous décomposons (éventuellement) le type de ligne :
SELECT (users_row).*, custom_addition FROM get_user_by_username('foo', true);
db<>jouez ici (tout afficher)
Si vous avez besoin de quelque chose de plus "dynamique", envisagez :
- Refactoriser une fonction PL/pgSQL pour renvoyer la sortie de diverses requêtes SELECT