AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 46410
Accepted
Stéphane
Stéphane
Asked: 2013-07-16 20:07:10 +0800 CST2013-07-16 20:07:10 +0800 CST 2013-07-16 20:07:10 +0800 CST

Como insiro uma linha que contém uma chave estrangeira?

  • 772

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 fooseja preenchida assim:

INSERT INTO foo (type) VALUES
    ( 'red' ),
    ( 'green' ),
    ( 'blue' );

Existe alguma maneira de inserir linhas barfacilmente referenciando a footabela? Ou devo fazê-lo em duas etapas, primeiro procurando o footipo 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'  );
postgresql foreign-key
  • 3 3 respostas
  • 428597 Views

3 respostas

  • Voted
  1. Best Answer
    ypercubeᵀᴹ
    2013-07-16T22:09:39+08:002013-07-16T22:09:39+08:00

    Sua sintaxe é quase boa, precisa de alguns parênteses nas subconsultas e funcionará:

    INSERT INTO bar (description, foo_id) VALUES
        ( 'testing',     (SELECT id from foo WHERE type='blue') ),
        ( 'another row', (SELECT id from foo WHERE type='red' ) );
    

    Testado no DB-Fiddle

    Outra maneira, com sintaxe mais curta, se você tiver muitos valores para inserir:

    WITH ins (description, type) AS
    ( VALUES
        ( 'more testing',   'blue') ,
        ( 'yet another row', 'green' )
    )  
    INSERT INTO bar
       (description, foo_id) 
    SELECT 
        ins.description, foo.id
    FROM 
      foo JOIN ins
        ON ins.type = foo.type ;
    
    • 106
  2. Erwin Brandstetter
    2013-07-17T12:40:05+08:002013-07-17T12:40:05+08:00

    AviãoINSERT

    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);
    
    • LEFT [OUTER] JOINem vez de [INNER] JOINsignifica que as linhas de val todas as linhas são mantidas , mesmo quando nenhuma correspondência é encontrada em foo. Em vez disso, NULLé inserido para foo_id(o que gera uma exceção se a coluna for definida NOT NULL).

    • A VALUESexpressã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 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, 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.

    INSERTlinhas FK ausentes ao mesmo tempo

    Para criar entradas ausentes em footempo real, em uma única instrução SQL , os CTEs são fundamentais:

    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);
    

    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 de DISTINCTna primeira INSERTdeclaração.

    Explicação passo a passo

    1. 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 fonte. Imediatamente LEFT JOINpara fooanexar as linhas foo_idpré-existentes . typeTodas as outras linhas ficam foo_id IS NULLassim.

    2. O 2º CTE insinsere novos tipos distintosfoo_id IS NULL ( ) em fooe retorna o recém-gerado foo_id- junto com o typepara unir novamente para inserir linhas.

    3. O externo final INSERTagora pode inserir um foo_idpara 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 KEYnã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:

    • Atomic UPDATE .. SELECT no Postgres
    • O SELECT ou INSERT está em uma função propensa a condições de corrida?
    • Como implemento insert-if-not-found para transações no nível de isolamento serializável?

    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 da VALUESexpressã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:

    'description1,type1;description2,type2;description3,type3'
    

    Em seguida, use isso para substituir a VALUESexpressão na declaraçã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 ou posterior

    Crie um tipo de linha personalizado para passagem de parâmetro. 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
      LANGUAGE sql AS
    $func$
    WITH val AS (SELECT * FROM unnest(_val))    -- well-known row type
    ,    typ AS (
       SELECT v.type, f.id                      -- id NOT NULL where type already exists
       FROM  (SELECT DISTINCT type FROM val) v  -- DISTINCT!
       LEFT   JOIN foo f USING (type)           -- assuming no concurrent update/delete on foo
                                                -- else you might lock rows here.
       )
    ,    ins AS ( 
       INSERT INTO foo AS f (type)
       SELECT type
       FROM   typ
       WHERE  id IS NULL
       ON     CONFLICT (type) DO UPDATE         -- RARE cases of concurrent inserts
       SET    type = EXCLUDED.type              -- overwrite to make visible
       RETURNING f.type, f.id
       )
    INSERT INTO bar AS b (description, foo_id)
    SELECT v.description, COALESCE(t.id, i.id)  -- assuming most types pre-exist
    FROM        val v
    LEFT   JOIN typ t 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 <> EXCLUDED.foo_id;         -- only if actually changed
    $func$;
    

    Ligar:

    SELECT f_insert_foobar(
         '(testing,blue)'
       , '(another row,red)'
       , '(new row1,purple)'
       , '(new row2,green)'
       , '("with,comma",green)'  -- added to demonstrate row syntax
       );
    

    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 SELECTou INSERTem foo: Qualquer typeum que ainda não exista na tabela FK, é inserido. Assumindo que a maioria dos tipos pré-existe.

    • ... se aplica INSERTou UPDATE(true "UPSERT") em bar: Se o descriptionjá existir, ele typeserá atualizado - mas somente se ele realmente mudar. Ver:

      • Como eu (ou posso) SELECT DISTINCT em várias colunas?
    • ... passa valores como tipos de linha conhecidos com um VARIADICparâmetro de função. Observe o máximo padrão de 100 parâmetros de função! Ver:

      • Selecione linhas de forma que os nomes correspondam aos elementos da matriz de entrada para a função pgsql

      Existem muitas outras maneiras de passar várias linhas ...

    Relacionado:

    • Como usar RETURNING com ON CONFLICT no PostgreSQL?
    • Como incluir linhas excluídas em RETURNING from INSERT ... ON CONFLICT
    • 54
  3. TomTom
    2013-07-16T21:23:10+08:002013-07-16T21:23:10+08:00

    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:

    • Id (inteiro ou algo assim) que é a chave primária no nível da tabela.
    • Identificador, que é um GUID usado como nível de aplicativo de ID estável (e pode ser exposto ao cliente em URLs etc.)
    • Código - uma string que pode estar lá e deve ser exclusiva se estiver lá (servidor sql: índice exclusivo filtrado em não nulo). Esse é um identificador de conjunto de clientes.

    Exemplo:

    • Conta (em um aplicativo de negociação) -> Id é um int usado para chaves estrangeiras. -> Identificador é um Guid e utilizado nos portais web etc. - sempre aceito. -> O código é definido manualmente. Regra: uma vez definido, não muda.

    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.

    • 6

relate perguntas

  • Posso ativar o PITR depois que o banco de dados foi usado

  • Práticas recomendadas para executar a replicação atrasada do deslocamento de tempo

  • Os procedimentos armazenados impedem a injeção de SQL?

  • Sequências Biológicas do UniProt no PostgreSQL

  • Qual é a diferença entre a replicação do PostgreSQL 9.0 e o Slony-I?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Conceder acesso a todas as tabelas para um usuário

    • 5 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    pedrosanta Listar os privilégios do banco de dados usando o psql 2011-08-04 11:01:21 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve