2015-01-20 69 views
4

我正在使用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 

正如你所看到的,它是基于谱系有序,这是位置标识(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 
+1

如果您需要通过他们的完整路径的项目,你可以很容易地获得所需的排序。请检查我的第二个答案。 – CrimsonKing 2015-01-21 12:21:04

回答

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行,但我怀疑,不应该给你的意见的问题。

我很好奇,如果有人有更聪明的方式来完成使用二进制相同的事情;我要看看我今晚晚些时候能算出数学。

+2

是的,这很简单,优雅,[效果很好 - 即使在迈阿密引发](http://sqlfiddle.com/#!3/53bc57/2)。 (而且,如果表格非常大,递归CTE方法已经限制了实用性。) – 2015-01-20 20:24:55

+0

这很精美,谢谢。我从来没有想到该解决方案适用于已经使用的查询。我确信所涉及的答案是清除我所拥有的并重新开始,所以真诚地谢谢你。在表格中,我最多可以有1000行。我目前在大约600行,这是瞬间运行(如此之低,实际上,它运行在0.0002秒(200微秒)) – Beems 2015-01-20 21:13:39

+0

我发现了一个问题,看起来如果我们到达有10个或更多孩子,我们以5.8,5.9,6.0的顺序结束......然后,当我们达到6.0时,我们可以有多个项目,其中订单是6.0,导致订单失去平衡。 – Beems 2015-01-20 22:13:40

-7

排序第一的顶级父名,然后血统

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

UPDATE

处理多位数的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 
+0

不,这不起作用,对于初学者来说,它假设所有的location_id值都是单位数字(你不能像这样硬编码子字符串),[它证明无论如何不会返回正确的结果](http: //sqlfiddle.com/#!3/53bc57/3)。不要介意基于相关子查询进行排序所带来的额外性能影响...... – 2015-01-20 20:27:01

+0

哦,好多人不喜欢! :)那么,这只是一个快速的建议,你明白了。比较可以调整一点来处理多位数的ID也。 – CrimsonKing 2015-01-20 20:55:45

+3

这不是喜欢或不喜欢。您更新的解决方案[也不起作用](http://sqlfiddle.com/#!3/53bc57/6) - 您是否尝试过这些查询?您仍然通过'lineage'进行排序--OP希望按照名称而不是按血统顺序排列同一级别的所有元素*按字母顺序排列*。 – 2015-01-20 21:03:18

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

+1

这是一个有趣的解决方案。使用连接的“全名”字段,实际上可以在代码隐藏中将其分开,并使用它来显示“面包屑”导航备份层次结构。 – Beems 2015-01-21 20:30:38