PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

PostgreSQL :ERREUR :42601 :une liste de définition de colonne est requise pour les fonctions renvoyant un enregistrement

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