Un type composite représente la structure d'une ligne ou d'un enregistrement ; il est en essence une simple liste de noms de champs et de leurs types de données. PostgreSQL autorise l'utilisation de types composites identiques de plusieurs façons à l'utilisation des types simples. Par exemple, une colonne d'une table peut être déclarée comme étant de type composite.
Voici deux exemples simples de définition de types composites :
CREATE TYPE complexe AS ( r double precision, i double precision ); CREATE TYPE element_inventaire AS ( nom text, id_fournisseur integer, prix numeric );
La syntaxe est comparable à CREATE TABLE
, sauf que seuls les noms
de champs et leurs types peuvent être spécifiés ; aucune contrainte (telle
que NOT NULL
) ne peut être incluse actuellement. Notez que le mot-clé
AS
est essentiel ; sans lui, le système penserait à un
autre genre de commande CREATE TYPE
et vous obtiendriez d'étranges
erreurs de syntaxe.
Après avoir défini les types, nous pouvons les utiliser pour créer des tables :
CREATE TABLE disponible ( element element_inventaire, nombre integer ); INSERT INTO disponible VALUES (ROW('fuzzy dice', 42, 1.99), 1000);
ou des fonctions :
CREATE FUNCTION prix_extension(element_inventaire, integer) RETURNS numeric AS 'SELECT $1.prix * $2' LANGUAGE SQL; SELECT prix_extension(element, 10) FROM disponible;
Quand vous créez une table, un type composite est automatiquement créé, avec le même nom que la table, pour représenter le type de ligne de la table. Par exemple, si nous avions dit :
CREATE TABLE element_inventaire ( nom text, id_fournisseur integer REFERENCES fournisseur, prix numeric CHECK (prix > 0) );
alors le même type composite element_inventaire
montré ci-dessus
aurait été créé et pourrait être utilisé comme ci-dessus. Néanmoins, notez
une restriction importante de l'implémentation actuelle : comme aucune
contrainte n'est associée avec un type composite, les contraintes indiquées
dans la définition de la table ne sont pas appliquées
aux valeurs du type composite en dehors de la table. (Pour contourner ceci,
créer un domaine
sur le type composite, et appliquer les contraintes désirées
en tant que contraintes CHECK
du domaine.)
Pour écrire une valeur composite comme une constante littérale, englobez les valeurs du champ dans des parenthèses et séparez-les par des virgules. Vous pouvez placer des guillemets doubles autour de chaque valeur de champ et vous devez le faire si elle contient des virgules ou des parenthèses (plus de détails ci-dessous). Donc, le format général d'une constante composite est le suivant :
'(val1
,val2
, ... )'
Voici un exemple :
'("fuzzy dice",42,1.99)'
qui serait une valeur valide du type element_inventaire
défini
ci-dessus. Pour rendre un champ NULL, n'écrivez aucun caractère dans sa
position dans la liste. Par exemple, cette constante spécifie un troisième
champ NULL :
'("fuzzy dice",42,)'
Si vous voulez un champ vide au lieu d'une valeur NULL, saisissez deux guillemets :
'("",42,)'
Ici, le premier champ est une chaîne vide non NULL alors que le troisième est NULL.
(Ces constantes sont réellement seulement un cas spécial de constantes génériques de type discutées dans la Section 4.1.2.7. La constante est initialement traitée comme une chaîne et passée à la routine de conversion de l'entrée de type composite. Une spécification explicite de type pourrait être nécessaire pour préciser le type à utiliser pour la conversion de la constante.)
La syntaxe d'expression ROW
pourrait aussi être utilisée
pour construire des valeurs composites. Dans la plupart des cas, ceci est
considérablement plus simple à utiliser que la syntaxe de chaîne littérale, car
vous n'avez pas à vous inquiéter des multiples couches de guillemets. Nous avons
déjà utilisé cette méthode ci-dessus :
ROW('fuzzy dice', 42, 1.99) ROW('', 42, NULL)
Le mot-clé ROW est optionnel si vous avez plus d'un champ dans l'expression, donc ceci peut être simplifié avec
('fuzzy dice', 42, 1.99) ('', 42, NULL)
La syntaxe de l'expression ROW
est discutée avec plus de détails
dans la Section 4.2.13.
Pour accéder à un champ d'une colonne composite, vous pouvez écrire un point
et le nom du champ, un peu comme la sélection d'un champ à partir d'un nom
de table. En fait, c'est tellement similaire que vous pouvez souvent utiliser
des parenthèses pour éviter une confusion de l'analyseur. Par exemple, vous
pouvez essayer de sélectionner des sous-champs à partir de notre exemple de
table, disponible
, avec quelque chose comme :
SELECT element.nom FROM disponible WHERE element.prix > 9.99;
Ceci ne fonctionnera pas, car le nom element
est pris pour le nom
d'une table, et non pas d'une colonne de disponible
, suivant les
règles de la syntaxe SQL. Vous devez l'écrire ainsi :
SELECT (element).nom FROM disponible WHERE (element).prix > 9.99;
ou si vous avez aussi besoin d'utiliser le nom de la table (par exemple dans une requête multitable), de cette façon :
SELECT (disponible.element).nom FROM disponible WHERE (disponible.element).prix > 9.99;
Maintenant, l'objet entre parenthèses est correctement interprété comme une
référence à la colonne element
, puis le sous-champ peut être
sélectionné à partir de lui.
Des problèmes syntaxiques similaires s'appliquent quand vous sélectionnez un champ à partir d'une valeur composite. En fait, pour sélectionner un seul champ à partir du résultat d'une fonction renvoyant une valeur composite, vous aurez besoin d'écrire quelque chose comme :
SELECT (ma_fonction(...)).champ FROM ...
Sans les parenthèses supplémentaires, ceci provoquera une erreur.
Le nom du champ spécial *
signifie « tous les
champs », comme expliqué dans Section 8.16.5.
Voici quelques exemples de la bonne syntaxe pour insérer et mettre à jour des colonnes composites. Tout d'abord, pour insérer ou modifier une colonne entière :
INSERT INTO matab (col_complexe) VALUES((1.1,2.2)); UPDATE matab SET col_complexe = ROW(1.1,2.2) WHERE ...;
Le premier exemple omet ROW
, le deuxième l'utilise ; nous
pouvons le faire des deux façons.
Nous pouvons mettre à jour un sous-champ individuel d'une colonne composite :
UPDATE matab SET col_complexe.r = (col_complexe).r + 1 WHERE ...;
Notez ici que nous n'avons pas besoin de (et, en fait, ne pouvons pas) placer
des parenthèses autour des noms de colonnes apparaissant juste après
SET
, mais nous avons besoin de parenthèses lors de la référence à
la même colonne dans l'expression à droite du signe d'égalité.
Et nous pouvons aussi spécifier des sous-champs comme cibles de la commande
INSERT
:
INSERT INTO matab (col_complexe.r, col_complexe.i) VALUES(1.1, 2.2);
Si tous les sous-champs d'une colonne ne sont pas spécifiés, ils sont remplis avec une valeur NULL.
Il existe différentes règles spéciales de syntaxe et de différents comportements associés avec les types composites dans les requêtes. Ces règles fournissent des raccourcis utiles, mais peuvent être difficiles à appréhender si vous ne connaissez pas la logique qui y est associée.
Dans PostgreSQL, une référence à un nom de table
(ou à un alias) dans une requête est réellement une référence au type
composite de la ligne courante de la table. Par exemple, si nous avons une
table element_inventaire
comme définie ci-dessus, nous pouvons écrire :
SELECT c FROM element_inventaire c;
Cette requête renvoie une seule colonne comprenant une valeur composite, et nous pourrions obtenir l'affichage suivant :
c ------------------------ ("fuzzy dice",42,1.99) (1 row)
Il faut noter néanmoins que les noms simples (c.-à-d. sans qualifiant) sont
traités comme des noms de colonnes puis comme des noms de table s'il n'y a
pas de correspondance avec les noms de colonnes. Donc cet exemple
fonctionne seulement parce qu'il n'existe pas de colonne nommée
c
dans les tables de la requête.
La syntaxe habituelle avec des noms de colonne qualifiés (comme
nom_table
.
nom_colonne
)
peut se comprendre en appliquant la sélection de champs à la valeur composite
de la ligne actuelle de la table. (Pour des raisons d'efficacité, ce n'est
pas réellement implémenté de cette façon.)
Quand nous écrivons
SELECT c.* FROM element_inventaire c;
alors, d'après le standard SQL, nous devrions obtenir le contenu de la table étendu en des colonnes séparées :
nom | id_fournisseur | prix ------------+----------------+------- fuzzy dice | 42 | 1.99 (1 row)
comme si la requête avait été écrite ainsi :
SELECT c.nom, c.id_fournisseur, c.prix FROM element_inventaire c;
PostgreSQL appliquera ce comportement étendu à toute
expression de valeur composite, bien que, comme indiqué ci-dessus, il est nécessaire d'ajouter des
parenthèses autour de la valeur à qui .*
est appliquée à chaque
fois qu'il ne s'agit pas d'un nom de table. Par exemple, si ma_fonction()
est une fonction renvoyant un type composite avec les colonnes
a
,
b
et c
, alors ces deux
requêtes donnent le même résultat :
SELECT (ma_fonction(x)).* FROM une_table; SELECT (ma_fonction(x)).a, (ma_fonction(x)).b, (ma_fonction(x)).c FROM une_table;
PostgreSQL gère le fait d'étendre les colonnes
en transformant la première forme en la seconde. De ce fait, dans cet
exemple, ma_fonction()
serait appelé trois fois par
ligne, quelle que soit la syntaxe utilisée. S'il s'agit d'une fonction peu
performante, vous pourriez souhaiter éviter cela, ce que vous pouvez faire
avec une requête de ce type :
SELECT (m).* FROM (SELECT ma_fonction(x) AS m FROM une_table OFFSET 0) ss;
Placer la fonction dans un élément LATERAL
du
FROM
l'aide à ne pas être invoquée plus d'une fois par
ligne. m.*
est toujours étendu en m.a, m.b,
m.c
, mais maintenant ces variables sont juste des références à
la sortie de l'élément FROM
. (Le mot-clé
LATERAL
est optionnel ici, mais nous le montrons pour
clarifier que la fonction obtient x
de la
some_table
.)
La syntaxe valeur_composite
.*
étend les colonnes avec un résultat de ce type quand il apparaît au niveau haut
d'une liste en sortie du
SELECT
, d'une liste RETURNING
dans des commandes
INSERT
/UPDATE
/DELETE
/MERGE
, d'une
clause VALUES
, ou d'un
constructeur de ligne.
Dans tous les autres contextes (incluant l'imbrication dans une de ces constructions), attacher .*
à une
valeur composite value ne change pas la valeur, car cela signifie « toutes les colonnes » et donc la valeur composite
est produite de nouveau. Par exemple, si une_fonction()
accepte un argument de valeur composite,
ces requêtes ont un résultat identique :
SELECT une_fonction(c.*) FROM element_inventaire c; SELECT une_fonction(c) FROM element_inventaire c;
Dans les deux cas, la ligne courante de
element_inventaire
est passée à la fonction sous
la forme d'un seul argument de type composite. Même si
.*
ne fait rien dans de tels cas, l'utiliser est
intéressant, car il est clair à sa lecture qu'on attend une valeur
composite. En particulier, l'analyseur considérera c
dans c.*
comme une référence au nom de la table ou de
l'alias, et non pas comme un nom de colonne, pour qu'il n'y ait pas
d'ambiguïté. Sans le .*
, il n'est pas clair si
c
est un nom de table ou de colonne et, de ce fait,
l'interprétation préférée sera celle d'un nom de colonne si une colonne
nommée c
existe.
Voici un autre exemple démontrant ces concepts avec toutes ces requêtes qui ont la même signification :
SELECT * FROM element_inventaire c ORDER BY c; SELECT * FROM element_inventaire c ORDER BY c.*; SELECT * FROM element_inventaire c ORDER BY ROW(c.*);
Toutes ces clauses ORDER BY
indiquent la valeur
composite de la ligne. Néanmoins, si
element_inventaire
contenait une colonne nommée
c
, le premier cas serait différent des autres,
car le tri se ferait uniquement sur cette colonne. Avec les noms de colonne
indiqués précédemment, ces requêtes sont aussi équivalentes à
celles-ci :
SELECT * FROM element_inventaire c ORDER BY ROW(c.nom, c.id_fournisseur, c.prix); SELECT * FROM element_inventaire c ORDER BY (c.nom, c.id_fournisseur, c.prix);
(Le dernier cas utilise un constructeur de ligne avec le mot-clé
ROW
omis.)
Un autre comportement syntaxique spécial avec les valeurs composites est
que nous pouvons utiliser la notation fonctionnelle
pour extraire un champ d'une valeur composite. La façon simple d'expliquer
ceci est que les notations
et champ
(table
)
sont interchangeables. Par exemple, ces requêtes sont équivalentes :
table
.champ
SELECT c.nom FROM element_inventaire c WHERE c.prix > 1000; SELECT nom(c) FROM element_inventaire c WHERE prix(c) > 1000;
De plus, si nous avons une fonction qui accepte un seul argument de type composite, nous pouvons l'appeler avec une de ces notations. Ces requêtes sont toutes équivalentes :
SELECT une_fonction(c) FROM element_inventaire c; SELECT une_fonction(c.*) FROM element_inventaire c; SELECT c.une_fonction FROM element_inventaire c;
Cette équivalence entre la notation fonctionnelle et la notation par champ
rend possible l'utilisation de fonctions sur les types composites pour
implémenter les « champs calculés ».
Une application utilisant la dernière requête ci-dessus n'aurait pas besoin
d'être directement attentive au fait que une_fonction
n'est pas une vraie colonne de la table.
À cause de ce comportement, il est déconseillé de donner une fonction qui
prend un argument de type composite simple du même nom que n'importe quel
champ de ce type composite. S'il existe une ambiguïté, l'interprétation du
nom de champ sera choisie si la syntaxe de nom de champ est utilisée, alors
que la fonction sera choisie si la syntaxe d'appel de fonction est
utilisée. Néanmoins, les versions de PostgreSQL
antérieures à la 11 choisiront toujours l'interprétation du nom de champ,
sauf si la syntaxe de l'appel requiert un appel de fonction. Une façon de
forcer l'interprétation en fonction pour les versions antérieures est de
qualifier le nom de la fonction avec le nom du schéma, autrement dit
.
schéma
.fonction
(valeurcomposite
)
La représentation texte externe d'une valeur composite consiste en des
éléments qui sont interprétés suivant les règles de conversion
d'entrées/sorties pour les types de champs individuels, plus des décorations
indiquant la structure composite. Ces décorations consistent en des parenthèses
((
et )
) autour de la valeur entière, ainsi que des
virgules (,
) entre les éléments adjacents. Des espaces blancs en
dehors des parenthèses sont ignorés, mais à l'intérieur des parenthèses, ils
sont considérés comme faisant partie de la valeur du champ et pourraient ou non
être significatifs suivant les règles de conversion de l'entrée pour le type
de données du champ. Par exemple, dans :
'( 42)'
l'espace blanc sera ignoré si le type du champ est un entier, mais pas s'il s'agit d'un champ de type texte.
Comme indiqué précédemment, lors de l'écriture d'une valeur composite, vous pouvez utiliser des guillemets doubles autour de chaque valeur de champ individuel. Vous devez le faire si la valeur du champ était susceptible de gêner l'analyseur de la valeur du champ composite. En particulier, les champs contenant des parenthèses, des virgules, des guillemets doubles ou des antislashs doivent être entre guillemets doubles. Pour placer un guillemet double ou un antislash dans la valeur d'un champ composite entre guillemets, faites-le précéder d'un antislash. (De plus, une paire de guillemets doubles à l'intérieur d'une valeur de champ à guillemets doubles est prise pour représenter un caractère guillemet double, en analogie avec les règles des guillemets simples dans les chaînes SQL littérales.) Autrement, vous pouvez éviter les guillemets et utiliser l'échappement par antislash pour protéger tous les caractères de données qui auraient été pris pour une syntaxe composite.
Une valeur de champ composite vide (aucun caractère entre les virgules ou
parenthèses) représente une valeur NULL. Pour écrire une valeur qui est une
chaîne vide plutôt qu'une valeur NULL, écrivez ""
.
La routine de sortie composite placera des guillemets doubles autour des valeurs de champs si elles sont des chaînes vides ou si elles contiennent des parenthèses, virgules, guillemets doubles, antislash ou espaces blancs. (Faire ainsi pour les espaces blancs n'est pas essentiel, mais aide à la lecture.) Les guillemets doubles et antislashs dans les valeurs des champs seront doublés.
Rappelez-vous que ce que vous allez saisir dans une commande SQL sera tout
d'abord interprété comme une chaîne littérale, puis comme un composite. Ceci
double le nombre d'antislash dont vous avez besoin (en supposant que la syntaxe
d'échappement des chaînes soit utilisée). Par exemple, pour insérer
un champ text
contenant un guillemet double et un antislash dans
une valeur composite, vous devez écrire :
INSERT ... VALUES ('("\"\\")');
Le processeur des chaînes littérales supprime un niveau d'antislash de façon
que ce qui arrive à l'analyseur de valeurs composites ressemble à
("\"\\")
. À son tour, la chaîne remplie par la routine d'entrée
du type de données text
devient "\
. (Si nous étions en
train de travailler avec un type de données dont la routine d'entrée traite
aussi les antislashs spécialement, bytea
par exemple, nous
pourrions avoir besoin d'au plus huit antislashs dans la commande pour obtenir
un antislash dans le champ composite stocké.) Le guillemet dollar (voir Section 4.1.2.4) pourrait être utilisé pour éviter le
besoin des antislashs doublés.
La syntaxe du constructeur ROW
est habituellement plus simple à
utiliser que la syntaxe du littéral composite lors de l'écriture de valeurs
composites dans des commandes SQL. Dans ROW
, les valeurs
individuelles d'un champ sont écrites de la même façon qu'elle l'auraient été
en n'étant pas membres du composite.