指定多个排序规则

时间:2015-01-20 20:00:17

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

我使用的是SQL Server 2008 R2,而且我遇到了一个复杂的排序问题或问题,我无法找到解决方案。

为了更好地解释,我在下面发布了一个样本结果查询。在这种情况下,我们尝试显示位置的层次结构,但在正确排序父/子关系时,它们在其关系中不是按字母顺序排列的。正如你所看到的,"东海岸"和#34;西海岸"是顶级位置,因为它们的父位置(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小提琴,所以你可以看到它是如何工作的。

最后,使用相同的数据,这是我想要看到的结果查询,在父母之下的关系中,项目按字母顺序排序。因此对于东海岸"祖父母和"纽约市"父母,其下列出的孩子是按字母顺序排列的。

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

3 个答案:

答案 0 :(得分:10)

您可以使用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行,这将会变得很麻烦,但我怀疑,鉴于你的意见,这不应该是一个问题。

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

答案 1 :(得分: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

答案 2 :(得分:-7)

首先排序顶级父母名称,然后排序血统

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