Vérification la plus rapide si la ligne existe dans PostgreSQL

177

J'ai un tas de lignes que je dois insérer dans le tableau, mais ces insertions sont toujours faites par lots. Je veux donc vérifier si une seule ligne du lot existe dans le tableau car je sais alors qu'ils ont tous été insérés.

Ce n'est donc pas une vérification de clé primaire, mais cela ne devrait pas trop avoir d'importance. Je voudrais ne vérifier qu'une seule ligne, donc ce count(*)n'est probablement pas bon, donc c'est quelque chose comme existsje suppose.

Mais comme je suis assez nouveau dans PostgreSQL, je préfère demander aux gens qui savent.

Mon lot contient des lignes avec la structure suivante:

userid | rightid | remaining_count

Donc, si la table contient des lignes fournies, useridcela signifie qu'elles y sont toutes présentes.

Valentin Kuzub
la source
Vous voulez voir si la table contient des lignes ou des lignes de votre lot?
JNK
toutes les lignes de mon lot oui. ils partagent tous le même champ mal éditer un peu.
Valentin Kuzub
Veuillez clarifier votre question. Vous souhaitez ajouter un lot d'enregistrements, tout ou rien? Y a-t-il quelque chose de spécial dans le compte? (BTW un mot réservé, peu pratique comme nom de colonne)
wildplasser
d'accord, j'essayais de simplifier un peu la situation réelle mais nous nous rapprochons de plus en plus de la mise en œuvre réelle. Une fois que ces lignes sont insérées (il y a un autre champ for_date), je commence à décrémenter les droits pour l'utilisateur spécifié car ils utilisent des droits spécifiques, une fois que les droits deviennent 0, ils ne peuvent plus effectuer ces actions pour cette date. thats the real story
Valentin Kuzub
1
Montrez simplement (la partie pertinente de) les définitions de table et dites ce que vous avez l'intention de faire.
wildplasser

Réponses:

345

Utilisez le mot clé EXISTS pour un retour TRUE / FALSE:

select exists(select 1 from contact where id=12)
StartupGuy
la source
21
Extension à ce sujet, vous pouvez nommer la colonne retournée pour une référence facile. Par exempleselect exists(select 1 from contact where id=12) AS "exists"
Rowan
3
C'est mieux, car il retournera toujours une valeur (true ou false) au lieu de parfois None (selon votre langage de programmation) qui pourrait ne pas s'étendre comme vous le souhaitez.
isaaclw
1
J'ai Seq Scan avec cette méthode. Je fais quelque chose de mal?
FiftiN
2
@ Michael.MI a une table DB avec 30 millions de lignes et quand j'utilise existsou limit 1j'ai une forte baisse de performances parce que Postgres utilise Seq Scan au lieu de Index Scan. Et analyzen'aide pas.
FiftiN
2
@maciek s'il vous plaît comprendre que 'id' est une clé primaire, donc "LIMIT 1" serait inutile car il n'y a qu'un seul enregistrement avec cet identifiant
StartupGuy
34

Que diriez-vous simplement:

select 1 from tbl where userid = 123 limit 1;

123est l'ID utilisateur du lot que vous êtes sur le point d'insérer.

La requête ci-dessus renverra un ensemble vide ou une seule ligne, selon qu'il existe des enregistrements avec l'ID utilisateur donné.

Si cela s'avère trop lent, vous pouvez envisager de créer un index sur tbl.userid.

si même une seule ligne du lot existe dans la table, dans ce cas, je n'ai pas à insérer mes lignes car je sais avec certitude qu'elles ont toutes été insérées.

Pour que cela reste vrai même si votre programme est interrompu au milieu du lot, je vous recommande de vous assurer de gérer correctement les transactions de la base de données (c'est-à-dire que le lot entier est inséré dans une seule transaction).

NPE
la source
11
Il peut être parfois plus facile par programme de "sélectionner count (*) from (select 1 ... limit 1)" car il est garanti de toujours renvoyer une ligne avec une valeur de count (*) de 0 ou 1.
David Aldridge
@DavidAldridge count (*) signifie toujours que toutes les lignes doivent être lues, alors que la limite 1 s'arrête au premier enregistrement et retourne
Imraan
3
@Imraan Je pense que vous avez mal interprété la requête. L' COUNTagit sur un imbriqué SELECTqui a au plus 1 ligne (car le LIMITest dans la sous-requête).
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: si vous voulez que tout le lot échoue en cas de duplication, alors (étant donné une contrainte de clé primaire)

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

fera exactement ce que vous voulez: soit il réussit, soit il échoue.

wildplasser
la source
Cela vérifiera chaque ligne. Il veut faire un seul chèque.
JNK
1
Non, il fait une seule vérification. La sous-requête n'est pas corrélée. Il renflouera une fois qu'une paire correspondante sera trouvée.
wildplasser
Vous avez raison, je pensais que cela faisait référence à la requête externe. +1 pour vous
JNK
BTW: puisque la requête est à l'intérieur d'une transaction, rien ne se passera si un identifiant en double devait être inséré, par conséquent la sous-requête peut être omise.
wildplasser
hmm je ne suis pas sûr de comprendre. Une fois les droits insérés, je commence à décrémenter la colonne de comptage. (Juste quelques détails pour l'image) Si des lignes existent déjà et que la sous-requête est omise, je pense que je vais mal obtenir des erreurs avec une clé unique en double jetée ou? (userid & right form that unique key)
Valentin Kuzub
1
select true from tablename where condition limit 1;

Je crois que c'est la requête que postgres utilise pour vérifier les clés étrangères.

Dans votre cas, vous pouvez également le faire en une seule fois:

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

comme l'a souligné @MikeM.

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

avec un index sur contact, il peut généralement réduire le coût en temps à 1 ms.

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

Si votre jeu de résultats contient une ligne, vous n'avez pas besoin d'insérer. Sinon, insérez vos enregistrements.

Fabian Barney
la source
si le groupe contient 100 lignes, il me renverra 100 lignes, vous pensez que c'est bien?
Valentin Kuzub
Vous pouvez le limiter à 1 ligne. Devrait mieux fonctionner. Jetez un œil à la réponse modifiée de @aix pour cela.
Fabian Barney
0

Si vous pensez à la performance, vous pouvez peut-être utiliser "PERFORM" dans une fonction comme celle-ci:

 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;
francs
la source
ne fonctionne pas avec moi: j'obtiens une erreur de syntaxe près de perform
Simon
1
c'est pl / pgsql, pas SQL, d'où l'erreur de syntaxe pour "PERFORM" si vous essayez de l'exécuter en tant que SQL
Mark K Cowan
-1

Je voudrais proposer une autre réflexion pour répondre spécifiquement à votre phrase: "Je veux donc vérifier si une seule ligne du lot existe dans le tableau parce qu'alors je sais qu'elles ont toutes été insérées ."

Vous rendez les choses efficaces en insérant des "lots" mais en effectuant ensuite des vérifications d'existence un enregistrement à la fois? Cela me semble contre-intuitif. Donc, quand vous dites "les insertions sont toujours effectuées par lots ", je suppose que vous voulez dire que vous insérez plusieurs enregistrements avec une instruction d'insertion . Vous devez vous rendre compte que Postgres est conforme à ACID. Si vous insérez plusieurs enregistrements (un lot de données) avec une instruction d'insertion , il n'est pas nécessaire de vérifier si certains ont été insérés ou non. L'instruction réussit ou échouera. Tous les enregistrements seront insérés ou aucun.

D'un autre côté, si votre code C # fait simplement un "set" d'instructions d'insertion séparées, par exemple, dans une boucle, et dans votre esprit, c'est un "batch" .. alors vous ne devriez pas en fait le décrire comme " les insertions se font toujours par lots ". Le fait que vous vous attendiez à ce qu'une partie de ce que vous appelez un "lot" ne soit en fait pas insérée, et par conséquent ressentiez le besoin d'une vérification, suggère fortement que c'est le cas, auquel cas vous avez un problème plus fondamental. Vous devez modifier votre paradigme pour insérer réellement plusieurs enregistrements avec une seule insertion et renoncer à vérifier si les enregistrements individuels l'ont fait.

Prenons cet exemple:

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

C'est en fait le paradigme de toute base de données compatible ACID ... pas seulement de Postgresql. En d'autres termes, vous êtes mieux si vous corrigez votre concept de «lot» et évitez d'avoir à faire des vérifications ligne par ligne en premier lieu.

StartupGuy
la source