Najszybsze sprawdzenie, czy wiersz istnieje w PostgreSQL

177

Mam kilka wierszy, które muszę wstawić do tabeli, ale te wstawki są zawsze wykonywane partiami. Chcę więc sprawdzić, czy w tabeli istnieje pojedynczy wiersz z partii, ponieważ wtedy wiem, że wszystkie zostały wstawione.

Więc nie jest to klucz podstawowy, ale nie powinien mieć większego znaczenia. Chciałbym sprawdzić tylko jeden wiersz, więc count(*)prawdopodobnie nie jest dobry, więc existsmyślę, że jest to coś takiego .

Ale ponieważ jestem całkiem nowy w PostgreSQL, wolałbym zapytać ludzi, którzy wiedzą.

Moja partia zawiera wiersze o następującej strukturze:

userid | rightid | remaining_count

Więc jeśli tabela zawiera jakiekolwiek wiersze z podaniem userid, oznacza to, że wszystkie tam są.

Valentin Kuzub
źródło
Chcesz sprawdzić, czy tabela ma JAKIEKOLWIEK wiersze lub jakiekolwiek wiersze z Twojej partii?
JNK,
wszystkie wiersze z mojej partii tak. wszystkie mają to samo pole, które trochę edytuje.
Valentin Kuzub,
Proszę wyjaśnić swoje pytanie. Chcesz dodać partię rekordów, wszystko czy nic? Czy jest coś specjalnego w hrabstwie? (
Swoją drogą
ok, próbowałem trochę uprościć rzeczywistą sytuację, ale jesteśmy coraz bliżej rzeczywistej realizacji. Po wstawieniu tych wierszy (tam jest kolejne pole for_date) zaczynam zmniejszać uprawnienia dla określonego użytkownika, ponieważ korzystają oni z określonych praw, a gdy uprawnienia osiągną wartość 0, nie mogą już wykonywać tych czynności dla tej daty. to prawdziwa historia
Valentin Kuzub,
1
Po prostu pokaż (odpowiednią część) definicje tabeli i powiedz, co zamierzasz zrobić.
wildplasser,

Odpowiedzi:

345

Użyj słowa kluczowego EXISTS dla powrotu PRAWDA / FAŁSZ:

select exists(select 1 from contact where id=12)
StartupGuy
źródło
21
Rozszerzenie w tym zakresie, możesz nazwać zwróconą kolumnę dla łatwego odniesienia. Np.select exists(select 1 from contact where id=12) AS "exists"
Rowan
3
Jest to lepsze, ponieważ zawsze zwraca wartość (prawda lub fałsz) zamiast czasami None (w zależności od twojego języka programowania), która może nie rozwinąć się w sposób, jakiego oczekujesz.
isaaclw
1
Mam skanowanie sekwencyjne przy użyciu tej metody. Robię coś źle?
FiftiN
2
@ Michael.MI ma tabelę DB z 30 milionami wierszy, a kiedy używam existslub limit 1mam duży spadek wydajności, ponieważ Postgres używa Seq Scan zamiast Index Scan. I analyzenie pomaga.
FiftiN
2
@maciek proszę zrozumieć, że 'id' jest kluczem podstawowym, więc „LIMIT 1” byłby bezcelowy, ponieważ istnieje tylko jeden rekord z tym identyfikatorem
StartupGuy
34

A może po prostu:

select 1 from tbl where userid = 123 limit 1;

gdzie 123jest identyfikator użytkownika partii, którą zamierzasz wstawić.

Powyższe zapytanie zwróci pusty zestaw lub pojedynczy wiersz, w zależności od tego, czy istnieją rekordy o podanym identyfikatorze użytkownika.

Jeśli okaże się to zbyt wolne, możesz spróbować utworzyć indeks tbl.userid.

jeśli choćby jeden wiersz z partii istnieje w tabeli, w takim przypadku nie muszę wstawiać swoich wierszy, ponieważ wiem na pewno, że wszystkie zostały wstawione.

Aby pozostało to prawdą, nawet jeśli program zostanie przerwany w połowie partii, radziłbym upewnić się, że odpowiednio zarządzasz transakcjami bazy danych (tj. Cała partia jest wstawiana w ramach jednej transakcji).

NPE
źródło
11
Czasami programowo łatwiej jest wybrać opcję „select count (*) from (select 1 ... limit 1)”, ponieważ zawsze gwarantuje się zwrócenie wiersza o wartości count (*) równej 0 lub 1.
David Aldridge
@DavidAldridge count (*) nadal oznacza, że ​​wszystkie wiersze muszą zostać odczytane, podczas gdy limit 1 zatrzymuje się na pierwszym rekordzie i zwraca
Imraan
3
@Imraan Myślę, że źle zinterpretowałeś zapytanie. COUNTDziała na zagnieżdżone SELECT, że zawiera co najwyżej 1 rząd (ponieważ LIMITjest podkwerendzie).
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: jeśli chcesz, aby cała partia zakończyła się niepowodzeniem w przypadku duplikatu, to (biorąc pod uwagę ograniczenie klucza podstawowego)

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

zrobi dokładnie to, czego chcesz: albo się powiedzie, albo zawiedzie.

wildplasser
źródło
Spowoduje to sprawdzenie każdego wiersza. Chce zrobić jeden czek.
JNK,
1
Nie, wykonuje pojedynczy test. Podzapytanie jest nieskorelowane. Wyskoczy po znalezieniu jednej pasującej pary.
wildplasser
Masz rację, myślałem, że odnosi się to do zewnętrznego zapytania. +1 do ciebie
JNK,
BTW: ponieważ zapytanie znajduje się wewnątrz transakcji, nic się nie stanie, jeśli zostanie wstawiony zduplikowany identyfikator, stąd podzapytanie można pominąć.
wildplasser,
hmm nie jestem pewien, czy rozumiem. Po wstawieniu praw zaczynam zmniejszać liczbę kolumn. (tylko kilka szczegółów do obrazu) Jeśli wiersze już istnieją i pominięto podzapytanie, myślę, że pojawią się błędy z wyrzuceniem duplikatu unikalnego klucza lub? (identyfikator użytkownika i prawidłowa forma tego unikalnego klucza)
Valentin Kuzub
1
select true from tablename where condition limit 1;

Uważam, że jest to zapytanie, którego używa postgres do sprawdzania kluczy obcych.

W twoim przypadku możesz to zrobić za jednym razem:

insert into yourtable select $userid, $rightid, $count where not (select true from yourtable where userid = $userid limit 1);
Royce
źródło
1

jak wskazał @MikeM.

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

z indeksem przy kontakcie może zwykle zmniejszyć koszt czasu do 1 ms.

CREATE INDEX index_contact on contact(id);
hcnak
źródło
0
SELECT 1 FROM user_right where userid = ? LIMIT 1

Jeśli zestaw wyników zawiera wiersz, nie musisz go wstawiać. W przeciwnym razie wprowadź swoje rekordy.

Fabian Barney
źródło
jeśli pęczek zawiera 100 wierszy, zwróci mi 100 wierszy, myślisz, że to dobrze?
Valentin Kuzub,
Możesz ograniczyć to do 1 wiersza. Powinien działać lepiej. W tym celu spójrz na zredagowaną odpowiedź od @aix.
Fabian Barney,
0

Jeśli myślisz o performace, być może możesz użyć „PERFORM” w funkcji takiej jak ta:

 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;
franki
źródło
nie działa ze mną: pojawia się błąd składni w pobliżu wykonania
Simon,
1
to jest pl / pgsql, a nie SQL, stąd błąd składni dla „PERFORM” przy próbie uruchomienia go jako SQL
Mark K Cowan
-1

Chciałbym zaproponować inną myśl, aby konkretnie odnieść się do twojego zdania: „Chcę więc sprawdzić, czy w tabeli istnieje pojedynczy wiersz z partii, ponieważ wtedy wiem, że wszystkie zostały wstawione ”.

Robisz rzeczy wydajnie, wstawiając „partie”, ale potem sprawdzanie istnienia sprawdza tylko jeden rekord na raz? Wydaje mi się to sprzeczne z intuicją. Więc kiedy mówisz „ wstawianie zawsze wykonuje się partiami ”, rozumiem, że masz na myśli wstawianie wielu rekordów za pomocą jednej instrukcji wstawiania . Musisz zdać sobie sprawę, że Postgres jest zgodny z ACID. Jeśli wstawiasz wiele rekordów (zestaw danych) za pomocą jednej instrukcji wstawiania , nie ma potrzeby sprawdzania, czy niektóre zostały wstawione, czy nie. Instrukcja przechodzi lub zakończy się niepowodzeniem. Wszystkie rekordy zostaną wstawione lub żadne.

Z drugiej strony, jeśli Twój kod C # po prostu wykonuje „set” oddzielne instrukcje wstawiania, na przykład w pętli, i myślisz, że jest to „partia”… to nie powinieneś opisywać tego jako „ wkładki są zawsze wykonywane partiami ”. Fakt, że spodziewasz się, że część tego, co nazywasz „pakietem”, może w rzeczywistości nie zostać włożona, a zatem odczuwasz potrzebę sprawdzenia, zdecydowanie sugeruje, że tak jest, w takim przypadku masz bardziej podstawowy problem. Musisz zmienić swój paradygmat, aby faktycznie wstawić wiele rekordów za pomocą jednej wstawki i zrezygnować z sprawdzania, czy pojedyncze rekordy to zrobiły.

Rozważmy ten przykład:

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

W rzeczywistości jest to paradygmat dla każdej bazy danych zgodnej z ACID .. nie tylko Postgresql. Innymi słowy, lepiej będzie, jeśli naprawisz koncepcję „partii” i unikniesz konieczności sprawdzania każdego wiersza w pierwszej kolejności.

StartupGuy
źródło