Documentation PostgreSQL 15.15 » Langage SQL » Types de données » Types composites

Précédent | Niveau supérieur | Suivant
8.15. Tableaux | Sommaire | 8.17. Types intervalle de valeurs

## 8.16. Types composites

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.

### 8.16.1. Déclaration de types composites

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.)

### 8.16.2. Construire des valeurs composites

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.

### 8.16.3. Accéder aux types composites

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.

### 8.16.4. Modifier les types composites

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.

### 8.16.5. Utiliser des types composites dans les requêtes

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;

### Astuce

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`, 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 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 ``champ`(`table`)` et ``table`.`champ`` sont interchangeables. Par exemple, ces requêtes sont équivalentes :

    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.

### Astuce

À 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`)`.

### 8.16.6. Syntaxe en entrée et sortie d'un type composite

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.

### Note

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.

### Astuce

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'elles l'auraient été en n'étant pas membres du composite.