Usando PostgreSQL v9.1. Tenho as seguintes tabelas:
CREATE TABLE foo
(
id BIGSERIAL NOT NULL UNIQUE PRIMARY KEY,
type VARCHAR(60) NOT NULL UNIQUE
);
CREATE TABLE bar
(
id BIGSERIAL NOT NULL UNIQUE PRIMARY KEY,
description VARCHAR(40) NOT NULL UNIQUE,
foo_id BIGINT NOT NULL REFERENCES foo ON DELETE RESTRICT
);
Digamos que a primeira tabela foo
seja preenchida assim:
INSERT INTO foo (type) VALUES
( 'red' ),
( 'green' ),
( 'blue' );
Existe alguma maneira de inserir linhas bar
facilmente referenciando a foo
tabela? Ou devo fazê-lo em duas etapas, primeiro procurando o foo
tipo que desejo e inserindo uma nova linha em bar
?
Aqui está um exemplo de pseudo-código mostrando o que eu esperava que pudesse ser feito:
INSERT INTO bar (description, foo_id) VALUES
( 'testing', SELECT id from foo WHERE type='blue' ),
( 'another row', SELECT id from foo WHERE type='red' );
Sua sintaxe é quase boa, precisa de alguns parênteses nas subconsultas e funcionará:
Testado no DB-Fiddle
Outra maneira, com sintaxe mais curta, se você tiver muitos valores para inserir:
Avião
INSERT
LEFT [OUTER] JOIN
em vez de[INNER] JOIN
significa que as linhas deval
todas as linhas são mantidas , mesmo quando nenhuma correspondência é encontrada emfoo
. Em vez disso,NULL
é inserido parafoo_id
(o que gera uma exceção se a coluna for definidaNOT NULL
).A
VALUES
expressão na subconsulta faz o mesmo que o CTE do @ypercube . Expressões de tabela comuns oferecem recursos adicionais e são mais fáceis de ler em grandes consultas, mas também representam barreiras de otimização (até Postgres 12). As subconsultas geralmente são um pouco mais rápidas quando nenhuma das opções acima é necessária.Você pode precisar de conversões de tipo explícitas. Como a
VALUES
expressão não está diretamente anexada a uma tabela (como emINSERT ... VALUES ...
), os tipos não podem ser derivados e os tipos de dados padrão são usados, a menos que sejam digitados explicitamente. Isso pode não funcionar em todos os casos. É o suficiente para fazê-lo na primeira fila, o resto cai na linha.INSERT
linhas FK ausentes ao mesmo tempoPara criar entradas ausentes em
foo
tempo real, em uma única instrução SQL , os CTEs são fundamentais:sqlfiddle antigo para Postgres 9.6 - funciona da mesma forma em 9.1. Veja também o novo violino abaixo!
Observe as duas linhas adicionais a serem inseridas. Ambos são roxos , o que ainda não existe no
foo
. Duas linhas para ilustrar a necessidade deDISTINCT
na primeiraINSERT
declaração.Explicação passo a passo
O 1º CTE
sel
fornece várias linhas de dados de entrada. A subconsultaval
com aVALUES
expressão pode ser substituída por uma tabela ou subconsulta como fonte. ImediatamenteLEFT JOIN
parafoo
anexar as linhasfoo_id
pré-existentes .type
Todas as outras linhas ficamfoo_id IS NULL
assim.O 2º CTE
ins
insere novos tipos distintosfoo_id IS NULL
( ) emfoo
e retorna o recém-geradofoo_id
- junto com otype
para unir novamente para inserir linhas.O externo final
INSERT
agora pode inserir umfoo_id
para cada linha: o tipo já existia ou foi inserido na etapa 2.Estritamente falando, ambas as inserções acontecem "em paralelo", mas como esta é uma única instrução, as restrições padrão
FOREIGN KEY
não reclamarão. A integridade referencial é imposta no final da instrução por padrão.Há uma pequena condição de corrida se você executar várias dessas consultas simultaneamente. Ver:
Realmente só acontece sob carga concorrente pesada, se é que acontece. Em comparação com as soluções de cache, como anunciadas em outra resposta, a chance é super pequena .
Função para uso repetido
Crie uma função SQL que receba uma matriz do tipo composto como parâmetro e use
unnest(param)
no lugar daVALUES
expressão.Ou, se a sintaxe de tal array parecer muito confusa, use uma string separada por vírgula como parâmetro
_param
. Por exemplo do formulário:Em seguida, use isso para substituir a
VALUES
expressão na declaração acima:Função com UPSERT no Postgres 9.5 ou posterior
Crie um tipo de linha personalizado para passagem de parâmetro. Poderíamos ficar sem ele, mas é mais simples:
Função:
Ligar:
db<>fique aqui
Rápido e sólido para ambientes com transações simultâneas.
Além das consultas acima, esta função ...
... se aplica
SELECT
ouINSERT
emfoo
: Qualquertype
um que ainda não exista na tabela FK, é inserido. Assumindo que a maioria dos tipos pré-existe.... se aplica
INSERT
ouUPDATE
(true "UPSERT") embar
: Se odescription
já existir, eletype
será atualizado - mas somente se ele realmente mudar. Ver:... passa valores como tipos de linha conhecidos com um
VARIADIC
parâmetro de função. Observe o máximo padrão de 100 parâmetros de função! Ver:Existem muitas outras maneiras de passar várias linhas ...
Relacionado:
Olho para cima. Você basicamente precisa dos IDs foo para inseri-los na barra.
Não é específico do postgres, btw. (e você não marcou assim) - geralmente é assim que o SQL funciona. Sem atalhos aqui.
No entanto, em termos de aplicação, você pode ter um cache de itens foo na memória. Minhas tabelas geralmente têm até 3 campos exclusivos:
Exemplo:
Obviamente, quando você deseja vincular algo a uma conta - primeiro você deve, tecnicamente, obter o ID - mas, dado que o Identificador e o Código nunca mudam quando estão lá, um cache positivo na memória pode impedir que a maioria das pesquisas atinja o banco de dados.