PostgreSQLLa base de données la plus sophistiquée au monde.

8.10. Tableaux

PostgreSQL™ permet de définir des colonnes de table comme des tableaux multidimensionnels de longueur variable. Il est possible de créer des tableaux de n'importe quel type, même utilisateur. Toutefois, les tableaux de type composite ou de domaines ne sont pas encore supportés.

8.10.1. Déclaration des types de tableaux

La création de la table suivante permet d'illustrer l'utilisation des types tableaux :

CREATE TABLE sal_emp (
    nom              text,
    paye_par_semaine integer[],
    planning         text[][]
);

Comme indiqué ci-dessus, un type de données tableau est nommé en ajoutant des crochets ([]) au type de données des éléments du tableau. La commande ci-dessus crée une table nommée sal_emp avec une colonne de type text (nom), un tableau à une dimension de type integer (paye_par_semaine), représentant le salaire d'un employé par semaine et un tableau à deux dimensions de type text (planning), représentant le planning hebdomadaire de l'employé.

La syntaxe de CREATE TABLE permet de préciser la taille exacte des tableaux, par exemple :

CREATE TABLE tictactoe (
    carres   integer[3][3]
);

Néanmoins, les développements actuels n'imposent pas le respect de la taille du tableau -- le comportement est identique à celui des tableaux dont la longueur n'est pas précisée.

En fait, l'implantation actuelle n'oblige pas non plus à déclarer le nombre de dimensions. Les tableaux d'un type d'élément particulier sont tous considérés comme étant du même type, quelque soit leur taille ou le nombre de dimensions. Déclarer le nombre de dimensions ou la taille dans CREATE TABLE n'a qu'un but documentaire. Le comportement de l'application n'en est pas affecté.

Une autre syntaxe, conforme au standard SQL, peut être utilisée pour les tableaux à une dimension. paye_par_semaine peut être définie ainsi :

paye_par_semaine  integer ARRAY[4],

Cette syntaxe nécessite une constante de type entier pour indiquer la taille du tableau. Néanmoins, comme indiqué précédemment, PostgreSQL™ n'impose aucune restriction sur la taille.

8.10.2. Saisie de valeurs de type tableau

Pour écrire une valeur de type tableau comme une constante littérale, on encadre les valeurs des éléments par des accolades et on les sépare par des virgules (ce n'est pas différent de la syntaxe C utilisée pour initialiser les structures). Des guillemets doubles peuvent être positionnés autour des valeurs des éléments. C'est d'ailleurs obligatoire si elles contiennent des virgules ou des accolades (plus de détails ci-dessous). Le format général d'une constante de type tableau est donc le suivant :

'{ val1 delim val2 delim ... }'

delim est le caractère de délimitation pour ce type, tel qu'il est enregistré dans son entrée pg_type. Parmi les types de données standard fournis par la distribution PostgreSQL™, le type box utilise un point-virgule (;) mais tous les autres utilisent une virgule (,). Chaque val est soit une constante du type des éléments du tableau soit un sous-tableau. Voici un exemple d'une constante tableau

'{{1,2,3},{4,5,6},{7,8,9}}'

Cette constante a deux dimensions, un tableau 3 par 3 consistant en trois sous-tableaux d'entiers.

Pour initialiser un élément d'un tableau à NULL, on écrit NULL pour la valeur de cet élément. (Toute variante majuscule et/ou minuscule de NULL est acceptée.) Si « NULL » doit être utilisé comme valeur de chaîne, on place des guillemets doubles autour.

Ces types de constantes de tableau sont en fait un cas particulier des constantes de type générique abordées dans la Section 4.1.2.5, « Constantes d'autres types ». La constante est traitée initialement comme une chaîne et passée à la routine de conversion d'entrées de tableau. Une spécification explicite du type peut être nécessaire.

Quelques instructions INSERT.

INSERT INTO sal_emp
    VALUES ('Bill',
    '{10000, 10000, 10000, 10000}',
    '{{"rendez-vous", "repas"}, {"entrainement", "présentation"}}');

INSERT INTO sal_emp
    VALUES ('Carol',
    '{20000, 25000, 25000, 25000}',
    '{{"petit-déjeuner", "consultation"}, {"rendez-vous", "repas"}}');

Le résultat des deux insertions précédentes ressemble à ceci :

SELECT * FROM sal_emp;
 nom   |      paye_par_semaine     |      planning
-------+---------------------------+--------------------
Bill   | {10000,10000,10000,10000} | {{rendez-vous,repas},{entrainement,présentation}}
Carol  | {20000,25000,25000,25000} | {{petit-déjeuner,consultation},{rendez-vous,repas}}
(2 rows)

La syntaxe du constructeur ARRAY peut aussi être utilisée :

INSERT INTO sal_emp
    VALUES ('Bill',
    ARRAY[10000, 10000, 10000, 10000],
    ARRAY[['rendez-vous', 'repas'], ['entrainement','présentation']]);

INSERT INTO sal_emp
    VALUES ('Carol',
    ARRAY[20000, 25000, 25000, 25000],
    ARRAY[['petit-déjeuner', 'consultation'], ['rendez-vous', 'repas']]);

Les éléments du tableau sont des constantes SQL ordinaires ou des expressions ; par exemple, les chaînes de caractères littérales sont encadrées par des guillemets simples au lieu de guillemets doubles comme cela est le cas dans un tableau littéral. La syntaxe du constructeur ARRAY est discutée plus en profondeur dans la Section 4.2.10, « Constructeurs de tableaux ».

Les tableaux multi-dimensionnels doivent avoir des échelles correspondantes pour chaque dimension. Une différence cause la levée d'une erreur. Par exemple :

INSERT INTO sal_emp
    VALUES ('Bill',
    '{10000, 10000, 10000, 10000}',
    '{{"rendez-vous", "repas"}, {"rendez-vous"}}');
ERROR:  multidimensional arrays must have array expressions with matching dimensions

8.10.3. Accès aux tableaux

Quelques requêtes lancées sur la table permettent d'éclairer le propos précédent. Tout d'abord, l'accès à un seul élément du tableau à la fois. Cette requête retrouve le nom des employés dont la paye a changé au cours de la deuxième semaine :

SELECT nom FROM sal_emp WHERE paye_par_semaine[1] <> paye_par_semaine[2];

 nom
-------
 Carol
(1 row)

Les indices du tableau sont écrits entre crochets. Par défaut, PostgreSQL™ utilise la convention des indices commençant à 1 pour les tableaux, c'est-à-dire un tableau à n éléments commence avec array[1] et finit avec array[n].

Récupérer la paye de la troisième semaine de tous les employés :

SELECT paye_par_semaine[3] FROM sal_emp;

 paye_par_semaine
------------------
          10000
          25000
(2 rows)

Il est également possible d'accéder à des parties rectangulaires arbitraires ou à des sous-tableaux. Une partie de tableau est indiquée par l'écriture extrémité basse:extrémité haute sur n'importe quelle dimension. Ainsi, La requête suivante retourne le premier élément du planning de Bill pour les deux premiers jours de la semaine :

SELECT planning[1:2][1:1] FROM sal_emp WHERE nom = 'Bill';

      planning
--------------------
 {{rendez-vous},{entrainement}}
(1 row)

Si l'une des dimensions est écrite comme une partie, c'est-à-dire si elle contient le caractère deux-points, alors toutes les dimensions sont traitées comme des parties. Si une dimension manque, elle est supposée valoir [1:1]. Si une dimension n'a qu'un numéro (pas de deux-points), elle est traitée comme allant de 1 au nombre indiqué. Par exemple, [2] est traitée comme [1:2], comme le montre cet exemple :

SELECT planning[1:2][2] FROM sal_emp WHERE nom = 'Bill';

         planning
---------------------------
 {{rendez-vous,repas},{entrainement,présentation}}
(1 row)

Une expression indicée de tableau retourne NULL si, soit le tableau, soit une des expressions est NULL. De plus, NULL est renvoyé si un indice se trouve en dehors de la plage du tableau (ce cas n'amène pas d'erreur). Par exemple, si planning a les dimensions [1:3][1:2], alors faire référence à planning[3][3] donne un résultat NULL. De la même façon, une référence sur un tableau avec une valeur d'indices incorrecte retourne une valeur NULL plutôt qu'une erreur.

Une expression de découpage d'un tableau est aussi NULL si, soit le tableau, soit une des expressions indicées est NULL. Néanmoins, dans certains cas particuliers comme la sélection d'une partie d'un tableau complètement en dehors de la plage de ce dernier, l'expression de cette partie est un tableau vide (zéro dimension) et non pas un tableau NULL. Si la partie demandée surcharge partiellement les limites du tableau, alors elle est réduite silencieusement à la partie surchargée.

Les dimensions actuelles de toute valeur d'un tableau sont disponibles avec la fonction array_dims :

SELECT array_dims(planning) FROM sal_emp WHERE nom = 'Carol';

 array_dims
------------
 [1:2][1:2]
(1 row)

array_dims donne un résultat de type text, ce qui est pratique à lire mais peut s'avérer plus difficile à interpréter par les programmes. Les dimensions sont aussi récupérables avec array_upper et array_lower, qui renvoient respectivement la limite haute et et la limite basse du tableau précisé.

SELECT array_upper(planning, 1) FROM sal_emp WHERE nom = 'Carol';

 array_upper
-------------
           2
(1 row)

8.10.4. Modification de tableaux

La valeur d'un tableau peut être complètement remplacée :

UPDATE sal_emp SET paye_par_semaine = '{25000,25000,27000,27000}'
    WHERE nom = 'Carol';

ou en utilisant la syntaxe de l'expression ARRAY :

UPDATE sal_emp SET paye_par_semaine = ARRAY[25000,25000,27000,27000]
    WHERE nom = 'Carol';

On peut aussi mettre à jour un seul élément d'un tableau :

UPDATE sal_emp SET paye_par_semaine[4] = 15000
    WHERE nom = 'Bill';

ou faire une mise à jour par tranche :

UPDATE sal_emp SET paye_par_semaine[1:2] = '{27000,27000}'
    WHERE nom = 'Carol';

Un tableau peut être agrandi en y stockant des éléments qui n'y sont pas déjà présents. Toute position entre ceux déjà présents et les nouveaux éléments est remplie avec la valeur NULL. Par exemple, si le tableau mon_tableau a actuellement quatre éléments, il en aura six après une mise à jour qui affecte mon_tableau[6] car mon_tableau[5] est alors rempli avec une valeur NULL. Actuellement, l'agrandissement de cette façon est seulement autorisé pour les tableaux à une dimension, et non pas pour les tableaux multidimensionnels.

L'affectation par parties d'un tableau permet la création de tableaux dont l'indice de départ n'est pas 1. On peut ainsi affecter, par exemple, mon_tableau[-2:7] pour créer un tableau avec des valeurs d'indices allant de -2 à 7.

Les valeurs de nouveaux tableaux peuvent aussi être construites en utilisant l'opérateur de concaténation, ||.

SELECT ARRAY[1,2] || ARRAY[3,4];
   ?column?
---------------
 {1,2,3,4}
(1 row)

SELECT ARRAY[5,6] || ARRAY[[1,2],[3,4]];
      ?column?
---------------------
 {{5,6},{1,2},{3,4}}
(1 row)

L'opérateur de concaténation autorise un élément à être placé au début ou à la fin d'un tableau à une dimension. Il accepte aussi deux tableaux à N dimensions, ou un tableau à N dimensions et un à N+1 dimensions.

Quand un élément seul est poussé soit au début soit à la fin d'un tableau à une dimension, le résultat est un tableau avec le même indice bas que l'opérande du tableau. Par exemple :

SELECT array_dims(1 || '[0:1]={2,3}'::int[]);
 array_dims
------------
 [0:2]
(1 row)

SELECT array_dims(ARRAY[1,2] || 3);
 array_dims
------------
 [1:3]
(1 row)

Lorsque deux tableaux ayant un même nombre de dimensions sont concaténés, le résultat conserve la limite inférieure de l'opérande gauche. Le résultat est un tableau comprenant chaque élément de l'opérande gauche suivi de chaque élément de l'opérande droit. Par exemple :

SELECT array_dims(ARRAY[1,2] || ARRAY[3,4,5]);
 array_dims
------------
 [1:5]
(1 row)

SELECT array_dims(ARRAY[[1,2],[3,4]] || ARRAY[[5,6],[7,8],[9,0]]);
 array_dims
------------
 [1:5][1:2]
(1 row)

Lorsqu'un tableau à N dimensions est placé au début ou à la fin d'un tableau à N+1 dimensions, le résultat est analogue au cas ci-dessus. Chaque sous-tableau de dimension N est en quelque sorte un élément de la dimension externe d'un tableau à N+1 dimensions. Par exemple :

SELECT array_dims(ARRAY[1,2] || ARRAY[[3,4],[5,6]]);
 array_dims
------------
 [1:3][1:2]
(1 row)

Un tableau peut aussi être construit en utilisant les fonctions array_prepend, array_append ou array_cat. Les deux premières ne supportent que les tableaux à une dimension alors que array_cat supporte les tableaux multidimensionnels. L'opérateur de concaténation vu plus haut est préférable à l'utilisation directe de ces fonctions. En fait, les fonctions existent principalement pour l'implantation de l'opérateur de concaténation. Néanmoins, elles peuvent être directement utiles dans la création d'agrégats utilisateur. Quelques exemples :

SELECT array_prepend(1, ARRAY[2,3]);
 array_prepend
---------------
 {1,2,3}
(1 row)

SELECT array_append(ARRAY[1,2], 3);
 array_append
--------------
 {1,2,3}
(1 row)

SELECT array_cat(ARRAY[1,2], ARRAY[3,4]);
   array_cat
---------------
 {1,2,3,4}
(1 row)

SELECT array_cat(ARRAY[[1,2],[3,4]], ARRAY[5,6]);
      array_cat
---------------------
 {{1,2},{3,4},{5,6}}
(1 row)

SELECT array_cat(ARRAY[5,6], ARRAY[[1,2],[3,4]]);
      array_cat
---------------------
 {{5,6},{1,2},{3,4}}

8.10.5. Recherche dans les tableaux

Pour rechercher une valeur dans un tableau, il faut vérifier chaque valeur dans le tableau. Ceci peut se faire à la main lorque la taille du tableau est connue. Par exemple :

SELECT * FROM sal_emp WHERE paye_par_semaine[1] = 10000 OR
                            paye_par_semaine[2] = 10000 OR
                            paye_par_semaine[3] = 10000 OR
                            paye_par_semaine[4] = 10000;

Ceci devient toutefois rapidement fastidieux pour les gros tableaux et n'est pas très utile si la taille du tableau n'est pas connue. Une autre méthode est décrite dans la Section 9.17, « Comparaisons de lignes et de tableaux ». La requête ci-dessus est remplaçable par :

SELECT * FROM sal_emp WHERE 10000 = ANY (paye_par_semaine);

De la même façon, on trouve les lignes où le tableau n'a que des valeurs égales à 10000 avec :

SELECT * FROM sal_emp WHERE 10000 = ALL (paye_par_semaine);
[Astuce]

Astuce

Les tableaux ne sont pas toujours initialisés ; rechercher des éléments spécifiques dans un tableau peut être un signe d'une mauvaise conception de la base de données. On utilise plutôt une table séparée avec une ligne pour chaque élément faisant parti du tableau. Cela simplifie la recherche et fonctionne mieux dans le cas d'un grand nombre d'éléments.

8.10.6. Syntaxe d'entrée et de sortie des tableaux

La représentation externe du type texte d'une valeur de tableau consiste en des éléments interprétés suivant les règles de conversion d'entrées/sorties pour le type de l'élément du tableau, plus des décorations indiquant la structure du tableau. L'affichage est constitué d'accolades ({ et }) autour des valeurs du tableau et de caractères de délimitation entre éléments adjacents. Le caractère délimiteur est habituellement une virgule (,) mais peut être différent : il est déterminé par le paramètre typdelim du type de l'élément tableau (parmi les types de données standards supportés par l'implémentation de PostgreSQL™, le type box utilise un point-virgule (;) mais tous les autres utilisent la virgule). Dans un tableau multidimensionnel, chaque dimension (row, plane, cube, etc.) utilise son propre niveau d'accolades et les délimiteurs doivent être utilisés entre des entités adjacentes au sein d'accolades de même niveau.

La routine de sortie du tableau place des guillemets doubles autour des valeurs des éléments si ce sont des chaînes vides, si elles contiennent des accolades, des caractères délimiteurs, des guillemets doubles, des antislashs ou des espaces ou si elles correspondent à NULL. Les guillemets doubles et les antislashs intégrés aux valeurs des éléments sont échappés à l'aide d'un antislash. Pour les types de données numériques, on peut supposer sans risque que les doubles guillemets n'apparaissent jamais, mais pour les types de données texte, il faut être préparé à gérer la présence et l'absence de guillemets.

Par défaut, la valeur de la limite basse d'un tableau est initialisée à 1. Pour représenter des tableaux avec des limites basses différentes, les indices du tableau doivent être indiqués explicitement avant d'écrire le contenu du tableau. Cet affichage est consititué de crochets ([]) autour de chaque limite basse et haute d'une dimension avec un délimiteur deux-points (:) entre les deux. L'affichage des dimensions du tableau est suivie par un signe d'égalité (=). Par exemple :

SELECT f1[1][-2][3] AS e1, f1[1][-1][5] AS e2
 FROM (SELECT '[1:1][-2:-1][3:5]={{{1,2,3},{4,5,6}}}'::int[] AS f1) AS ss;

 e1 | e2
----+----
  1 |  6
(1 row)

La routine de sortie du tableau inclut les dimensions explicites dans le resultat uniquement lorsqu'au moins une limite basse est différente de 1.

Si la valeur écrite pour un élément est NULL (toute variante), l'élément est considéré NULL. La présence de guillemets ou d'antislashs désactive ce fonctionnement et autorise la saisie de la valeur litérale de la chaîne « NULL ». De plus, pour une compatibilité ascendante avec les versions antérieures à la version 8.2 de PostgreSQL™, le paramètre de configuration array_nulls doit être désactivé (off) pour supprimer la reconnaissance de NULL comme un NULL.

Comme indiqué précédemment, lors de l'écriture d'une valeur de tableau, des guillemets doubles peuvent être positionnés autour de chaque élément individuel du tableau. Il faut le faire si leur absence autour d'un élément induit en erreur l'analyseur de la valeur du tableau. Par exemple, les éléments contenant des crochets, virgules (ou tout caractère délimiteur), guillemets doubles, antislashs ou espace (en début comme en fin) doivent avoir des guillemets doubles. Les chaînes vides et les chaînes NULL doivent aussi être entre guillemets. Pour placer un guillemet double ou un antislash dans une valeur d'élément d'un tableau, on utilise la syntaxe d'échappement des chaînes et on le précède d'un antislash. Au-delà, tous les caractères de données qui sont utilisés dans la syntaxe du tableau peuvent être échappés.

Des espaces peuvent être ajoutées avant un crochet gauche ou après un crochet droit. Comme avant tout élément individuel. Dans tous les cas, les espaces sont ignorées. En revanche, les espaces à l'intérieur des éléments entre guillemets doubles ou entourées de caractères autres que des espaces ne sont pas ignorées.

[Note]

Note

Toute ce qui est écrit dans une commande SQL est d'abord interprété en tant que chaîne littérale puis en tant que tableau. Ceci double le nombre d'antislash nécessaire. Par exemple, pour insérer une valeur de tableau de type text contenant un antislash et un guillemet double, il faut écrire

INSERT ... VALUES (E'{"\\\\","\\""}');

Le processeur de la chaîne d'échappement supprime un niveau d'antislash, donc l'analyseur de tableau reçoit {"\\","\""}. En conséquence, les chaînes remplissant l'entrée du type de données text deviennent respectivement \ et ". (Si la routine d'entrée du type de données utilisé traite aussi les antislash de manière spéciale, bytea par exemple, il peut être nécessaire d'avoir jusqu'à huit antislash dans la commande pour en obtenir un dans l'élément stocké.) Les guillemets dollar (voir Section 4.1.2.2, « Constantes de chaînes avec guillemet dollar ») peuvent être utilisés pour éviter de doubler les antislash.

[Astuce]

Astuce

La syntaxe du constructeur ARRAY (voir Section 4.2.10, « Constructeurs de tableaux ») est souvent plus facile à utiliser que la syntaxe de tableau littéral lors de l'écriture des valeurs du tableau en commandes SQL. Avec ARRAY, les valeurs de l'élément individuel sont écrites comme elles le seraient si elles ne faisaient pas partie d'un tableau.