PostgreSQL DISTINCT ON với thứ tự khác nhau theo


216

Tôi muốn chạy truy vấn này:

SELECT DISTINCT ON (address_id) purchases.address_id, purchases.*
FROM purchases
WHERE purchases.product_id = 1
ORDER BY purchases.purchased_at DESC

Nhưng tôi nhận được lỗi này:

PG :: Error: ERROR: CHỌN DISTINCT ON biểu thức phải khớp với biểu thức ORDER BY ban đầu

Thêm biểu thức address_idđầu tiên làm ORDER BYim lặng lỗi, nhưng tôi thực sự không muốn thêm sắp xếp address_id. Có thể làm mà không cần đặt hàng bằng cách address_id?


Điều khoản đặt hàng của bạn đã buy_at không address_id. Bạn có thể làm rõ câu hỏi của mình không.
Teja

Đơn đặt hàng của tôi đã mua vì tôi muốn nó, nhưng postgres cũng yêu cầu địa chỉ (xem thông báo lỗi).
sl_orms

3
Đã trả lời đầy đủ tại đây - stackoverflow.com/questions/9796078/ Từ Cảm ơn stackoverflow.com/users/268273/
mosty- mostacho

Cá nhân tôi nghĩ rằng việc yêu cầu DISTINCT ON để khớp với ORDER BY là rất đáng nghi ngờ, vì có nhiều trường hợp sử dụng hợp pháp để có chúng khác nhau. Có một bài đăng trên postgresql.uservoice đang cố gắng thay đổi điều này cho những người cảm thấy tương tự. postgresql.uservoice.com/forums/21853-general/suggestions/ Kẻ
dấu chấm phẩy

có cùng một vấn đề, và phải đối mặt với cùng một giới hạn. Hiện tại tôi đã chia nó thành một truy vấn phụ và sau đó đặt hàng, nhưng nó cảm thấy bẩn.
Công viên Guy

Câu trả lời:


207

Tài liệu nói:

DISTINCT ON (biểu thức [, ...]) chỉ giữ hàng đầu tiên của mỗi bộ hàng trong đó các biểu thức đã cho ước tính bằng nhau. [...] Lưu ý rằng "hàng đầu tiên" của mỗi bộ là không thể đoán trước trừ khi ORDER BY được sử dụng để đảm bảo rằng hàng mong muốn xuất hiện đầu tiên. [...] (Các) biểu thức DISTINCT ON phải khớp với (các) biểu thức ORDER BY ngoài cùng bên trái.

Tài liệu chính thức

Vì vậy, bạn sẽ phải thêm address_idđơn đặt hàng bằng cách.

Ngoài ra, nếu bạn đang tìm kiếm toàn bộ hàng chứa sản phẩm được mua gần đây nhất cho từng sản phẩm address_idvà kết quả được sắp xếp theo purchased_atthì bạn đang cố gắng giải quyết vấn đề N lớn nhất cho mỗi nhóm có thể được giải quyết bằng các phương pháp sau:

Giải pháp chung nên hoạt động trong hầu hết các DBMS:

SELECT t1.* FROM purchases t1
JOIN (
    SELECT address_id, max(purchased_at) max_purchased_at
    FROM purchases
    WHERE product_id = 1
    GROUP BY address_id
) t2
ON t1.address_id = t2.address_id AND t1.purchased_at = t2.max_purchased_at
ORDER BY t1.purchased_at DESC

Một giải pháp định hướng PostgreSQL khác dựa trên câu trả lời của @ hkf:

SELECT * FROM (
  SELECT DISTINCT ON (address_id) *
  FROM purchases 
  WHERE product_id = 1
  ORDER BY address_id, purchased_at DESC
) t
ORDER BY purchased_at DESC

Vấn đề được làm rõ, mở rộng và giải quyết ở đây: Chọn các hàng được sắp xếp theo một số cột và phân biệt trên một cột khác


40
Nó hoạt động, nhưng đưa ra thứ tự sai. Đó là lý do tại sao tôi muốn thoát khỏi address_id trong mệnh đề thứ tự
sl_orms

1
Tài liệu rất rõ ràng: Bạn không thể bởi vì hàng được chọn sẽ không thể đoán trước được
Mosty Mostacho

3
Nhưng có thể có một cách khác để chọn mua hàng mới nhất cho các địa chỉ disticnt?
sl_orms

1
Nếu bạn cần đặt hàng theo Purchasing.purchasing_at, bạn có thể thêm buy_at vào điều kiện DISTINCT của mình : SELECT DISTINCT ON (purchases.purchased_at, address_id). Tuy nhiên, hai bản ghi có cùng địa chỉ address_id nhưng giá trị buy_at khác nhau sẽ dẫn đến trùng lặp trong tập trả về. Hãy chắc chắn rằng bạn nhận thức được dữ liệu bạn đang truy vấn.
Brendan Benson

23
Tinh thần của câu hỏi là rõ ràng. Không cần phải chọn về ngữ nghĩa. Thật đáng buồn khi câu trả lời được chấp nhận và bình chọn nhiều nhất không giúp bạn giải quyết vấn đề.
nicooga

55

Bạn có thể đặt hàng theo address_id trong truy vấn con, sau đó đặt hàng theo những gì bạn muốn trong một truy vấn bên ngoài.

SELECT * FROM 
    (SELECT DISTINCT ON (address_id) purchases.address_id, purchases.* 
    FROM "purchases" 
    WHERE "purchases"."product_id" = 1 ORDER BY address_id DESC ) 
ORDER BY purchased_at DESC

3
Nhưng điều này sẽ chậm hơn chỉ là một truy vấn, phải không?
sl_orms

2
Rất ít có. Mặc dù vì bạn đã mua hàng. * Trong bản gốc của bạn select, tôi không nghĩ đây là mã sản xuất?
hkf

8
Tôi muốn thêm rằng đối với các phiên bản mới hơn của postgres, bạn cần đặt bí danh cho truy vấn con. Ví dụ: SELECT * FROM (.. CHỌN VỀ DISTINCT (address_id) purchases.address_id mua * FROM "mua" WHERE "mua" "product_id" = 1 ORDER BY DESC address_id) AS tmp ORDER BY tmp.purchased_at DESC
aembke

Điều này sẽ trở lại address_idhai lần (không cần). Nhiều khách hàng có vấn đề với tên cột trùng lặp. ORDER BY address_id DESClà vô nghĩa và sai lệch. Nó không có gì hữu ích trong truy vấn này. Kết quả là một lựa chọn tùy ý từ mỗi bộ hàng giống nhau address_id, không phải là hàng mới nhất purchased_at. Câu hỏi mơ hồ không hỏi rõ ràng, nhưng đó gần như chắc chắn là ý định của OP. Tóm lại: không sử dụng truy vấn này . Tôi đăng bài thay thế với lời giải thích.
Erwin Brandstetter

Đã làm cho tôi. Câu trả lời chính xác.
Matt West

46

Một truy vấn con có thể giải quyết nó:

SELECT *
FROM  (
    SELECT DISTINCT ON (address_id) *
    FROM   purchases
    WHERE  product_id = 1
    ) p
ORDER  BY purchased_at DESC;

Các biểu thức hàng đầu ORDER BYphải đồng ý với các cột trong DISTINCT ON, vì vậy bạn không thể sắp xếp theo các cột khác nhau trong cùng một SELECT.

Chỉ sử dụng một bổ sung ORDER BYtrong truy vấn con nếu bạn muốn chọn một hàng cụ thể từ mỗi bộ:

SELECT *
FROM  (
    SELECT DISTINCT ON (address_id) *
    FROM   purchases
    WHERE  product_id = 1
    ORDER  BY address_id, purchased_at DESC  -- get "latest" row per address_id
    ) p
ORDER  BY purchased_at DESC;

Nếu purchased_atcó thể NULL, hãy xem xét DESC NULLS LAST. Nhưng hãy chắc chắn để phù hợp với chỉ số của bạn nếu bạn có ý định sử dụng nó. Xem:

Liên quan, với giải thích thêm:


Bạn không thể sử dụng DISTINCT ONmà không có sự phù hợp ORDER BY. Truy vấn đầu tiên yêu cầu một ORDER BY address_idtruy vấn con.
Aristotle Pagaltzis

4
@AristotlePagaltzis: Nhưng bạn có thể . Bất cứ nơi nào bạn có được điều đó, nó không chính xác. Bạn có thể sử dụng DISTINCT ONmà không cần ORDER BYtrong cùng một truy vấn. Bạn nhận được một hàng tùy ý từ mỗi bộ đồng đẳng được xác định bởi DISTINCT ONmệnh đề trong trường hợp này. Hãy thử nó hoặc làm theo các liên kết ở trên để biết chi tiết và liên kết đến hướng dẫn. ORDER BYtrong cùng một truy vấn (giống nhau SELECT) không thể không đồng ý với DISTINCT ON. Tôi cũng đã giải thích điều đó.
Erwin Brandstetter

Huh, bạn nói đúng. Tôi đã mù quáng về hàm ý của không thể đoán trước được trừ khi ORDER BYđược sử dụng ghi chú của Wap trong các tài liệu bởi vì nó không có ý nghĩa với tôi rằng tính năng này được triển khai để có thể xử lý các bộ giá trị không liên tiếp mà không cho phép bạn khai thác điều đó với một trật tự rõ ràng. Làm phiền.
Aristotle Pagaltzis

@AristotlePagaltzis: Đó là bởi vì, bên trong, Postgres sử dụng một trong (ít nhất) hai thuật toán riêng biệt: hoặc duyệt qua danh sách được sắp xếp hoặc làm việc với các giá trị băm - bất cứ điều gì hứa hẹn sẽ nhanh hơn. Trong trường hợp sau, kết quả không được sắp xếp theo DISTINCT ONbiểu thức (chưa).
Erwin Brandstetter

2
Cảm ơn bạn. Câu trả lời của bạn luôn rõ ràng và hữu ích!
Andrey Deineko

10

Chức năng cửa sổ có thể giải quyết điều đó trong một lần:

SELECT DISTINCT ON (address_id) 
   LAST_VALUE(purchases.address_id) OVER wnd AS address_id
FROM "purchases"
WHERE "purchases"."product_id" = 1
WINDOW wnd AS (
   PARTITION BY address_id ORDER BY purchases.purchased_at DESC
   ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)

7
Sẽ thật tốt nếu ai đó giải thích các truy vấn.
Gajus

@Gajus: Giải thích ngắn: nó không hoạt động, chỉ trả về sự khác biệt address_id. Các nguyên tắc có thể làm việc, mặc dù. Ví dụ liên quan: stackoverflow.com/a/22064571/939860 hoặc stackoverflow.com/a/11533808/939860 . Nhưng có những truy vấn ngắn hơn và / hoặc nhanh hơn cho vấn đề hiện tại.
Erwin Brandstetter

5

Đối với bất kỳ ai sử dụng Flask-SQLAlchemy, điều này hiệu quả với tôi

from app import db
from app.models import Purchases
from sqlalchemy.orm import aliased
from sqlalchemy import desc

stmt = Purchases.query.distinct(Purchases.address_id).subquery('purchases')
alias = aliased(Purchases, stmt)
distinct = db.session.query(alias)
distinct.order_by(desc(alias.purchased_at))

2
Có, hoặc thậm chí dễ dàng hơn, tôi đã có thể sử dụng:query.distinct(foo).from_self().order(bar)
Laurent Meyer

@LaurentMeyer có nghĩa là Purchases.querygì?
reubano

Vâng, ý tôi là Purchasing.query
Laurent Meyer

-2

Bạn cũng có thể thực hiện điều này bằng cách sử dụng nhóm theo mệnh đề

   SELECT purchases.address_id, purchases.* FROM "purchases"
    WHERE "purchases"."product_id" = 1 GROUP BY address_id,
purchases.purchased_at ORDER purchases.purchased_at DESC

Điều này không chính xác (trừ khi purchaseschỉ có hai cột address_idpurchased_at). Do đó GROUP BY, bạn sẽ cần sử dụng hàm tổng hợp để lấy giá trị của mỗi cột không được sử dụng để nhóm, vì vậy tất cả các giá trị sẽ đến từ các hàng khác nhau của nhóm trừ khi bạn trải qua môn thể dục xấu xí và kém hiệu quả. Điều này chỉ có thể được sửa bằng cách sử dụng các chức năng của cửa sổ chứ không phải GROUP BY.
Aristotle Pagaltzis
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.