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 / 151431
Accepted
Shaun McCready
Shaun McCready
Asked: 2016-10-05 11:19:33 +0800 CST2016-10-05 11:19:33 +0800 CST 2016-10-05 11:19:33 +0800 CST

Problema UPSERT do PostgreSQL com valores NULL

  • 772

Estou tendo um problema ao usar o novo recurso UPSERT no Postgres 9.5

Eu tenho uma tabela que é usada para agregar dados de outra tabela. A chave composta é composta por 20 colunas, 10 das quais podem ser anuláveis. Abaixo, criei uma versão menor do problema que estou tendo, especificamente com valores NULL.

CREATE TABLE public.test_upsert (
upsert_id serial,
name character varying(32) NOT NULL,
status integer NOT NULL,
test_field text,
identifier character varying(255),
count integer,
CONSTRAINT upsert_id_pkey PRIMARY KEY (upsert_id),
CONSTRAINT test_upsert_name_status_test_field_key UNIQUE (name, status, test_field)
);

A execução desta consulta funciona conforme necessário (primeiro insira, depois as inserções subsequentes simplesmente incrementam a contagem):

INSERT INTO test_upsert as tu(name,status,test_field,identifier, count) 
VALUES ('shaun',1,'test value','ident', 1)
ON CONFLICT (name,status,test_field) DO UPDATE set count = tu.count + 1 
where tu.name = 'shaun' AND tu.status = 1 AND tu.test_field = 'test value';

No entanto, se eu executar esta consulta, 1 linha será inserida a cada vez, em vez de incrementar a contagem da linha inicial:

INSERT INTO test_upsert as tu(name,status,test_field,identifier, count) 
VALUES ('shaun',1,null,'ident', 1)
ON CONFLICT (name,status,test_field) DO UPDATE set count = tu.count + 1  
where tu.name = 'shaun' AND tu.status = 1 AND tu.test_field = null;

Este é o meu problema. Preciso simplesmente incrementar o valor de contagem e não criar várias linhas idênticas com valores nulos.

Tentando adicionar um índice exclusivo parcial:

CREATE UNIQUE INDEX test_upsert_upsert_id_idx
ON public.test_upsert
USING btree
(name COLLATE pg_catalog."default", status, test_field, identifier);

No entanto, isso produz os mesmos resultados, várias linhas nulas sendo inseridas ou esta mensagem de erro ao tentar inserir:

ERRO: não há restrição exclusiva ou de exclusão correspondente à especificação ON CONFLICT

Já tentei adicionar detalhes extras no índice parcial, como WHERE test_field is not null OR identifier is not null. No entanto, ao inserir, recebo a mensagem de erro de restrição.

postgresql null
  • 2 2 respostas
  • 21922 Views

2 respostas

  • Voted
  1. Best Answer
    Erwin Brandstetter
    2016-10-05T18:03:45+08:002016-10-05T18:03:45+08:00

    esclarecer o ON CONFLICT DO UPDATEcomportamento

    Considere o manual aqui :

    Para cada linha individual proposta para inserção, a inserção continua ou, se uma restrição de árbitro ou índice especificado por conflict_targetfor violado, a alternativa conflict_actioné tomada.

    Ênfase em negrito minha. Portanto, você não precisa repetir predicados para colunas incluídas no índice exclusivo na WHEREcláusula para o UPDATE(o conflict_action):

    INSERT INTO test_upsert AS tu
           (name   , status, test_field  , identifier, count) 
    VALUES ('shaun', 1     , 'test value', 'ident'   , 1)
    ON CONFLICT (name, status, test_field) DO UPDATE
    SET count = tu.count + 1;
    WHERE tu.name = 'shaun' AND tu.status = 1 AND tu.test_field = 'test value'

    A violação única já estabelece o que sua WHEREcláusula adicionada aplicaria de forma redundante.

    Esclarecer índice parcial

    Adicione uma WHEREcláusula para torná-lo um índice parcial real, como você mesmo mencionou (mas com lógica invertida):

    CREATE UNIQUE INDEX test_upsert_partial_idx
    ON public.test_upsert (name, status)
    WHERE test_field IS NULL;  -- not: "is not null"
    

    Para usar este índice parcial em seu UPSERT, você precisa de uma correspondência como @ypercube demonstra :conflict_target

    ON CONFLICT (name, status) WHERE test_field IS NULL
    

    Agora o índice parcial acima é inferido. No entanto , como o manual também observa :

    [...] um índice exclusivo não parcial (um índice exclusivo sem um predicado) será inferido (e, portanto, usado por ON CONFLICT) se um índice que satisfaça todos os outros critérios estiver disponível.

    Se você tiver um índice adicional (ou apenas) apenas (name, status)ele será (também) usado. Um índice em (name, status, test_field)explicitamente não seria inferido. Isso não explica seu problema, mas pode ter aumentado a confusão durante o teste.

    Soluções

    AIUI, nenhuma das opções acima resolve seu problema , ainda. Com o índice parcial, apenas casos especiais com valores NULL correspondentes seriam capturados. E outras linhas duplicadas seriam inseridas se você não tiver outros índices/restrições exclusivos correspondentes ou levantaria uma exceção se tivesse. Acho que não é isso que você quer. Você escreve:

    A chave composta é composta por 20 colunas, 10 das quais podem ser anuláveis.

    O que exatamente você considera uma duplicata? Postgres (de acordo com o padrão SQL) não considera dois valores NULL iguais. O manual:

    Em geral, uma restrição única é violada se houver mais de uma linha na tabela onde os valores de todas as colunas incluídas na restrição são iguais. No entanto, dois valores nulos nunca são considerados iguais nesta comparação. Isso significa que, mesmo na presença de uma restrição exclusiva, é possível armazenar linhas duplicadas que contenham um valor nulo em pelo menos uma das colunas restritas. Esse comportamento está de acordo com o padrão SQL, mas ouvimos dizer que outros bancos de dados SQL podem não seguir essa regra. Portanto, tenha cuidado ao desenvolver aplicativos destinados a serem portáteis.

    Relacionado:

    • Permitir nulo em coluna única

    Suponho que você deseja queNULLos valores em todas as 10 colunas anuláveis ​​sejam considerados iguais. É elegante e prático cobrir uma única coluna anulável com um índice parcial adicional, como demonstrado aqui:

    • Restrição exclusiva de várias colunas do PostgreSQL e valores NULL

    Mas isso fica fora de controle rapidamente para mais colunas anuláveis. Você precisaria de um índice parcial para cada combinação distinta de colunas anuláveis. Para apenas 2 desses, são 3 índices parciais para (a), (b)e (a,b). O número está crescendo exponencialmente com 2^n - 1. Para suas 10 colunas anuláveis, para cobrir todas as combinações possíveis de valores NULL, você já precisaria de 1.023 índices parciais. Não vá.

    A solução simples: substitua os valores NULL e defina as colunas envolvidas NOT NULL, e tudo funcionaria bem com uma UNIQUErestrição simples.

    Se essa não for uma opção, sugiro uma expressão index com COALESCEpara substituir NULL no índice:

    CREATE UNIQUE INDEX test_upsert_solution_idx
        ON test_upsert (name, status, COALESCE(test_field, ''));

    A string vazia ( '') é um candidato óbvio para tipos de caracteres, mas você pode usar qualquer valor legal que nunca apareça ou que possa ser dobrado com NULL de acordo com sua definição de "único".

    Em seguida, use esta declaração:

    INSERT INTO test_upsert as tu(name,status,test_field,identifier, count) 
    VALUES ('shaun', 1, null        , 'ident', 11)  -- works with
         , ('bob'  , 2, 'test value', 'ident', 22)  -- and without NULL
    ON     CONFLICT (name, status, COALESCE(test_field, '')) DO UPDATE  -- match expr. index
    SET    count = COALESCE(tu.count + EXCLUDED.count, EXCLUDED.count, tu.count);
    

    Como @ypercube, suponho que você realmente deseja adicionar countao arquivo count. Como a coluna pode ser NULL, adicionar NULL definiria a coluna NULL. Se você definir count NOT NULL, poderá simplificar.

    Ou considere esta resposta posterior relacionada usando um valor de hash para a linha:

    • Por que minha restrição UNIQUE não é acionada?

    Uma ideia diferente seria apenas eliminar o conflict_target da instrução para cobrir todas as violações exclusivas . Em seguida, você pode definir vários índices exclusivos para uma definição mais sofisticada do que deveria ser "exclusivo". Mas isso não vai voar com ON CONFLICT DO UPDATE. O manual mais uma vez:

    Para ON CONFLICT DO NOTHING, é opcional especificar um conflict_target; quando omitido, os conflitos com todas as restrições utilizáveis ​​(e índices exclusivos) são tratados. Para ON CONFLICT DO UPDATE, um conflict_target deve ser fornecido.

    • 31
  2. ypercubeᵀᴹ
    2016-10-05T11:54:50+08:002016-10-05T11:54:50+08:00

    Acho que o problema é que você não tem um índice parcial e a ON CONFLICTsintaxe não corresponde ao test_upsert_upsert_id_idxíndice, mas à outra restrição exclusiva.

    Se você definir o índice como parcial (com WHERE test_field IS NULL):

    CREATE UNIQUE INDEX test_upsert_upsert_id_idx
    ON public.test_upsert
    USING btree
    (name COLLATE pg_catalog."default", status)
    WHERE test_field IS NULL ;
    

    e essas linhas já na tabela:

    INSERT INTO test_upsert as tu
        (name, status, test_field, identifier, count) 
    VALUES 
        ('shaun', 1, null, 'ident', 1),
        ('maria', 1, null, 'ident', 1) ;
    

    então a query terá sucesso:

    INSERT INTO test_upsert as tu
        (name, status, test_field, identifier, count) 
    VALUES 
        ('peter', 1,   17, 'ident', 1),
        ('shaun', 1, null, 'ident', 3),
        ('maria', 1, null, 'ident', 7)
    ON CONFLICT 
        (name, status) WHERE test_field IS NULL   -- the conflicting condition
    DO UPDATE SET
        count = tu.count + EXCLUDED.count 
    WHERE                                         -- when to update
        tu.name = 'shaun' AND tu.status = 1 ;     -- if you don't want all of the
                                                  -- updates to happen
    

    com os seguintes resultados:

    ('peter', 1,   17, 'ident', 1)  -- no conflict: row inserted
    
    ('shaun', 1, null, 'ident', 3)  -- conflict: no insert
                               -- matches where: row updated with count = 1+3 = 4
    
    ('maria', 1, null, 'ident', 1)  -- conflict: no insert
                         -- doesn't match where: no update
    
    • 8

relate perguntas

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

  • Sequências Biológicas do UniProt no PostgreSQL

  • NULL ou NOT NULL por padrão?

  • Quando usar NULL e quando usar uma string vazia?

  • 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

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 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

    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
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • 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
    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