【问题标题】:Specifying multiple ordering rules指定多个排序规则
【发布时间】:2015-01-21 20:05:31
【问题描述】:

我正在使用 SQL Server 2008 R2,但我有一个复杂的订购问题或问题,我找不到解决方案。

为了更好地解释,我在下面发布了一个示例结果查询。在此,我们试图显示位置的层次结构,但是虽然父/子关系排序正确,但它们在它们的关系中不是按字母顺序排列的。如您所见,“东海岸”和“西海岸”都是顶级位置,因为它们的父位置 (f_locationparent) 等于 (0)。但是,我希望在“西海岸”之前显示“东海岸”。显然,我不能简单地按f_locationname 排序,然后按f_lineage 排序,因为这样关系将不会以正确的顺序显示。重要提示:顶级位置的父位置始终为 (0),因为它们没有父位置。

f_locationid    f_locationparent    f_locationname  f_level f_lineage
-------------------------------------------------------------------------
4               0                   West Coast      0       0_4
5               4                   Los Angeles     1       0_4_5
6               5                   Del Rey         2       0_4_5_6
7               5                   Reseda          2       0_4_5_7
8               5                   Crenshaw        2       0_4_5_8
9               0                   East Coast      0       0_9
10              9                   New York City   1       0_9_10
1               10                  Queens          2       0_9_10_1
2               10                  Bronx           2       0_9_10_2
3               10                  Manhattan       2       0_9_10_3

以下是当前查询:

;WITH   cte_locationlineage AS 
 (
 SELECT  a.f_locationid, a.f_locationparent, a.f_locationname, 0 AS f_level,
   CONVERT(varchar(30), '0_' + convert(varchar(10), f_locationid)) f_lineage
 FROM    tb__templocations a 
 WHERE   f_locationparent = '0' 
 UNION ALL
    SELECT  a.f_locationid,
           a.f_locationparent,
           a.f_locationname,
           c.f_level + 1,
           CONVERT(varchar(30), f_lineage + '_' 
                   + convert(varchar(10), a.f_locationid))
   FROM    cte_locationlineage c
   JOIN    tb__templocations a
       ON a.f_locationparent = c.f_locationID
 )
 SELECT *
 FROM   cte_locationlineage c
 ORDER BY f_lineage

如您所见,它是根据血统排序的,血统是位置 ID (f_locationID) 的组合。不幸的是,如您所见,位置 ID 并不总是按字母顺序排列。

Here 是一个 SQL Fiddle,所以你可以看到它是如何工作的。

最后,使用相同的数据,这是我希望看到的结果查询,在父项下的关系中,项目按字母顺序排列。所以对于“东海岸”的祖父母和“纽约市”的父母,下面列出的孩子是按字母顺序排列的。

f_locationid    f_locationparent    f_locationname  f_level f_lineage
-------------------------------------------------------------------------
9               0                   East Coast      0       0_9
10              9                   New York City   1       0_9_10
2               10                  Bronx           2       0_9_10_2
3               10                  Manhattan       2       0_9_10_3    
1               10                  Queens          2       0_9_10_1
4               0                   West Coast      0       0_4
5               4                   Los Angeles     1       0_4_5
8               5                   Crenshaw        2       0_4_5_8  
6               5                   Del Rey         2       0_4_5_6 
7               5                   Reseda          2       0_4_5_7

【问题讨论】:

  • 如果您按完整路径订购商品,您可以轻松获得所需的订购。请检查我的第二个答案。

标签: sql-server tsql sql-server-2008-r2


【解决方案1】:

您可以使用ROW_NUMBER() 提供帮助:

;WITH   cte_locationlineage AS 
 (
 SELECT  a.f_locationid, a.f_locationparent, a.f_locationname, 0 AS f_level,
       CONVERT(varchar(30), '0_' + convert(varchar(10), f_locationid)) f_lineage,
       CAST(ROW_NUMBER() OVER(ORDER BY f_locationname) as decimal(8,4)) as ordering
 FROM    tb__templocations a 
 WHERE   f_locationparent = '0' 
 UNION ALL
    SELECT  a.f_locationid,
         a.f_locationparent,
         a.f_locationname,
         c.f_level + 1,
         CONVERT(varchar(30), f_lineage + '_' + convert(varchar(10), a.f_locationid)),
         cast(c.ordering + (CAST(ROW_NUMBER() OVER(ORDER BY a.f_locationname) 
           as decimal(8,4))/POWER(10,c.f_level + 1)) as decimal(8,4))
   FROM    cte_locationlineage c
   JOIN    tb__templocations a
       ON a.f_locationparent = c.f_locationID
 )
 SELECT *
 FROM   cte_locationlineage c
 ORDER BY c.ordering

这样,您可以结合您的级别和您的位置名称来对列表中的内容进行排序。

但值得注意的是,如果您的表非常大,这可能不实用。当您遇到越来越大的数据集时,ROW_NUMBER() 会变得相当慢。

编辑: 成为问题的一件事是,如果您在一个级别中有超过九行,例如上面的示例。您必须增加幅度以反映足够的“空间”来保存信息。例如,这适用于每个级别最多 99 行:

;WITH   cte_locationlineage AS 
 (
 SELECT  a.f_locationid, a.f_locationparent, a.f_locationname, 0 AS f_level,
         CONVERT(varchar(30), '0_' + convert(varchar(10), f_locationid)) f_lineage,
         CAST(ROW_NUMBER() OVER(ORDER BY f_locationname) as decimal(12,8)) as ordering
 FROM    tb__templocations a 
 WHERE   f_locationparent = '0' 
 UNION ALL
    SELECT  a.f_locationid,
           a.f_locationparent,
           a.f_locationname,
           c.f_level + 1,
           CONVERT(varchar(30), f_lineage + '_' + convert(varchar(10), a.f_locationid)),
           cast(c.ordering + (CAST(ROW_NUMBER() OVER(ORDER BY a.f_locationname) as decimal(12,8))
                    /POWER(10,(c.f_level + 1)*2)) as decimal(12,8))
   FROM    cte_locationlineage c
   JOIN    tb__templocations a
       ON a.f_locationparent = c.f_locationID
 )
 SELECT *
 FROM   cte_locationlineage c
 ORDER BY c.ordering

显然,如果每层的行数远高于 999 行,这将变得很麻烦,但我怀疑考虑到您的 cmets,这应该不是问题。

我很好奇是否有人有更聪明的方法来使用二进制完成同样的事情;我要看看今晚晚些时候我能不能算出数学。

【讨论】:

  • 是的,这很简单,很优雅,而且works great - even with Miami thrown in。 (而且,如果表非常大,递归 CTE 方法已经限制了实用性。)
  • 这个很精致,谢谢。我从没想过该解决方案会适合已经使用的查询。我想肯定的答案是抹去我所有的东西并重新开始,所以真诚地谢谢你。最多,我的表中可能有 1000 行。我目前大约有 600 行,而且它会立即运行(如此之低,事实上,它在 0.0002 秒(200 微秒)内运行。
  • 我发现了一个问题。似乎如果我们到达一个有 10 个或更多孩子的父母,我们最终会得到 5.8、5.9、6.0... |然后,当我们达到 6.0 时,我们可以有多个订单为 6.0 的商品,从而使订单失去平衡。
  • @Beems 是的,你必须增加数量级......所以将小数(8,4)更改为小数(12,8)并将你的 POWER 指数乘以 2。所以你必须记住你可能的组合。如果您认为每个级别将有超过 99 行,那么您必须升级 3 级,等等。如果您愿意,我可以通过一个工作示例更新答案。
  • 不错。一种不使用力量,仅使用弦乐的变体。适用于多达 33 个级别和多达 99 个具有相同父级的项目:sql-fiddle。可以通过更改(例如)'0''000'varchar(2) to varchar(4) 来轻松扩展,以便每个父级最多有 9999 个项目。 varchar(99) 必须变为 varchar((4+1)*X),其中 X 是层次结构的最高级别。
【解决方案2】:

您可以按完整路径名对行进行排序,使用未使用的字符来连接名称。

WITH   cte_locationlineage AS (
    SELECT  a.F_LocationId, a.f_locationparent, a.f_locationname, 
            0 AS f_level,
            CONVERT(varchar(30), '0_' + convert(varchar(10), F_LocationId)) as f_lineage,
            CONVERT(varchar(max), a.f_locationname) as Fullname -- Add this line
    FROM    tb__templocations a 
    WHERE   f_locationparent = '0' 
    UNION ALL
    SELECT  a.F_LocationId,
            a.f_locationparent,
            a.f_locationname,
            c.f_level + 1,
            CONVERT(varchar(30), f_lineage + '_' 
                   + convert(varchar(10), a.F_LocationId)),
            CONVERT(varchar(max), c.fullname + '_' + a.f_locationname) -- Add this line
   FROM     cte_locationlineage c
   JOIN     tb__templocations a ON a.f_locationparent = c.F_LocationId)

SELECT *
FROM   cte_locationlineage c
ORDER BY fullname -- Change this line

SQL Fiddle

【讨论】:

  • 这是一个有趣的解决方案。使用串联的“全名”字段,您实际上可以在代码隐藏中将其分解并使用它来显示“面包屑”导航备份层次结构。
【解决方案3】:

首先按父辈的名字排序,然后是血统

ORDER BY (select f_locationname from cte_locationlineage d 
  where d.F_Lineage = substring(c.F_lineage,1,3)), f_lineage

更新

处理多位 id 的:

ORDER BY (select f_locationname from cte_locationlineage d 
  where d.f_level = 0 and c.F_LiNEAGE + '_' like d.f_lineage + '[_]%'), f_lineage

【讨论】:

  • 不,这行不通,对于初学者来说,它假定所有 location_id 值都是一位数(您不能像那样对子字符串进行硬编码),并且it demonstrably doesn't return the right results anyway。不要介意基于相关子查询的排序对性能的额外影响......
  • 哦,这么多不喜欢! :) 好吧,这只是一个快速的提议,你明白了。比较可以稍微调整以处理多位数的 id。
  • 这与喜欢或不喜欢无关。您更新的解决方案doesn't work either - 您是否尝试过这些查询中的任何一个?您仍在按lineage 排序 - OP 希望按名称而不是沿袭顺序按字母顺序 对同一级别的所有元素进行排序。
  • 是的,我当然试过了。只是我认为字母顺序仅适用于顶级项目。漏掉了同级子节点也会被排序的部分。
猜你喜欢
  • 1970-01-01
  • 2016-07-05
  • 2013-09-26
  • 2017-02-15
  • 2010-10-23
  • 2011-07-31
  • 2018-02-19
  • 2016-04-04
  • 2012-09-11
相关资源
最近更新 更多