Comment retourner le résultat d'un SELECT à l'intérieur d'une fonction dans PostgreSQL?

106

J'ai cette fonction dans PostgreSQL, mais je ne sais pas comment renvoyer le résultat de la requête:

CREATE OR REPLACE FUNCTION wordFrequency(maxTokens INTEGER)
  RETURNS SETOF RECORD AS
$$
BEGIN
    SELECT text, count(*), 100 / maxTokens * count(*)
    FROM (
        SELECT text
    FROM token
    WHERE chartype = 'ALPHABETIC'
    LIMIT maxTokens
    ) as tokens
    GROUP BY text
    ORDER BY count DESC
END
$$
LANGUAGE plpgsql;

Mais je ne sais pas comment renvoyer le résultat de la requête dans la fonction PostgreSQL.

J'ai trouvé que le type de retour devrait être SETOF RECORD, non? Mais la commande de retour n'est pas correcte.

Quel est le bon moyen de le faire?

Renato Dinhani
la source
Pourquoi les comptez-vous? avez-vous des jetons en double dans votre TABLEAU de jetons? Aussi: veuillez ajouter la définition du tableau à votre question.
wildplasser
1
Est-ce votre fonction entière? Si vous n'avez aucune autre instruction dans la fonction, vous devez simplement la faire LANGUAGE SQL.
jpmc26

Réponses:

135

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) 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$  LANGUAGE plpgsql;

Appel:

SELECT * FROM word_frequency(123);

Explication:

  • Il est beaucoup plus pratique de définir explicitement le type de retour que de simplement le déclarer comme enregistrement. De cette façon, vous n'avez pas à fournir une liste de définitions de colonne à chaque appel de fonction.RETURNS TABLEest une façon de faire cela. Il y en a d'autres. Les types de données des OUTparamètres doivent correspondre exactement à ce qui est renvoyé par la requête.

  • Choisissez OUTsoigneusement les noms des paramètres. Ils sont visibles dans le corps de fonction presque partout. Qualifiez les colonnes 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 conflit de dénomination potentiel entre le OUTparamètre cntet l'alias de colonne du même nom. Dans ce cas particulier (RETURN QUERY SELECT ... ) Postgres utilise l'alias de colonne sur le OUTparamètre de toute façon. Cela peut cependant être ambigu dans d'autres contextes. Il existe plusieurs moyens d'éviter toute confusion:

    1. Utilisez la position ordinale de l'élément dans la liste SELECT: ORDER BY 2 DESC . Exemple:
    2. Répétez l'expression ORDER BY count(*).
    3. (Non applicable ici.) Définissez le paramètre de configuration plpgsql.variable_conflictou utilisez la commande spéciale#variable_conflict error | use_variable | use_column dans la fonction. Voir:
  • N'utilisez pas «texte» ou «compte» comme noms de colonne. Les deux sont légaux à utiliser dans PostgreSQL, mais "count" est un mot réservé dans SQL standard et un nom de fonction de base et "texte" est un type de données de base. Peut conduire à des erreurs déroutantes. J'utilise txtetcnt dans mes exemples.

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

  • Lorsque vous travaillez avec une division entière, il est préférable de multiplier d'abord et de diviser plus tard, pour minimiser l'erreur d'arrondi. Mieux encore: travaillez avec numeric(ou un type à virgule flottante). Voir ci-dessous.

Alternative

Voici à quoi je pense que 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) 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$  LANGUAGE plpgsql;

L'expression sum(t.cnt) OVER ()est une fonction de fenêtre . Vous pouvez 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.

Une déclaration expliciteRETURN finale n'est pas requise (mais autorisée) lorsque vous travaillez avec des OUTparamètres ou RETURNS TABLE(qui utilise implicitement des OUTparamètres).

round()avec deux paramètres ne fonctionne que pour les numerictypes. count()dans la sous-requête produit un bigintrésultat et un sum()over this bigintproduit un numericrésultat, ainsi nous traitons numericautomatiquement un nombre et tout se met en place.

Erwin Brandstetter
la source
Merci beaucoup pour votre réponse et vos corrections. Fonctionne bien maintenant (j'ai seulement changé le type de rapport en numérique).
Renato Dinhani
@ RenatoDinhaniConceição Cool! J'ai ajouté une version qui peut ou non répondre à une question supplémentaire que vous n'avez pas réellement posée. ;)
Erwin Brandstetter
Bien, la seule chose est que je pense que vous avez besoin d'un RETURN;avant cela END;, du moins je l'ai fait - mais je fais une UNION donc je ne suis pas sûr si cela rend les choses différentes.
yekta le
@yekta: J'ai ajouté quelques informations concernant le rôle de RETURN. Correction d'une erreur sans rapport et ajout de quelques améliorations tout en y étant.
Erwin Brandstetter le
1
Quelle est la manière de faire cela lorsque vous ne voulez pas contraindre ce qui est dans Return TABLE (). TABLEAU DE RETOUR IE (*)?
Nick
1

Salut s'il vous plaît vérifier le lien ci-dessous

https://www.postgresql.org/docs/current/xfunc-sql.html

EX:

CREATE FUNCTION sum_n_product_with_tab (x int)
RETURNS TABLE(sum int, product int) AS $$
    SELECT $1 + tab.y, $1 * tab.y FROM tab;
$$ LANGUAGE SQL;
Moumita Das
la source