Làm cách nào để tôi (hoặc tôi có thể) CHỌN DISTINCT trên nhiều cột?


415

Tôi cần lấy tất cả các hàng từ một bảng trong đó 2 cột kết hợp đều khác nhau. Vì vậy, tôi muốn tất cả các doanh số không có bất kỳ doanh số nào khác xảy ra trong cùng một ngày với cùng một mức giá. Doanh số duy nhất dựa trên ngày và giá sẽ được cập nhật lên trạng thái hoạt động.

Vì vậy, tôi đang suy nghĩ:

UPDATE sales
SET status = 'ACTIVE'
WHERE id IN (SELECT DISTINCT (saleprice, saledate), id, count(id)
             FROM sales
             HAVING count = 1)

Nhưng bộ não của tôi đau đớn đi xa hơn thế.

Câu trả lời:


436
SELECT DISTINCT a,b,c FROM t

khoảng tương đương với:

SELECT a,b,c FROM t GROUP BY a,b,c

Đó là một ý tưởng tốt để làm quen với cú pháp GROUP BY, vì nó mạnh hơn.

Đối với truy vấn của bạn, tôi sẽ làm như thế này:

UPDATE sales
SET status='ACTIVE'
WHERE id IN
(
    SELECT id
    FROM sales S
    INNER JOIN
    (
        SELECT saleprice, saledate
        FROM sales
        GROUP BY saleprice, saledate
        HAVING COUNT(*) = 1 
    ) T
    ON S.saleprice=T.saleprice AND s.saledate=T.saledate
 )

117
Truy vấn này, trong khi chính xác và được chấp nhận trong năm nay, là cực kỳ không hiệu quả và không cần thiết như vậy. Đừng dùng cái này. Tôi đã cung cấp một giải pháp thay thế và một số giải thích trong một câu trả lời khác.
Erwin Brandstetter

1
không CHỌN DISTINCT a, b, c TỪ chính xác điều tương tự như CHỌN a, b, c TỪ NHÓM THEO a, b, c?
famargar

8
@famargar cho trường hợp đơn giản, tuy nhiên, chúng có ý nghĩa khác nhau về mặt ngữ nghĩa và chúng khác nhau về những gì bạn có thể làm cho bước khi xây dựng truy vấn lớn hơn. Thêm vào đó, mọi người trên các diễn đàn công nghệ thường có thể cực kỳ khoa trương về mọi thứ, tôi thấy việc thêm từ chồn vào bài viết của mình trong ngữ cảnh này là rất hữu ích.
Joel Coehoorn

344

Nếu bạn kết hợp các câu trả lời cho đến nay, dọn dẹp và cải thiện, bạn sẽ đến truy vấn ưu việt này:

UPDATE sales
SET    status = 'ACTIVE'
WHERE  (saleprice, saledate) IN (
    SELECT saleprice, saledate
    FROM   sales
    GROUP  BY saleprice, saledate
    HAVING count(*) = 1 
    );

Đó là nhiều nhanh hơn so với cả hai người. Có hiệu suất của câu trả lời hiện được chấp nhận theo yếu tố 10 - 15 (trong các thử nghiệm của tôi trên PostgreQuery 8.4 và 9.1).

Nhưng điều này vẫn còn xa tối ưu. Sử dụng một NOT EXISTS(chống) bán tham gia để có hiệu suất thậm chí tốt hơn. EXISTSlà SQL chuẩn, đã tồn tại mãi mãi (ít nhất là từ PostgreSQL 7.2, rất lâu trước khi câu hỏi này được hỏi) và phù hợp hoàn hảo với các yêu cầu được trình bày:

UPDATE sales s
SET    status = 'ACTIVE'
WHERE  NOT EXISTS (
   SELECT FROM sales s1                     -- SELECT list can be empty for EXISTS
   WHERE  s.saleprice = s1.saleprice
   AND    s.saledate  = s1.saledate
   AND    s.id <> s1.id                     -- except for row itself
   )
AND    s.status IS DISTINCT FROM 'ACTIVE';  -- avoid empty updates. see below

db <> fiddle ở đây
Fiddle SQL cũ

Khóa duy nhất để xác định hàng

Nếu bạn không có khóa chính hoặc khóa duy nhất cho bảng ( idtrong ví dụ), bạn có thể thay thế bằng cột hệ thống ctidcho mục đích của truy vấn này (nhưng không phải cho một số mục đích khác):

   AND    s1.ctid <> s.ctid

Mỗi bảng nên có một khóa chính. Thêm một nếu bạn chưa có. Tôi đề nghị một serialhoặc một IDENTITYcột trong Postgres 10+.

Liên quan:

Làm thế nào là nhanh hơn?

Truy vấn con trong EXISTSchống bán tham gia có thể ngừng đánh giá ngay khi tìm thấy bản sao đầu tiên (không có ý định tìm kiếm thêm). Đối với một bảng cơ sở với một vài bản sao, điều này chỉ hiệu quả hơn một chút. Với rất nhiều các bản sao này trở thành cách hiệu quả hơn.

Không bao gồm các cập nhật trống

Đối với các hàng đã có status = 'ACTIVE'bản cập nhật này sẽ không thay đổi bất cứ điều gì, nhưng vẫn chèn một phiên bản hàng mới với chi phí đầy đủ (áp dụng ngoại lệ nhỏ). Thông thường, bạn không muốn điều này. Thêm một WHEREđiều kiện khác như đã trình bày ở trên để tránh điều này và làm cho nó nhanh hơn nữa:

Nếu statusđược xác định NOT NULL, bạn có thể đơn giản hóa thành:

AND status <> 'ACTIVE';

Kiểu dữ liệu của cột phải hỗ trợ <>toán tử. Một số loại như jsonkhông. Xem:

Sự khác biệt tinh tế trong xử lý NULL

Truy vấn này (không giống như câu trả lời hiện được chấp nhận bởi Joel ) không coi các giá trị NULL là bằng nhau. Hai hàng sau đây (saleprice, saledate)sẽ đủ điều kiện là "khác biệt" (mặc dù trông giống hệt mắt người):

(123, NULL)
(123, NULL)

Cũng chuyển trong một chỉ mục duy nhất và hầu hết mọi nơi khác, vì các giá trị NULL không so sánh bằng nhau theo tiêu chuẩn SQL. Xem:

OTOH, GROUP BY, DISTINCThoặc DISTINCT ON ()giá trị điều trị NULL như bằng nhau. Sử dụng một kiểu truy vấn phù hợp tùy thuộc vào những gì bạn muốn đạt được. Bạn vẫn có thể sử dụng truy vấn nhanh hơn này IS NOT DISTINCT FROMthay vì =cho bất kỳ hoặc tất cả các so sánh để làm cho NULL so sánh bằng nhau. Hơn:

Nếu tất cả các cột được so sánh được xác định NOT NULL, không có chỗ cho sự bất đồng.


16
Câu trả lời tốt. Tôi là một anh chàng máy chủ sql, vì vậy đề xuất đầu tiên về việc sử dụng bộ dữ liệu với kiểm tra IN () sẽ không xảy ra với tôi. Đề xuất không tồn tại thường sẽ kết thúc với cùng một kế hoạch thực hiện trong máy chủ sql như tham gia bên trong.
Joel Coehoorn

2
Đẹp. Việc giải thích làm tăng đáng kể giá trị của câu trả lời. Tôi gần như muốn chạy một số thử nghiệm với Oracle để xem các kế hoạch so sánh với Postgres và SQLServer như thế nào.
Peter

2
@alairock: Bạn lấy cái đó ở đâu? Đối với Postgres, điều ngược lại là đúng. Trong khi đếm tất cả các hàng, count(*)hơn hiệu quả hơn count(<expression>). Hãy thử nó. Postgres có triển khai nhanh hơn cho biến thể này của hàm tổng hợp. Có lẽ bạn đang nhầm lẫn Postgres với một số RDBMS khác?
Erwin Brandstetter

6
@alairock: Tôi tình cờ là đồng tác giả của trang đó và nó không nói bất cứ điều gì thuộc loại này.
Erwin Brandstetter

2
@ErwinBrandstetter, bạn luôn luôn đúng với câu trả lời của bạn trên ngăn xếp. Bạn đã giúp đỡ trong suốt những năm qua theo một cách gần như không thể tưởng tượng được. Đối với ví dụ này, tôi biết một vài cách khác nhau để giải quyết vấn đề của mình, nhưng tôi muốn thấy rằng ai đó đã kiểm tra hiệu quả giữa các khả năng. Cảm ơn bạn.
WebWanderer

24

Vấn đề với truy vấn của bạn là khi sử dụng mệnh đề GROUP BY (mà về cơ bản bạn làm bằng cách sử dụng riêng biệt), bạn chỉ có thể sử dụng các cột mà bạn nhóm theo hoặc tổng hợp các hàm. Bạn không thể sử dụng id cột vì có các giá trị khác nhau. Trong trường hợp của bạn, luôn luôn chỉ có một giá trị vì mệnh đề HAVING, nhưng hầu hết RDBMS không đủ thông minh để nhận ra điều đó.

Điều này sẽ hoạt động tuy nhiên (và không cần tham gia):

UPDATE sales
SET status='ACTIVE'
WHERE id IN (
  SELECT MIN(id) FROM sales
  GROUP BY saleprice, saledate
  HAVING COUNT(id) = 1
)

Bạn cũng có thể sử dụng MAX hoặc AVG thay vì MIN, điều quan trọng là chỉ sử dụng hàm trả về giá trị của cột nếu chỉ có một hàng khớp.


1

Tôi muốn chọn các giá trị riêng biệt từ một cột 'GrondOfLucht' nhưng chúng phải được sắp xếp theo thứ tự như được đưa ra trong cột 'sắp xếp'. Tôi không thể nhận các giá trị riêng biệt của chỉ một cột bằng cách sử dụng

Select distinct GrondOfLucht,sortering
from CorWijzeVanAanleg
order by sortering

Nó cũng sẽ cung cấp cho cột 'sắp xếp' và vì 'GrondOfLucht' VÀ 'sắp xếp' không phải là duy nhất, kết quả sẽ là TẤT CẢ các hàng.

sử dụng NHÓM để chọn các bản ghi của 'GrondOfLucht' theo thứ tự được đưa ra bởi 'sắp xếp

SELECT        GrondOfLucht
FROM            dbo.CorWijzeVanAanleg
GROUP BY GrondOfLucht, sortering
ORDER BY MIN(sortering)

Điều này về cơ bản giải thích những gì câu trả lời được chấp nhận, nhưng tôi khuyên bạn không nên sử dụng những tên như vậy cho một ví dụ (ít nhất là dịch chúng). Tái bút: Tôi khuyên bạn nên luôn đặt tên mọi thứ bằng tiếng Anh trong tất cả các dự án ngay cả khi bạn là người Hà Lan.
Kerwin Sneijder

0

Nếu DBMS của bạn không hỗ trợ khác biệt với nhiều cột như thế này:

select distinct(col1, col2) from table

Đa lựa chọn nói chung có thể được thực hiện một cách an toàn như sau:

select distinct * from (select col1, col2 from table ) as x

Vì điều này có thể hoạt động trên hầu hết các DBMS và điều này được dự kiến ​​sẽ nhanh hơn nhóm theo giải pháp vì bạn đang tránh chức năng nhóm.

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.