web-dev-qa-db-fra.com

PostgreSQL: ERREUR: 42601: une liste de définitions de colonnes est requise pour les fonctions retournant "record"

(Avertissement: débutant PostgreSQL.)

OK, pour autant que je sache, ma fonction ressemble bien aux échantillons que j'ai vus. Quelqu'un peut-il m'indiquer comment faire en sorte que cela fonctionne?

create or replace function get_user_by_username(
    username varchar(250),
    online boolean
    ) returns setof record as $$
declare result record;
begin

    if online then 
        update users
        set last_activity = current_timestamp
        where user_name = username;
    end if;

    return query
    select
        user_id,
        user_name,
        last_activity,
        created,
        email,
        approved,
        last_lockout,
        last_login,
        last_password_changed,
        password_question,
        comment
    from
        users
    where
        user_name = username
    limit 1;

    return;
end;
$$ language plpgsql;
35
Jeremy Holovacs

si vous souhaitez créer une fonction renvoyant setof record, vous devrez définir les types de colonnes dans votre instruction select

Plus d'informations

Votre requête devrait ressembler à ceci:

select * from get_user_by_username('Username', True) as 
  f(user_id integer, user_name varchar, last_activity, varchar, created date, email        archar, approved boolean, last_lockout timestamp, last_login timestamp, 
  last_password_changed timestamp, password_question varchar, comment varchar)

(vous devrez probablement changer les types de données)

Je préfère personnellement l'approche des types. il garantit que si la fonction est modifiée, toutes les requêtes retourneront des résultats corrects. Cela pourrait être pénible car chaque fois que vous modifiez les arguments de la fonction, vous devrez recréer/supprimer des types également.

Par exemple:

CREATE TYPE return_type as 
(user_id integer,
 user_name varchar,
 last_activity varchar,
 created timestamp,
 email varchar,
 approved boolean,
 last_lockout timestamp ,
 last_login timestamp,
 last_password_changed timestamp,
 password_question varchar,
 comment varchar);

create or replace function get_user_by_username( username varchar(250), online 

boolean) returns setof return_type as $$
declare _rec return_type;
begin
    if online then 
        update users
        set last_activity = current_timestamp
        where user_name = username;
    end if;
    for _rec in select
        user_id,
        user_name,
        last_activity,
        created,
        email,
        approved,
        last_lockout,
        last_login,
        last_password_changed,
        password_question,
        comment
      from
        users
      where
        user_name = username
      limit 1 
    loop

      return next _rec;

    end loop

end;
$$ language plpgsql;
29
ertx

Renvoyer les colonnes sélectionnées

CREATE OR REPLACE FUNCTION get_user_by_username(_username text, _online bool)
  RETURNS TABLE (
    user_id int
   ,user_name text
   ,last_activity timestamp
   , ... ) AS
$func$
BEGIN

IF _online THEN
   RETURN QUERY
   UPDATE users u 
   SET    last_activity = current_timestamp
   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$  LANGUAGE plpgsql;

Appel:

SELECT * FROM get_user_by_username('myuser', TRUE)

Points majeurs

  • Vous aviez DECLARE result record; Mais vous n'avez pas utilisé la variable. J'ai supprimé la cruauté.

  • Vous pouvez renvoyer l'enregistrement directement à partir de UPDATE, ce qui est beaucoup plus rapide que d'appeler une instruction SELECT supplémentaire. Utilisez RETURN QUERY Et UPDATE avec une clause RETURNING .
    Si l'utilisateur n'est pas _online, Choisissez par défaut un SELECT simple.

  • Si vous ne nommez pas les noms de colonne (tablename.columnname) Dans les requêtes à l'intérieur de la fonction, méfiez-vous des conflits de dénomination entre les noms de colonne et les paramètres nommés, qui sont visibles (la plupart) 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 utilisez pour les noms de colonne: comme un trait de soulignement (_username).

  • Si users.username est défini unique dans votre tableau, alors LIMIT 1 dans la deuxième requête est juste vicié.
    Si ce n'est pas pas , alors le UPDATE peut mettre à jour plusieurs lignes, ce qui est très probablement faux .
    J'ai supposé un username unique et j'ai supprimé la cruauté.

  • Définissez le type de retour de la fonction (comme @ertx l'a démontré) ou vous devrez fournir une liste de définition de colonne dans chaque appel de fonction, ce qui est gênant.

  • La création d'un type à cet effet (comme @ertx proposé) est une approche valide, mais probablement exagérée pour une seule fonction. C'était le chemin à parcourir dans les anciennes versions de PostgreSQL avant d'avoir RETURNS TABLE à cet effet - 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.

  • Il est probablement inutile de définir une restriction de longueur (varchar(250)) pour le paramètre. J'ai simplifié pour taper text.

Retourner toute la table

Si vous souhaitez renvoyer toutes les colonnes de la table users, il existe un moyen plus simple. PostgreSQL définit automatiquement un type composite du même nom pour chaque table . Dans ce cas, vous pouvez utiliser RETURNS SETOF users Et simplifier considérablement la requête:

CREATE OR REPLACE FUNCTION get_user_by_username(_username text, _online bool)
  RETURNS SETOF users 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$  LANGUAGE plpgsql;

Si vous avez besoin de quelque chose de plus "dynamique", pensez à:

41
Erwin Brandstetter