Làm cách nào để áp dụng ORDER BY và LIMIT kết hợp với hàm tổng hợp?


8

Một câu đố cho câu hỏi của tôi có thể được tìm thấy trên https://dbfiddle.uk/?rdbms=postgres_10&fiddle=3cd9335fa07565960c1837aa65143685 .

Tôi có một cách bố trí bảng đơn giản:

class
person: belongs to a class

Tôi muốn chọn tất cả các lớp và đối với mỗi lớp, tôi muốn hai định danh người đầu tiên của những người thuộc về được sắp xếp theo tên giảm dần.

Tôi đã giải quyết điều này bằng truy vấn sau:

select     c.identifier, array_agg(p.identifier order by p.name desc) as persons
from       class as c
left join lateral (
             select   p.identifier, p.name
             from     person as p
             where    p.class_identifier = c.identifier
             order by p.name desc
             limit    2
           ) as p
on         true
group by   c.identifier
order by   c.identifier

Lưu ý: Tôi có thể đã sử dụng một truy vấn con tương quan trong SELECTmệnh đề, nhưng tôi đang cố gắng tránh điều đó như là một phần của quá trình học tập.

Như bạn thấy, tôi đang áp dụng order by p.name descở hai nơi:

  • trong truy vấn con
  • trong hàm tổng hợp

Có cách nào để tránh điều đó? Chuyến tàu của tôi

  • Đầu tiên, rõ ràng tôi không thể loại bỏ order bytruy vấn con, vì điều đó sẽ đưa ra một truy vấn không đáp ứng yêu cầu của tôi như đã nêu ở trên.

  • Thứ hai, tôi nghĩ rằng order byhàm tổng hợp không thể bỏ đi, vì thứ tự hàng của truy vấn con không nhất thiết được bảo toàn trong hàm tổng hợp?

Tôi có nên viết lại truy vấn?


1
Tôi nghĩ bạn sẽ phải sử dụng hai thứ tự cách này hay cách khác. Một cách tiếp cận khác là sử dụng hàm cửa sổ, chẳng hạn như row_number (), nhưng điều đó sẽ cần một thứ tự của chính nó.
Lennart

(identifier)chìa khóa chính của class?
ypercubeᵀᴹ

@ ypercubeᵀᴹ Vâng, đúng rồi. Tại sao vậy?
Jarius Hebzo

Câu trả lời:


4

Tôi đang nộp đơn order by p.name descở hai nơi ... Có cách nào để tránh điều đó không?

Đúng. Tổng hợp với một hàm tạo ARRAY trong truy vấn con bên:

SELECT c.identifier, p.persons
FROM   class c
CROSS  JOIN LATERAL (
   SELECT ARRAY (
      SELECT identifier
      FROM   person
      WHERE  class_identifier = c.identifier
      ORDER  BY name DESC
      LIMIT  2
      ) AS persons
   ) p
ORDER  BY c.identifier;

Bạn cũng không cần GROUP BYở bên ngoài SELECTtheo cách này. Ngắn hơn, sạch hơn, nhanh hơn.

Tôi đã thay thế LEFT JOINbằng một đơn giản CROSS JOINvì hàm tạo ARRAY luôn trả về đúng 1 hàng. (Giống như bạn đã chỉ ra trong một bình luận.)

db <> fiddle ở đây.

Liên quan:

Thứ tự các hàng trong truy vấn con

Để giải quyết bình luận của bạn :

Tôi đã học được rằng thứ tự của các hàng trong truy vấn con không bao giờ được đảm bảo được giữ nguyên trong truy vấn bên ngoài.

Ồ không. Mặc dù tiêu chuẩn SQL không cung cấp bất kỳ đảm bảo nào, nhưng có các đảm bảo hạn chế trong Postgres. Hướng dẫn sử dụng:

Thứ tự này không được chỉ định theo mặc định, nhưng có thể được kiểm soát bằng cách viết một ORDER BYmệnh đề trong lệnh gọi tổng hợp, như trong Mục 4.2.7 . Ngoài ra, việc cung cấp các giá trị đầu vào từ một truy vấn con được sắp xếp thường sẽ hoạt động. Ví dụ:

SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;

Lưu ý rằng cách tiếp cận này có thể thất bại nếu mức truy vấn bên ngoài chứa xử lý bổ sung, chẳng hạn như nối, bởi vì điều đó có thể khiến đầu ra của truy vấn con được sắp xếp lại trước khi tổng hợp được tính toán.

Nếu tất cả những gì bạn làm trong cấp độ tiếp theo là tổng hợp các hàng, thứ tự được đảm bảo tích cực. Bất kỳ, những gì chúng tôi cung cấp cho nhà xây dựng ARRAY cũng là một truy vấn con . Đó không phải là vấn đề. Nó cũng sẽ làm việc với array_agg():

SELECT c.identifier, p.persons
FROM   class c
CROSS  JOIN LATERAL (
   SELECT array_agg(identifier) AS persons
   FROM  (
      SELECT identifier
      FROM   person
      WHERE  class_identifier = c.identifier
      ORDER  BY name DESC
      LIMIT  2
      ) sub
   ) p
ORDER  BY c.identifier;

Nhưng tôi hy vọng các nhà xây dựng ARRAY sẽ nhanh hơn cho trường hợp này. Xem:


Điều đó khá thú vị. Tôi đã học được rằng thứ tự của các hàng trong truy vấn con không bao giờ được đảm bảo được giữ nguyên trong truy vấn bên ngoài. Vì vậy, trong trường hợp cụ thể này, tại sao phải sửa để giả định rằng các hàng trong truy vấn con trong cùng được đưa vào theo đúng thứ tự để ARRAY(...)xây dựng?
Jarius Hebzo

Trả lời câu hỏi của riêng tôi: đây không thực sự là một truy vấn con (như trong SELECT ... FROM (SELECT ... FROM ...)). Đây là một SELECTtrên SELECT: SELECT ARRAY(SELECT ... FROM ...).
Jarius Hebzo

1
Liên quan đến điều này: dba.stackexchange.com/a/159717/157363
Jarius Hebzo

1
@JariusHebzo: Tôi đã thêm một chút để giải quyết vấn đề thứ tự hàng trong các truy vấn con.
Erwin Brandstetter

2
Sẽ là chính xác để nói rằng trong cả hai truy vấn, chúng ta có thể thay thế left lateral joinbằng chỉ lateral join? Trong trường hợp không có người, truy vấn đầu tiên trả về một mảng trống, truy vấn thứ hai null, phải không? Điều này mâu thuẫn với câu cuối cùng của dba.stackexchange.com/questions/173831/iêu , nhưng tôi nghĩ rằng thông tin đó là sai? Tôi nghĩ rằng chúng ta cần kiểm tra p.persons is not null(trong trường hợp truy vấn đầu tiên) hoặc p.persons != '{}'(trong trường hợp truy vấn thứ hai) để chỉ xuất các lớp có ít nhất một người?
Jarius Hebzo

2

Đây là một giải pháp thay thế, nhưng nó không tốt hơn những gì bạn đã có:

with enumeration (class_identifier, identifier, name, n) as (
    select  p.class_identifier, p.identifier, p.name
         , row_number() over (partition by p.class_identifier 
                              order by p.name desc)
    from     person as p
)
select c.identifier, array_agg(e.identifier order by e.n) as persons
from class as c
left join  enumeration e
    on c.identifier = e.class_identifier
where e.n <= 2
group by   c.identifier
order by   c.identifier;

Đây là một cách tiếp cận thú vị, cảm ơn vì điều đó. Bây giờ tôi hiểu nhận xét của bạn ở trên, thực sự, chúng tôi luôn cần hai thứ tự, không có cách nào xung quanh đó. Tôi nghĩ rằng điều này trả lời câu hỏi của tôi!
Jarius Hebzo

Tôi tự hỏi làm thế nào điều này sẽ thực hiện với một cơ sở dữ liệu thực sự . CTE enumerationsẽ giữ personbảng hoàn chỉnh , nếu tôi không nhầm (do bản chất của CTE là rào cản bộ nhớ trong PostgreQuery). Về cơ bản, nó là một bản sao trong bộ nhớ của personbảng. Điều này có thể không lý tưởng, vì về cơ bản chúng ta chỉ cần một vài hàng từ bảng đó (chính xác là 2 cho mỗi lớp). Có lẽ chúng ta nên thêm một select * from (...) where n <= 2truy vấn xung quanh enumeration(thay vì trong truy vấn chính)? Bằng cách này, CTE enumerationkhông còn chứa toàn bộ personbảng.
Jarius Hebzo

Tôi hy vọng điều này có ý nghĩa, tôi gặp khó khăn khi giải thích nó trong chiếc hộp nhỏ bé này. Tôi đã chứng minh điều đó trong câu đố này: dbfiddle.uk/ từ
Jarius Hebzo 30/07/18

1
Nó có thể sẽ tồi tệ hơn truy vấn của bạn. Tôi đã thêm nó như một món ăn cho thougt vì dường như bạn đang điều tra các kỹ thuật khác nhau
Lennart

1
Tôi thực sự đánh giá cao điều đó! Tôi thực sự đang cố gắng mở rộng kiến ​​thức SQL của mình bằng cách thử những thứ khác nhau, mà không thực sự biết những gì tôi đang làm mọi lúc. Câu trả lời của bạn rất hữu ích!
Jarius Hebzo
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.