Verificação mais rápida se existe linha no PostgreSQL

177

Eu tenho um monte de linhas que preciso inserir na tabela, mas essas inserções são sempre feitas em lotes. Então, eu quero verificar se existe uma única linha do lote na tabela, porque sei que todas foram inseridas.

Portanto, não é uma verificação de chave primária, mas não deve importar muito. Eu gostaria de verificar apenas uma linha, então count(*)provavelmente não é bom, então é algo que existseu acho.

Mas como sou bastante novo no PostgreSQL, prefiro perguntar às pessoas que o conhecem.

Meu lote contém linhas com a seguinte estrutura:

userid | rightid | remaining_count

Portanto, se a tabela contiver alguma linha com, useridisso significa que todas elas estão presentes lá.

Valentin Kuzub
fonte
Deseja ver se a tabela possui ALGUMA linha ou alguma linha do seu lote?
JNK
qualquer linha do meu lote sim. todos eles compartilham o mesmo campo e editaremos um pouco.
Valentin Kuzub 19/09/11
Esclareça sua pergunta. Deseja adicionar um lote de registros, tudo ou nada? Existe algo de especial na contagem? (Entre uma palavra reservada, impraticável como um nome de coluna)
wildplasser
Ok, eu estava tentando simplificar um pouco a situação real, mas estamos cada vez mais próximos da implementação real. Depois que essas linhas são inseridas (existe outro campo para data), começo a diminuir os direitos para o usuário especificado, pois eles usam direitos específicos, uma vez que os direitos se tornam 0, eles não podem mais executar essas ações nessa data. a verdadeira história #
Valentin Kuzub 19/09/11
1
Apenas mostre (a parte relevante) as definições da tabela e diga o que você pretende fazer.
wildplasser 19/09/11

Respostas:

345

Use a palavra-chave EXISTS para retornar VERDADEIRO / FALSO:

select exists(select 1 from contact where id=12)
StartupGuy
fonte
21
Além disso, você pode nomear a coluna retornada para facilitar a referência. Por exemploselect exists(select 1 from contact where id=12) AS "exists"
Rowan
3
Isso é melhor, pois sempre retornará um valor (verdadeiro ou falso) em vez de às vezes Nenhum (dependendo da linguagem de programação) que pode não se expandir da maneira esperada.
isaaclw
1
Eu tenho o Seq Scan usando esse método. Eu faço algo errado?
FiftiN
2
@ Michael.MI tem tabela de banco de dados com 30 milhões de linhas e quando eu uso existsou limit 1tenho forte queda de desempenho, porque o Postgres usa a Seq Scan em vez da Index Scan. E analyzenão ajuda.
FiftiN 07/07
2
@maciek, entenda que 'id' é uma chave primária; portanto, “LIMIT 1” não faria sentido, pois existe apenas um registro com esse id.
StartupGuy 26/09/18
34

Que tal simplesmente:

select 1 from tbl where userid = 123 limit 1;

onde 123está o ID do usuário do lote que você está prestes a inserir.

A consulta acima retornará um conjunto vazio ou uma única linha, dependendo se há registros com o ID do usuário fornecido.

Se isso for muito lento, você pode criar um índice tbl.userid.

se existir uma única linha do lote na tabela, nesse caso, não preciso inserir minhas linhas porque tenho certeza de que todas foram inseridas.

Para que isso permaneça verdadeiro, mesmo que seu programa seja interrompido no meio do lote, eu recomendo que você gerencie as transações do banco de dados de maneira apropriada (ou seja, que todo o lote seja inserido em uma única transação).

NPE
fonte
11
Às vezes, pode ser mais fácil programaticamente "selecionar contagem (*) de (selecione 1 ... limite 1)", pois é garantido que sempre retorne uma linha com um valor de contagem (*) de 0 ou 1.
David Aldridge
@DavidAldridge count (*) ainda significa que todas as linhas têm de ser lido, enquanto limite de 1 pára no primeiro registro e retornos
Imraan
3
@ Imraan Acho que você interpretou mal a consulta. A COUNTatua sobre uma aninhada SELECTque tem, no máximo, 1 linha (porque o LIMITé na subconsulta).
Jpmc26
9
INSERT INTO target( userid, rightid, count )
  SELECT userid, rightid, count 
  FROM batch
  WHERE NOT EXISTS (
    SELECT * FROM target t2, batch b2
    WHERE t2.userid = b2.userid
    -- ... other keyfields ...
    )       
    ;

BTW: se você deseja que todo o lote falhe em caso de duplicação, então (dada uma restrição de chave primária)

INSERT INTO target( userid, rightid, count )
SELECT userid, rightid, count 
FROM batch
    ;

fará exatamente o que você deseja: ou é bem-sucedido ou falha.

wildplasser
fonte
Isso irá verificar cada linha. Ele quer fazer uma única verificação.
JNK
1
Não, ele faz uma única verificação. A subconsulta não está correlacionada. Ele será resgatado assim que um par correspondente for encontrado.
wildplasser
Você está certo, pensei que se referia à consulta externa. +1 para você
JNK
BTW: como a consulta está dentro de uma transação, nada acontecerá se um ID duplicado for inserido, portanto, a subconsulta pode ser omitida.
wildplasser
hmm, não sei se entendi. Depois que os direitos são inseridos, começo a diminuir a coluna de contagem. (apenas alguns detalhes para a imagem) Se as linhas já existirem e a subconsulta for omitida, acho que vou obter erros com a chave exclusiva duplicada lançada ou? (ID de usuário e forma correta essa chave única)
Valentin Kuzub
1
select true from tablename where condition limit 1;

Eu acredito que esta é a consulta que o postgres usa para verificar chaves estrangeiras.

No seu caso, você também pode fazer isso de uma só vez:

insert into yourtable select $userid, $rightid, $count where not (select true from yourtable where userid = $userid limit 1);
Royce
fonte
1

como o @MikeM apontou.

select exists(select 1 from contact where id=12)

com o índice no contato, geralmente pode reduzir o custo do tempo para 1 ms.

CREATE INDEX index_contact on contact(id);
hcnak
fonte
0
SELECT 1 FROM user_right where userid = ? LIMIT 1

Se o seu conjunto de resultados contiver uma linha, você não precisará inserir. Caso contrário, insira seus registros.

Fabian Barney
fonte
se o grupo contiver 100 linhas, ele retornará 100 linhas, você acha que isso é bom?
Valentin Kuzub 19/09/11
Você pode limitar a 1 linha. Deve ter um desempenho melhor. Veja a resposta editada do @aix para isso.
Fabian Barney
0

Se você pensa sobre o desempenho, pode ser que você possa usar "PERFORM" em uma função como esta:

 PERFORM 1 FROM skytf.test_2 WHERE id=i LIMIT 1;
  IF FOUND THEN
      RAISE NOTICE ' found record id=%', i;  
  ELSE
      RAISE NOTICE ' not found record id=%', i;  
 END IF;
francos
fonte
não funciona comigo: recebo um erro de sintaxe próximo ao desempenho
Simon
1
que de pl / pgsql, não SQL, daí o erro de sintaxe para "executar" se tentar executá-lo como SQL
Mark K Cowan
-1

Gostaria de propor outro pensamento para abordar especificamente sua frase: "Quero verificar se existe uma única linha do lote na tabela, porque sei que todas foram inseridas ".

Você está tornando as coisas eficientes, inserindo "lotes", mas depois verificando a existência, um registro de cada vez? Isso parece contra-intuitivo para mim. Então, quando você diz " inserções sempre são feitas em lotes ", entendo que você esteja inserindo vários registros com uma instrução de inserção . Você precisa perceber que o Postgres é compatível com ACID. Se você estiver inserindo vários registros (um lote de dados) com uma instrução de inserção , não será necessário verificar se alguns foram inseridos ou não. A declaração é aprovada ou falhará. Todos os registros serão inseridos ou nenhum.

Por outro lado, se o seu código C # estiver simplesmente fazendo um conjunto de instruções de inserção separadas, por exemplo, em um loop, e em sua mente, este é um "lote" .. então você não deve descrevê-lo como " inserções são sempre feitas em lotes ". O fato de que você espera que parte do que você chama de "lote" possa não ser realmente inserido e, portanto, sinta a necessidade de uma verificação, sugere fortemente que esse é o caso, caso em que você tem um problema mais fundamental. Você precisa alterar seu paradigma para realmente inserir vários registros com uma inserção e deixar de verificar se os registros individuais o fizeram.

Considere este exemplo:

CREATE TABLE temp_test (
    id SERIAL PRIMARY KEY,
    sometext TEXT,
    userid INT,
    somethingtomakeitfail INT unique
)
-- insert a batch of 3 rows
;;
INSERT INTO temp_test (sometext, userid, somethingtomakeitfail) VALUES
('foo', 1, 1),
('bar', 2, 2),
('baz', 3, 3)
;;
-- inspect the data of what we inserted
SELECT * FROM temp_test
;;
-- this entire statement will fail .. no need to check which one made it
INSERT INTO temp_test (sometext, userid, somethingtomakeitfail) VALUES
('foo', 2, 4),
('bar', 2, 5),
('baz', 3, 3)  -- <<--(deliberately simulate a failure)
;;
-- check it ... everything is the same from the last successful insert ..
-- no need to check which records from the 2nd insert may have made it in
SELECT * FROM temp_test

Na verdade, esse é o paradigma de qualquer banco de dados compatível com ACID. Não apenas o Postgresql. Em outras palavras, você estará melhor se corrigir o conceito de "lote" e evitar a necessidade de fazer verificações linha por linha em primeiro lugar.

StartupGuy
fonte