【问题标题】:How to apply DENSE_RANK() to records ordered by multiple columns?如何将 DENSE_RANK() 应用于按多列排序的记录?
【发布时间】:2021-03-05 16:28:21
【问题描述】:

我正在编写一个 SQL 存储过程,它会为每个 AliasName 检索我在 NameAssociations 表中与之关联的 MasterNames,该表包含 AliasNameIdMasterNameIdMatchRank,以及StatusCode 列。我将使用存储过程在 C# 中对服务器端数据进行分页,所以我希望 startRow endRow 部分保持不变。

CREATE PROCEDURE [dbo].[NameAssociationsGetNameAssociations]
    @StartRow INT = 1,
    @EndRow INT = 1
AS
    WITH result_set AS (
        SELECT DENSE_RANK() OVER (ORDER BY an.Id) AS rowNum,        
        an.Id as AliasId, an.AliasName,
        mn.Id as MasterId, mn.MasterName, mn.CodeId, mn.RowUpdateVersion AS ConcurrencyToken, na.MatchRank
    FROM
        NameAssociations na 
        INNER JOIN AliasNames an
        ON na.AliasNameId = an.Id 
        INNER JOIN MasterNames mn
        ON na.MasterNameId = mn.Id 
    WHERE
        na.StatusCode = 0
    ) 
    SELECT
        rowNum, 
        AliasId as Id, AliasName,
        MasterId as Id, MasterName, CodeId, ConcurrencyToken, MatchRank
    FROM
        result_set
    WHERE
        rowNum BETWEEN @StartRow AND @EndRow

此过程有效并检索正确编号的行:

rowNum Id AliasName Id MasterName CodeId ConcurrencyToken MatchRank
1 5 BName 34 SomeName1 2 0x0000021 1
2 6 DName 21 SomeName2 3 0x0000003 2
2 6 DName 2 SomeName3 1 0x00000A2 1
2 6 DName 40 SomeName4 1 0x00000B4 3
3 7 AName 11 SomeName5 1 0x000005B 1

所以基本上,对于每个AliasName,我都会得到一个MasterNames 的列表。

但是这些行没有按照我希望的方式排序,即AliasName ASC、Id(别名)ASC 和MatchRank ASC,看起来像这样:

rowNum Id AliasName Id MasterName CodeId ConcurrencyToken MatchRank
1 7 AName 11 SomeName5 1 0x000005B 1
2 5 BName 34 SomeName1 2 0x0000021 1
3 6 DName 2 SomeName3 1 0x00000A2 1
3 6 DName 21 SomeName2 3 0x0000003 2
3 6 DName 40 SomeName4 1 0x00000B4 3

您可以看到别名的名称是按字母顺序排列的,然后关联的 master 被重新排序,所以 MatchRank 是按 ASC 顺序排列的。所有这一切,而 rowNum 是正确的,这是我一直难以实现的。

我尝试过PARTITION BY an.Id ORDER BY an.AliasName ASC, an.Id ASC, na.MatchRank ASC,但我一定误解了ORDER BY 在这种情况下的作用,因为结果错误。结果看起来它仅按MatchRank ASC 对记录进行排序并按它进行分区。我希望它按这三列对数据进行排序,然后按an.Id 对其进行分区。

如何编写查询以使输出结果看起来像表的第二个示例?我希望这一切都清楚了。

【问题讨论】:

  • PARTITION BY 基本上是聚合的GROUP BY,因此每个新的Id 都会重新编号。我想你想要ORDER BY AliasName, AliasId, MatchRank 而没有PARTITION。实际输出行的顺序取决于您没有的外部ORDER BY,因此理论上这些行可能以与行编号结果不同的顺序返回。重新分页,你可能会觉得这很有趣:stackoverflow.com/a/66484823/14868997
  • 您的查询和示例输出在逻辑上不匹配。 partition by an.id 将为每个新的 id 值重新开始排名。我想你这里真的有order by
  • 是的,这是一个ORDER BY an.Id

标签: sql sql-server


【解决方案1】:

忽略脚本中看起来像转录/混淆错误,实际上会使您的 SQL 无效(dense_rank 必须有 order by),我认为您的解决方案实际上非常简单。

dense_rank 函数使用order by 中的值来确定哪些记录获得相同的排名值,所以如果要按AliasName 对输出进行排序,还想确保记录相同AliasName 但不同的 id 值(不知道为什么会这样,但防御性编码通常是一个好习惯),您只需在 order by 中包含 both 值,在要应用排序的顺序。在您的情况下,这看起来像以下最小解决方案:

查询

declare @t table(id int, AliasName varchar(10), val int);
insert into @t values (1,'b',1),(2,'c',2),(2,'c',3),(2,'cc',4),(22,'c',5),(3,'a',6);

select *
      ,dense_rank() over (order by id) as rid
      ,dense_rank() over (order by AliasName, id) as rAliasName
from @t;

输出

id AliasName val rid rAliasName
3 a 6 3 1
1 b 1 1 2
2 c 2 2 3
2 c 3 2 3
22 c 5 4 4
2 cc 4 2 5

【讨论】:

    猜你喜欢
    • 2022-01-19
    • 1970-01-01
    • 2020-02-17
    • 1970-01-01
    • 2021-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多