我们有一个大数据库在我们DB侧分页。这是快速回返的一页的50行数以百万计的记录在一个很小一部分的第二种。

用户可以定义自己的排序,基本上有选择什么排序列。列是动态的,有些数值,有些日期和某些文本。

虽然大多数的排序作为预期的文本排序在一个愚蠢的方式。好吧,我说了愚蠢的,是有意义的计算机,但是阻碍用户。

例如,排序由一串的记录id给的东西,如:

rec1
rec10
rec14
rec2
rec20
rec3
rec4

...等。

我想这取帐户的数量,因此:

rec1
rec2
rec3
rec4
rec10
rec14
rec20

我不能控制的输入(否则我的格式领导000秒)我可以不依赖一个单一的格式有些东西像"{阿尔法码}-{部代码}-{id rec}".

我知道有几种方法来这样做,但不能拉下的所有记录,对它们进行排序,因为这将是缓慢的。

没有人知道一种方式快速应用的一个自然的排序在Sql服务器?


我们使用:

ROW_NUMBER() over (order by {field name} asc)

然后我们呼通过。

我们可以添加的触发器,虽然我们不会的。他们所有的输入参数化等等,但我不能改变该格式-如果他们放在"rec2"和"rec10"他们希望他们能够返回就是这样,和在自然顺序。


我们拥有有效的用户输入下面的不同格式的针对不同客户。

一个可能去rec1rec2,rec3,...rec100,rec101

而另一个可能去:grp1rec1,grp1rec2,...grp20rec300,grp20rec301

当我说我们不能控制输入我的意思是,我们不能强制用户更改这些标准-他们有一个值像grp1rec1我不能格式化,它作为grp01rec001,因为这将可改变的东西用于查询和链接到外部系统。

这些格式各不相同,但往往是混合的字母和数字。

排序,这些在C#是容易的-只要打破它 { "grp", 20, "rec", 301 } 然后比较序列的数值。

然而可能还有数以百万计的记录和数据分页的,我需要排序,以完成SQL服务器。

SQL server种各样的价值,而不是比较C#我可分割的价值进行比较,但在SQL我需要一些逻辑,(很快)得到一个单一的价值,始终排序。

@莫比斯-你的答案可能的工作,但它不会感觉像一个丑陋的妥协,以增加排序的关键所有这些文本的价值观。

有帮助吗?

解决方案

大多数SQL-基础的解决方案,我已经看到休息的时候得到的数据很复杂(例如多一个或两个数字)。最初我尝试实施NaturalSort功能在T-SQL,满足我的要求(除其他事项外,处理一个任意数量的数字串内的),但是表现是 方式 太缓慢。

最后,我写了一个标CLR功能在C#允许自然进行排序,甚至与未优化码的性能要求它从SQL服务器是令人眼花缭乱的速度快。它具有以下特点:

  • 将排序第一个1 000名字或者使正确地(很容易地修改在代码或制成的一个参数)
  • 适当地各种各样的小数,因此123.333来之前123.45
  • 由于上述,将可能不理想的IP地址是正确;如果你想的不同行为,修改码
  • 支持排序的一串与一个任意数量的数字在这
  • 将正确排序数达到25数字长(很容易地修改在代码或制成的一个参数)

代码是:

using System;
using System.Data.SqlTypes;
using System.Text;
using Microsoft.SqlServer.Server;

public class UDF
{
    [SqlFunction(DataAccess = DataAccessKind.None, IsDeterministic=true)]
    public static SqlString Naturalize(string val)
    {
        if (String.IsNullOrEmpty(val))
            return val;

        while(val.Contains("  "))
            val = val.Replace("  ", " ");

        const int maxLength = 1000;
        const int padLength = 25;

        bool inNumber = false;
        bool isDecimal = false;
        int numStart = 0;
        int numLength = 0;
        int length = val.Length < maxLength ? val.Length : maxLength;

        //TODO: optimize this so that we exit for loop once sb.ToString() >= maxLength
        var sb = new StringBuilder();
        for (var i = 0; i < length; i++)
        {
            int charCode = (int)val[i];
            if (charCode >= 48 && charCode <= 57)
            {
                if (!inNumber)
                {
                    numStart = i;
                    numLength = 1;
                    inNumber = true;
                    continue;
                }
                numLength++;
                continue;
            }
            if (inNumber)
            {
                sb.Append(PadNumber(val.Substring(numStart, numLength), isDecimal, padLength));
                inNumber = false;
            }
            isDecimal = (charCode == 46);
            sb.Append(val[i]);
        }
        if (inNumber)
            sb.Append(PadNumber(val.Substring(numStart, numLength), isDecimal, padLength));

        var ret = sb.ToString();
        if (ret.Length > maxLength)
            return ret.Substring(0, maxLength);

        return ret;
    }

    static string PadNumber(string num, bool isDecimal, int padLength)
    {
        return isDecimal ? num.PadRight(padLength, '0') : num.PadLeft(padLength, '0');
    }
}

注册,这样,你可以叫它从SQL服务器运行以下命令在查询分析仪:

CREATE ASSEMBLY SqlServerClr FROM 'SqlServerClr.dll' --put the full path to DLL here
go
CREATE FUNCTION Naturalize(@val as nvarchar(max)) RETURNS nvarchar(1000) 
EXTERNAL NAME SqlServerClr.UDF.Naturalize
go

然后,你可以使用它,像这样:

select *
from MyTable
order by dbo.Naturalize(MyTextField)

注意到:如果你得到错误SQL Server沿线的 执行的用户代码。净框架是残疾人。启用"clr启用了"结构的选项。, ,按照指示 在这里, 启用。确保你考虑的安全影响之前这样做。如果你是不是该数据库管理员,确保你讨论这与你的管理作出任何修改之前向该服务器的结构。

注2:这种代码不正确支持国际化(例如,假定小数点的标记".", 不是最优化的速度,等等。建议在改进它欢迎!

编辑: 改名的功能 归化入籍 而不是的 NaturalSort, ,因为它没有做任何实际的排序。

其他提示

order by LEN(value), value

不完美的,但是运作良好,在很多情况下。

我知道这是一个古老的问题,但我只是碰到它,因为它没有得到接受的答案。

我总是用的方法类似于这样的:

SELECT [Column] FROM [Table]
ORDER BY RIGHT(REPLICATE('0', 1000) + LTRIM(RTRIM(CAST([Column] AS VARCHAR(MAX)))), 1000)

唯一的共同时,这一问题是如果你的专栏不会投VARCHAR(最大值),或者如果LEN([列])>1000(但是你可以改变那1000到别的东西如果你想要的),但是可以使用这个粗略的想法为什么你需要的。

这也是很糟糕的性能比通常由[列],但它不会给你这结果是要求为在任择议定书》。

编辑:只是为了进一步澄清,这上面不会工作如果你有小数值,如有 1, 1.151.5, ,(它们将作为排序 {1, 1.5, 1.15}),因为这是不是什么要求在运,但是,可以很容易地通过:

SELECT [Column] FROM [Table]
ORDER BY REPLACE(RIGHT(REPLICATE('0', 1000) + LTRIM(RTRIM(CAST([Column] AS VARCHAR(MAX)))) + REPLICATE('0', 100 - CHARINDEX('.', REVERSE(LTRIM(RTRIM(CAST([Column] AS VARCHAR(MAX))))), 1)), 1000), '.', '0')

结果是: {1, 1.15, 1.5}

并且仍然完全在SQL。这将不排序的IP地址,因为你现在进入非常具体的数字组合,而不是简单的文字+数。

RedFilter的答案 是伟大的,对于合理的大小的数据集在那里的索引不是至关重要的,但是如果你想要一个指数,几个调整是必需的。

首先,标记的功能不做任何数据访问,并正在确定性和精确:

[SqlFunction(DataAccess = DataAccessKind.None,
                          SystemDataAccess = SystemDataAccessKind.None,
                          IsDeterministic = true, IsPrecise = true)]

接下来,MSSQL有900字节的限制索引的关键尺寸,因此,如果在入籍的价值是唯一的值指数,它必须在最450个字符长。如果指标包括多个列,返回值必须甚至更小。两个变化:

CREATE FUNCTION Naturalize(@str AS nvarchar(max)) RETURNS nvarchar(450)
    EXTERNAL NAME ClrExtensions.Util.Naturalize

和在C#码:

const int maxLength = 450;

最后,你需要添加一个计算列表,并且它必须保留(因为MSSQL无法证明 Naturalize 是的确定性和精确),这意味着归化价值是实际存在表中但仍然保持自动:

ALTER TABLE YourTable ADD nameNaturalized AS dbo.Naturalize(name) PERSISTED

现在,您可以创建该指数!

CREATE INDEX idx_YourTable_n ON YourTable (nameNaturalized)

我还做了几个变化RedFilter的代码:使用的字符用于清晰度,合并重复空间清除入主循环,一旦退出的结果是超过的限制,设定最长不substring等。这里的结果:

using System.Data.SqlTypes;
using System.Text;
using Microsoft.SqlServer.Server;

public static class Util
{
    [SqlFunction(DataAccess = DataAccessKind.None, SystemDataAccess = SystemDataAccessKind.None, IsDeterministic = true, IsPrecise = true)]
    public static SqlString Naturalize(string str)
    {
        if (string.IsNullOrEmpty(str))
            return str;

        const int maxLength = 450;
        const int padLength = 15;

        bool isDecimal = false;
        bool wasSpace = false;
        int numStart = 0;
        int numLength = 0;

        var sb = new StringBuilder();
        for (var i = 0; i < str.Length; i++)
        {
            char c = str[i];
            if (c >= '0' && c <= '9')
            {
                if (numLength == 0)
                    numStart = i;
                numLength++;
            }
            else
            {
                if (numLength > 0)
                {
                    sb.Append(pad(str.Substring(numStart, numLength), isDecimal, padLength));
                    numLength = 0;
                }
                if (c != ' ' || !wasSpace)
                    sb.Append(c);
                isDecimal = c == '.';
                if (sb.Length > maxLength)
                    break;
            }
            wasSpace = c == ' ';
        }
        if (numLength > 0)
            sb.Append(pad(str.Substring(numStart, numLength), isDecimal, padLength));

        if (sb.Length > maxLength)
            sb.Length = maxLength;
        return sb.ToString();
    }

    private static string pad(string num, bool isDecimal, int padLength)
    {
        return isDecimal ? num.PadRight(padLength, '0') : num.PadLeft(padLength, '0');
    }
}

我知道这是一位老在这一点上,但是我在寻找一个更好的解决方案,我碰到这个问题。目前,我正在使用一个功能,以便通过。它的工作的现我的目的排序记录的命名混合的字母数字('项目1','项目10','项目2'等)

CREATE FUNCTION [dbo].[fnMixSort]
(
    @ColValue NVARCHAR(255)
)
RETURNS NVARCHAR(1000)
AS

BEGIN
    DECLARE @p1 NVARCHAR(255),
        @p2 NVARCHAR(255),
        @p3 NVARCHAR(255),
        @p4 NVARCHAR(255),
        @Index TINYINT

    IF @ColValue LIKE '[a-z]%'
        SELECT  @Index = PATINDEX('%[0-9]%', @ColValue),
            @p1 = LEFT(CASE WHEN @Index = 0 THEN @ColValue ELSE LEFT(@ColValue, @Index - 1) END + REPLICATE(' ', 255), 255),
            @ColValue = CASE WHEN @Index = 0 THEN '' ELSE SUBSTRING(@ColValue, @Index, 255) END
    ELSE
        SELECT  @p1 = REPLICATE(' ', 255)

    SELECT  @Index = PATINDEX('%[^0-9]%', @ColValue)

    IF @Index = 0
        SELECT  @p2 = RIGHT(REPLICATE(' ', 255) + @ColValue, 255),
            @ColValue = ''
    ELSE
        SELECT  @p2 = RIGHT(REPLICATE(' ', 255) + LEFT(@ColValue, @Index - 1), 255),
            @ColValue = SUBSTRING(@ColValue, @Index, 255)

    SELECT  @Index = PATINDEX('%[0-9,a-z]%', @ColValue)

    IF @Index = 0
        SELECT  @p3 = REPLICATE(' ', 255)
    ELSE
        SELECT  @p3 = LEFT(REPLICATE(' ', 255) + LEFT(@ColValue, @Index - 1), 255),
            @ColValue = SUBSTRING(@ColValue, @Index, 255)

    IF PATINDEX('%[^0-9]%', @ColValue) = 0
        SELECT  @p4 = RIGHT(REPLICATE(' ', 255) + @ColValue, 255)
    ELSE
        SELECT  @p4 = LEFT(@ColValue + REPLICATE(' ', 255), 255)

    RETURN  @p1 + @p2 + @p3 + @p4

END

然后打电话

select item_name from my_table order by fnMixSort(item_name)

它很容易三倍的处理时间一个简单的数据阅读,所以它不可能是完美的解决方案。

这里有一个解决方案编写SQL2000年。它可以改进的新的SQL版本。

/**
 * Returns a string formatted for natural sorting. This function is very useful when having to sort alpha-numeric strings.
 *
 * @author Alexandre Potvin Latreille (plalx)
 * @param {nvarchar(4000)} string The formatted string.
 * @param {int} numberLength The length each number should have (including padding). This should be the length of the longest number. Defaults to 10.
 * @param {char(50)} sameOrderChars A list of characters that should have the same order. Ex: '.-/'. Defaults to empty string.
 *
 * @return {nvarchar(4000)} A string for natural sorting.
 * Example of use: 
 * 
 *      SELECT Name FROM TableA ORDER BY Name
 *  TableA (unordered)              TableA (ordered)
 *  ------------                    ------------
 *  ID  Name                        ID  Name
 *  1.  A1.                         1.  A1-1.       
 *  2.  A1-1.                       2.  A1.
 *  3.  R1             -->          3.  R1
 *  4.  R11                         4.  R11
 *  5.  R2                          5.  R2
 *
 *  
 *  As we can see, humans would expect A1., A1-1., R1, R2, R11 but that's not how SQL is sorting it.
 *  We can use this function to fix this.
 *
 *      SELECT Name FROM TableA ORDER BY dbo.udf_NaturalSortFormat(Name, default, '.-')
 *  TableA (unordered)              TableA (ordered)
 *  ------------                    ------------
 *  ID  Name                        ID  Name
 *  1.  A1.                         1.  A1.     
 *  2.  A1-1.                       2.  A1-1.
 *  3.  R1              -->         3.  R1
 *  4.  R11                         4.  R2
 *  5.  R2                          5.  R11
 */
ALTER FUNCTION [dbo].[udf_NaturalSortFormat](
    @string nvarchar(4000),
    @numberLength int = 10,
    @sameOrderChars char(50) = ''
)
RETURNS varchar(4000)
AS
BEGIN
    DECLARE @sortString varchar(4000),
        @numStartIndex int,
        @numEndIndex int,
        @padLength int,
        @totalPadLength int,
        @i int,
        @sameOrderCharsLen int;

    SELECT 
        @totalPadLength = 0,
        @string = RTRIM(LTRIM(@string)),
        @sortString = @string,
        @numStartIndex = PATINDEX('%[0-9]%', @string),
        @numEndIndex = 0,
        @i = 1,
        @sameOrderCharsLen = LEN(@sameOrderChars);

    -- Replace all char that have the same order by a space.
    WHILE (@i <= @sameOrderCharsLen)
    BEGIN
        SET @sortString = REPLACE(@sortString, SUBSTRING(@sameOrderChars, @i, 1), ' ');
        SET @i = @i + 1;
    END

    -- Pad numbers with zeros.
    WHILE (@numStartIndex <> 0)
    BEGIN
        SET @numStartIndex = @numStartIndex + @numEndIndex;
        SET @numEndIndex = @numStartIndex;

        WHILE(PATINDEX('[0-9]', SUBSTRING(@string, @numEndIndex, 1)) = 1)
        BEGIN
            SET @numEndIndex = @numEndIndex + 1;
        END

        SET @numEndIndex = @numEndIndex - 1;

        SET @padLength = @numberLength - (@numEndIndex + 1 - @numStartIndex);

        IF @padLength < 0
        BEGIN
            SET @padLength = 0;
        END

        SET @sortString = STUFF(
            @sortString,
            @numStartIndex + @totalPadLength,
            0,
            REPLICATE('0', @padLength)
        );

        SET @totalPadLength = @totalPadLength + @padLength;
        SET @numStartIndex = PATINDEX('%[0-9]%', RIGHT(@string, LEN(@string) - @numEndIndex));
    END

    RETURN @sortString;
END

这里是一个其他的解决方案,我喜欢:http://www.dreamchain.com/sql-and-alpha-numeric-sort-order/

这不是Microsoft SQL,但由于我结束了在这里时,我在寻找一个解决方案Postgres,我认为添加这里会帮助他人。

varchar 数据:

BR1
BR2
External Location
IR1
IR2
IR3
IR4
IR5
IR6
IR7
IR8
IR9
IR10
IR11
IR12
IR13
IR14
IR16
IR17
IR15
VCR

这个工作最好的对我说:

ORDER BY substring(fieldName, 1, 1), LEN(fieldName)

如果你有麻烦载的数据数据库进行排序在C#然后我敢肯定你会失望的任何方法在这样做编程方式的数据库。当服务器是要排序,它得到了计算"认为"订单,只是因为你会有...每次。

我建议您添加一个额外的列储存的预处理排序串,使用某些C#方法,在数据是第一次插入。你可能会试图转换成数字为固定宽度范围,例如使"xyz1"将变成"xyz00000001".然后你可以用正常SQL服务器分类。

在风险廷我自己的喇叭,我写了一个演示文章的执行问题提出的CodingHorror的文章。感觉到自由 偷我的代码.

我刚刚读过一篇文章的某个地方有关这一主题。关键的一点是:你只需要整数值进行排序的数据,而"rec"串属于用户界面。你能拆分的信息中的两个领域,说阿尔法和数、排序通过alpha和数(单独)和然后显示一串由α+num.你可以使用一个计算列撰写的字符串或一个图。希望它能帮助

你可以使用以下代码解决的问题:

Select *, 
    substring(Cote,1,len(Cote) - Len(RIGHT(Cote, LEN(Cote) - PATINDEX('%[0-9]%', Cote)+1)))alpha,
    CAST(RIGHT(Cote, LEN(Cote) - PATINDEX('%[0-9]%', Cote)+1) AS INT)intv 
FROM Documents 
   left outer join Sites ON Sites.IDSite = Documents.IDSite 
Order BY alpha, intv

问候, rabihkahaleh@hotmail.com

只要你按

ORDER BY 
cast (substring(name,(PATINDEX('%[0-9]%',name)),len(name))as int)

 ##

我还是不理解(也许因为我可怜的英文)。

你可以尝试:

ROW_NUMBER() OVER (ORDER BY dbo.human_sort(field_name) ASC)

但是,它不会的工作对于数以百万计的记录。

这就是为什么我建议使用其触发 填充 单独的人的价值.

此外:

  • 建T-SQL功能是真的 缓慢和微软建议使用 .网的功能来代替。
  • 人的价值 是恒定的,因此没有计算每次 当查询。
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top