Eu tenho duas colunas na tabela col1
, col2
ambas são indexadas exclusivas (col1 é exclusiva e também col2).
Eu preciso inserir nesta tabela, usar ON CONFLICT
sintaxe e atualizar outras colunas, mas não posso usar ambas as colunas na conflict_target
cláusula.
Funciona:
INSERT INTO table
...
ON CONFLICT ( col1 )
DO UPDATE
SET
-- update needed columns here
Mas como fazer isso para várias colunas, algo assim:
...
ON CONFLICT ( col1, col2 )
DO UPDATE
SET
....
postgresql
upsert
postgresql-9.5
Oto Shavadze
fonte
fonte
Respostas:
Uma tabela de amostra e dados
CREATE TABLE dupes(col1 int primary key, col2 int, col3 text, CONSTRAINT col2_unique UNIQUE (col2) ); INSERT INTO dupes values(1,1,'a'),(2,2,'b');
Reproduzindo o problema
INSERT INTO dupes values(3,2,'c') ON CONFLICT (col1) DO UPDATE SET col3 = 'c', col2 = 2
Vamos chamar isso de Q1. O resultado é
O que diz a documentação
Isso dá a impressão de que a consulta a seguir deve funcionar, mas não funciona porque, na verdade, seria necessário um índice exclusivo em col1 e col2. No entanto, tal índice não garantiria que col1 e col2 seriam únicas individualmente, o que é um dos requisitos do OP.
INSERT INTO dupes values(3,2,'c') ON CONFLICT (col1,col2) DO UPDATE SET col3 = 'c', col2 = 2
Vamos chamar essa consulta de Q2 (falha com um erro de sintaxe)
Por quê?
O Postgresql se comporta dessa forma porque o que deveria acontecer quando ocorre um conflito na segunda coluna não está bem definido. Existem várias possibilidades. Por exemplo, na consulta Q1 acima, o postgresql deve ser atualizado
col1
quando há um conflitocol2
? Mas e se isso levar a outro conflitocol1
? como o postgresql deve lidar com isso?Uma solução
Uma solução é combinar ON CONFLICT com UPSERT antiquado .
CREATE OR REPLACE FUNCTION merge_db(key1 INT, key2 INT, data TEXT) RETURNS VOID AS $$ BEGIN LOOP -- first try to update the key UPDATE dupes SET col3 = data WHERE col1 = key1 and col2 = key2; IF found THEN RETURN; END IF; -- not there, so try to insert the key -- if someone else inserts the same key concurrently, or key2 -- already exists in col2, -- we could get a unique-key failure BEGIN INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col1) DO UPDATE SET col3 = data; RETURN; EXCEPTION WHEN unique_violation THEN BEGIN INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col2) DO UPDATE SET col3 = data; RETURN; EXCEPTION WHEN unique_violation THEN -- Do nothing, and loop to try the UPDATE again. END; END; END LOOP; END; $$ LANGUAGE plpgsql;
Você precisaria modificar a lógica desta função armazenada para que ela atualize as colunas exatamente da maneira que você deseja. Invoque como
SELECT merge_db(3,2,'c'); SELECT merge_db(1,2,'d');
fonte
ON CONFLICT
requer um índice exclusivo * para fazer a detecção de conflito. Portanto, você só precisa criar um índice exclusivo em ambas as colunas:t=# create table t (id integer, a text, b text); CREATE TABLE t=# create unique index idx_t_id_a on t (id, a); CREATE INDEX t=# insert into t values (1, 'a', 'foo'); INSERT 0 1 t=# insert into t values (1, 'a', 'bar') on conflict (id, a) do update set b = 'bar'; INSERT 0 1 t=# select * from t; id | a | b ----+---+----- 1 | a | bar
* Além de índices exclusivos, você também pode usar restrições de exclusão . Estas são um pouco mais gerais do que restrições exclusivas. Suponha que sua tabela tenha colunas para
id
evalid_time
(evalid_time
é atsrange
) e você queira permitirid
s duplicados , mas não para períodos de tempo sobrepostos. Uma restrição única não o ajudará, mas com uma restrição de exclusão, você pode dizer "exclua novos registros se foremid
iguais a um antigoid
e também sevalid_time
sobrepuseremvalid_time
".fonte
ON CONFLICT
?on conflict
comando. O erro é apenas "a coluna my_index_name não existe".Hoje em dia é (parece) impossível. Nem a última versão da
ON CONFLICT
sintaxe permite repetir a cláusula, nem com CTE é possível: não é possível quebrar o INSERT de ON CONFLICT para adicionar mais alvos de conflito.fonte
Se você estiver usando o postgres 9.5, poderá usar o espaço EXCLUÍDO.
Exemplo retirado de O que há de novo no PostgreSQL 9.5 :
INSERT INTO user_logins (username, logins) VALUES ('Naomi',1),('James',1) ON CONFLICT (username) DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;
fonte
OU E
fonte
Vlad teve a ideia certa.
Primeiro, você deve criar uma restrição exclusiva da tabela nas colunas.
col1, col2
Depois de fazer isso, você pode fazer o seguinte:INSERT INTO dupes values(3,2,'c') ON CONFLICT ON CONSTRAINT dupes_pkey DO UPDATE SET col3 = 'c', col2 = 2
fonte
Tipo de hacky, mas resolvi isso concatenando os dois valores de col1 e col2 em uma nova coluna, col3 (como um índice dos dois) e comparei com isso. Isso só funciona se você precisar que corresponda a AMBOS col1 e col2.
INSERT INTO table ... ON CONFLICT ( col3 ) DO UPDATE SET -- update needed columns here
Onde col3 = a concatenação dos valores de col1 e col2.
fonte
on conflict
.Normalmente, você pode (eu acho) gerar uma declaração com apenas uma
on conflict
que especifica a única restrição que é relevante para o que você está inserindo.Porque normalmente, apenas uma restrição é a "relevante" de cada vez. (Se muitos, então estou me perguntando se algo é estranho / desenhado de forma estranha, hmm.)
Exemplo:
(Licença: Não CC0, apenas CC-By)
// there're these unique constraints: // unique (site_id, people_id, page_id) // unique (site_id, people_id, pages_in_whole_site) // unique (site_id, people_id, pages_in_category_id) // and only *one* of page-id, category-id, whole-site-true/false // can be specified. So only one constraint is "active", at a time. val thingColumnName = thingColumnName(notfificationPreference) val insertStatement = s""" insert into page_notf_prefs ( site_id, people_id, notf_level, page_id, pages_in_whole_site, pages_in_category_id) values (?, ?, ?, ?, ?, ?) -- There can be only one on-conflict clause. on conflict (site_id, people_id, $thingColumnName) <—— look do update set notf_level = excluded.notf_level """ val values = List( siteId.asAnyRef, notfPref.peopleId.asAnyRef, notfPref.notfLevel.toInt.asAnyRef, // Only one of these is non-null: notfPref.pageId.orNullVarchar, if (notfPref.wholeSite) true.asAnyRef else NullBoolean, notfPref.pagesInCategoryId.orNullInt) runUpdateSingleRow(insertStatement, values)
E:
A
on conflict
cláusula é gerada dinamicamente, dependendo do que estou tentando fazer. Se estou inserindo uma preferência de notificação, para uma página - então pode haver um conflito exclusivo, nasite_id, people_id, page_id
restrição. E se eu estiver configurando preferências de notificação para uma categoria - então, em vez disso, sei que a restrição que pode ser violada ésite_id, people_id, category_id
.Portanto, posso, e muito provavelmente você também, no seu caso ?, gerar o correto
on conflict (... columns )
, porque sei o que quero fazer e, então, sei qual das muitas restrições exclusivas é a que pode ser violada.fonte
ON CONFLICT é uma solução muito desajeitada, execute
UPDATE dupes SET key1=$1, key2=$2 where key3=$3 if rowcount > 0 INSERT dupes (key1, key2, key3) values ($1,$2,$3);
funciona em Oracle, Postgres e todos os outros bancos de dados
fonte