Вмъкването на един ред в таблица е това, което идва на ум, когато мислите за израза INSERT в PostgreSQL. Той обаче има още няколко трика в ръкава на upit! Прочетете, за да откриете някои от по-интересните неща, които можете да правите с INSERT.
Групово копиране
Да приемем, че искате периодично да правите моментни снимки на таблица – всички редове в таблицата трябва да бъдат копирани в друга таблица, с допълнителна колона с времеви печат, обозначаваща кога е направена моментната снимка. Ето как можете да създадете и попълните таблицата за първи път:
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)
И оттам нататък можете да използвате INSERT..SELECT
форма на израз INSERT за копиране на редове от една таблица и вмъкване в друга. Можете също да попълните допълнителни стойности в реда на целевата таблица.
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
В PostgreSQL 9.5, ON CONFLICT
клаузата беше добавена към INSERT. Това позволява на разработчиците на приложения да пишат по-малко код и да вършат повече работа в SQL.
Ето таблица с двойки ключ, стойност:
demo=# SELECT * FROM kv;
key | value
------+-----------
host | 127.0.0.1
port | 5432
(2 rows)
Често срещан случай на използване е да вмъкнете ред само ако не съществува – и ако го има, не го презаписвайте. Това се прави с ON CONFLICT..DO NOTHING
клауза на оператора 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)
Друга често срещана употреба е да вмъкнете ред, ако не съществува, и да актуализирате стойността, ако съществува. Това може да стане с ON CONFLICT..DO UPDATE
клауза.
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)
В първия случай по-горе стойността на „host“ беше презаписана с новата стойност, а във втория случай стойността на „ssl“ беше вмъкната като трети ред.
Още по-сложни случаи на използване могат да бъдат реализирани с DO UPDATE
. Помислете за таблицата по-долу, където освен ключ и стойност има колона, наречена „натрупване“. За редове, където натрупването е вярно, стойностите са предназначени да бъдат акумулирани като разделен със запетая низ. За други редове стойностите са с една стойност.
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)
WHERE
Клаузата може да се използва или за презаписване на колоната „стойност“, или за добавяне към нея, в зависимост от стойността на „натрупване“, както следва:
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)
Първото изявление не натрупа стойността на „3306“ в „port“, тъй като „accumulate“ беше изключено за този ред. Следващите две изявления добавиха стойностите „127.0.0.1“ и „10.0.10.1“ към стойността на „слушай“, тъй като „акумулиране“ беше вярно.
Връщане на генерирани стойности
Стойности, генерирани от PostgreSQL по време на вмъкване, като стойности по подразбиране или автоматично увеличени SERIAL стойности, могат да бъдат върнати с помощта на RETURNING
клауза на оператора INSERT.
Да приемем, че трябва да генерирате произволни UUID като ключове за редове в таблица. Можете да оставите PostgreSQL да върши работата по генерирането на UUID и да ви върне генерираната стойност по следния начин:
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)
Преместване на редове с CTE клаузи
Можете дори да местите редове между таблици с INSERT, като използвате WITH
клауза. Ето две таблици със списъци със задачи за различни години.
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)
За да преместите елементите на задачи, които все още не са завършени през 2018 г. до 2019 г., можете основно да изтриете такива редове от таблицата за 2018 г. и да ги вмъкнете в таблицата за 2019 г. с един изстрел:
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)
За да научите повече за интелигентния малък израз INSERT, вижте документацията и експериментирайте!