ฉันจะเลือก DISTINCT ในหลายคอลัมน์ได้อย่างไร (หรือฉันสามารถ)


415

ฉันต้องการดึงแถวทั้งหมดจากตารางที่มี 2 คอลัมน์รวมกันแตกต่างกันทั้งหมด ดังนั้นฉันต้องการยอดขายทั้งหมดที่ไม่มียอดขายอื่น ๆ ที่เกิดขึ้นในวันเดียวกันในราคาเดียวกัน ยอดขายที่ไม่ซ้ำกันตามวันและราคาจะได้รับการอัปเดตเป็นสถานะที่ใช้งานอยู่

ดังนั้นฉันคิดว่า:

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

แต่สมองของฉันเจ็บไปไกลกว่านั้น

คำตอบ:


436
SELECT DISTINCT a,b,c FROM t

คือประมาณเทียบเท่ากับ:

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

เป็นความคิดที่ดีที่จะคุ้นเคยกับไวยากรณ์ GROUP GROUP เนื่องจากมีประสิทธิภาพมากกว่า

สำหรับคำถามของคุณฉันจะทำเช่นนี้:

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
ข้อความค้นหานี้แม้จะถูกต้องและได้รับการยอมรับในปีนี้ แต่ก็ไม่มีประสิทธิภาพและไม่จำเป็นอย่างยิ่ง อย่าใช้สิ่งนี้ ฉันให้ทางเลือกและคำอธิบายในคำตอบอื่น
Erwin Brandstetter

1
ไม่ได้เลือก DISTINCT a, b, c จาก t อย่างเดียวกับ SELECT a, b, c จากกลุ่ม t ตาม a, b, c?
famargar

8
@famargar สำหรับกรณีง่าย ๆ แต่มีความหมายต่างกันในเชิงความหมายและมีความแตกต่างในแง่ของสิ่งที่คุณสามารถทำได้สำหรับขั้นตอนเมื่อสร้างคิวรีที่มีขนาดใหญ่ขึ้น นอกจากนี้ผู้คนในฟอรัมเทคโนโลยีมักจะมีความคิดอย่างมากเกี่ยวกับสิ่งต่าง ๆ ฉันพบว่ามันมักจะมีประโยชน์ในการเพิ่มคำพังพอนลงในโพสต์ของฉันในบริบทนี้
Joel Coehoorn

344

หากคุณได้รวบรวมคำตอบมารวมกันทำความสะอาดและปรับปรุงคุณจะได้รับแบบสอบถามที่ยอดเยี่ยมนี้:

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

ซึ่งเป็นมากเร็วกว่าทั้งของพวกเขา Nukes ประสิทธิภาพของคำตอบที่ได้รับการยอมรับในปัจจุบันโดยปัจจัย 10 - 15 (ในการทดสอบของฉันเกี่ยวกับ PostgreSQL 8.4 และ 9.1)

แต่นี่ยังห่างไกลจากความเหมาะสม ใช้การNOT EXISTSเข้าร่วมกึ่ง (ต่อต้าน) เพื่อประสิทธิภาพที่ดียิ่งขึ้น EXISTSเป็น SQL มาตรฐานได้รับรอบอย่างถาวร (อย่างน้อยตั้งแต่ PostgreSQL 7.2 นานก่อนที่คำถามนี้ถูกถาม) และเหมาะกับความต้องการที่นำเสนออย่างสมบูรณ์แบบ:

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 ที่นี่
Old SQL Fiddle

คีย์ที่ไม่ซ้ำเพื่อระบุแถว

หากคุณไม่มีคีย์หลักหรือคีย์เฉพาะสำหรับตาราง ( idในตัวอย่าง) คุณสามารถแทนที่ด้วยคอลัมน์ระบบctidเพื่อวัตถุประสงค์ในการสืบค้นนี้ (แต่ไม่ใช่เพื่อจุดประสงค์อื่น):

   AND    s1.ctid <> s.ctid

ทุกตารางควรมีคีย์หลัก เพิ่มหนึ่งถ้าคุณยังไม่มี ผมขอแนะนำให้serialหรือIDENTITYคอลัมน์ใน Postgres 10+

ที่เกี่ยวข้อง:

มันเร็วแค่ไหน?

เคียวรีย่อยในการEXISTSต่อต้านการรวมกึ่งสามารถหยุดการประเมินได้ทันทีที่พบ dupe แรก (ไม่ต้องมองหาอีกต่อไป) สำหรับตารางพื้นฐานที่มีการทำซ้ำสองสามรายการนี้จะมีประสิทธิภาพเพียงเล็กน้อยเท่านั้น ที่มีจำนวนมากที่ซ้ำกันนี้จะกลายเป็นวิธีที่มีประสิทธิภาพมากขึ้น

ไม่รวมการอัปเดตที่ว่างเปล่า

สำหรับแถวที่มีstatus = 'ACTIVE'การอัปเดตนี้แล้วจะไม่เปลี่ยนแปลงอะไรเลย แต่ยังคงแทรกเวอร์ชันแถวใหม่ด้วยค่าใช้จ่ายเต็มรูปแบบ (มีข้อยกเว้นเล็กน้อย) โดยปกติคุณไม่ต้องการสิ่งนี้ เพิ่มWHEREเงื่อนไขอื่นเช่นที่แสดงด้านบนเพื่อหลีกเลี่ยงปัญหานี้และทำให้เร็วขึ้น:

หากstatusมีการกำหนดไว้NOT NULLคุณสามารถทำให้:

AND status <> 'ACTIVE';

ประเภทข้อมูลของคอลัมน์จะต้องสนับสนุน<>ผู้ปฏิบัติงาน บางประเภทjsonไม่ชอบ ดู:

ความแตกต่างเล็กน้อยในการจัดการ NULL

แบบสอบถามนี้ (ต่างจากคำตอบที่ Joel ยอมรับในปัจจุบัน ) ไม่ถือว่าค่า NULL เท่ากัน สองแถวสำหรับต่อไปนี้(saleprice, saledate)จะถือว่าเป็น "ชัดเจน" (แม้ว่าจะดูคล้ายกับดวงตามนุษย์):

(123, NULL)
(123, NULL)

ส่งผ่านไปยังดัชนีที่ไม่ซ้ำกันและเกือบทุกที่อื่นเนื่องจากค่า NULL ไม่เปรียบเทียบเท่ากับตามมาตรฐาน SQL ดู:

OTOH, GROUP BY, DISTINCTหรือDISTINCT ON ()ค่าเป็นศูนย์การรักษาที่เท่าเทียมกัน ใช้สไตล์การสืบค้นที่เหมาะสมขึ้นอยู่กับสิ่งที่คุณต้องการบรรลุ คุณยังสามารถใช้แบบสอบถามที่เร็วกว่านี้IS NOT DISTINCT FROMแทน=การเปรียบเทียบใด ๆ หรือทั้งหมดเพื่อให้ค่า NULL เปรียบเทียบเท่ากัน มากกว่า:

หากมีการกำหนดคอลัมน์ทั้งหมดที่เปรียบเทียบจะNOT NULLไม่มีที่ว่างสำหรับความขัดแย้ง


16
คำตอบที่ดี. ฉันเป็น sql server guy ดังนั้นคำแนะนำแรกของการใช้ tuple ที่มีการตรวจสอบ IN () จะไม่เกิดขึ้นกับฉัน ข้อเสนอแนะที่ไม่มีอยู่มักจะจบลงด้วยแผนการดำเนินการเดียวกันในเซิร์ฟเวอร์ sql เช่นเดียวกับการเข้าร่วมภายใน
Joel Coehoorn

2
ดี คำอธิบายจะเพิ่มมูลค่าของคำตอบอย่างมาก ฉันเกือบอยากทดลองทดสอบกับ Oracle เพื่อดูว่าแผนเปรียบเทียบกับ Postgres และ SQLServer อย่างไร
ปีเตอร์

2
@alairock: คุณได้รับมาจากไหน สำหรับ Postgres ตรงกันข้ามจะเป็นจริง ขณะที่การนับแถวทั้งหมดcount(*)เป็นมากขึ้นcount(<expression>)ได้อย่างมีประสิทธิภาพกว่า แค่ลองดู. Postgres มีการใช้งานที่รวดเร็วขึ้นสำหรับฟังก์ชันชุดรวมนี้ บางทีคุณอาจสับสนกับ Postgres กับ RDBMS อื่นบ้างไหม?
Erwin Brandstetter

6
@alairock: ฉันบังเอิญได้ร่วมเขียนหน้านั้นและมันไม่ได้พูดอะไรเลย
Erwin Brandstetter

2
@ErwinBrandstetter คุณจะได้รับคำตอบที่ตรงข้ามกับคุณเสมอ คุณได้ช่วยเหลือมาหลายปีด้วยวิธีที่เกือบจะจินตนาการไม่ได้ สำหรับตัวอย่างนี้ฉันรู้วิธีที่แตกต่างกันสองสามวิธีในการแก้ปัญหาของฉัน แต่ฉันต้องการเห็นว่ามีคนทดสอบประสิทธิภาพระหว่างความเป็นไปได้ ขอบคุณ.
WebWanderer

24

ปัญหาเกี่ยวกับการสืบค้นของคุณคือเมื่อใช้ GROUP BY clause (ซึ่งคุณต้องทำโดยการใช้คำสั่งที่ชัดเจน) คุณสามารถใช้คอลัมน์ที่คุณจัดกลุ่มตามหรือฟังก์ชั่นรวม คุณไม่สามารถใช้รหัสคอลัมน์ได้เนื่องจากอาจมีค่าแตกต่างกัน ในกรณีของคุณจะมีเพียงหนึ่งค่าเนื่องจากข้อ HAVING แต่ RDBMS ส่วนใหญ่ไม่ฉลาดพอที่จะรับรู้ได้

สิ่งนี้ควรใช้งานได้ (และไม่จำเป็นต้องเข้าร่วม):

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

คุณสามารถใช้ MAX หรือ AVG แทน MIN ได้สิ่งสำคัญคือต้องใช้ฟังก์ชันที่คืนค่าคอลัมน์ถ้ามีแถวที่ตรงกันเพียงแถวเดียวเท่านั้น


1

ฉันต้องการเลือกค่าที่แตกต่างจากหนึ่งคอลัมน์ 'GrondOfLucht' แต่ควรเรียงลำดับตามที่กำหนดในคอลัมน์ 'sortering' ฉันไม่สามารถรับค่าที่แตกต่างของการใช้เพียงหนึ่งคอลัมน์

Select distinct GrondOfLucht,sortering
from CorWijzeVanAanleg
order by sortering

นอกจากนี้ยังจะให้คอลัมน์ 'ตัวเรียงลำดับ' และเนื่องจาก 'GrondOfLucht' และ 'ตัวเรียงลำดับ' ไม่ซ้ำกันผลลัพธ์จะเป็นแถวทั้งหมด

ใช้กลุ่มเพื่อเลือกบันทึกของ 'GrondOfLucht' ตามลำดับที่กำหนดโดย 'ตัวเรียงลำดับ

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

โดยทั่วไปจะอธิบายสิ่งที่คำตอบที่ยอมรับได้ แต่ฉันขอแนะนำไม่ให้ใช้ชื่อดังกล่าวเป็นตัวอย่าง (อย่างน้อยแปลให้) PS: ฉันแนะนำให้ตั้งชื่อทุกอย่างเป็นภาษาอังกฤษในทุกโครงการแม้ว่าคุณจะเป็นชาวดัตช์
Kerwin Sneijders

0

หาก DBMS ของคุณไม่สนับสนุนการแยกคอลัมน์ที่หลากหลายเช่นนี้:

select distinct(col1, col2) from table

การเลือกหลายตัวโดยทั่วไปสามารถดำเนินการได้อย่างปลอดภัยดังนี้:

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

เนื่องจากสามารถทำงานกับ DBMS ส่วนใหญ่และคาดว่าจะเร็วกว่ากลุ่มโดยวิธีการแก้ปัญหาตามที่คุณหลีกเลี่ยงฟังก์ชั่นการจัดกลุ่ม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.