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

Exécuter une requête tableau croisé dynamique

Ce que vous demandez est impossible . SQL est un langage strictement typé. Les fonctions PostgreSQL doivent déclarer un type de retour (RETURNS .. ) au moment de la création .

Un moyen limité de contourner ce problème consiste à utiliser des fonctions polymorphes. Si vous pouvez fournir le type de retour au moment de l'appel de la fonction . Mais cela ne ressort pas de votre question.

  • Refactoriser une fonction PL/pgSQL pour renvoyer la sortie de diverses requêtes SELECT

Vous pouvez renvoie un résultat complètement dynamique avec des enregistrements anonymes. Mais vous devez alors fournir une liste de définitions de colonne avec chaque appel. Et comment connaissez-vous les colonnes renvoyées ? Attrape 22.

Il existe différentes solutions de contournement, en fonction de ce dont vous avez besoin ou avec lequel vous pouvez travailler. Étant donné que toutes vos colonnes de données semblent partager le même type de données, je suggère de renvoyer un tableau :text[] . Ou vous pouvez renvoyer un type de document comme hstore ou json . Connexe :

  • Alternative dynamique au pivot avec CASE et GROUP BY

  • Convertir dynamiquement les clés hstore en colonnes pour un ensemble de clés inconnu

Mais il pourrait être plus simple d'utiliser simplement deux appels :1 :Laissez Postgres construire la requête. 2 :Exécuter et récupérer les lignes renvoyées.

  • Sélectionner plusieurs valeurs max() à l'aide d'une seule instruction SQL

Je n'utiliserais pas du tout la fonction d'Eric Minikel telle que présentée dans votre question du tout . Il n'est pas à l'abri d'une injection SQL au moyen d'identifiants malformés de manière malveillante. Utilisez format() pour créer des chaînes de requête, sauf si vous exécutez une version obsolète antérieure à Postgres 9.1.

Une mise en œuvre plus courte et plus propre pourrait ressembler à ceci :

CREATE OR REPLACE FUNCTION xtab(_tbl regclass, _row text, _cat text
                              , _expr text  -- still vulnerable to SQL injection!
                              , _type regtype)
  RETURNS text AS
$func$
DECLARE
   _cat_list text;
   _col_list text;
BEGIN

-- generate categories for xtab param and col definition list    
EXECUTE format(
 $$SELECT string_agg(quote_literal(x.cat), '), (')
        , string_agg(quote_ident  (x.cat), %L)
   FROM  (SELECT DISTINCT %I AS cat FROM %s ORDER BY 1) x$$
 , ' ' || _type || ', ', _cat, _tbl)
INTO  _cat_list, _col_list;

-- generate query string
RETURN format(
'SELECT * FROM crosstab(
   $q$SELECT %I, %I, %s
      FROM   %I
      GROUP  BY 1, 2  -- only works if the 3rd column is an aggregate expression
      ORDER  BY 1, 2$q$
 , $c$VALUES (%5$s)$c$
   ) ct(%1$I text, %6$s %7$s)'
, _row, _cat, _expr  -- expr must be an aggregate expression!
, _tbl, _cat_list, _col_list, _type
);

END
$func$ LANGUAGE plpgsql;

Même appel de fonction que votre version originale. La fonction crosstab() est fourni par le module supplémentaire tablefunc qui doit être installé. Notions de base :

  • Requête croisée PostgreSQL

Cela gère les noms de colonne et de table en toute sécurité. Notez l'utilisation des types d'identifiant d'objet regclass et regtype . Fonctionne également pour les noms qualifiés de schéma.

  • Nom de table en tant que paramètre de fonction PostgreSQL

Cependant, il n'est pas complètement sûr pendant que vous passez une chaîne à exécuter en tant qu'expression (_expr - cellc dans votre requête d'origine). Ce type d'entrée est intrinsèquement dangereux contre l'injection SQL et ne doit jamais être exposé au grand public.

  • Injection SQL dans les fonctions Postgres vs requêtes préparées

N'analyse la table qu'une fois pour les deux listes de catégories et devrait être un peu plus rapide.

Impossible de renvoyer des types de lignes complètement dynamiques car ce n'est strictement pas possible.