Câu trả lời hiện được chấp nhận không trả lời câu hỏi. Và nó là sai về nguyên tắc. a BETWEEN x AND y
dịch sang:
a >= x AND a <= y
Bao gồm giới hạn trên, trong khi mọi người thường cần loại trừ nó:
a >= x AND a < y
Với ngày tháng bạn có thể dễ dàng điều chỉnh. Đối với năm 2009, hãy sử dụng '2009-12-31' làm giới hạn trên.
Nhưng nó không đơn giản với dấu thời gian cho phép các chữ số phân số. Các phiên bản Postgres hiện đại sử dụng một số nguyên 8 byte bên trong để lưu trữ tối đa 6 giây phân số (độ phân giải µs). Biết được điều này, chúng tôi có thể vẫn làm cho nó hoạt động, nhưng điều đó không trực quan và phụ thuộc vào chi tiết triển khai. Ý tưởng tồi.
Hơn nữa, a BETWEEN x AND y
không tìm thấy các phạm vi chồng chéo. Chúng tôi cần:
b >= x AND a < y
Và những người chơi không bao giờ rời bỏ vẫn chưa được xem xét.
Câu trả lời thích hợp
Giả sử năm 2009
, Tôi sẽ diễn đạt lại câu hỏi mà không thay đổi ý nghĩa của nó:
"Tìm tất cả các cầu thủ của một đội nhất định đã tham gia trước năm 2010 và không rời khỏi trước năm 2009."
Truy vấn cơ bản:
SELECT p.*
FROM team t
JOIN contract c USING (name_team)
JOIN player p USING (name_player)
WHERE t.name_team = ?
AND c.date_join < date '2010-01-01'
AND c.date_leave >= date '2009-01-01';
Nhưng còn nhiều hơn thế nữa:
Nếu tính toàn vẹn của tham chiếu được thực thi với các ràng buộc FK, bảng team
bản thân nó chỉ là nhiễu trong truy vấn và có thể bị loại bỏ.
Mặc dù cùng một người chơi có thể rời đi và tham gia lại cùng một đội, chúng tôi cũng cần phải gấp các bản sao có thể có, ví dụ:với DISTINCT
.
Và chúng tôi có thể cần cung cấp cho một trường hợp đặc biệt:người chơi không bao giờ rời đi. Giả sử những người chơi đó có NULL trong date_leave
.
"Một cầu thủ không được biết là đã ra đi được cho là sẽ chơi cho đội cho đến ngày nay."
Truy vấn tinh chỉnh:
SELECT DISTINCT p.*
FROM contract c
JOIN player p USING (name_player)
WHERE c.name_team = ?
AND c.date_join < date '2010-01-01'
AND (c.date_leave >= date '2009-01-01' OR c.date_leave IS NULL);
Quyền ưu tiên của toán tử chống lại chúng tôi, AND
liên kết trước OR
. Chúng ta cần dấu ngoặc đơn.
Câu trả lời liên quan với DISTINCT
được tối ưu hóa (nếu trùng lặp là phổ biến):
- Nhiều đến nhiều bàn - Hiệu suất kém
Thông thường, tên thể nhân không phải là duy nhất và khóa chính thay thế được sử dụng. Nhưng rõ ràng là name_player
là khóa chính của player
. Nếu tất cả những gì bạn cần là tên người chơi, chúng tôi không cần bảng player
trong truy vấn:
SELECT DISTINCT name_player
FROM contract
WHERE name_team = ?
AND date_join < date '2010-01-01'
AND (date_leave >= date '2009-01-01' OR date_leave IS NULL);
SQL OVERLAPS
toán tử
Hướng dẫn sử dụng:
OVERLAPS
tự động lấy giá trị trước đó của cặp làm giá trị bắt đầu. Mỗi khoảng thời gian được coi là đại diện cho giai đoạn nửa mở cửastart <= time < end
, trừ khistart
vàend
đều bằng nhau trong trường hợp nào thì nó đại diện cho thời điểm duy nhất đó.
Để chăm sóc NULL
tiềm năng giá trị, COALESCE
có vẻ dễ dàng nhất:
SELECT DISTINCT name_player
FROM contract
WHERE name_team = ?
AND (date_join, COALESCE(date_leave, CURRENT_DATE)) OVERLAPS
(date '2009-01-01', date '2010-01-01'); -- upper bound excluded
Loại phạm vi có hỗ trợ chỉ mục
Trong Postgres 9.2 trở lên bạn cũng có thể hoạt động với loại phạm vi thực tế :
SELECT DISTINCT name_player
FROM contract
WHERE name_team = ?
AND daterange(date_join, date_leave) &&
daterange '[2009-01-01,2010-01-01)'; -- upper bound excluded
Các loại phạm vi thêm một số chi phí và chiếm nhiều không gian hơn. 2 x date
=8 byte; 1 x daterange
=14 byte trên đĩa hoặc 17 byte trong RAM. Nhưng kết hợp với toán tử chồng chéo &&
truy vấn có thể được hỗ trợ bằng chỉ mục GiST.
Ngoài ra, không cần các giá trị NULL trong trường hợp đặc biệt. NULL có nghĩa là "phạm vi mở" trong một loại phạm vi - chính xác là những gì chúng ta cần. Định nghĩa bảng thậm chí không cần phải thay đổi:chúng tôi có thể tạo loại phạm vi ngay lập tức - và hỗ trợ truy vấn với chỉ mục biểu thức phù hợp:
CREATE INDEX mv_stock_dr_idx ON mv_stock USING gist (daterange(date_join, date_leave));
Có liên quan:
- Bảng lịch sử chứng khoán trung bình