指定多个排序规则
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 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