web-dev-qa-db-fra.com

Comment générer un CROSS JOIN pivoté dont la définition de table résultante est inconnue?

Étant donné deux tables avec un nombre de lignes non défini avec un nom et une valeur, comment afficher un CROSS JOIN Pivoté d'une fonction sur leurs valeurs .

CREATE TEMP TABLE foo AS
SELECT x::text AS name, x::int
FROM generate_series(1,10) AS t(x);

CREATE TEMP TABLE bar AS
SELECT x::text AS name, x::int
FROM generate_series(1,5) AS t(x);

Par exemple, si cette fonction était une multiplication, comment générer une table (de multiplication) comme celle ci-dessous,

Common multiplication table of 1..12

Toutes ces (arg1,arg2,result) Lignes peuvent être générées avec

SELECT foo.name AS arg1, bar.name AS arg2, foo.x*bar.x AS result
FROM foo
CROSS JOIN bar; 

Donc ce n'est qu'une question de présentation, J'aimerais que cela fonctionne aussi avec un nom personnalisé - un nom qui n'est pas simplement l'argument CASTed au texte mais défini dans le tableau,

CREATE TEMP TABLE foo AS
SELECT chr(x+64) AS name, x::int
FROM generate_series(1,10) AS t(x);

CREATE TEMP TABLE bar AS
SELECT chr(x+72) AS name, x::int
FROM generate_series(1,5) AS t(x);

Je pense que ce serait facilement faisable avec un CROSSTAB capable d'un type de retour dynamique.

SELECT * FROM crosstab(
  '
    SELECT foo.x AS arg1, bar.x AS arg2, foo.x*bar.x
    FROM foo
    CROSS JOIN bar
  ', 'SELECT DISTINCT name FROM bar'
) AS **MAGIC**

Mais sans le **MAGIC**, Je reçois

ERROR:  a column definition list is required for functions returning "record"
LINE 1: SELECT * FROM crosstab(

Pour référence, en utilisant les exemples ci-dessus avec des noms, cela ressemble plus à ce que veut crosstab() de tablefunc.

SELECT * FROM crosstab(
  '
    SELECT foo.x AS arg1, bar.x AS arg2, foo.x*bar.x
    FROM foo
    CROSS JOIN bar
  '
) AS t(row int, i int, j int, k int, l int, m int);

Mais, maintenant nous revenons à faire des hypothèses sur le contenu et la taille de la table bar dans notre exemple. Donc si,

  1. Les tables sont de longueur indéfinie,
  2. Ensuite, la jointure croisée représente un cube de dimension indéfinie (à cause de ci-dessus),
  3. Les noms de catégories (langage croisé) sont dans le tableau

Que pouvons-nous faire de mieux dans PostgreSQL sans une "liste de définitions de colonnes" pour générer ce type de présentation?

18
Evan Carroll

Cas simple, SQL statique

La solution non dynamique avec crosstab() pour le cas simple:

SELECT * FROM crosstab(
  'SELECT b.x, f.name, f.x * b.x AS prod
   FROM   foo f, bar b
   ORDER  BY 1, 2'
   ) AS ct (x int, "A" int, "B" int, "C" int, "D" int, "E" int
                 , "F" int, "G" int, "H" int, "I" int, "J" int);

Je commande les colonnes résultantes par foo.name, Pas par foo.x. Les deux sont triés en parallèle, mais ce n'est que la configuration simple. Choisissez le bon ordre de tri pour votre cas. La valeur réelle valeur de la deuxième colonne n'est pas pertinente dans cette requête (forme à 1 paramètre de crosstab()).

Nous n'avons même pas besoin de crosstab() avec 2 paramètres car il n'y a pas de valeurs manquantes par définition. Voir:

(Vous avez corrigé la requête de tableau croisé dans la question en remplaçant foo par bar dans une modification ultérieure. Cela corrige également la requête, mais continue de travailler avec les noms de foo.)

Type de retour inconnu, SQL dynamique

Les noms et types de colonnes ne peuvent pas être dynamiques. SQL exige de connaître le nombre, les noms et les types de colonnes résultantes au moment de l'appel. Soit par déclaration explicite, soit à partir d'informations dans les catalogues système (c'est ce qui se passe avec SELECT * FROM tbl: Postgres recherche la définition de table enregistrée.)

Vous voulez que Postgres dérive les colonnes résultantes de data dans une table utilisateur. Ça ne va pas arriver.

D'une manière ou d'une autre, vous avez besoin deux aller-retour vers le serveur. Soit vous créez un curseur, puis vous le parcourez. Ou vous créez une table temporaire, puis sélectionnez-la. Ou vous enregistrez un type et l'utilisez dans l'appel.

Ou vous générez simplement la requête en une étape et l'exécutez à la suivante:

SELECT $$SELECT * FROM crosstab(
  'SELECT b.x, f.name, f.x * b.x AS prod
   FROM   foo f, bar b
   ORDER  BY 1, 2'
   ) AS ct (x int, $$
 || string_agg(quote_ident(name), ' int, ' ORDER BY name) || ' int)'
FROM   foo;

Cela génère la requête ci-dessus, dynamiquement. Exécutez-le à l'étape suivante.

J'utilise des guillemets ($$) Pour simplifier la gestion des devis imbriqués. Voir:

quote_ident() est essentiel pour échapper aux noms de colonnes autrement illégaux (et éventuellement se défendre contre l'injection SQL).

En relation:

12
Erwin Brandstetter

Que pouvons-nous faire de mieux dans PostgreSQL sans une "liste de définitions de colonnes" pour générer ce type de présentation?

Si vous définissez cela comme un problème de présentation, vous pouvez envisager une fonctionnalité de présentation post-requête.

Les nouvelles versions de psql (9.6) sont fournies avec \crosstabview, montrant un résultat dans une représentation croisée sans prise en charge SQL (puisque SQL ne peut pas produire cela directement, comme mentionné dans la réponse de @ Erwin: SQL demande de connaître le nombre, les noms et les types de colonnes résultantes au moment de l'appel )

Par exemple, votre première requête donne:

SELECT foo.name AS arg1, bar.name AS arg2, foo.x*bar.x AS result
FROM foo
CROSS JOIN bar
\crosstabview

 arg1 | 1  | 2  | 3  | 4  | 5  
------+----+----+----+----+----
 1    |  1 |  2 |  3 |  4 |  5
 2    |  2 |  4 |  6 |  8 | 10
 3    |  3 |  6 |  9 | 12 | 15
 4    |  4 |  8 | 12 | 16 | 20
 5    |  5 | 10 | 15 | 20 | 25
 6    |  6 | 12 | 18 | 24 | 30
 7    |  7 | 14 | 21 | 28 | 35
 8    |  8 | 16 | 24 | 32 | 40
 9    |  9 | 18 | 27 | 36 | 45
 10   | 10 | 20 | 30 | 40 | 50
(10 rows)

Le deuxième exemple avec ASCII noms de colonne donne:

SELECT foo.name AS arg1, bar.name AS arg2, foo.x*bar.x
    FROM foo
    CROSS JOIN bar
  \crosstabview

 arg1 | I  | J  | K  | L  | M  
------+----+----+----+----+----
 A    |  1 |  2 |  3 |  4 |  5
 B    |  2 |  4 |  6 |  8 | 10
 C    |  3 |  6 |  9 | 12 | 15
 D    |  4 |  8 | 12 | 16 | 20
 E    |  5 | 10 | 15 | 20 | 25
 F    |  6 | 12 | 18 | 24 | 30
 G    |  7 | 14 | 21 | 28 | 35
 H    |  8 | 16 | 24 | 32 | 40
 I    |  9 | 18 | 27 | 36 | 45
 J    | 10 | 20 | 30 | 40 | 50
(10 rows)

Voir manuel psql et https://wiki.postgresql.org/wiki/Crosstabview pour en savoir plus.

11
Daniel Vérité

Ce n'est pas une solution définitive

C'est ma meilleure approche jusqu'à présent. Encore faut-il convertir le tableau final en colonnes.

J'ai d'abord le produit cartésien des deux tables:

select foo.name xname, bar.name yname, (foo.x * bar.x)::text as val,
       ((row_number() over ()) - 1) / (select count(*)::integer from foo) as row
 from bar
     cross join foo
 order by bar.name, foo.name

Mais, j'ai ajouté un numéro de ligne juste pour identifier chaque ligne de la première table.

((row_number() over ()) - 1) / (select count(*)::integer from foo)

Ensuite, j'ai construit le résultat dans ce format:

[Row name] [Array of values]


select col_name, values
from
(
select '' as col_name, array_agg(name) as values from foo
UNION
select fy.name as col_name,
    (select array_agg(t.val) as values
    from  
        (select foo.name xname, bar.name yname, (foo.x * bar.x)::text as val,
              ((row_number() over ()) - 1) / (select count(*)::integer from foo) as row
        from bar
           cross join foo
        order by bar.name, foo.name) t
    where t.row = fy.row)
from
    (select name, (row_number() over(order by name)) - 1 as row from bar) fy
) a
order by col_name;

+---+---------------------+
|   |      ABCDEFGHIJ     |
+---+---------------------+
| I |     12345678910     |
+---+---------------------+
| J |   2468101214161820  |
+---+---------------------+
| K |  36912151821242730  |
+---+---------------------+
| L |  481216202428323640 |
+---+---------------------+
| M | 5101520253035404550 |
+---+---------------------+ 

Le convertir en chaîne délimitée par des comas:

select col_name, values
from
(
select '' as col_name, array_to_string(array_agg(name),',') as values from foo
UNION
select fy.name as col_name,
    (select array_to_string(array_agg(t.val),',') as values
    from  
        (select foo.name xname, bar.name yname, (foo.x * bar.x)::text as val,
              ((row_number() over ()) - 1) / (select count(*)::integer from foo) as row
        from bar
           cross join foo
        order by bar.name, foo.name) t
    where t.row = fy.row)
from
    (select name, (row_number() over(order by name)) - 1 as row from bar) fy
) a
order by col_name;


+---+------------------------------+
|   | A,B,C,D,E,F,G,H,I,J          |
+---+------------------------------+
| I | 1,2,3,4,5,6,7,8,9,10         |
+---+------------------------------+
| J | 2,4,6,8,10,12,14,16,18,20    |
+---+------------------------------+
| K | 3,6,9,12,15,18,21,24,27,30   |
+---+------------------------------+
| L | 4,8,12,16,20,24,28,32,36,40  |
+---+------------------------------+
| M | 5,10,15,20,25,30,35,40,45,50 |
+---+------------------------------+

(Juste pour l'essayer plus tard: http://rextester.com/NBCYXA218 )

1
McNets

En remarque, il semble que SQL: 2016 s'adaptera à cela avec Fonctions de tableau polymorphe (ISO/IEC TR 19075-7: 2017)

J'ai trouvé le lien Quoi de neuf dans SQL: 2016 mais l'auteur ne développe pas beaucoup.

0
Evan Carroll