จำกัด การจัดกลุ่มใน PostgreSQL: แสดงแถวแรก N สำหรับแต่ละกลุ่มหรือไม่


179

ฉันต้องใช้ N แถวแรกสำหรับแต่ละกลุ่มเรียงลำดับโดยคอลัมน์ที่กำหนดเอง

รับตารางต่อไปนี้:

db=# SELECT * FROM xxx;
 id | section_id | name
----+------------+------
  1 |          1 | A
  2 |          1 | B
  3 |          1 | C
  4 |          1 | D
  5 |          2 | E
  6 |          2 | F
  7 |          3 | G
  8 |          2 | H
(8 rows)

ฉันต้องการ 2 แถวแรก (เรียงตามชื่อ ) สำหรับแต่ละsection_idเช่นผลลัพธ์ที่คล้ายกับ:

 id | section_id | name
----+------------+------
  1 |          1 | A
  2 |          1 | B
  5 |          2 | E
  6 |          2 | F
  7 |          3 | G
(5 rows)

ฉันใช้ PostgreSQL 8.3.5

คำตอบ:


279

โซลูชั่นใหม่ (PostgreSQL 8.4)

SELECT
  * 
FROM (
  SELECT
    ROW_NUMBER() OVER (PARTITION BY section_id ORDER BY name) AS r,
    t.*
  FROM
    xxx t) x
WHERE
  x.r <= 2;

8
สิ่งนี้ใช้ได้กับ PostgreSQL 8.4 เช่นกัน (ฟังก์ชั่นหน้าต่างเริ่มต้นด้วย 8.4)
Bruno

2
คำตอบในตำราเรียนเกี่ยวกับข้อ จำกัด ในการจัดกลุ่ม
piggybox

4
! น่ากลัว มันทำงานได้อย่างไม่มีที่ติ ฉันอยากรู้ว่ามีวิธีการทำเช่นนี้group byหรือไม่?
NurShomik

1
สำหรับผู้ที่ทำงานร่วมกับแถวมากกว่าล้านแถวและพยายามทำสิ่งนี้อย่างมีประสิทธิภาพจริง ๆ คำตอบที่ดีที่สุดคือหนทางที่จะไป อย่าลืมเครื่องเทศด้วยการจัดทำดัชนีที่เหมาะสม
Presser Diligent Key

37

ตั้งแต่ v9.3 คุณสามารถเข้าร่วมด้านข้างได้

select distinct t_outer.section_id, t_top.id, t_top.name from t t_outer
join lateral (
    select * from t t_inner
    where t_inner.section_id = t_outer.section_id
    order by t_inner.name
    limit 2
) t_top on true
order by t_outer.section_id;

มันอาจจะเร็วกว่าแต่แน่นอนคุณควรทดสอบประสิทธิภาพโดยเฉพาะกับข้อมูลของคุณและกรณีใช้


4
IMO โซลูชันที่เป็นความลับมากโดยเฉพาะกับชื่อเหล่านั้น แต่เป็นรหัสที่ดี
villasv

1
โซลูชันที่มี LATERAL JOIN นี้อาจเร็วกว่าโซลูชันที่มีฟังก์ชั่นหน้าต่าง (ในบางกรณี) หากคุณมีดัชนีเรียงตามt_inner.nameคอลัมน์
Artur Rashitov

แบบสอบถามเข้าใจง่ายกว่าหากไม่มีการเข้าร่วมด้วยตนเอง ในกรณีdistinctนั้นไม่จำเป็น ตัวอย่างจะแสดงในลิงค์โพสต์ที่ดีที่สุด
gillesB

เพื่อนนี่คือจิตใจ 120 มิลลิวินาทีแทน 9 วินาทีให้ผลด้วยโซลูชัน "ROW_NUMBER" ขอบคุณ!
Presser Diligent Key

เราจะเลือกคอลัมน์ทั้งหมดของ t_top ได้อย่างไร ตาราง t มีคอลัมน์ json และฉันได้รับ "ไม่สามารถระบุตัวดำเนินการความเท่าเทียมกันสำหรับประเภท json postgres" ข้อผิดพลาดเมื่อฉันเลือกdistinct t_outer.section_id, t_top.*
suat

12

นี่เป็นอีกวิธีการหนึ่ง (PostgreSQL <= 8.3)

SELECT
  *
FROM
  xxx a
WHERE (
  SELECT
    COUNT(*)
  FROM
    xxx
  WHERE
    section_id = a.section_id
  AND
    name <= a.name
) <= 2

2
SELECT  x.*
FROM    (
        SELECT  section_id,
                COALESCE
                (
                (
                SELECT  xi
                FROM    xxx xi
                WHERE   xi.section_id = xo.section_id
                ORDER BY
                        name, id
                OFFSET 1 LIMIT 1
                ),
                (
                SELECT  xi
                FROM    xxx xi
                WHERE   xi.section_id = xo.section_id
                ORDER BY 
                        name DESC, id DESC
                LIMIT 1
                )
                ) AS mlast
        FROM    (
                SELECT  DISTINCT section_id
                FROM    xxx
                ) xo
        ) xoo
JOIN    xxx x
ON      x.section_id = xoo.section_id
        AND (x.name, x.id) <= ((mlast).name, (mlast).id)

แบบสอบถามอยู่ใกล้กับที่ฉันต้องการมากยกเว้นว่าจะไม่แสดงส่วนที่มีน้อยกว่า 2 แถวนั่นคือแถวที่มี ID = 7 จะไม่ถูกส่งกลับ มิฉะนั้นฉันชอบวิธีการของคุณ
Kouber Saparev

ขอบคุณฉันเพิ่งมาถึงโซลูชันเดียวกันกับ COALESCE แต่คุณเร็วขึ้น :-)
Kouber Saparev

จริงๆแล้วอนุประโยคย่อยสุดท้ายของ JOIN สามารถทำให้เป็น: ... และ x.id <= (mlast) .id เนื่องจาก ID ได้รับการเลือกตามฟิลด์ชื่อแล้วหรือไม่?
Kouber Saparev

@Kouber: ในตัวอย่างของคุณname'และid' s จะถูกจัดเรียงในลำดับเดียวกันดังนั้นคุณจะไม่เห็นมัน ทำชื่อในลำดับย้อนกลับและคุณจะเห็นว่าแบบสอบถามเหล่านี้ให้ผลลัพธ์ที่แตกต่าง
Quassnoi

2
        -- ranking without WINDOW functions
-- EXPLAIN ANALYZE
WITH rnk AS (
        SELECT x1.id
        , COUNT(x2.id) AS rnk
        FROM xxx x1
        LEFT JOIN xxx x2 ON x1.section_id = x2.section_id AND x2.name <= x1.name
        GROUP BY x1.id
        )
SELECT this.*
FROM xxx this
JOIN rnk ON rnk.id = this.id
WHERE rnk.rnk <=2
ORDER BY this.section_id, rnk.rnk
        ;

        -- The same without using a CTE
-- EXPLAIN ANALYZE
SELECT this.*
FROM xxx this
JOIN ( SELECT x1.id
        , COUNT(x2.id) AS rnk
        FROM xxx x1
        LEFT JOIN xxx x2 ON x1.section_id = x2.section_id AND x2.name <= x1.name
        GROUP BY x1.id
        ) rnk
ON rnk.id = this.id
WHERE rnk.rnk <=2
ORDER BY this.section_id, rnk.rnk
        ;

ฟังก์ชั่น CTE และ Window ได้รับการแนะนำในรุ่นเดียวกันดังนั้นฉันจึงไม่เห็นประโยชน์ของวิธีแก้ปัญหาแรก
a_horse_with_no_name

โพสต์คือสามปี นอกจากนี้ยังอาจมีการใช้งานที่ขาดพวกเขา (เขยิบเขยิบบอกว่าไม่มาก) นอกจากนี้ยังอาจถือเป็นการออกกำลังกายในการสร้างแบบสอบถามแบบโบราณ (แม้ว่า CTE จะไม่ใช่แบบเก่า ๆ )
wildplasser

โพสต์จะถูกติดแท็ก "postgresql" และเวอร์ชัน PostgreSQL ที่แนะนำ CTEs ยังแนะนำฟังก์ชั่นหน้าต่าง ดังนั้นความคิดเห็นของฉัน (ฉันเห็นว่ามันเก่า - และ PG 8.3 ไม่มี)
a_horse_with_no_name

โพสต์กล่าวถึง 8.3.5 และฉันเชื่อว่าพวกเขาได้รับการแนะนำใน 8.4 นอกจากนี้ยังเป็นการดีที่จะรู้เกี่ยวกับสถานการณ์ทางเลือก IMHO
wildplasser

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