自定义 T-SQL 排序依据 (IComparer)
Custom T-SQL Order By (IComparer)
T-SQL 是否有能力对字符串进行自定义比较(用于排序),等同于 .NET 的 IComparer
?
比如能够给 Order By
一个用户定义的函数,它接受 2 个字符串和 returns 一个表示它们如何比较的值(大于、小于、等于)?
我目前有一个 C# ICompararer
实现用于对代码中的内容进行排序,但现在我需要从存储过程中生成相同的排序输出。
供参考,这是我试图在 TSQL 中实现的 IComparer
。
public class SemanticComparer : IComparer<string>
{
private static Regex _splitter = new Regex("\W+");
public int Compare(string x, string y)
{
string[] partsX = _splitter.Split(x);
string[] partsY = _splitter.Split(y);
int shortest = Math.Min(partsX.Length, partsY.Length);
for (int index = 0; index < shortest; index++)
{
int intX, intY;
int result;
if (int.TryParse(partsX[index], out intX) && int.TryParse(partsY[index], out intY))
{
result = intX.CompareTo(intY);
}
else
{
result = string.Compare(partsX[index], partsY[index], StringComparison.Ordinal);
}
if (result != 0)
{
return result;
}
}
return 0;
}
}
它需要能够对看起来像这样的东西进行排序(按照它们应该输出的顺序):
- 2-101.11(A)(B)
- 9.1
- 9.2
- 9.2.1
- 9.02.2
- 9.02.3
- 9.3
- 9.3.1
- 9.3.2
- 10.
- 11.
- 11.1
- 11.2.a
- 11.2.b
- 11.2.c
- 11a.2.a
- 11b.2.b
- 21 美国联邦法规 110.10
- 046.981(e)(m)
其中每个非单词字符将字符串拆分为多个段,如果可能则尝试对它们进行数字比较,如果不能则作为字符串进行比较。段数可以任意"depth"。
不幸的是,CLR 存储过程不是一个选项。
with data as (
select c from (values
('9.1'), ('9.2'), ('9.2.1'), ('9.02.2'), ('9.02.3'), ('9.3'), ('9.3.1'), ('9.3.2'),
('10.'), ('11.'), ('11.1'), ('11.2.a'), ('11.2.b'), ('11.2.c'), ('11a.2.a'), ('11b.2.b')
) t(c)
)
select c, '[' +
right('00000' +
substring(c, 1, charindex('.', c + '.0.0', 1) - 1) +
case when substring(c + '.0.0', charindex('.', c + '.0.0', 1) - 1, 1) between '0' and '9' then ' ' else '' end,
6
) + '.' +
right('00000' +
substring(c, charindex('.', c + '.0.0', 1) + 1, charindex('.', c + '.0.0', charindex('.', c + '.0.0', 1) + 1) - charindex('.', c + '.0.0', 1) - 1) +
case when right('0' +
substring(c, charindex('.', c + '.0.0', 1) + 1, charindex('.', c + '.0.0', charindex('.', c + '.0.0', 1) + 1) - charindex('.', c + '.0.0', 1) - 1),
1
) between '0' and '9' then ' ' else '' end,
6
) + '.' +
right('00000' +
substring(c, charindex('.', c + '.0.0', charindex('.', c + '.0.0', 1) + 1) + 1, 10) +
case when right('0' +
substring(c, charindex('.', c + '.0.0', charindex('.', c + '.0.0', 1) + 1) + 1, 10),
1
) between '0' and '9' then ' ' else '' end,
6
) + ']'
from data
order by 2;
我把它放在一起是为了好玩。如您所见,SQL 中的字符串解析通常工作量很大,并且有很多重复的子表达式。
这里的想法是,您似乎可以将值转换为规范化格式, 可通过常规字母排序进行排序。前面的数字部分是zero-padded。每个 "field" 的末尾允许有一个字母字符,否则会附加一个 space。举个例子 11.2.a
变成 [00011 .00002 .00000a]
它相当灵活,您当然可以将此逻辑包装在一个标量函数中。我会让你决定这整件事是不是个好主意。
http://rextester.com/LYL6977(略有改善?)
T-SQL 是否有能力对字符串进行自定义比较(用于排序),等同于 .NET 的 IComparer
?
比如能够给 Order By
一个用户定义的函数,它接受 2 个字符串和 returns 一个表示它们如何比较的值(大于、小于、等于)?
我目前有一个 C# ICompararer
实现用于对代码中的内容进行排序,但现在我需要从存储过程中生成相同的排序输出。
供参考,这是我试图在 TSQL 中实现的 IComparer
。
public class SemanticComparer : IComparer<string>
{
private static Regex _splitter = new Regex("\W+");
public int Compare(string x, string y)
{
string[] partsX = _splitter.Split(x);
string[] partsY = _splitter.Split(y);
int shortest = Math.Min(partsX.Length, partsY.Length);
for (int index = 0; index < shortest; index++)
{
int intX, intY;
int result;
if (int.TryParse(partsX[index], out intX) && int.TryParse(partsY[index], out intY))
{
result = intX.CompareTo(intY);
}
else
{
result = string.Compare(partsX[index], partsY[index], StringComparison.Ordinal);
}
if (result != 0)
{
return result;
}
}
return 0;
}
}
它需要能够对看起来像这样的东西进行排序(按照它们应该输出的顺序):
- 2-101.11(A)(B)
- 9.1
- 9.2
- 9.2.1
- 9.02.2
- 9.02.3
- 9.3
- 9.3.1
- 9.3.2
- 10.
- 11.
- 11.1
- 11.2.a
- 11.2.b
- 11.2.c
- 11a.2.a
- 11b.2.b
- 21 美国联邦法规 110.10
- 046.981(e)(m)
其中每个非单词字符将字符串拆分为多个段,如果可能则尝试对它们进行数字比较,如果不能则作为字符串进行比较。段数可以任意"depth"。
不幸的是,CLR 存储过程不是一个选项。
with data as (
select c from (values
('9.1'), ('9.2'), ('9.2.1'), ('9.02.2'), ('9.02.3'), ('9.3'), ('9.3.1'), ('9.3.2'),
('10.'), ('11.'), ('11.1'), ('11.2.a'), ('11.2.b'), ('11.2.c'), ('11a.2.a'), ('11b.2.b')
) t(c)
)
select c, '[' +
right('00000' +
substring(c, 1, charindex('.', c + '.0.0', 1) - 1) +
case when substring(c + '.0.0', charindex('.', c + '.0.0', 1) - 1, 1) between '0' and '9' then ' ' else '' end,
6
) + '.' +
right('00000' +
substring(c, charindex('.', c + '.0.0', 1) + 1, charindex('.', c + '.0.0', charindex('.', c + '.0.0', 1) + 1) - charindex('.', c + '.0.0', 1) - 1) +
case when right('0' +
substring(c, charindex('.', c + '.0.0', 1) + 1, charindex('.', c + '.0.0', charindex('.', c + '.0.0', 1) + 1) - charindex('.', c + '.0.0', 1) - 1),
1
) between '0' and '9' then ' ' else '' end,
6
) + '.' +
right('00000' +
substring(c, charindex('.', c + '.0.0', charindex('.', c + '.0.0', 1) + 1) + 1, 10) +
case when right('0' +
substring(c, charindex('.', c + '.0.0', charindex('.', c + '.0.0', 1) + 1) + 1, 10),
1
) between '0' and '9' then ' ' else '' end,
6
) + ']'
from data
order by 2;
我把它放在一起是为了好玩。如您所见,SQL 中的字符串解析通常工作量很大,并且有很多重复的子表达式。
这里的想法是,您似乎可以将值转换为规范化格式, 可通过常规字母排序进行排序。前面的数字部分是zero-padded。每个 "field" 的末尾允许有一个字母字符,否则会附加一个 space。举个例子 11.2.a
变成 [00011 .00002 .00000a]
它相当灵活,您当然可以将此逻辑包装在一个标量函数中。我会让你决定这整件事是不是个好主意。
http://rextester.com/LYL6977(略有改善?)