自定义T-SQL排序依据(IComparer)

时间:2017-01-19 17:58:12

标签: sql-server tsql sorting icomparer

T-SQL是否有能力对字符串进行自定义比较(用于排序),相当于.NET的IComparer

能够给Order By一个用户定义函数的能力,它接受2个字符串并返回一个表示它们如何比较的值(大于,小于,等于)?

我目前有一个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 CFR 110.10
  • 046.981(E)(米)

每个非单词字符将字符串拆分为段,如果可能,尝试以数字方式对它们进行比较,如果不是,则尝试作为字符串。段数可以有任何“深度”。

不幸的是,CLR存储过程不是一种选择。

1 个答案:

答案 0 :(得分:2)

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中的字符串解析通常需要大量工作并且有很多重复的子表达式。

这里的想法是,您可以通过常规字母排序以 排序的规范化格式转换您的值。前面的数字部分是零填充的。在每个“字段”的末尾允许单个字母字符,否则附加空格。例如,11.2.a成为[00011 .00002 .00000a]

它相当灵活,你当然可以在标量函数中包含这个逻辑。我会让你决定这件事是不是一个好主意。

http://rextester.com/KZX77690

http://rextester.com/LYL6977(略有改进?)