Kiểm tra nhanh nhất nếu hàng tồn tại trong PostgreSQL


177

Tôi có một loạt các hàng mà tôi cần chèn vào bảng, nhưng các chèn này luôn được thực hiện theo lô. Vì vậy, tôi muốn kiểm tra xem một hàng duy nhất trong lô có tồn tại trong bảng không vì sau đó tôi biết tất cả chúng đã được chèn.

Vì vậy, nó không phải là một kiểm tra khóa chính, nhưng không nên quan trọng quá nhiều. Tôi chỉ muốn kiểm tra một hàng duy nhất để count(*)có thể không tốt, vì vậy nó giống như existstôi đoán.

Nhưng vì tôi khá mới với PostgreSQL, tôi muốn hỏi những người biết.

Lô của tôi chứa các hàng có cấu trúc sau:

userid | rightid | remaining_count

Vì vậy, nếu bảng chứa bất kỳ hàng nào với điều kiện useridcó nghĩa là tất cả chúng đều có mặt ở đó.


Bạn muốn xem bảng có BẤT K row hàng nào hay bất kỳ hàng nào trong lô của bạn không?
JNK

bất kỳ hàng từ lô của tôi có. tất cả họ chia sẻ cùng một lĩnh vực chỉnh sửa một chút.
Valentin Kuzub

Hãy làm rõ câu hỏi của bạn. Bạn muốn thêm một loạt các hồ sơ, tất cả hoặc không có gì? Có điều gì đặc biệt về tính? (BTW một từ dành riêng, không thực tế như một tên cột)
wildplasser

được rồi, tôi đã cố gắng đơn giản hóa tình hình thực tế một chút nhưng chúng tôi đang ngày càng tiến gần hơn đến việc thực hiện thực tế. Khi các hàng đó được chèn vào (có một trường khác for_date), tôi bắt đầu giảm quyền cho người dùng được chỉ định khi họ sử dụng các quyền cụ thể, khi quyền trở thành 0, họ không thể thực hiện các hành động đó nữa cho ngày đó. đó là câu chuyện có thật
Valentin Kuzub

1
Chỉ cần hiển thị (phần có liên quan) của các định nghĩa bảng và cho biết những gì bạn định làm.
wildplasser

Câu trả lời:


345

Sử dụng từ khóa EXISTS cho lợi nhuận TRUE / FALSE:

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

21
Mở rộng về điều này, bạn có thể đặt tên cột trả về để tham khảo dễ dàng. Ví dụ:select exists(select 1 from contact where id=12) AS "exists"
Rowan

3
Điều này tốt hơn, bởi vì nó sẽ luôn trả về một giá trị (đúng hoặc sai) thay vì đôi khi Không (tùy thuộc vào ngôn ngữ lập trình của bạn) có thể không mở rộng theo cách bạn mong đợi.
isaaclw

1
Tôi có Seq Scan với việc sử dụng phương pháp này. Tôi làm gì sai?
FiftiN

2
@ Michael.MI có bảng DB với 30 triệu hàng và khi tôi sử dụng existshoặc limit 1tôi giảm hiệu suất mạnh vì Postgres sử dụng Seq Scan thay vì Index Scan. Và analyzekhông giúp được gì.
FiftiN

2
@maciek vui lòng hiểu rằng 'id' là khóa chính, do đó, GIỚI HẠN GIỚI HẠN 1 sẽ là vô nghĩa vì chỉ có một bản ghi với id đó
StartupGuy

34

Làm thế nào về đơn giản:

select 1 from tbl where userid = 123 limit 1;

nơi 123là userid của hàng loạt mà bạn sắp chèn.

Truy vấn trên sẽ trả về một tập hợp trống hoặc một hàng đơn, tùy thuộc vào việc có các bản ghi với userid đã cho hay không.

Nếu điều này trở nên quá chậm, bạn có thể xem xét việc tạo một chỉ mục trên tbl.userid.

nếu thậm chí một hàng từ lô tồn tại trong bảng, trong trường hợp đó tôi không phải chèn các hàng của mình vì tôi biết chắc chắn tất cả chúng đều được chèn.

Để điều này vẫn đúng ngay cả khi chương trình của bạn bị gián đoạn giữa đợt, tôi khuyên bạn nên đảm bảo rằng bạn quản lý các giao dịch cơ sở dữ liệu một cách thích hợp (nghĩa là toàn bộ lô được chèn trong một giao dịch).


11
Đôi khi có thể dễ dàng hơn về mặt lập trình để "chọn đếm (*) từ (chọn 1 ... giới hạn 1)" vì nó được đảm bảo luôn trả về một hàng có giá trị đếm (*) bằng 0 hoặc 1.
David Aldridge

Số lượng @DavidAldridge (*) vẫn có nghĩa là tất cả các hàng phải được đọc, trong khi giới hạn 1 dừng ở bản ghi đầu tiên và trả về
Imraan

3
@Imraan Tôi nghĩ bạn đã hiểu sai truy vấn. Các COUNThành vi trên một lồng SELECTcó nhiều nhất 1 hàng (vì LIMITnằm trong truy vấn con).
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: nếu bạn muốn toàn bộ lô bị lỗi trong trường hợp trùng lặp, thì (đưa ra một ràng buộc khóa chính)

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

sẽ làm chính xác những gì bạn muốn: hoặc nó thành công, hoặc nó thất bại.


Điều này sẽ kiểm tra từng hàng. Anh ấy muốn làm một kiểm tra duy nhất.
JNK

1
Không, nó kiểm tra một lần duy nhất. Các truy vấn phụ là không tương thích. Nó sẽ bảo lãnh một khi một cặp phù hợp được tìm thấy.
wildplasser

Đúng bạn, tôi nghĩ rằng nó đề cập đến các truy vấn bên ngoài. +1 cho bạn
JNK

BTW: vì truy vấn nằm trong một giao dịch, sẽ không có gì xảy ra nếu một id trùng lặp được chèn vào, do đó truy vấn con có thể được bỏ qua.
wildplasser

hmm tôi không chắc tôi hiểu Sau khi quyền được chèn, tôi bắt đầu giảm số cột. (chỉ một số chi tiết cho hình ảnh) Nếu các hàng đã tồn tại và truy vấn con bị bỏ qua, tôi nghĩ rằng tôi sẽ gặp lỗi với khóa duy nhất trùng lặp hay sao? (userid & đúng dạng khóa duy nhất)
Valentin Kuzub

1
select true from tablename where condition limit 1;

Tôi tin rằng đây là truy vấn mà postgres sử dụng để kiểm tra khóa ngoại.

Trong trường hợp của bạn, bạn cũng có thể làm điều này trong một lần:

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

1

như @MikeM đã chỉ ra.

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

với chỉ số khi tiếp xúc, nó thường có thể giảm chi phí thời gian xuống còn 1 ms.

CREATE INDEX index_contact on contact(id);

0
SELECT 1 FROM user_right where userid = ? LIMIT 1

Nếu tập kết quả của bạn chứa một hàng thì bạn không phải chèn. Nếu không thì chèn hồ sơ của bạn.


Nếu bó chứa 100 hàng nó sẽ trả về cho tôi 100 hàng, bạn nghĩ điều đó có tốt không?
Valentin Kuzub

Bạn có thể giới hạn ở 1 hàng. Nên thực hiện tốt hơn. Hãy xem câu trả lời được chỉnh sửa từ @aix cho điều đó.
Fabian Barney

0

Nếu bạn nghĩ về hiệu suất, có thể bạn có thể sử dụng "PERFORM" trong một chức năng như thế này:

 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;

không hoạt động với tôi: Tôi gặp lỗi cú pháp gần thực hiện
Simon

1
đó là pl / pssql, không phải SQL, do đó lỗi cú pháp cho "PERFORM" nếu cố chạy nó dưới dạng SQL
Mark K Cowan

-1

Tôi muốn đề xuất một ý nghĩ khác để giải quyết cụ thể câu của bạn: "Vì vậy, tôi muốn kiểm tra xem một hàng duy nhất trong lô có tồn tại trong bảng không vì sau đó tôi biết tất cả chúng đều được chèn vào ."

Bạn đang làm cho mọi thứ hiệu quả bằng cách chèn vào "lô" nhưng sau đó thực hiện kiểm tra sự tồn tại một bản ghi tại một thời điểm? Điều này dường như phản trực giác với tôi. Vì vậy, khi bạn nói "việc chèn luôn được thực hiện theo đợt " thì tôi hiểu là bạn đang chèn nhiều bản ghi với một câu lệnh chèn . Bạn cần nhận ra rằng Postgres tuân thủ ACID. Nếu bạn đang chèn nhiều bản ghi (một lô dữ liệu) bằng một câu lệnh chèn , không cần kiểm tra xem một số có được chèn hay không. Tuyên bố hoặc thông qua hoặc nó sẽ thất bại. Tất cả các hồ sơ sẽ được chèn hoặc không có.

Mặt khác, nếu mã C # của bạn chỉ đơn giản là thực hiện một câu lệnh chèn riêng biệt "set", ví dụ, trong một vòng lặp và trong tâm trí của bạn, thì đây là một "đợt" .. thì thực tế bạn không nên mô tả nó là " chèn luôn được thực hiện theo lô ". Thực tế là bạn mong đợi rằng một phần của cái mà bạn gọi là "lô", thực sự có thể không được chèn vào, và do đó cảm thấy cần phải kiểm tra, đề nghị mạnh mẽ đây là trường hợp, trong trường hợp đó bạn có vấn đề cơ bản hơn. Bạn cần thay đổi mô hình của mình để thực sự chèn nhiều bản ghi với một lần chèn và kiểm tra trước nếu các bản ghi riêng lẻ thực hiện.

Xem xét ví dụ này:

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

Trên thực tế, đây là mô hình cho bất kỳ DB tuân thủ ACID nào .. không chỉ Postgresql. Nói cách khác, bạn sẽ tốt hơn nếu bạn sửa khái niệm "lô" của mình và tránh phải thực hiện bất kỳ kiểm tra hàng nào ở vị trí đầu tiên.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.