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

Comment renvoyer le résultat d'un SELECT dans une fonction dans PostgreSQL ?

Utilisez RETURN QUERY :

CREATE OR REPLACE FUNCTION word_frequency(_max_tokens int)
  RETURNS TABLE (txt   text   -- also visible as OUT parameter inside function
               , cnt   bigint
               , ratio bigint)
  LANGUAGE plpgsql AS
$func$
BEGIN
   RETURN QUERY
   SELECT t.txt
        , count(*) AS cnt                 -- column alias only visible inside
        , (count(*) * 100) / _max_tokens  -- I added brackets
   FROM  (
      SELECT t.txt
      FROM   token t
      WHERE  t.chartype = 'ALPHABETIC'
      LIMIT  _max_tokens
      ) t
   GROUP  BY t.txt
   ORDER  BY cnt DESC;                    -- potential ambiguity 
END
$func$;

Appel :

SELECT * FROM word_frequency(123);

Définir explicitement le type de retour est beaucoup plus pratique que de renvoyer un record générique . De cette façon, vous n'avez pas à fournir une liste de définition de colonne avec chaque appel de fonction. RETURNS TABLE est une façon de le faire. Il y en a d'autres. Types de données de OUT les paramètres doivent correspondre exactement à ce qui est renvoyé par la requête.

Choisissez des noms pour OUT paramètres avec soin. Ils sont visibles dans le corps de la fonction presque n'importe où. Qualifiez les colonnes de table du même nom pour éviter les conflits ou les résultats inattendus. Je l'ai fait pour toutes les colonnes de mon exemple.

Mais notez le potentiel conflit de nom entre le OUT paramètre cnt et l'alias de colonne du même nom. Dans ce cas particulier (RETURN QUERY SELECT ... ) Postgres utilise l'alias de colonne sur le OUT paramètre de toute façon. Cela peut cependant être ambigu dans d'autres contextes. Il existe plusieurs manières d'éviter toute confusion :

  1. Utilisez la position ordinale de l'élément dans la liste SELECT :ORDER BY 2 DESC . Exemple :
    • Sélectionner la première ligne de chaque groupe GROUP BY ?
  2. Répéter l'expression ORDER BY count(*) .
  3. (Non applicable ici.) Définissez le paramètre de configuration plpgsql.variable_conflict ou utilisez la commande spéciale #variable_conflict error | use_variable | use_column dans la fonction. Voir :
    • Conflit de nom entre le paramètre de la fonction et le résultat de JOIN avec la clause USING

N'utilisez pas "text" ou "count" comme noms de colonnes. Les deux sont légaux à utiliser dans Postgres, mais "count" est un mot réservé en SQL standard et un nom de fonction de base et "texte" est un type de données de base. Peut conduire à des erreurs confuses. J'utilise txt et cnt dans mes exemples, vous voudrez peut-être des noms plus explicites.

Ajout d'un ; manquant et corrigé une erreur de syntaxe dans l'en-tête. (_max_tokens int) , pas (int maxTokens) - tapez après nom .

Lorsque vous travaillez avec une division entière, il est préférable de multiplier d'abord et de diviser ensuite, afin de minimiser l'erreur d'arrondi. Ou travaillez avec numeric ou un type à virgule flottante. Voir ci-dessous.

Alternative

C'est ce que je pense votre requête devrait ressembler à (calcul d'une part relative par jeton ):

CREATE OR REPLACE FUNCTION word_frequency(_max_tokens int)
  RETURNS TABLE (txt            text
               , abs_cnt        bigint
               , relative_share numeric)
  LANGUAGE plpgsql AS
$func$
BEGIN
   RETURN QUERY
   SELECT t.txt, t.cnt
        , round((t.cnt * 100) / (sum(t.cnt) OVER ()), 2)  -- AS relative_share
   FROM  (
      SELECT t.txt, count(*) AS cnt
      FROM   token t
      WHERE  t.chartype = 'ALPHABETIC'
      GROUP  BY t.txt
      ORDER  BY cnt DESC
      LIMIT  _max_tokens
      ) t
   ORDER  BY t.cnt DESC;
END
$func$;

L'expression sum(t.cnt) OVER () est une fonction de fenêtre. Vous pourriez utiliser un CTE au lieu de la sous-requête. Jolie, mais une sous-requête est généralement moins chère dans des cas simples comme celui-ci (principalement avant Postgres 12).

Un dernier RETURN explicite l'instruction n'est pas requis (mais autorisé) lorsque vous travaillez avec OUT paramètres ou RETURNS TABLE (qui utilise implicitement OUT paramètres).

round() avec deux paramètres ne fonctionne que pour numeric les types. count() dans la sous-requête produit un bigint résultat et un sum() sur ce bigint produit un numeric résultat, on a donc affaire à un numeric numéro automatiquement et tout se met en place.