Inserir uma única linha em uma tabela é o que vem à mente quando você pensa na instrução INSERT no PostgreSQL. No entanto, tem mais alguns truques na manga! Continue lendo para descobrir algumas das coisas mais interessantes que você pode fazer com INSERT.
Copiando em massa
Digamos que você queira capturar periodicamente snapshots de uma tabela – todas as linhas da tabela devem ser copiadas para outra tabela, com uma coluna de timestamp adicional indicando quando o snapshot foi tirado. Veja como você pode criar e preencher a tabela na primeira vez:
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)
E a partir de então, você pode usar o
INSERT..SELECT
forma de instrução INSERT para copiar linhas de uma tabela e inserir em outra. Você também pode preencher valores extras na linha da tabela de destino. 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
No PostgreSQL 9.5, o
ON CONFLICT
cláusula foi adicionada a INSERT. Isso permite que os desenvolvedores de aplicativos escrevam menos código e trabalhem mais em SQL. Aqui está uma tabela de pares de chave e valor:
demo=# SELECT * FROM kv;
key | value
------+-----------
host | 127.0.0.1
port | 5432
(2 rows)
Um caso de uso comum é inserir uma linha somente se ela não existir – e se existir, não sobrescreva. Isso é feito com o
ON CONFLICT..DO NOTHING
cláusula da instrução 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)
Outro uso comum é inserir uma linha se ela não existir e atualizar o valor, se existir. Isso pode ser feito com o
ON CONFLICT..DO UPDATE
cláusula. 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)
No primeiro caso acima o valor de 'host' foi substituído pelo novo valor, e no segundo caso o valor de 'ssl' foi inserido como terceira linha.
Casos de uso ainda mais sofisticados podem ser realizados com
DO UPDATE
. Considere a tabela abaixo, onde além de chave e valor, existe uma coluna chamada “acumular”. Para linhas em que acumular é verdadeiro, os valores devem ser acumulados como uma string separada por vírgulas. Para outras linhas, os valores são de valor único. 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)
O
WHERE
cláusula pode ser usada para sobrescrever a coluna “valor” ou anexá-la, dependendo do valor de “acumular”, assim: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)
A primeira instrução não acumulou o valor de '3306' em 'port' porque 'accumulate' estava desativado para essa linha. As duas declarações seguintes adicionaram os valores ‘127.0.0.1’ e ‘10.0.10.1’ ao valor de ‘ouvir’, porque ‘acumular’ era verdadeiro.
Retornando valores gerados
Valores gerados pelo PostgreSQL durante a inserção, como valores padrão ou valores SERIAL autoincrementados, podem ser retornados usando o
RETURNING
cláusula da instrução INSERT. Suponha que você precise gerar UUIDs aleatórios como chaves para linhas em uma tabela. Você pode deixar o PostgreSQL fazer o trabalho de gerar os UUIDs e fazer com que ele retorne o valor gerado para você assim:
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)
Movendo linhas com cláusulas CTE
Você pode até mesmo mover linhas entre tabelas com INSERT, usando o
WITH
cláusula.Aqui estão duas tabelas com listas de tarefas para anos diferentes. 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)
Para mover os itens de tarefas que ainda não foram concluídos em 2018 para 2019, você pode basicamente excluir essas linhas da tabela 2018 e inseri-las na tabela 2019 de uma só vez:
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)
Para saber mais sobre a pequena instrução INSERT inteligente, confira a documentação e experimente!