Tham gia trái mà không có hàng trùng lặp


8

Tôi có hai bảng được gọi recordrecord_history. Đối với mỗi bản ghi, có thể có nhiều hơn một lịch sử. Họ có thể được tham gia bởi idrecord_id. Tôi muốn nhận tất cả các recordmục của record_historydữ liệu gần đây . Tôi đã tạo truy vấn như,

SELECT rec.id, rec.name, rech1.data AS last_history_data
FROM record rec
LEFT OUTER JOIN record_history rech1 ON (rec.id = rech1.record_id)
LEFT OUTER JOIN record_history rech2 ON (rec.id = rech2.record_id AND rech2.ts > rech1.ts)
WHERE rech2.id IS NULL
ORDER BY rec.id DESC

Ở đây, tôi nhận được một cái mới nhất bởi ts. Điều này hoạt động miễn là không có tsmục trùng lặp . Nếu dấu thời gian gần đây được lặp lại record_history, truy vấn này trả về nhiều hơn một hàng cho một bản ghi. Làm thế nào chúng ta có thể áp dụng giới hạn ở đây trên liên kết bên trái để hạn chế các hàng trùng lặp?


bạn không chọn gì từ rech2?
Evan Carroll

@EvanCarroll rech2được sử dụng ở đây chọn record_history đầu tiên và nhu cầu của tôi cho rech2 là phảiNULL
rar

Câu trả lời:


11

Trừ khi bạn ở phiên bản Postgres rất cũ, bạn không cần tham gia kép. Bạn có thể nhận được kết quả tương tự bằng cách sử dụng một LATERALtham gia .

Các kết quả trùng lặp có thể tránh được trong phương pháp của bạn bằng cách thêm một điều kiện thứ hai bên cạnh rec.id = rech2.record_id. Với LATERALphương thức nối, việc sử dụng LIMITlà tránh nó bằng mọi cách. Có thể chỉ có 1 hàng được trả về từ truy vấn con bên. Chúng ta có thể thêm một điều kiện thứ hai để lựa chọn có tính xác định (từ hai hoặc nhiều hàng có cùng dấu thời gian):

SELECT rec.id, rec.name, rech.data AS last_history_data
FROM record AS rec
     LEFT OUTER JOIN LATERAL
     ( SELECT rech.data
       FROM record_history AS rech
       WHERE rec.id = rech.record_id
       ORDER BY rech.ts DESC
                -- ,rech.id DESC               -- optional
       LIMIT 1 
     ) AS rech
     ON TRUE
ORDER BY rec.id DESC ;

Về cách thực hiện điều này với phương thức ban đầu (2 tham gia và IS NULLkiểm tra), bạn có thể thay đổi ONđiều kiện - giả sử có một idcột trong bảng lịch sử sao cho (id)hoặc ít nhất (ts, id)là duy nhất:

LEFT OUTER JOIN record_history rech2 
ON rec.id = rech2.record_id 
   AND (rech2.ts > rech1.ts OR rech2.ts = rech1.ts AND rech2.id > rech1.id)

Nhân tiện, bạn có thể thay thế phép LEFTnối thứ hai đó và IS NULLkiểm tra bằng NOT EXISTStruy vấn con có cùng kết quả và có thể có hiệu quả tương tự (hoặc thậm chí với NOT INtruy vấn con mặc dù điều đó cần được chăm sóc thêm cho các cột không thể, không được đề xuất).


Tuyệt quá! Điều đó đã làm việc. Không biết về LATERAL. Đó là một học tập tốt cho tôi. Cảm ơn!
RaR

@ypercube The duplicate results can be avoided in your method by adding a second condition besides the rec.id = rech2.record_idĐiều kiện nào chúng ta có thể thêm vào đây để tránh trùng lặp?
RaR

1
Tại sao làm left join lateral .. on (true)hơn là CROSS JOIN LATERAL ()?
Evan Carroll

3
@Evan vì có thể có hàng trong recordmà không có hàng liên quan record_history.
ypercubeᵀᴹ

1
@EvanCarroll, xin lỗi, tôi không đề cập rõ ràng. Tôi muốn tất cả recordcác mục ngay cả khi không có mục lịch sử cho một số.
RaR
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.