เลือก 10 อันดับแรกสำหรับแต่ละประเภท


207

ฉันต้องการส่งกลับ 10 อันดับแรกจากแต่ละส่วนในแบบสอบถามเดียว ใครช่วยได้ด้วยวิธีทำ ส่วนคือหนึ่งในคอลัมน์ในตาราง

ฐานข้อมูลคือ SQL Server 2005 ฉันต้องการคืน 10 อันดับแรกตามวันที่ที่ป้อน ส่วนคือธุรกิจท้องถิ่นและคุณลักษณะ สำหรับหนึ่งวันที่เฉพาะฉันต้องการเฉพาะแถวบนสุด (10) แถวธุรกิจ (รายการล่าสุด) แถวบนสุด (10) แถวท้องถิ่นและคุณลักษณะด้านบน (10)


คำตอบใด ๆ เหล่านี้ใช้ได้กับคุณหรือไม่?
Kyle Delaney

3
ผมคิดว่าเราจะไม่ทราบ ...
Denny

เป็นเวลา 12 ปีแล้วและเราไม่รู้ว่ามีใครทำงานบ้าง
กลิ่นหอม

คำตอบ:


221

หากคุณใช้ SQL 2005 คุณสามารถทำสิ่งนี้ได้ ...

SELECT rs.Field1,rs.Field2 
    FROM (
        SELECT Field1,Field2, Rank() 
          over (Partition BY Section
                ORDER BY RankCriteria DESC ) AS Rank
        FROM table
        ) rs WHERE Rank <= 10

หาก RankCriteria ของคุณมีความสัมพันธ์กันคุณอาจส่งคืนมากกว่า 10 แถวและโซลูชันของ Matt อาจดีกว่าสำหรับคุณ


31
ถ้าคุณต้องการ 10 อันดับแรกให้เปลี่ยนเป็น RowNumber () แทน Rank () ไม่มีความผูกพันแล้ว
Mike L

3
วิธีนี้ใช้ได้ผล แต่โปรดทราบว่าอันดับ () มีแนวโน้มที่จะเปลี่ยนเป็นการจัดเรียงตารางแบบเต็มโดยตัววางแผนคิวรีหากไม่มีดัชนีที่มีคีย์แรกคือ RankCriteria ในกรณีนี้คุณอาจได้รับไมล์สะสมที่ดีกว่าการเลือกส่วนที่แตกต่างกันและการสมัครไขว้เพื่อเลือก 10 อันดับแรกที่เรียงลำดับตาม RankCriteria
Joe Kearney

คำตอบที่ดี! ทำให้ฉันเกือบจะเป็นสิ่งที่ฉันต้องการ ฉันลงเอยด้วยการDENSE_RANKที่ไม่มีช่องว่างในการนับ +1
Michael Stramel

1
@ ใบหน้ามันเป็นเพียงนามแฝงบนโต๊ะ
Darrel Miller

15
สำหรับทุกคนที่ใช้ SQL Server ฟังก์ชัน RowNumber () ที่ Mike L กล่าวถึงคือ ROW_NUMBER ()
randomraccoon

99

ใน T-SQL ฉันจะทำ:

WITH TOPTEN AS (
    SELECT *, ROW_NUMBER() 
    over (
        PARTITION BY [group_by_field] 
        order by [prioritise_field]
    ) AS RowNo 
    FROM [table_name]
)
SELECT * FROM TOPTEN WHERE RowNo <= 10

2
: โปรดอธิบายเพิ่มเติมเกี่ยวกับโซลูชันของคุณ อ้างถึง: วิธีการตอบ
askmish

แบบสอบถามแบบใช้เลือกข้อมูลที่ CTE สามารถมีส่วนคำสั่งที่ไหน?
toha

1
@toha ใช่มันสามารถ
KindaTechy

1
แม้ว่าคุณจะพูดว่า "ใน T-SQL" นี้จะทำงานสำหรับฐานข้อมูลใด ๆ ที่ใช้ROW_NUMBERฟังก์ชั่น ตัวอย่างเช่นฉันใช้โซลูชันนี้ใน SQLite
โทนี่

มันใช้งานได้กับ postgres sql เช่นกัน ฉันแค่ต้องใช้ "สั่งซื้อโดย [Prioritise_field] desc"
พูน

35

สิ่งนี้ใช้ได้กับ SQL Server 2005 (แก้ไขเพื่อแสดงความกระจ่างของคุณ):

select *
from Things t
where t.ThingID in (
    select top 10 ThingID
    from Things tt
    where tt.Section = t.Section and tt.ThingDate = @Date
    order by tt.DateEntered desc
    )
    and t.ThingDate = @Date
order by Section, DateEntered desc

2
วิธีนี้ใช้ไม่ได้กับแถวที่มาตราเป็นโมฆะ คุณจะต้องพูดว่า "where (tt.Section เป็นโมฆะและ t.Section เป็นโมฆะ) หรือ tt.Section = t.Section"
Matt Hamilton

29
SELECT r.*
FROM
(
    SELECT
        r.*,
        ROW_NUMBER() OVER(PARTITION BY r.[SectionID] ORDER BY r.[DateEntered] DESC) rn
    FROM [Records] r
) r
WHERE r.rn <= 10
ORDER BY r.[DateEntered] DESC

ตารางที่มีนามแฝงคืออะไร
Chalky

@Chalky rมันพิมพ์ผิดควรจะเป็น แก้ไขแล้ว.
lorond

ทำงานเหมือนจับใจ ขอบคุณ!
Ron Nuni

18

ฉันทำอย่างนี้:

SELECT a.* FROM articles AS a
  LEFT JOIN articles AS a2 
    ON a.section = a2.section AND a.article_date <= a2.article_date
GROUP BY a.article_id
HAVING COUNT(*) <= 10;

อัปเดต: ตัวอย่างของ GROUP BY นี้ทำงานใน MySQL และ SQLite เท่านั้นเนื่องจากฐานข้อมูลเหล่านั้นได้รับอนุญาตมากกว่า SQL มาตรฐานที่เกี่ยวข้องกับ GROUP BY การใช้ SQL ส่วนใหญ่ต้องการให้คอลัมน์ทั้งหมดในรายการเลือกที่ไม่ได้เป็นส่วนหนึ่งของการแสดงออกรวมยังอยู่ในกลุ่มตาม


1
มันใช้งานได้หรือไม่ ฉันค่อนข้างแน่ใจว่าคุณ "a.somecolumn ไม่ถูกต้องในรายการที่เลือกเนื่องจากมันไม่ได้อยู่ในฟังก์ชั่นรวมหรือกลุ่มตามข้อ" สำหรับทุกคอลัมน์ในบทความยกเว้น article_id ..
Blorgbeard ออกเมื่อ

1
คุณควรจะสามารถรวมคอลัมน์อื่น ๆ ที่ขึ้นอยู่กับหน้าที่การใช้งานขึ้นอยู่กับคอลัมน์ชื่อในกลุ่มตาม คอลัมน์ที่ไม่ได้ขึ้นอยู่กับการใช้งานนั้นไม่ชัดเจน แต่คุณถูกต้องขึ้นอยู่กับการใช้งาน RDBMS มันทำงานได้ใน MySQL แต่ IIRC ล้มเหลวใน InterBase / Firebird
Bill Karwin

1
จะสามารถใช้งานได้หรือไม่ในกรณีที่ระเบียนสิบเอ็ดอันดับสูงสุดสำหรับส่วนทั้งหมดมีวันที่เดียวกัน พวกเขาทั้งหมดจะมีจำนวน 11 และผลลัพธ์จะเป็นชุดที่ว่างเปล่า
Arth

ไม่คุณต้องมีวิธีการทำลายความสัมพันธ์ถ้าพวกเขาทั้งหมดมีวันที่เดียวกัน ดูstackoverflow.com/questions/121387/…สำหรับตัวอย่าง
Bill Karwin

1
@carlosgg หากบทความมีความสัมพันธ์แบบกลุ่มต่อกลุ่มคุณจะต้องมีตารางทางแยกเพื่อจับคู่บทความกับส่วนของพวกเขา จากนั้นแบบสอบถามของคุณจะต้องเข้าร่วมกับตารางจุดตัดสำหรับความสัมพันธ์ m2m และจัดกลุ่มตาม article_id และส่วน คุณควรเริ่มต้น แต่ฉันจะไม่เขียนคำตอบทั้งหมดในความคิดเห็น
Bill Karwin

16

ถ้าเราใช้ SQL Server> = 2005 เราสามารถแก้ปัญหาได้ด้วยการเลือกเพียงตัวเดียวเท่านั้น:

declare @t table (
    Id      int ,
    Section int,
    Moment  date
);

insert into @t values
(   1   ,   1   , '2014-01-01'),
(   2   ,   1   , '2014-01-02'),
(   3   ,   1   , '2014-01-03'),
(   4   ,   1   , '2014-01-04'),
(   5   ,   1   , '2014-01-05'),

(   6   ,   2   , '2014-02-06'),
(   7   ,   2   , '2014-02-07'),
(   8   ,   2   , '2014-02-08'),
(   9   ,   2   , '2014-02-09'),
(   10  ,   2   , '2014-02-10'),

(   11  ,   3   , '2014-03-11'),
(   12  ,   3   , '2014-03-12'),
(   13  ,   3   , '2014-03-13'),
(   14  ,   3   , '2014-03-14'),
(   15  ,   3   , '2014-03-15');


-- TWO earliest records in each Section

select top 1 with ties
    Id, Section, Moment 
from
    @t
order by 
    case 
        when row_number() over(partition by Section order by Moment) <= 2 
        then 0 
        else 1 
    end;


-- THREE earliest records in each Section

select top 1 with ties
    Id, Section, Moment 
from
    @t
order by 
    case 
        when row_number() over(partition by Section order by Moment) <= 3 
        then 0 
        else 1 
    end;


-- three LATEST records in each Section

select top 1 with ties
    Id, Section, Moment 
from
    @t
order by 
    case 
        when row_number() over(partition by Section order by Moment desc) <= 3 
        then 0 
        else 1 
    end;

1
+1 ฉันชอบวิธีนี้เพราะความเรียบง่าย แต่คุณสามารถอธิบายวิธีการใช้top 1งานกับcaseคำสั่งในorder byข้อที่ส่งคืน 0 หรือ 1 ได้หรือไม่
Ceres

3
TOP 1 ทำงานกับ WITH TIES ได้ที่นี่ WITH TIES หมายความว่าเมื่อ ORDER BY = 0 แล้ว SELECT จะบันทึกนี้ (เนื่องจาก TOP 1) และอื่น ๆ ทั้งหมดที่มี ORDER = 0 (เนื่องจาก WITH TIES)
Vadim Loboda

9

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

select top 10 * from table where section=1
union
select top 10 * from table where section=2
union
select top 10 * from table where section=3

3
นี่จะเป็นวิธีที่ง่ายที่สุดในการทำ
Hector Sosa Jr

3
แต่สิ่งนี้จะไม่มีประสิทธิภาพหากคุณมี 150 หรือหากหมวดหมู่นั้นแปรผันตามวันสัปดาห์เป็นต้น
Rafa Barragan

1
แน่นอน แต่จะอ้างอิง OP: "ส่วนต่างๆเป็นธุรกิจท้องถิ่นและคุณลักษณะ" หากคุณมีสามประเภทคงที่นี่เป็นวิธีที่ดีที่สุดที่จะทำ
Blorgbeard ออก

9

ฉันรู้ว่ากระทู้นี้ค่อนข้างเก่า แต่ฉันเพิ่งเจอปัญหาที่คล้ายกัน (เลือกบทความใหม่ล่าสุดจากแต่ละหมวดหมู่) และนี่เป็นวิธีแก้ปัญหาที่ฉันได้รับ:

WITH [TopCategoryArticles] AS (
    SELECT 
        [ArticleID],
        ROW_NUMBER() OVER (
            PARTITION BY [ArticleCategoryID]
            ORDER BY [ArticleDate] DESC
        ) AS [Order]
    FROM [dbo].[Articles]
)
SELECT [Articles].* 
FROM 
    [TopCategoryArticles] LEFT JOIN 
    [dbo].[Articles] ON
        [TopCategoryArticles].[ArticleID] = [Articles].[ArticleID]
WHERE [TopCategoryArticles].[Order] = 1

นี่คล้ายกับโซลูชันของ Darrel แต่เอาชนะปัญหาอันดับที่อาจส่งคืนแถวมากกว่าที่ตั้งใจไว้


ทำไม่ต้องใช้บริการกับ CTE Sir? มันลดการใช้หน่วยความจำหรือไม่?
toha

@toha เนื่องจาก CTE นั้นง่ายและเข้าใจง่ายขึ้น
วิศวกรที่กลับรายการ

คำตอบที่ดี !! มันสามารถปรับให้เหมาะสมโดยใช้ด้านในJOINแทนLEFT JOINเนื่องจากจะไม่มีระเบียนสำหรับTopCategoryArticlesโดยไม่มีArticleระเบียนที่สอดคล้องกัน
วิศวกรที่กลับรายการ

6

พยายามต่อไปนี้และมันก็ใช้ได้กับความสัมพันธ์ด้วย

SELECT rs.Field1,rs.Field2 
FROM (
    SELECT Field1,Field2, ROW_NUMBER() 
      OVER (Partition BY Section
            ORDER BY RankCriteria DESC ) AS Rank
    FROM table
    ) rs WHERE Rank <= 10

5

หากคุณต้องการสร้างผลลัพธ์ที่จัดกลุ่มตามส่วนจะแสดงเฉพาะระเบียนnอันดับแรกจากแต่ละส่วนดังนี้:

SECTION     SUBSECTION

deer        American Elk/Wapiti
deer        Chinese Water Deer
dog         Cocker Spaniel
dog         German Shephard
horse       Appaloosa
horse       Morgan

... ดังนั้นข้อมูลต่อไปนี้น่าจะใช้ได้กับฐานข้อมูล SQL ทั้งหมด ถ้าคุณต้องการ 10 อันดับแรกเพียงแค่เปลี่ยน 2 เป็น 10 ต่อท้ายแบบสอบถาม

select
    x1.section
    , x1.subsection
from example x1
where
    (
    select count(*)
    from example x2
    where x2.section = x1.section
    and x2.subsection <= x1.subsection
    ) <= 2
order by section, subsection;

เพื่อติดตั้ง:

create table example ( id int, section varchar(25), subsection varchar(25) );

insert into example select 0, 'dog', 'Labrador Retriever';
insert into example select 1, 'deer', 'Whitetail';
insert into example select 2, 'horse', 'Morgan';
insert into example select 3, 'horse', 'Tarpan';
insert into example select 4, 'deer', 'Row';
insert into example select 5, 'horse', 'Appaloosa';
insert into example select 6, 'dog', 'German Shephard';
insert into example select 7, 'horse', 'Thoroughbred';
insert into example select 8, 'dog', 'Mutt';
insert into example select 9, 'horse', 'Welara Pony';
insert into example select 10, 'dog', 'Cocker Spaniel';
insert into example select 11, 'deer', 'American Elk/Wapiti';
insert into example select 12, 'horse', 'Shetland Pony';
insert into example select 13, 'deer', 'Chinese Water Deer';
insert into example select 14, 'deer', 'Fallow';

สิ่งนี้ไม่ทำงานเมื่อฉันต้องการเพียงบันทึกแรกสำหรับแต่ละส่วน มันกำจัดกลุ่มส่วนทั้งหมดที่มีมากกว่า 1 บันทึก ฉันลองโดยแทนที่ <= 2 ด้วย <= 1
nils

@nils มีสามส่วนค่าคือกวางสุนัขและม้า หากคุณเปลี่ยนข้อความค้นหาเป็น <= 1 คุณจะได้รับหนึ่งส่วนย่อยสำหรับแต่ละส่วน: American Elk / Wapiti for deer, Cocker Spaniel สำหรับสุนัขและ Appaloosa สำหรับม้า เหล่านี้เป็นค่าแรกในแต่ละส่วนตามตัวอักษร แบบสอบถามมีขึ้นเพื่อกำจัดค่าอื่น ๆ ทั้งหมด
Craig

แต่เมื่อฉันพยายามเรียกใช้คิวรีของคุณมันจะกำจัดทุกอย่างเพราะจำนวนคือ> = 1 สำหรับทุกอย่าง มันไม่รักษาส่วนย่อยที่ 1 สำหรับแต่ละส่วน คุณลองเรียกใช้คิวรีของคุณสำหรับ <= 1 แล้วแจ้งให้เราทราบหากคุณได้รับส่วนย่อยแรกสำหรับแต่ละส่วน
nils

@nils สวัสดีฉันได้สร้างฐานข้อมูลการทดสอบเล็ก ๆ นี้จากสคริปต์และเรียกใช้แบบสอบถามโดยใช้ <= 1 และมันคืนค่าส่วนย่อยแรกจากแต่ละส่วน คุณใช้เซิร์ฟเวอร์ฐานข้อมูลใด มีโอกาสที่เกี่ยวข้องกับฐานข้อมูลที่คุณเลือกอยู่เสมอ ฉันเพิ่งรันสิ่งนี้ใน MySQL เพราะมันมีประโยชน์และมันทำงานได้อย่างที่คาดไว้ ฉันค่อนข้างแน่ใจว่าเมื่อฉันทำมันเป็นครั้งแรก (ฉันต้องการให้แน่ใจว่าสิ่งที่ฉันโพสต์ทำงานได้จริงโดยไม่ต้อง debuggin) ฉันค่อนข้างแน่ใจว่าฉันได้ใช้ Sybase SQL Anywhere หรือ MS SQL Server
Craig

มันทำงานได้อย่างสมบูรณ์แบบสำหรับฉันใน mysql ฉันเปลี่ยนคำถามเล็กน้อยไม่แน่ใจว่าทำไมเขาใช้ <= สำหรับเขต varchar ในส่วนย่อย .. ฉันเปลี่ยนเป็นและ x2.subsection = x1.subsection
Mahen Nakar

4

Might ยูเนี่ยนการทำงานของผู้ประกอบการสำหรับคุณ? มีหนึ่งตัวเลือกสำหรับแต่ละส่วนจากนั้นรวมเข้าด้วยกัน เดาว่ามันจะใช้ได้เฉพาะกับจำนวนส่วนที่แน่นอน


4

Q) การค้นหาระเบียน X อันดับสูงสุดจากแต่ละกลุ่ม (Oracle)

SQL> select * from emp e 
  2  where e.empno in (select d.empno from emp d 
  3  where d.deptno=e.deptno and rownum<3)
  4  order by deptno
  5  ;

 EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM     DEPTNO

  7782 CLARK      MANAGER         7839 09-JUN-81       2450                    10
  7839 KING       PRESIDENT            17-NOV-81       5000                    10
  7369 SMITH      CLERK           7902 17-DEC-80        800                    20
  7566 JONES      MANAGER         7839 02-APR-81       2975                    20
  7499 ALLEN      SALESMAN        7698 20-FEB-81       1600        300         30
  7521 WARD       SALESMAN        7698 22-FEB-81       1250        500         30

เลือก 6 แถว



คำถามเกี่ยวกับ SQL Server ไม่ใช่ Oracle
Craig

2

ในขณะที่คำถามเกี่ยวกับ SQL Server 2005 คนส่วนใหญ่ได้ย้ายและถ้าพวกเขาพบคำถามนี้สิ่งที่อาจเป็นคำตอบที่ต้องการในสถานการณ์อื่น ๆ คือการใช้CROSS APPLYอย่างใดอย่างหนึ่งตามที่แสดงในโพสต์บล็อกนี้

SELECT *
FROM t
CROSS APPLY (
  SELECT TOP 10 u.*
  FROM u
  WHERE u.t_id = t.t_id
  ORDER BY u.something DESC
) u

แบบสอบถามนี้เกี่ยวข้องกับ 2 ตาราง การสืบค้นของ OP เกี่ยวข้องกับตาราง 1 ตารางเท่านั้นในกรณีที่โซลูชันหน้าต่างที่ใช้ฟังก์ชันอาจมีประสิทธิภาพมากกว่า


1

คุณสามารถลองวิธีนี้ ข้อความค้นหานี้ส่งคืน 10 เมืองที่มีประชากรมากที่สุดในแต่ละประเทศ

   SELECT city, country, population
   FROM
   (SELECT city, country, population, 
   @country_rank := IF(@current_country = country, @country_rank + 1, 1) AS country_rank,
   @current_country := country 
   FROM cities
   ORDER BY country, population DESC
   ) ranked
   WHERE country_rank <= 10;

วิธีการแก้ปัญหานี้ไม่ได้ผ่านกรณีทดสอบเมื่อเรามีตารางที่มีบันทึกของประเทศหนึ่งที่มีประชากร 9 คนเหมือนกันตัวอย่างเช่นจะคืนค่า null แทนการส่งคืนระเบียนที่มีอยู่ทั้งหมด 9 รายการตามลำดับ ข้อเสนอแนะใด ๆ เพื่อแก้ไขปัญหานี้?
Mojgan Mazouchi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.