crosstab et autres) #
Le module tablefunc inclut plusieurs fonctions
permettant de renvoyer des tables (c'est-à-dire plusieurs lignes). Ces
fonctions sont utiles directement et comme exemples sur la façon d'écrire
des fonctions C qui renvoient plusieurs lignes.
Ce module est considéré comme « trusted », ce qui signifie qu'il
peut être installé par des utilisateurs simples (sans attribut
SUPERUSER) et qui ont l'attribut CREATE
sur la base de données courante.
Tableau F.31 résume les fonctions fournies par le
module tablefunc.
Tableau F.31. Fonctions tablefunc
normal_rand #normal_rand(int numvals, float8 mean, float8 stddev) returns setof float8
normal_rand produit un ensemble de valeurs
distribuées au hasard (distribution gaussienne).
numvals est le nombre de valeurs que la fonction
doit renvoyer. mean est la moyenne de la
distribution normale des valeurs et stddev est la
déviation standard de la distribution normale des valeurs.
Par exemple, cet appel demande 1000 valeurs avec une moyenne de 5 et une déviation standard de 3 :
test=# SELECT * FROM normal_rand(1000, 5, 3);
normal_rand
----------------------
1.56556322244898
9.10040991424657
5.36957140345079
-0.369151492880995
0.283600703686639
.
.
.
4.82992125404908
9.71308014517282
2.49639286969028
(1000 rows)
crosstab(text) #crosstab(text sql) crosstab(text sql, int N)
La fonction crosstab est utilisé pour créer un
affichage « pivot » où les données sont listées de gauche à
droite plutôt que de haut en bas. Par exemple, avec ces données
row1 val11
row1 val12
row1 val13
...
row2 val21
row2 val22
row2 val23
...
l'affiche ressemble à ceci
row1 val11 val12 val13 ...
row2 val21 val22 val23 ...
...
La fonction crosstab prend un paramètre texte qui est
une requête SQL produisant des données brutes formatées de la façon
habituelle et produit une table avec un autre formatage.
Le paramètre sql est une instruction SQL qui produit
l'ensemble source des données. Cette instruction doit renvoyer une colonne
row_name, une colonne
category et une colonne
value. N est un paramètre
obsolète, ignoré quand il est fourni (auparavant, il devait correspondre
au nombre de colonnes de valeurs en sortie, mais maintenant ceci est
déterminé par la requête appelant).
Par exemple, la requête fournie peut produire un ensemble ressemblant à ceci :
row_name cat value ----------+-------+------- row1 cat1 val1 row1 cat2 val2 row1 cat3 val3 row1 cat4 val4 row2 cat1 val5 row2 cat2 val6 row2 cat3 val7 row2 cat4 val8
La fonction crosstab déclare renvoyer un setof
record, donc les noms et types réels des colonnes doivent être
définis dans la clause FROM de l'instruction
SELECT appelante. Par exemple :
statement, for example:
SELECT * FROM crosstab('...') AS ct(row_name text, category_1 text, category_2 text);
Cet exemple produit un ensemble ressemblant à ceci :
<== value columns ==>
row_name category_1 category_2
---------+------------+------------
row1 val1 val2
row2 val5 val6
La clause FROM doit définir la sortie comme une
colonne row_name (du même type que la première
colonne du résultat de la requête SQL) suivie par N colonnes
value (tous du même type de données que la
troisième colonne du résultat de la requête SQL). Vous pouvez configurer
autant de colonnes de valeurs en sortie que vous voulez. Les noms des
colonnes en sortie n'ont pas d'importance en soi.
La fonction crosstab produit une ligne en sortie
pour chaque groupe consécutif de lignes en entrée avec la même valeur
row_name. Elle remplit les colonnes de
value, de gauche à droite, avec les champs
value provenant de ces lignes. S'il y a moins
de lignes dans un groupe que de colonnes value
en sortie, les colonnes supplémentaires sont remplies avec des valeurs
NULL ; s'il y a trop de ligne, les colonnes en entrée supplémentaires
sont ignorées.
En pratique, la requête SQL devrait toujours spécifier ORDER BY
1,2 pour s'assurer que les lignes en entrée sont bien ordonnées,
autrement dit que les valeurs de même row_name
sont placées ensemble et sont correctement ordonnées dans la ligne. Notez
que crosstab ne fait pas attention à la deuxième
colonne du résultat de la requête ; elle est là pour permettre le
tri, pour contrôler l'ordre dans lequel les valeurs de la troisième
colonne apparaissent dans la page.
Voici un exemple complet :
CREATE TABLE ct(id SERIAL, rowid TEXT, attribute TEXT, value TEXT);
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att1','val1');
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att2','val2');
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att3','val3');
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att4','val4');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att1','val5');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att2','val6');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att3','val7');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att4','val8');
SELECT *
FROM crosstab(
'select rowid, attribute, value
from ct
where attribute = ''att2'' or attribute = ''att3''
order by 1,2')
AS ct(row_name text, category_1 text, category_2 text, category_3 text);
row_name | category_1 | category_2 | category_3
----------+------------+------------+------------
test1 | val2 | val3 |
test2 | val6 | val7 |
(2 rows)
Vous pouvez toujours éviter d'avoir à écrire une clause
FROM pour définir les colonnes en sortie, en définissant
une fonction crosstab personnalisée qui a le type de ligne désiré en
sortie en dur dans sa définition. Ceci est décrit dans la prochaine
section. Une autre possibilité est d'embarquer la clause
FROM requise dans la définition d'une vue.
Voir aussi la commande \crosstabview
dans psql. Elle fournit des fonctionnalités
similaires à crosstab().
crosstabN(text) #
crosstabN(text sql)
Les fonctions crosstab
sont des exemples de configuration de fonctions d'emballage pour la
fonction généraliste Ncrosstab. Cela vous permet de
ne pas avoir à écrire les noms et types des colonnes dans la requête
SELECT appelante. Le module
tablefunc inclut
crosstab2, crosstab3 et
crosstab4, dont les types de ligne en sortie sont
définis ainsi :
CREATE TYPE tablefunc_crosstab_N AS (
row_name TEXT,
category_1 TEXT,
category_2 TEXT,
.
.
.
category_N TEXT
);
Du coup, ces fonctions peuvent être utilisées directement quand la
requête en entrée produit des colonnes row_name
et value de type text, et que vous
voulez 2, 3 ou 4 colonnes de valeur en sortie. Autrement, elles se
comportent exactement la fonction crosstab décrite
précédemment.
L'exemple de la section précédente pourrait aussi fonctionner ainsi :
SELECT * FROM crosstab3( 'select rowid, attribute, value from ct where attribute = ''att2'' or attribute = ''att3'' order by 1,2');
Ces fonctions sont fournies principalement comme exemples. Vous pouvez
créer vos propres types de retour et fonctions basées sur la fonction
crosstab(). Il existe deux façons de le faire :
Créer un type composite décrivant les colonnes désirées en sortie,
similaire aux exemples disponibles dans le fichier
contrib/tablefunc/tablefunc--1.0.sql. Ensuite, définir un
nom de fonction unique acceptant un paramètre de type text
et renvoyant setof nom_de_votre_type, mais renvoyant à
la fonction C crosstab. Par exemple, si votre
source de données produit des noms de ligne qui sont de type
text, et des valeurs qui sont de type float8,
et que vous voulez cinq colonnes de valeurs :
CREATE TYPE my_crosstab_float8_5_cols AS (
my_row_name text,
my_category_1 float8,
my_category_2 float8,
my_category_3 float8,
my_category_4 float8,
my_category_5 float8
);
CREATE OR REPLACE FUNCTION crosstab_float8_5_cols(text)
RETURNS setof my_crosstab_float8_5_cols
AS '$libdir/tablefunc','crosstab' LANGUAGE C STABLE STRICT;
Utiliser des paramètres OUT pour définir
implicitement le type en retour. Le même exemple pourrait s'écrire
ainsi :
CREATE OR REPLACE FUNCTION crosstab_float8_5_cols(
IN text,
OUT my_row_name text,
OUT my_category_1 float8,
OUT my_category_2 float8,
OUT my_category_3 float8,
OUT my_category_4 float8,
OUT my_category_5 float8)
RETURNS setof record
AS '$libdir/tablefunc','crosstab' LANGUAGE C STABLE STRICT;
crosstab(text, text) #crosstab(text source_sql, text category_sql)
La limite principale de la forme à un paramètre de
crosstab est qu'elle traite toutes les valeurs d'un
groupe de la même façon, en insérant chaque valeur dans la première
colonne disponible. Si vous voulez les colonnes de valeur correspondant
à des catégories spécifiques de données, et que certains groupes n'ont pas
de données pour certaines des catégories, alors cela ne fonctionne pas.
La forme à deux paramètres de la fonction crosstab
gère ce cas en fournissant une liste explicite des catégories correspondant
aux colonnes en sortie.
source_sql est une instruction SQL qui produit
l'ensemble source des données. Cette instruction doit renvoyer une colonne
row_name, une colonne
category et une colonne
value. Elle pourrait aussi avoir une ou
plusieurs colonnes « extra ». La colonne
row_name doit être la première. Les colonnes
category et value
doivent être les deux dernières colonnes, dans cet ordre. Toutes les
colonnes entre row_name et
category sont traitées en « extra ».
Les colonnes « extra » doivent être les mêmes pour toutes les
lignes avec la même valeur row_name.
Par exemple, source_sql produit un ensemble
ressemblant à ceci :
SELECT row_name, extra_col, cat, value FROM foo ORDER BY 1; row_name extra_col cat value ----------+------------+-----+--------- row1 extra1 cat1 val1 row1 extra1 cat2 val2 row1 extra1 cat4 val4 row2 extra2 cat1 val5 row2 extra2 cat2 val6 row2 extra2 cat3 val7 row2 extra2 cat4 val8
category_sql est une instruction SQL qui produit
l'ensemble des catégories. Cette instruction doit renvoyer seulement une
colonne. Cela doit produire au moins une ligne, sinon une erreur sera
générée. De plus, cela ne doit pas produire de valeurs dupliquées, sinon
une erreur sera aussi générée. category_sql doit
ressembler à ceci :
SELECT DISTINCT cat FROM foo ORDER BY 1;
cat
-------
cat1
cat2
cat3
cat4
La fonction crosstab déclare renvoyer setof
record, donc les noms et types réels des colonnes en sortie
doivent être définis dans la clause FROM de la requête
SELECT appelante, par exemple :
SELECT * FROM crosstab('...', '...')
AS ct(row_name text, extra text, cat1 text, cat2 text, cat3 text, cat4 text);
Ceci produira un résultat ressemblant à ceci :
<== value columns ==>
row_name extra cat1 cat2 cat3 cat4
---------+-------+------+------+------+------
row1 extra1 val1 val2 val4
row2 extra2 val5 val6 val7 val8
La clause FROM doit définir le bon nombre de colonnes en
sortie avec les bons types de données. S'il y a N
colonnes dans le résultat de la requête source_sql,
les N-2 premiers d'entre eux doivent
correspondre aux N-2 premières colonnes en
sortie. Les colonnes restantes en sortie doivent avoir le type de la
dernière colonne du résultat de la requête The remaining output columns
source_sql, et il doit y en avoir autant que de
lignes dans le résultat de la requête category_sql.
La fonction crosstab produit une ligne en sortie
pour chaque groupe consécutif de lignes en entrée avec la même valeur
row_name. La colonne en sortie
row_name ainsi que toutes colonnes
« extra » sont copiées à partir de la première ligne du
groupe. Les colonnes value en sortie sont
remplies avec les champs value à partir des
lignes ayant une correspondance avec des valeurs
category. Si la
category d'une ligne ne correspond pas à une
sortie de la requête category_sql, sa
value est ignorée. Les colonnes en sortie dont
la catégorie correspondante est absente de toute ligne en entrée du
groupe sont remplies avec des valeurs NULL.
En pratique, la requête source_sql doit toujours
spécifier ORDER BY 1 pour s'assurer que les valeurs du
même row_name sont assemblées. Néanmoins,
l'ordre des catégories dans un groupe n'est pas important. De plus, il est
essentiel que l'ordre du résultat de la requête
category_sql corresponde à l'ordre des colonnes
spécifiées en sortie.
Voici deux exemples complets :
create table sales(year int, month int, qty int); insert into sales values(2007, 1, 1000); insert into sales values(2007, 2, 1500); insert into sales values(2007, 7, 500); insert into sales values(2007, 11, 1500); insert into sales values(2007, 12, 2000); insert into sales values(2008, 1, 1000); select * from crosstab( 'select year, month, qty from sales order by 1', 'select m from generate_series(1,12) m' ) as ( year int, "Jan" int, "Feb" int, "Mar" int, "Apr" int, "May" int, "Jun" int, "Jul" int, "Aug" int, "Sep" int, "Oct" int, "Nov" int, "Dec" int ); year | Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec ------+------+------+-----+-----+-----+-----+-----+-----+-----+-----+------+------ 2007 | 1000 | 1500 | | | | | 500 | | | | 1500 | 2000 2008 | 1000 | | | | | | | | | | | (2 rows)
CREATE TABLE cth(rowid text, rowdt timestamp, attribute text, val text);
INSERT INTO cth VALUES('test1','01 March 2003','temperature','42');
INSERT INTO cth VALUES('test1','01 March 2003','test_result','PASS');
INSERT INTO cth VALUES('test1','01 March 2003','volts','2.6987');
INSERT INTO cth VALUES('test2','02 March 2003','temperature','53');
INSERT INTO cth VALUES('test2','02 March 2003','test_result','FAIL');
INSERT INTO cth VALUES('test2','02 March 2003','test_startdate','01 March 2003');
INSERT INTO cth VALUES('test2','02 March 2003','volts','3.1234');
SELECT * FROM crosstab
(
'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
'SELECT DISTINCT attribute FROM cth ORDER BY 1'
)
AS
(
rowid text,
rowdt timestamp,
temperature int4,
test_result text,
test_startdate timestamp,
volts float8
);
rowid | rowdt | temperature | test_result | test_startdate | volts
-------+--------------------------+-------------+-------------+--------------------------+--------
test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987
test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 | 3.1234
(2 rows)
Vous pouvez créer des fonctions prédéfinies pour éviter d'avoir à écrire
les noms et types des colonnes en résultat dans chaque requête. Voir les
exemples dans la section précédente. La fonction C sous-jacente pour cette
forme de crosstab est appelée
crosstab_hash.
connectby #
connectby(text relname, text keyid_fld, text parent_keyid_fld
[, text orderby_fld ], text start_with, int max_depth
[, text branch_delim ])
La fonction connectby réalise un affichage de données
hiérarchiques stockées dans une table. La table doit avoir un champ clé
qui identifie de façon unique les lignes et un champ clé qui référence
le parent de chaque ligne. connectby peut afficher le
sous-arbre à partir de n'importe quelle ligne.
Tableau F.32 explique les paramètres.
Tableau F.32. Paramètres connectby
| Paramètre | Description |
|---|---|
relname | Nom de la relation source |
keyid_fld | Nom du champ clé |
parent_keyid_fld | Nom du champ clé du parent |
orderby_fld | Nom du champ des autres relations (optionnel) |
start_with | Valeur de la clé de la ligne de début |
max_depth | Profondeur maximum pour la descente, ou zéro pour une profondeur illimitée |
branch_delim | Chaîne pour séparer les clés des branches (optionnel) |
Les champs clé et clé du parent peuvent être de tout type mais ils
doivent être du même type. Notez que la valeur
start_with doit être saisi comme une chaîne de
caractères, quelque soit le type du champ clé.
La fonction connectby déclare renvoyer un setof
record, donc les noms et types réels des colonnes en sortie
doivent être définis dans la clause FROM de
l'instruction SELECT appelante, par exemple :
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~')
AS t(keyid text, parent_keyid text, level int, branch text, pos int);
Des deux premières colonnes en sortie sont utilisées pour la clé de la
ligne en cours et la clé de son parent ; elles doivent correspondre
au type du champ clé de la table. La troisième colonne est la
profondeur de l'arbre et doit être du type integer. Si un
paramètre branch_delim est renseigné, la prochaine
colonne en sortie est l'affichage de la branche et doit être de type
text. Enfin, si le paramètre
orderby_fld est renseigné, la dernière colonne en
sortie est un numéro de série et doit être de type integer.
La colonne « branch » en sortie affiche le chemin des clés
utilisé pour atteindre la ligne actuelle. Les clés sont séparées
par la chaîne branch_delim spécifiée. Si l'affichage
des branches n'est pas voulu, omettez le paramètre
branch_delim et la colonne branche dans la liste
des colonnes en sortie.
Si l'ordre des relations du même parent est important, incluez le
paramètre orderby_fld pour indiquer par quel
champ ordonner les relations. Ce champ doit être de tout type de données
triable. La liste des colonnes en sortie doit inclure une colonne numéro
de série de type integer si, et seulement si,
orderby_fld est spécifiée.
Les paramètres représentant table et noms de champs sont copiés tels
quel dans les requêtes SQL que connectby génère en
interne. Du coup, ajoutez des guillemets doubles si les noms utilisent
majuscules et minuscules ou s'ils contiennent des caractères spéciaux.
Vous pouvez aussi avoir besoin de qualifier le nom de la table avec le
nom du schéma.
Dans les grosses tables, les performances seront faibles sauf si un index est créé sur le champ clé parent.
Il est important que la chaîne branch_delim
n'apparaisse pas dans les valeurs des clés, sinon
connectby pourrait rapporter des erreurs de récursion
infinie totalement erronées. Notez que si
branch_delim n'est pas fourni, une valeur par défaut
~ est utilisé pour des raisons de détection de
récursion.
Voici un exemple :
CREATE TABLE connectby_tree(keyid text, parent_keyid text, pos int);
INSERT INTO connectby_tree VALUES('row1',NULL, 0);
INSERT INTO connectby_tree VALUES('row2','row1', 0);
INSERT INTO connectby_tree VALUES('row3','row1', 0);
INSERT INTO connectby_tree VALUES('row4','row2', 1);
INSERT INTO connectby_tree VALUES('row5','row2', 0);
INSERT INTO connectby_tree VALUES('row6','row4', 0);
INSERT INTO connectby_tree VALUES('row7','row3', 0);
INSERT INTO connectby_tree VALUES('row8','row6', 0);
INSERT INTO connectby_tree VALUES('row9','row5', 0);
-- with branch, without orderby_fld (order of results is not guaranteed)
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'row2', 0, '~')
AS t(keyid text, parent_keyid text, level int, branch text);
keyid | parent_keyid | level | branch
-------+--------------+-------+---------------------
row2 | | 0 | row2
row4 | row2 | 1 | row2~row4
row6 | row4 | 2 | row2~row4~row6
row8 | row6 | 3 | row2~row4~row6~row8
row5 | row2 | 1 | row2~row5
row9 | row5 | 2 | row2~row5~row9
(6 rows)
-- without branch, without orderby_fld (order of results is not guaranteed)
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'row2', 0)
AS t(keyid text, parent_keyid text, level int);
keyid | parent_keyid | level
-------+--------------+-------
row2 | | 0
row4 | row2 | 1
row6 | row4 | 2
row8 | row6 | 3
row5 | row2 | 1
row9 | row5 | 2
(6 rows)
-- with branch, with orderby_fld (notice that row5 comes before row4)
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~')
AS t(keyid text, parent_keyid text, level int, branch text, pos int);
keyid | parent_keyid | level | branch | pos
-------+--------------+-------+---------------------+-----
row2 | | 0 | row2 | 1
row5 | row2 | 1 | row2~row5 | 2
row9 | row5 | 2 | row2~row5~row9 | 3
row4 | row2 | 1 | row2~row4 | 4
row6 | row4 | 2 | row2~row4~row6 | 5
row8 | row6 | 3 | row2~row4~row6~row8 | 6
(6 rows)
-- without branch, with orderby_fld (notice that row5 comes before row4)
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'pos', 'row2', 0)
AS t(keyid text, parent_keyid text, level int, pos int);
keyid | parent_keyid | level | pos
-------+--------------+-------+-----
row2 | | 0 | 1
row5 | row2 | 1 | 2
row9 | row5 | 2 | 3
row4 | row2 | 1 | 4
row6 | row4 | 2 | 5
row8 | row6 | 3 | 6
(6 rows)
Joe Conway