Schnellste Überprüfung, ob eine Zeile in PostgreSQL vorhanden ist


177

Ich habe eine Reihe von Zeilen, die ich in die Tabelle einfügen muss, aber diese Einfügungen erfolgen immer stapelweise. Ich möchte also überprüfen, ob eine einzelne Zeile aus dem Stapel in der Tabelle vorhanden ist, da ich dann weiß, dass alle eingefügt wurden.

Es ist also keine Primärschlüsselprüfung, sollte aber nicht zu wichtig sein. Ich möchte nur eine einzelne Zeile überprüfen, also ist es count(*)wahrscheinlich nicht gut, also ist es so etwas wie existsich denke.

Aber da ich ziemlich neu in PostgreSQL bin, würde ich lieber Leute fragen, die es wissen.

Mein Stapel enthält Zeilen mit folgender Struktur:

userid | rightid | remaining_count

Wenn die Tabelle also Zeilen enthält userid, bedeutet dies, dass alle dort vorhanden sind.


Sie möchten sehen, ob die Tabelle irgendwelche Zeilen oder Zeilen aus Ihrem Stapel enthält?
JNK

irgendwelche Zeilen aus meinem Stapel ja. Sie alle teilen sich das gleiche Feld und bearbeiten es ein wenig.
Valentin Kuzub

Bitte klären Sie Ihre Frage. Sie möchten einen Stapel von Datensätzen hinzufügen, alles oder nichts? Hat Count etwas Besonderes? (Übrigens ein reserviertes Wort, unpraktisch als Spaltenname)
Wildplasser

Okay, ich habe versucht, die tatsächliche Situation ein wenig zu vereinfachen, aber wir nähern uns immer mehr der tatsächlichen Umsetzung. Sobald diese Zeilen eingefügt sind (es gibt ein weiteres Feld für_Datum), beginne ich, die Rechte für einen bestimmten Benutzer zu verringern, da sie bestimmte Rechte verwenden. Sobald die Rechte 0 werden, können sie diese Aktionen für dieses Datum nicht mehr ausführen. das ist die wahre Geschichte
Valentin Kuzub

1
Zeigen Sie einfach (den relevanten Teil von) die Tabellendefinitionen und teilen Sie mit, was Sie vorhaben.
Wildplasser

Antworten:


345

Verwenden Sie das Schlüsselwort EXISTS für die Rückgabe TRUE / FALSE:

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

21
In dieser Erweiterung können Sie die zurückgegebene Spalte zum einfachen Nachschlagen benennen. ZBselect exists(select 1 from contact where id=12) AS "exists"
Rowan

3
Dies ist besser, da immer ein Wert (wahr oder falsch) anstelle von manchmal "Keine" (abhängig von Ihrer Programmiersprache) zurückgegeben wird, der möglicherweise nicht die erwartete Art und Weise erweitert.
isaaclw

1
Ich habe Seq Scan mit dieser Methode. Ich mache etwas falsch?
FiftiN

2
@ Michael.MI haben eine DB-Tabelle mit 30 Millionen Zeilen und wenn ich sie verwende existsoder wenn ich limit 1einen starken Leistungsabfall habe, weil Postgres Seq Scan anstelle von Index Scan verwendet. Und analyzehilft nicht.
FiftiN

2
@maciek Bitte haben Sie Verständnis dafür, dass 'id' ein Primärschlüssel ist, daher wäre "LIMIT 1" sinnlos, da es nur einen Datensatz mit dieser ID gibt
StartupGuy

34

Wie wäre es einfach:

select 1 from tbl where userid = 123 limit 1;

wo 123 ist die Benutzer-ID des Stapels, den Sie einfügen möchten?

Die obige Abfrage gibt entweder einen leeren Satz oder eine einzelne Zeile zurück, je nachdem, ob Datensätze mit der angegebenen Benutzer-ID vorhanden sind.

Wenn sich herausstellt, dass dies zu langsam ist, können Sie einen Index für erstellen tbl.userid.

Wenn in der Tabelle auch nur eine einzelne Zeile aus dem Stapel vorhanden ist, muss ich in diesem Fall meine Zeilen nicht einfügen, da ich sicher bin, dass sie alle eingefügt wurden.

Damit dies auch dann der Fall ist, wenn Ihr Programm während des Stapels unterbrochen wird, sollten Sie sicherstellen, dass Sie die Datenbanktransaktionen ordnungsgemäß verwalten (dh, dass der gesamte Stapel in eine einzelne Transaktion eingefügt wird).


11
Manchmal ist es programmatisch einfacher, "count (*) aus (select 1 ... limit 1)" auszuwählen, da garantiert immer eine Zeile mit dem Wert count (*) von 0 oder 1 zurückgegeben wird.
David Aldridge

@ DavidAldridge count (*) bedeutet immer noch, dass alle Zeilen gelesen werden müssen, während Limit 1 beim ersten Datensatz stoppt und zurückkehrt
Imraan

3
@ Imraan Ich denke, Sie haben die Abfrage falsch interpretiert. Das COUNTwirkt auf eine verschachtelte SELECT, die höchstens 1 Zeile hat (weil sich das LIMITin der Unterabfrage befindet).
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 ...
    )       
    ;

Übrigens: Wenn Sie möchten, dass der gesamte Stapel im Falle eines Duplikats fehlschlägt , dann (unter Berücksichtigung einer Primärschlüsseleinschränkung)

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

wird genau das tun, was Sie wollen: entweder ist es erfolgreich oder es schlägt fehl.


Dadurch wird jede Zeile überprüft. Er möchte einen einzigen Check machen.
JNK

1
Nein, es wird eine einzige Überprüfung durchgeführt. Die Unterabfrage ist nicht korreliert. Sobald ein passendes Paar gefunden wurde, wird es gerettet.
Wildplasser

Richtig, ich dachte, es bezieht sich auf die äußere Abfrage. +1 an Sie
JNK

Übrigens: Da sich die Abfrage in einer Transaktion befindet, passiert nichts, wenn eine doppelte ID eingefügt wird. Daher kann die Unterabfrage weggelassen werden.
Wildplasser

hmm ich bin mir nicht sicher ich verstehe. Nachdem die Rechte eingefügt wurden, beginne ich, die Anzahl der Spalten zu verringern. (nur einige Details für das Bild) Wenn bereits Zeilen vorhanden sind und die Unterabfrage weggelassen wird, werden meiner Meinung nach Fehler mit doppeltem eindeutigen Schlüssel ausgelöst, oder? (Benutzer-ID & rechts von diesem eindeutigen Schlüssel)
Valentin Kuzub

1
select true from tablename where condition limit 1;

Ich glaube, dass dies die Abfrage ist, die postgres zum Überprüfen von Fremdschlüsseln verwendet.

In Ihrem Fall können Sie dies auch auf einmal tun:

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

1

wie @MikeM betonte.

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

Mit dem Index bei Kontakt können die Zeitkosten normalerweise auf 1 ms reduziert werden.

CREATE INDEX index_contact on contact(id);

0
SELECT 1 FROM user_right where userid = ? LIMIT 1

Wenn Ihre Ergebnismenge eine Zeile enthält, müssen Sie diese nicht einfügen. Andernfalls fügen Sie Ihre Unterlagen ein.


Wenn das Bündel 100 Zeilen enthält, werden mir 100 Zeilen zurückgegeben. Findest du das gut?
Valentin Kuzub

Sie können es auf 1 Zeile beschränken. Sollte besser abschneiden. Schauen Sie sich dazu die bearbeitete Antwort von @aix an.
Fabian Barney

0

Wenn Sie über die Leistung nachdenken, können Sie möglicherweise "PERFORM" in einer Funktion wie dieser verwenden:

 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;

funktioniert bei mir nicht: Ich bekomme einen Syntaxfehler in der Nähe von perform
Simon

1
Das ist pl / pgsql, nicht SQL, daher der Syntaxfehler für "PERFORM", wenn versucht wird, es als SQL auszuführen
Mark K Cowan

-1

Ich möchte einen anderen Gedanken vorzuschlagen speziell Ihren Satz anzusprechen: „Also ich , wenn eine einzelne Zeile aus der Charge überprüfen möchten in der Tabelle existiert , weil dann ich weiß , sie alle wurden eingefügt .“

Sie machen die Dinge effizient, indem Sie sie in "Stapel" einfügen, aber dann die Existenzprüfungen jeweils für einen Datensatz durchführen? Dies scheint mir nicht intuitiv zu sein. Also , wenn Sie sagen , „ Einsätze werden immer in Chargen getanIch nehme an , Sie meinen Sie mehrere Datensätze mit einem Insert - Anweisung einfügen . Sie müssen erkennen, dass Postgres ACID-konform ist. Wenn Sie mehrere Datensätze (einen Datenstapel) mit einer Einfügeanweisung einfügen , müssen Sie nicht überprüfen, ob einige eingefügt wurden oder nicht. Die Anweisung besteht entweder oder sie schlägt fehl. Alle Datensätze werden eingefügt oder keine.

Auf der anderen Seite, wenn Ihr C # -Code einfach separate Einfügeanweisungen "setzt", zum Beispiel in einer Schleife, und in Ihrem Kopf ist dies ein "Stapel". Dann sollten Sie ihn tatsächlich nicht als "beschreiben". Einsätze erfolgen immer chargenweise ". Die Tatsache, dass Sie erwarten, dass ein Teil dessen, was Sie als "Stapel" bezeichnen, möglicherweise nicht eingefügt wird und daher eine Überprüfung erforderlich ist, legt den Schluss nahe, dass dies der Fall ist. In diesem Fall haben Sie ein grundlegenderes Problem. Sie müssen Ihr Paradigma ändern, um tatsächlich mehrere Datensätze mit einer Einfügung einzufügen, und auf die Überprüfung verzichten, ob die einzelnen Datensätze es geschafft haben.

Betrachten Sie dieses Beispiel:

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

Dies ist in der Tat das Paradigma für jede ACID-kompatible Datenbank. Nicht nur für Postgresql. Mit anderen Worten, Sie sind besser dran, wenn Sie Ihr "Batch" -Konzept korrigieren und zunächst keine zeilenweisen Überprüfungen durchführen müssen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.