L'insertion d'une seule ligne dans une table est ce qui vient à l'esprit lorsque vous pensez à l'instruction INSERT dans PostgreSQL. Il a cependant quelques tours de plus dans sa manche ! Lisez la suite pour découvrir certaines des choses les plus intéressantes que vous pouvez faire avec INSERT.
Copier en masse
Supposons que vous souhaitiez capturer périodiquement des instantanés d'une table - toutes les lignes de la table doivent être copiées dans une autre table, avec une colonne d'horodatage supplémentaire indiquant quand l'instantané a été pris. Voici comment vous pouvez créer et remplir le tableau la première fois :
demo=# SELECT * FROM mytable;
ticker | quote
--------+-------
FOO | $4.01
BAR | $1.42
(2 rows)
demo=# CREATE TABLE snaps_of_mytable AS
demo-# SELECT current_timestamp AS snapped_at, *
demo-# FROM mytable;
SELECT 2
demo=#
demo=# SELECT * FROM snaps_of_mytable ;
snapped_at | ticker | quote
-----------------------------+--------+-------
2018-10-09 04:16:22.3613+00 | FOO | $4.01
2018-10-09 04:16:22.3613+00 | BAR | $1.42
(2 rows)
Et à partir de là, vous pouvez utiliser le INSERT..SELECT
forme d'instruction INSERT pour copier des lignes d'une table et les insérer dans une autre. Vous pouvez également saisir des valeurs supplémentaires dans la ligne du tableau de destination.
demo=# INSERT INTO snaps_of_mytable
demo-# SELECT current_timestamp AS snapped_at, *
demo-# FROM mytable;
INSERT 0 2
demo=#
demo=# SELECT * FROM snaps_of_mytable ;
snapped_at | ticker | quote
-------------------------------+--------+-------
2018-10-09 04:16:22.3613+00 | FOO | $4.01
2018-10-09 04:16:22.3613+00 | BAR | $1.42
2018-10-09 04:18:53.432224+00 | BAR | $1.42
2018-10-09 04:18:53.432224+00 | FOO | $4.10
(4 rows)
Upserts
Dans PostgreSQL 9.5, le ON CONFLICT
clause a été ajoutée à INSERT. Cela permet aux développeurs d'applications d'écrire moins de code et de travailler plus en SQL.
Voici un tableau des paires clé, valeur :
demo=# SELECT * FROM kv;
key | value
------+-----------
host | 127.0.0.1
port | 5432
(2 rows)
Un cas d'utilisation courant consiste à insérer une ligne uniquement si elle n'existe pas - et si c'est le cas, ne pas écraser. Ceci est fait avec le ON CONFLICT..DO NOTHING
clause de l'instruction INSERT :
demo=# INSERT INTO kv (key, value) VALUES ('port', '3306')
demo-# ON CONFLICT (key) DO NOTHING;
INSERT 0 0
demo=# SELECT * FROM kv;
key | value
------+-----------
host | 127.0.0.1
port | 5432
(2 rows)
Une autre utilisation courante consiste à insérer une ligne si elle n'existe pas et à mettre à jour la valeur, si elle existe. Cela peut être fait avec le ON CONFLICT..DO UPDATE
clause.
demo=# INSERT INTO kv (key, value) VALUES ('host', '10.0.10.1')
demo-# ON CONFLICT (key) DO UPDATE SET value=EXCLUDED.value;
INSERT 0 1
demo=# INSERT INTO kv (key, value) VALUES ('ssl', 'off')
demo-# ON CONFLICT (key) DO UPDATE SET value=EXCLUDED.value;
INSERT 0 1
demo=# SELECT * FROM kv;
key | value
------+-----------
host | 10.0.10.1
port | 5432
ssl | off
(3 rows)
Dans le premier cas ci-dessus, la valeur de 'host' a été remplacée par la nouvelle valeur, et dans le second cas, la valeur de 'ssl' a été insérée en troisième ligne.
Des cas d'utilisation encore plus sophistiqués peuvent être réalisés avec DO UPDATE
. Considérez le tableau ci-dessous, où en plus de la clé et de la valeur, il y a une colonne appelée "accumuler". Pour les lignes où cumul vaut true, les valeurs sont censées être cumulées sous la forme d'une chaîne séparée par des virgules. Pour les autres lignes, les valeurs sont à valeur unique.
demo=# CREATE TABLE kv2 (
demo(# key text PRIMARY KEY,
demo(# accumulate boolean NOT NULL DEFAULT false,
demo(# value text
demo(# );
CREATE TABLE
demo=# INSERT INTO kv2 VAlUES
demo-# ('port', false, '5432'),
demo-# ('listen', true, NULL);
INSERT 0 2
demo=# SELECT * FROM kv2;
key | accumulate | value
--------+------------+-------
port | f | 5432
listen | t |
(2 rows)
Le WHERE
La clause peut être utilisée soit pour écraser la colonne "value", soit pour y ajouter, selon la valeur de "accumulate", comme ceci :
demo=# INSERT INTO kv2 AS t (key, value) VALUES ('port', '3306')
demo-# ON CONFLICT (key) DO UPDATE SET value = concat_ws(',', t.value, EXCLUDED.value)
demo-# WHERE t.accumulate;
INSERT 0 0
demo=# INSERT INTO kv2 AS t (key, value) VALUES ('listen', '127.0.0.1')
demo-# ON CONFLICT (key) DO UPDATE SET value = concat_ws(',', t.value, EXCLUDED.value)
demo-# WHERE t.accumulate;
INSERT 0 1
demo=# INSERT INTO kv2 AS t (key, value) VALUES ('listen', '10.0.10.1')
demo-# ON CONFLICT (key) DO UPDATE SET value = concat_ws(',', t.value, EXCLUDED.value)
demo-# WHERE t.accumulate;
INSERT 0 1
demo=# SELECT * FROM kv2;
key | accumulate | value
--------+------------+---------------------
port | f | 5432
listen | t | 127.0.0.1,10.0.10.1
(2 rows)
La première instruction n'a pas accumulé la valeur de '3306' dans 'port' car 'accumulate' était désactivé pour cette ligne. Les deux instructions suivantes ont ajouté les valeurs "127.0.0.1" et "10.0.10.1" à la valeur de "écouter", car "accumuler" était vrai.
Renvoyer les valeurs générées
Les valeurs générées par PostgreSQL lors de l'insertion, telles que les valeurs par défaut ou les valeurs SERIAL auto-incrémentées, peuvent être renvoyées à l'aide du RETURNING
clause de l'instruction INSERT.
Supposons que vous deviez générer des UUID aléatoires en tant que clés pour les lignes d'une table. Vous pouvez laisser PostgreSQL faire le travail de génération des UUID et qu'il vous renvoie la valeur générée comme ceci :
demo=# INSERT INTO kv (key, value) VALUES (gen_random_uuid(), 'foo') RETURNING key;
key
--------------------------------------
d93ceaa5-30a8-4285-83c5-7defa79e2f90
(1 row)
INSERT 0 1
demo=# INSERT INTO kv (key, value) VALUES (gen_random_uuid(), 'bar') RETURNING key;
key
--------------------------------------
caf9c5d9-9a79-4b26-877f-a75a083b0c79
(1 row)
INSERT 0 1
demo=# SELECT * FROM kv;
key | value
--------------------------------------+-------
d93ceaa5-30a8-4285-83c5-7defa79e2f90 | foo
caf9c5d9-9a79-4b26-877f-a75a083b0c79 | bar
(2 rows)
Déplacer des lignes avec des clauses CTE
Vous pouvez même déplacer des lignes entre les tables avec INSERT, en utilisant le WITH
clause.Voici deux tableaux avec des listes de tâches pour différentes années.
demo=# SELECT * FROM todos_2018;
what | done
----------------+------
thing to do #1 | t
thing to do #2 | t
thing to do #3 | f
(3 rows)
demo=# SELECT * FROM todos_2019;
what | done
------+------
(0 rows)
Pour déplacer les tâches qui ne sont pas encore terminées en 2018 vers 2019, vous pouvez essentiellement supprimer ces lignes du tableau 2018 et les insérer dans le tableau 2019 en une seule fois :
demo=# WITH items AS (
demo(# DELETE FROM todos_2018
demo(# WHERE NOT done
demo(# RETURNING *
demo(# )
demo-# INSERT INTO todos_2019 SELECT * FROM items;
INSERT 0 1
demo=# SELECT * FROM todos_2018;
what | done
----------------+------
thing to do #1 | t
thing to do #2 | t
(2 rows)
demo=# SELECT * FROM todos_2019;
what | done
----------------+------
thing to do #3 | f
(1 row)
Pour en savoir plus sur la petite instruction INSERT intelligente, consultez la documentation et expérimentez !