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ą.


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)

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).


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.


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);

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);

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.


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;

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.