【问题标题】:SQL query to return top N rows per ID across a range of IDsSQL 查询返回一系列 ID 中每个 ID 的前 N ​​行
【发布时间】:2010-10-08 04:53:45
【问题描述】:

假设我有一个包含几亿行的表,看起来像这样:

memID | foo  | bar  | foobar
1     | blah | blah | blah
1     | blah | blah | blah
1     | blah | blah | blah
1     | blah | blah | blah
1     | blah | blah | blah
1     | blah | blah | blah
1     | blah | blah | blah
2     | blah | blah | blah
2     | blah | blah | blah
2     | blah | blah | blah
2     | blah | blah | blah
2     | blah | blah | blah
.
.
.
10001 | blah | blah | blah
10001 | blah | blah | blah

我需要一个查询,该查询将返回成员 ID 范围内每个 memID 的前 N ​​行。 例如,如果 N = 3 并且范围是 0-2 它应该返回

memID | foo  | bar  | foobar
1     | blah | blah | blah
1     | blah | blah | blah
1     | blah | blah | blah
2     | blah | blah | blah
2     | blah | blah | blah
2     | blah | blah | blah  

我考虑了几种方法,首先创建一个巨大的

SELECT TOP (3) *
FROM table
WHERE memID = 0
UNION ALL
SELECT TOP (3) *
FROM table
WHERE memID = 1
.
.
.

在代码中查询。由于可能显而易见的原因,这并不是一个真正现实的选择。

第二种方法是创建一个临时表并循环遍历 memID 的范围,将每个 memID 的 TOP 3 插入到该临时表中。

WHILE @MemID < 10000 AND @MemID > 0
  BEGIN
    INSERT INTO tmp_Table
    SELECT TOP (3) *
     FROM table
     WHERE memID = @MemID

    SET @MemID = @MemID + 1
    END

这可行,但我想知道是否有一个更优雅的单一查询解决方案我缺少。

Cadaeic 给了我一个无需修修补补​​的答案,但感谢所有建议分析的人,看来我需要认真阅读。

【问题讨论】:

  • 定义“顶部”。您需要指定一些条件,以选择具有给定 id 的行。
  • 已编辑以更清楚地表明 TOP 是一个函数。
  • 有主键吗?这样会更容易。
  • @blogsdon:我不认为这就是 Joel 的意思。您根据什么标准判断哪一行是 in TOP 3,哪一行不是?顶部通常伴随着一个 ORDER BY 来定义顶部的内容。还是更像是“最多三个任意样本行”?
  • 哦,我明白了,除了它们都需要具有相同的 memID 之外,没有其他标准。所以是的,您对“最多三个任意样本行”的描述是正确的。

标签: sql sql-server database optimization


【解决方案1】:
declare @startID int, @endID int, @rowsEach int
select @startID = 0, @endID = 2, @rowsEach = 3


select *
from
(
    select memID, foo, bar, foobar, row_number() over (partition by dense_rank order by dense_rank) [rank_row]
    from
    (
        select memID, foo, bar, foobar, dense_rank() over (order by memID) [dense_rank]
        from #test
        where memID between @startID and @endID
    ) a
) b
where rank_row <= @rowsEach

结果:

memID       foo  bar  foobar rank_row
----------- ---- ---- ------ --------------------
1           blah blah blah   1
1           blah blah blah   2
1           blah blah blah   3
2           blah blah blah   1
2           blah blah blah   2
2           blah blah blah   3

如果您想在本地测试,这里是设置代码:

create table #test
(
      memID     int not null
    , foo       char(4) not null
    , bar       char(4) not null
    , foobar    char(4) not null
)

insert into #test (memID, foo, bar, foobar)
select 1, 'blah', 'blah', 'blah'
union all
select 1, 'blah', 'blah', 'blah'
union all
select 1, 'blah', 'blah', 'blah'
union all
select 1, 'blah', 'blah', 'blah'
union all
select 1, 'blah', 'blah', 'blah'
union all
select 1, 'blah', 'blah', 'blah'
union all
select 1, 'blah', 'blah', 'blah'
union all
select 2, 'blah', 'blah', 'blah'
union all
select 2, 'blah', 'blah', 'blah'
union all
select 2, 'blah', 'blah', 'blah'
union all
select 2, 'blah', 'blah', 'blah'
union all
select 10001, 'blah', 'blah', 'blah'
union all
select 10001, 'blah', 'blah', 'blah'
union all
select 10001, 'blah', 'blah', 'blah'

【讨论】:

  • 这行得通。感谢所有建议分析的人,我还有很多阅读要做!
  • 为什么要在每个内部查询中保留 select 子句?为什么不使用 IN 语句执行外部选择,而在 IN 语句中运行仅返回主键的内部查询?您也可以通过主键连接来做类似的事情......
【解决方案2】:
SQL> select ename,sal,
  2   row_number()
  3     over (order by sal desc)rn,
  4   rank()
  5     over (order by sal desc)rnk,
  6   dense_rank()
  7     over (order by sal desc)drnk
  8   from emp
  9  order by sal desc
 10  /

ENAME    SAL   RN   RNK   DRNK
-----   ----   --   ---   ----
 KING   5000    1     1      1
 FORD   3000    2     2      2
SCOTT   3000    3     2      2
JONES   2975    4     4      3
BLAKE   2850    5     5      4
CLARK   2450    6     6      5

【讨论】:

    【解决方案3】:

    如果您使用的是 SQL Server 2005 或 2008,您可能需要调查 Ranking Functions

    【讨论】:

      【解决方案4】:

      使用分析。我没有对此进行测试,但应该很接近:

      SELECT memID, foo, bar, foobar 
      FROM  (
             SELECT memID, foo, bar, foobar, 
                    RANK() OVER (PARTITION BY memID ORDER BY memID) AS 'nRank'
             FROM   table
             WHERE  memID BETWEEN 0 AND 2)
      WHERE  nRank <= 3
      

      【讨论】:

        【解决方案5】:
        SELECT * FROM Member m
        Join ( Select TOP(3) * From Table Order By Table.Id) as t 
            On t.MemberId = m.MemberId
        Where m BETWEEN 0 and 10000
        

        应该做的伎俩

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-10-28
          • 1970-01-01
          • 2023-03-27
          • 1970-01-01
          • 1970-01-01
          • 2015-06-16
          相关资源
          最近更新 更多