O erro que você recebe:
O comando ON CONFLICT DO UPDATE não pode afetar a linha uma segunda vez
indica que você está tentando upser a mesma linha mais de uma vez em um único comando. Em outras palavras:você tem dupes em
(name, url, email)
em seus VALUES
Lista. Dobre duplicatas (se for uma opção) e deve funcionar. Mas você terá que decidir qual linha escolher de cada conjunto de dupes. INSERT INTO feeds_person (created, modified, name, url, email)
SELECT DISTINCT ON (name, url, email) *
FROM (
VALUES
('blah', 'blah', 'blah', 'blah', 'blah')
-- ... more
) v(created, modified, name, url, email) -- match column list
ON CONFLICT (name, url, email) DO UPDATE
SET url = feeds_person.url
RETURNING id;
Como usamos um
VALUES
independente expressão agora, você precisa adicionar conversões de tipo explícitas para tipos não padrão. Como:VALUES
(timestamptz '2016-03-12 02:47:56+01'
, timestamptz '2016-03-12 02:47:56+01'
, 'n3', 'u3', 'e3')
...
Seu
timestamptz
as colunas precisam de um cast de tipo explícito, enquanto os tipos de string podem operar com o padrão text
. (Você ainda pode transmitir para varchar(n)
agora mesmo.) Existem maneiras de determinar qual linha escolher de cada conjunto de dupes:
- Selecionar a primeira linha em cada grupo GROUP BY?
Você está certo, não há (atualmente) nenhuma maneira de ser excluído linhas no
RETURNING
cláusula. Cito o Wiki do Postgres:
Observe queRETURNING
não torna visível o "EXCLUDED.*
" alias doUPDATE
(apenas o genérico "TARGET.*
" o alias está visível lá). Acredita-se que isso cria uma ambiguidade irritante para os casos simples e comuns [30] com pouco ou nenhum benefício. Em algum momento no futuro, poderemos buscar uma maneira de expor seRETURNING
- as tuplas projetadas foram inseridas e atualizadas, mas isso provavelmente não precisa ser feito na primeira iteração confirmada do recurso [31].
No entanto , você não deve atualizar linhas que não deveriam ser atualizadas. Atualizações vazias são quase tão caras quanto atualizações regulares - e podem ter efeitos colaterais indesejados. Você não precisa estritamente de UPSERT para começar, seu caso se parece mais com "SELECT ou INSERT". Relacionado:
- O SELECT ou INSERT está em uma função propensa a condições de corrida?
Um maneira mais limpa de inserir um conjunto de linhas seria com CTEs de modificação de dados:
WITH val AS (
SELECT DISTINCT ON (name, url, email) *
FROM (
VALUES
(timestamptz '2016-1-1 0:0+1', timestamptz '2016-1-1 0:0+1', 'n', 'u', 'e')
, ('2016-03-12 02:47:56+01', '2016-03-12 02:47:56+01', 'n1', 'u3', 'e3')
-- more (type cast only needed in 1st row)
) v(created, modified, name, url, email)
)
, ins AS (
INSERT INTO feeds_person (created, modified, name, url, email)
SELECT created, modified, name, url, email FROM val
ON CONFLICT (name, url, email) DO NOTHING
RETURNING id, name, url, email
)
SELECT 'inserted' AS how, id FROM ins -- inserted
UNION ALL
SELECT 'selected' AS how, f.id -- not inserted
FROM val v
JOIN feeds_person f USING (name, url, email);
A complexidade adicional deve pagar por grandes tabelas onde
INSERT
é a regra e SELECT
a exceção. Originalmente, eu adicionei um
NOT EXISTS
predicado no último SELECT
para evitar duplicatas no resultado. Mas isso era redundante. Todas as CTEs de uma única consulta veem os mesmos instantâneos de tabelas. O conjunto retornou com ON CONFLICT (name, url, email) DO NOTHING
é mutuamente exclusivo para o conjunto retornado após o INNER JOIN
nas mesmas colunas. Infelizmente, isso também abre uma pequena janela para uma condição de corrida . Se ...
- uma transação simultânea insere linhas conflitantes
- ainda não se comprometeu
- mas se compromete eventualmente
... algumas linhas podem ser perdidas.
Você pode apenas
INSERT .. ON CONFLICT DO NOTHING
, seguido por um SELECT
separado consulta para todas as linhas - dentro da mesma transação para superar isso. O que, por sua vez, abre outra pequena janela para uma condição de corrida se transações simultâneas podem confirmar gravações na tabela entre INSERT
e SELECT
(no padrão READ COMMITTED
nível de isolamento). Pode ser evitado com REPEATABLE READ
isolamento de transação (ou mais estrito). Ou com um bloqueio de gravação (possivelmente caro ou até inaceitável) em toda a mesa. Você pode obter qualquer comportamento que precisar, mas pode haver um preço a pagar. Relacionado:
- Como usar RETURNING com ON CONFLICT no PostgreSQL?
- Retorne linhas de INSERT com ON CONFLICT sem precisar atualizar