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] JOINvez de [INNER] JOINsignifica 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 VALUESexpressã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.
idcomo o nome da coluna é um anti-padrão generalizado. Deve ser foo_ide bar_idou qualquer coisa descritiva. Ao ingressar em várias tabelas, você acaba com várias colunas, todas nomeadas id...
Considere simples textou em varcharvez de varchar(n). Se você realmente precisar impor uma restrição de comprimento, adicione uma CHECKrestrição:
Pode ser necessário adicionar conversões de tipo explícitas. Como a VALUESexpressã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 fooreal, 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 DISTINCTna primeira INSERTinstrução.
Explicação passo a passo
O 1º CTE selfornece várias linhas de dados de entrada. A subconsulta valcom a VALUESexpressão pode ser substituída por uma tabela ou subconsulta como origem. Imediatamente LEFT JOINpara fooanexar as linhas foo_idpré-existentes type. Todas as outras linhas ficam foo_id IS NULLassim.
O 2º CTE insinsere novos tipos distintos ( foo_id IS NULL) fooe retorna os recém-gerados foo_id- junto com o typepara se juntar novamente para inserir linhas.
O exterior final INSERTagora 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 KEYrestriçõ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 VALUESexpressã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 VALUESexpressã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 SELECTou INSERTativa foo: qualquer um typeque 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 INSERTou UPDATE(verdadeiro "UPSERT") em bar: Se o descriptionjá 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 typerealmente mudar:
... passa valores como tipos de linha conhecidos com um VARIADICparâmetro Observe o máximo padrão de 100 parâmetros! Comparar:
Existem muitas outras maneiras de passar várias linhas ...
Palavras-chave: