指定多个排序规则

Specifying multiple ordering rules

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

为了更好地解释,我在下面发布了一个示例结果查询。在此,我们试图显示位置的层次结构,但是虽然 parent/child 关系排序正确,但它们在关系中不是按字母顺序排列的。如您所见,"East Coast" 和 "West Coast" 都是 top-level 位置,因为它们的 parent 位置 (f_locationparent) 等于 (0)。但是,我希望 "East Coast" 在 "West Coast" 之前显示。显然,我不能简单地按 f_locationname 排序,然后再按 f_lineage 排序,因为这样就不会以正确的顺序显示关系。重要说明:top-level 个位置将始终具有 (0) 的 parent 个位置,因为它们没有 parent 个。

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,所以你可以看看它是如何工作的。

最后,使用相同的数据,这是我希望看到的结果查询,在 parent 下的关系中,项目按字母顺序排列。所以对于 "East Coast" grandparent 和 "New York City" parent,下面列出的 children 是按字母顺序排列的。

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

您可以使用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

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

但值得注意的是,如果您的 table 非常大,这可能不切实际。当您 运行 进入越来越大的数据集时,ROW_NUMBER() 可能会变得相当慢。

编辑: 一个问题是,如果您在一个级别中有超过 9 行,如上例所示。您必须增加幅度以反映足够的 "spaces" 来保存信息。例如,这适用于每个级别最多 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 行,这将变得很麻烦,但根据您的评论,我认为这应该不是问题。

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

先按顶级 parent 姓名排序,然后按血统排序

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

更新

要处理 multi-digit 个 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

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

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