INSERT Simples
INSERT INTO bar (description, foo_id)
SELECT val.description, f.id
FROM (
VALUES
(text 'testing', text 'blue') -- explicit type declaration; see below
, ('another row', 'red' )
, ('new row1' , 'purple') -- purple does not exist in foo, yet
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type);
O uso de um em LEFT [OUTER] JOIN
vez de [INNER] JOIN
significa que as linhas de val
não são descartadas quando nenhuma correspondência é encontrada foo
. Em vez disso, NULL
é inserido para foo_id
.
A VALUES
expressão na subconsulta faz o mesmo que o CTE do @ ypercube . As expressões de tabela comum oferecem recursos adicionais e são mais fáceis de ler em grandes consultas, mas também representam barreiras de otimização. Portanto, as subconsultas geralmente são um pouco mais rápidas quando nenhuma das opções acima é necessária.
id
como o nome da coluna é um anti-padrão generalizado. Deve ser foo_id
e bar_id
ou qualquer coisa descritiva. Ao ingressar em várias tabelas, você acaba com várias colunas, todas nomeadas id
...
Considere simples text
ou em varchar
vez de varchar(n)
. Se você realmente precisar impor uma restrição de comprimento, adicione uma CHECK
restrição:
Pode ser necessário adicionar conversões de tipo explícitas. Como a VALUES
expressão não está diretamente anexada a uma tabela (como em INSERT ... VALUES ...
), os tipos não podem ser derivados e os tipos de dados padrão são usados sem declaração explícita de tipo, o que pode não funcionar em todos os casos. É o suficiente para fazê-lo na primeira linha, o resto ficará alinhado.
INSERIR linhas FK ausentes ao mesmo tempo
Se você deseja criar entradas inexistentes em tempo foo
real, em uma única instrução SQL , os CTEs são instrumentais:
WITH sel AS (
SELECT val.description, val.type, f.id AS foo_id
FROM (
VALUES
(text 'testing', text 'blue')
, ('another row', 'red' )
, ('new row1' , 'purple')
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type)
)
, ins AS (
INSERT INTO foo (type)
SELECT DISTINCT type FROM sel WHERE foo_id IS NULL
RETURNING id AS foo_id, type
)
INSERT INTO bar (description, foo_id)
SELECT sel.description, COALESCE(sel.foo_id, ins.foo_id)
FROM sel
LEFT JOIN ins USING (type);
Observe as duas novas linhas fictícias a serem inseridas. Ambos são roxos , o que ainda não existe foo
. Duas linhas para ilustrar a necessidade DISTINCT
na primeira INSERT
instrução.
Explicação passo a passo
O 1º CTE sel
fornece várias linhas de dados de entrada. A subconsulta val
com a VALUES
expressão pode ser substituída por uma tabela ou subconsulta como origem. Imediatamente LEFT JOIN
para foo
anexar as linhas foo_id
pré-existentes type
. Todas as outras linhas ficam foo_id IS NULL
assim.
O 2º CTE ins
insere novos tipos distintos ( foo_id IS NULL
) foo
e retorna os recém-gerados foo_id
- junto com o type
para se juntar novamente para inserir linhas.
O exterior final INSERT
agora pode inserir um foo.id para cada linha: o tipo pré-existente ou foi inserido na etapa 2.
Estritamente falando, as duas inserções acontecem "em paralelo", mas como essa é uma declaração única , as FOREIGN KEY
restrições padrão não irão reclamar. A integridade referencial é imposta no final da instrução por padrão.
SQL Fiddle para Postgres 9.3. (Funciona da mesma maneira em 9.1.)
Há uma pequena condição de corrida se você executar várias dessas consultas simultaneamente. Leia mais em perguntas relacionadas aqui e aqui e aqui . Realmente só acontece sob carga simultânea pesada, se é que alguma vez. Em comparação com soluções de cache como anunciadas em outra resposta, a chance é super pequena .
Função para uso repetido
Para uso repetido, eu criaria uma função SQL que pegasse uma matriz de registros como parâmetro e usasse unnest(param)
no lugar da VALUES
expressão.
Ou, se a sintaxe para matrizes de registros estiver muito bagunçada para você, use uma string separada por vírgula como parâmetro _param
. Por exemplo do formulário:
'description1,type1;description2,type2;description3,type3'
Em seguida, use isso para substituir a VALUES
expressão na instrução acima:
SELECT split_part(x, ',', 1) AS description
split_part(x, ',', 2) AS type
FROM unnest(string_to_array(_param, ';')) x;
Função com UPSERT no Postgres 9.5
Crie um tipo de linha personalizado para a passagem de parâmetros. Poderíamos ficar sem ele, mas é mais simples:
CREATE TYPE foobar AS (description text, type text);
Função:
CREATE OR REPLACE FUNCTION f_insert_foobar(VARIADIC _val foobar[])
RETURNS void AS
$func$
WITH val AS (SELECT * FROM unnest(_val)) -- well-known row type
, ins AS (
INSERT INTO foo AS f (type)
SELECT DISTINCT v.type -- DISTINCT!
FROM val v
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
RETURNING f.type, f.id
)
INSERT INTO bar AS b (description, foo_id)
SELECT v.description, COALESCE(f.id, i.id) -- assuming most types pre-exist
FROM val v
LEFT JOIN foo f USING (type) -- already existed
LEFT JOIN ins i USING (type) -- newly inserted
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
$func$ LANGUAGE sql;
Ligar:
SELECT f_insert_foobar(
'(testing,blue)'
, '(another row,red)'
, '(new row1,purple)'
, '(new row2,purple)'
, '("with,comma",green)' -- added to demonstrate row syntax
);
Rápido e sólido para ambientes com transações simultâneas.
Além das consultas acima, isso ...
... aplica-se SELECT
ou INSERT
ativa foo
: qualquer um type
que ainda não exista na tabela FK é inserido. Supondo que a maioria dos tipos preexista. Para ter certeza absoluta e descartar as condições de corrida, as linhas existentes de que precisamos são bloqueadas (para que transações simultâneas não possam interferir). Se isso for muito paranóico para o seu caso, você pode substituir:
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
com
ON CONFLICT(type) DO NOTHING
... aplica-se INSERT
ou UPDATE
(verdadeiro "UPSERT") em bar
: Se o description
já existir, type
é atualizado:
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
Mas somente se type
realmente mudar:
... passa valores como tipos de linha conhecidos com um VARIADIC
parâmetro Observe o máximo padrão de 100 parâmetros! Comparar:
Existem muitas outras maneiras de passar várias linhas ...
Palavras-chave: