PostgreSQL เปิด DISTINCT ON ที่ต่างกันโดย ORDER BY


216

ฉันต้องการเรียกใช้แบบสอบถามนี้:

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

แต่ฉันได้รับข้อผิดพลาดนี้:

PG :: ข้อผิดพลาด: ข้อผิดพลาด: การเลือก DISTINCT ON ของนิพจน์ต้องตรงกับ ORDER เริ่มต้นโดยนิพจน์

เพิ่มaddress_idเป็นครั้งแรกORDER BYเงียบแสดงออกข้อผิดพลาด address_idแต่ผมไม่ต้องการที่จะเพิ่มการเรียงลำดับมากกว่า มันเป็นไปได้ที่จะทำโดยไม่ต้องสั่งซื้อโดยaddress_id?


ส่วนคำสั่งซื้อของคุณได้ซื้อแล้วไม่ใช่ที่อยู่ _ คุณสามารถตอบคำถามของคุณให้ชัดเจน
Teja

คำสั่งซื้อของฉันมีการซื้อเพราะฉันต้องการ แต่ postgres ก็ขอที่อยู่ด้วย (ดูข้อความแสดงข้อผิดพลาด)
sl_bug

3
ตอบอย่างเต็มที่ที่นี่ - stackoverflow.com/questions/9796078/… ขอบคุณstackoverflow.com/users/268273/mosty-mostacho
sl_bug

โดยส่วนตัวแล้วฉันคิดว่าการกำหนด DISTINCT ON เพื่อให้ตรงกับ ORDER BY นั้นน่าสงสัยมากเนื่องจากมีกรณีการใช้งานที่ถูกต้องตามกฎหมายที่หลากหลาย มีการโพสต์ใน postgresql.uservoice พยายามเปลี่ยนสิ่งนี้สำหรับผู้ที่รู้สึกคล้ายกัน postgresql.uservoice.com/forums/21853-general/suggestions/…
semicolon

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

คำตอบ:


208

เอกสารอธิบายว่า:

DISTINCT ON (expression [, ... ]) เก็บเฉพาะแถวแรกของแถวแต่ละชุดที่นิพจน์ที่กำหนดให้ประเมินเท่ากัน [... ] โปรดทราบว่า "แถวแรก" ของแต่ละชุดจะไม่สามารถคาดเดาได้เว้นแต่จะใช้ ORDER BY เพื่อให้แน่ใจว่าแถวที่ต้องการปรากฏขึ้นเป็นครั้งแรก [... ] นิพจน์ DISTINCT ON ต้องตรงกับคำสั่ง OREST BY ทางซ้ายสุด

เอกสารอย่างเป็นทางการ

ดังนั้นคุณจะต้องเพิ่มaddress_idคำสั่งซื้อโดย

หรือหากคุณกำลังมองหาแถวที่มีผลิตภัณฑ์ที่ซื้อล่าสุดสำหรับแต่ละรายการaddress_idและผลลัพธ์นั้นเรียงลำดับตามpurchased_atนั้นคุณกำลังพยายามแก้ไขปัญหา N ต่อกลุ่มที่ยิ่งใหญ่ที่สุดซึ่งสามารถแก้ไขได้ด้วยวิธีการต่อไปนี้:

โซลูชันทั่วไปที่ควรทำงานใน 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

โซลูชัน PostgreSQL ที่มุ่งเน้นเพิ่มเติมตามคำตอบของ @ 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

ปัญหาที่ได้รับการชี้แจงขยายและแก้ไขที่นี่: การเลือกแถวที่เรียงตามคอลัมน์และแตกต่างกัน


40
มันใช้งานได้ แต่สั่งผิด นั่นเป็นเหตุผลที่ฉันต้องการกำจัด address_id ตามลำดับ clause
sl_bug

1
เอกสารมีความชัดเจน: คุณทำไม่ได้เนื่องจากแถวที่เลือกจะไม่สามารถคาดเดาได้
Mosty Mostacho

3
แต่อาจมีวิธีอื่นในการเลือกซื้อล่าสุดสำหรับที่อยู่ disticnt หรือไม่
sl_bug

1
หากคุณจำเป็นต้องสั่งซื้อโดย purchases.purchased_at คุณสามารถเพิ่ม purchased_at SELECT DISTINCT ON (purchases.purchased_at, address_id)กับเงื่อนไขที่แตกต่างของคุณ: อย่างไรก็ตามระเบียนสองรายการที่มี address_id เดียวกัน แต่ค่าที่ซื้อแตกต่างกันจะส่งผลให้เกิดรายการซ้ำในชุดที่ส่งคืน ตรวจสอบให้แน่ใจว่าคุณรับรู้ข้อมูลที่คุณกำลังสืบค้นอยู่
Brendan Benson

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

55

คุณสามารถสั่งซื้อตาม address_id ในแบบสอบถามย่อยจากนั้นเรียงลำดับตามสิ่งที่คุณต้องการในแบบสอบถามภายนอก

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
แต่จะช้ากว่าการสืบค้นเพียงครั้งเดียวใช่ไหม
sl_bug

2
ใช่เล็กน้อยมาก แม้ว่าในเมื่อคุณมีการซื้อ * ในแบบดั้งเดิมselectฉันไม่คิดว่านี่เป็นรหัสการผลิตใช่ไหม
hkf

8
ฉันจะเพิ่มมันสำหรับรุ่น postgres ที่ใหม่กว่าคุณต้องตั้งชื่อแทนแบบสอบถามย่อย ตัวอย่างเช่น: SELECT * FROM (เลือก DISTINCT ON (address_id) Payments.address_id, การซื้อ * จาก "ซื้อ" WHERE "ซื้อ". "product_id" = 1 ORDER by address_id DESC) AS tmp สั่งซื้อโดย tmp.purchased_at DESC
aembke

สิ่งนี้จะกลับมาaddress_idสองครั้ง (โดยไม่จำเป็น) ลูกค้าหลายคนมีปัญหากับชื่อคอลัมน์ที่ซ้ำกัน ORDER BY address_id DESCไม่มีจุดหมายและทำให้เข้าใจผิด ไม่มีประโยชน์ใด ๆ ในแบบสอบถามนี้ ผลที่ได้คือเลือกโดยพลการจากชุดของแต่ละแถวเดียวกับที่ไม่แถวที่มีล่าสุดaddress_id purchased_atคำถามที่คลุมเครือไม่ได้ถามอย่างชัดเจน แต่นั่นก็เป็นความตั้งใจของ OP อย่างแน่นอน กล่าวโดยย่อ: อย่าใช้ข้อความค้นหานี้ ฉันโพสต์ทางเลือกพร้อมคำอธิบาย
Erwin Brandstetter

ทำงานให้ฉัน คำตอบที่ดี
Matt West

46

แบบสอบถามย่อยสามารถแก้ได้:

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

การแสดงออกชั้นนำในการORDER BYได้เห็นด้วยกับคอลัมน์ในเพื่อให้คุณสามารถสั่งซื้อโดยไม่ได้แตกต่างกันในคอลัมน์เดียวกันDISTINCT ONSELECT

ใช้เพิ่มเติมORDER BYในแบบสอบถามย่อยเท่านั้นหากคุณต้องการเลือกแถวใดชุดหนึ่งจากแต่ละชุด:

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;

ถ้าpurchased_atสามารถที่จะพิจารณาNULL DESC NULLS LASTแต่ให้แน่ใจว่าตรงกับดัชนีของคุณถ้าคุณตั้งใจจะใช้ ดู:

เกี่ยวข้องกับคำอธิบายเพิ่มเติม:


คุณไม่สามารถใช้โดยไม่ต้องจับคู่DISTINCT ON ORDER BYแบบสอบถามแรกต้องการORDER BY address_idแบบสอบถามย่อยภายใน
อริสโตเติล Pagaltzis

4
@AristotlePagaltzis: แต่คุณสามารถ ไม่ว่าคุณจะได้รับจากที่ใดก็ไม่ถูกต้อง คุณสามารถใช้DISTINCT ONโดยไม่ต้องORDER BYอยู่ในแบบสอบถามเดียวกัน คุณจะได้รับแถวโดยพลการจากเพียร์แต่ละชุดที่กำหนดโดยDISTINCT ONข้อในกรณีนี้ ลองใช้หรือทำตามลิงค์ด้านบนเพื่อดูรายละเอียดและลิงค์ไปยังคู่มือ ORDER BYในแบบสอบถามเดียวกัน (เหมือนกันSELECT) DISTINCT ONก็ไม่สามารถเห็นด้วยกับ ฉันก็อธิบายอย่างนั้นเช่นกัน
Erwin Brandstetter

ใช่แล้วคุณพูดถูก ฉันมองไม่เห็นความหมายของORDER BYโน้ต“ ที่ไม่สามารถคาดการณ์ได้เว้นแต่จะใช้” ในเอกสารเพราะมันไม่สมเหตุสมผลกับฉันว่าคุณลักษณะนี้ได้รับการติดตั้งเพื่อให้สามารถจัดการกับค่าที่ไม่ต่อเนื่อง… แต่จะไม่อนุญาตให้คุณ ใช้ประโยชน์จากคำสั่งที่ชัดเจน น่ารำคาญ
อริสโตเติล Pagaltzis

@AristotlePagaltzis: นั่นเป็นเพราะภายใน Postgres ใช้อัลกอริทึมที่แตกต่างกันอย่างน้อยหนึ่งอย่าง (อย่างน้อย) สองอัน : ข้ามการเรียงลำดับรายการหรือทำงานกับค่าแฮช - แล้วแต่อย่างใดจะสัญญาว่าจะเร็วขึ้น ในกรณีต่อมาผลลัพธ์จะไม่เรียงตามDISTINCT ONนิพจน์ (ยัง)
Erwin Brandstetter

2
ขอบคุณ. คำตอบของคุณชัดเจนและเป็นประโยชน์เสมอ
Andrey Deineko

10

ฟังก์ชั่นหน้าต่างอาจแก้ได้ในครั้งเดียว:

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
มันจะดีถ้ามีคนอธิบายแบบสอบถาม
Gajus

@Gajus: คำอธิบายสั้น: address_idมันไม่ได้ทำงานเพียงผลตอบแทนที่แตกต่างกัน หลักการสามารถใช้งานได้ ตัวอย่างที่เกี่ยวข้อง: stackoverflow.com/a/22064571/939860หรือstackoverflow.com/a/11533808/939860 แต่มีคำถามที่สั้นลงและ / หรือเร็วกว่าสำหรับปัญหานี้
Erwin Brandstetter

5

สำหรับทุกคนที่ใช้ Flask-SQLAlchemy สิ่งนี้ใช้ได้สำหรับฉัน

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
ใช่หรือง่ายยิ่งขึ้นฉันสามารถใช้:query.distinct(foo).from_self().order(bar)
Laurent Meyer

@LaurentMeyer คุณหมายถึงPurchases.queryอะไร
reubano

ใช่ฉันหมายถึง Purchases.query
Laurent Meyer

-2

คุณสามารถทำได้โดยใช้กลุ่มตามข้อ

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

สิ่งนี้ไม่ถูกต้อง (เว้นแต่จะpurchasesมีเพียงสองคอลัมน์address_idและpurchased_at) เนื่องจากGROUP BYคุณจะต้องใช้ฟังก์ชั่นรวมเพื่อรับค่าของแต่ละคอลัมน์ที่ไม่ได้ใช้สำหรับการจัดกลุ่มดังนั้นค่าเหล่านั้นจะมาจากแถวต่างๆของกลุ่มยกเว้นว่าคุณผ่านยิมนาสติกที่น่าเกลียดและไม่มีประสิทธิภาพ GROUP BYนี้สามารถแก้ไขได้เพียงโดยใช้ฟังก์ชั่นหน้าต่างมากกว่า
อริสโตเติล Pagaltzis
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.