按首先匹配的数字排序,然后按第二个匹配的数字排序,依此类推

时间:2018-06-11 06:19:35

标签: sql sql-server sql-order-by

首先匹配数字,然后按SQL

中的第二个匹配数字排序

假设我有一个表条目如下。

Btc0504
Btc_0007_Shd_01
Btc_007_Shd_01
Bcd0007_Shd_7
ptc00044
Brg0007_Shd_6
Btc0075_Shd
Bcc43
MR_Tst_etc0565
wtc0004_Shd_4
vtc_Btc0605

所以它应该带来如下记录。

wtc0004_Shd_4
Bcc43
ptc00044
Btc_007_Shd_01
Btc_0007_Shd_01
Brg0007_Shd_6
Bcd0007_Shd_7
Btc0075_Shd
Btc0504
MR_Tst_etc0565
Btc_vtc0605

所以基本上它只按数字排序,单词只是数字的分隔符。

这里中间字符串可以是任何数字。

它们没有固定,这种模式也没有修复。

因此可以有更多的字符串和数字。即a1b2c3d4e5 ...,u7g2u9w2s8 ......

因此需要动态解决方案。

下面给出了示例表。

http://rextester.com/IDQ22263

5 个答案:

答案 0 :(得分:2)

假设您最多有2个数字块,每个数字最多为10位数,我为您创建了一个这样的CLR UDF示例(DbProject - SQL CLR数据库项目):

setInterval(function(){ 
$.getJSON('www.yourtradelog.com/api/first-hour-trades', function(data) {
        $.each(data, function(firstHourTrades, element) {
            $("#msg1").append($('<div>', {
                text: element.first
            }));
        });
    });
}, 10000);

我将此添加到'test'数据库中,如下所示:

using System.Collections.Generic;
using System.Data.SqlTypes;
using System.Text.RegularExpressions;

public partial class UserDefinedFunctions
{
    [Microsoft.SqlServer.Server.SqlFunction]
    public static SqlString CustomStringParser(SqlString str)
    {
        int depth = 2; // 2 numbers at most
        int width = 10; // 10 digits at most

        List<string> numbers = new List<string>();
        var matches = Regex.Matches((string)str, @"\d+");
        foreach (Match match in matches)
        {
            numbers.Add(int.Parse(match.Value).ToString().PadLeft(width, '0'));
        }
        return string.Join("", numbers.ToArray()).PadRight(depth*width);
    }
}

注意:SQL Server 2012(2017年有严格的安全问题需要处理)。

最后用这个T-SQL测试:

IF EXISTS ( SELECT  *
            FROM    sys.objects
            WHERE   object_id = OBJECT_ID(N'[dbo].[ufn_MyCustomParser]') AND
                    type IN ( N'FN', N'IF', N'TF', N'FS', N'FT' ) )
  DROP FUNCTION [dbo].[ufn_MyCustomParser]
GO
IF EXISTS ( SELECT  *
            FROM    sys.[assemblies] AS [a]
            WHERE   [a].[name] = 'DbProject' AND
                    [a].[is_user_defined] = 1 )
  DROP ASSEMBLY DbProject;
GO


CREATE ASSEMBLY DbProject
FROM 'C:\SQLCLR\DbProject\DbProject\bin\Debug\DbProject.dll'
WITH PERMISSION_SET = SAFE;
GO

CREATE FUNCTION ufn_MyCustomParser ( @csv NVARCHAR(4000))
RETURNS NVARCHAR(4000)
AS EXTERNAL NAME
  DbProject.[UserDefinedFunctions].CustomStringParser;
GO

输出:

declare @MyTable table (col1 varchar(50));
insert into @MyTable values
('Btc0504'),
('Btc0007_Shd_7'),
('Btc0007_Shd_01'),
('Btc0007_Shd_6'),
('MR_Tst_Btc0565'),
('Btc0004_Shd_4'),
('Btc_BwwwQAZtc0605'),
('Btc_Bwwwwe12541edddddtc0605'),
('QARTa1b2');
SELECT * FROM @MyTable
ORDER BY dbo.ufn_MyCustomParser(col1);

答案 1 :(得分:1)

下面的查询执行以下操作:它使用patindex函数来提取模式字符串中的索引:

  1. 首先,它提取数字的开头,搜索数字。

  2. 其次,它提取数字的结尾,搜索数字后跟非数字。

  3. 完成此操作后,我们将所有内容从字符串中提取出来并在将其转换(转换)为整数后按其排序。

    尝试此查询:

    declare @tbl table (col1 varchar(50));
    insert into @tbl values
    ('Btc0504'),
    ('Btc0007_Shd_7'),
    ('Btc0007_Shd_6'),
    ('MR_Tst_Btc0565'),
    ('Btc0004_Shd_4'),
    ('Btc_Btc0605');
    
    select col1 from (
        select col1,
               PATINDEX('%[0-9]%', col1) [startIndex],
               case PATINDEX('%[0-9][^0-9]%', col1) when 0 then LEN(col1) else     PATINDEX('%[0-9][^0-9]%', col1) end [endIndex]
        from @tbl
    ) [a]
    order by CAST(SUBSTRING(col1, startIndex, endIndex - startIndex + 1) as int)
    

    我提出了另一种解决方案,它非常紧凑,更通用:

    ;with cte as (
        select 1 [n], col1, STUFF(col1, PATINDEX('%[^0-9]%', col1), 1, '.') refined_col1 from @tbl
        union all
        select n+1, col1, STUFF(refined_col1, PATINDEX('%[^0-9.]%', refined_col1), 1, '.') from cte
        where n < 100 -- <--this number must be greater than the greatest amount of non-digits in a col1, this way, you are sure that you'll remove all unnecesary characters
    )
    
    select col1, refined_col1 from cte
    where PATINDEX('%[^0-9.]%', refined_col1) = 0
    order by CAST(replace(refined_col1, '.', '') as int)
    option (maxrecursion 0)
    

答案 2 :(得分:0)

我将开始回答说,最适合您的长期解决方案是修复您的数据模型。如果您需要在查询中使用条目的各个部分,进行排序等,那么请考虑将它们存储在单独的真正的列中。

话虽这么说,一种解决方法是使用基本字符串操作来提取您想要用于排序的两个组件。请注意,我们必须将它们转换为数字,否则它们将无法正确排序为文本。

SELECT *
FROM entries
ORDER BY
    CAST(SUBSTRING(entry, PATINDEX('%Btc[0-9]%', entry) + 3, 4) AS INT),
    CASE WHEN CHARINDEX('Shd_', entry) > 0
         THEN
         CAST(SUBSTRING(entry,
                        CHARINDEX('Shd_', entry) + 4,
                        LEN(entry) - CHARINDEX('Shd_', entry) -4) AS INT)
         ELSE 1 END;

enter image description here

Demo

答案 3 :(得分:0)

  

一开始我不推荐下一个方法   在性能方面,您应该确定数据的根本原因。

为了处理动态输入,我认为您应该创建UDF函数来仅提取下一个数字: -

catch (...)

然后将其用作下一个: -

CREATE FUNCTION dbo.udf_ExtratcNumbersOnly
(@string VARCHAR(256))
RETURNS int
AS
BEGIN
    WHILE PATINDEX('%[^0-9]%',@string) <> 0
    SET @string = STUFF(@string,PATINDEX('%[^0-9]%',@string),1,'')
    RETURN cast (@string as int)
END
GO

结果: -

declare @MyTable table (col1 varchar(50));
insert into @MyTable values
('Btc0504'),
('Btc0007_Shd_7'),
('Btc0007_Shd_6'),
('MR_Tst_Btc0565'),
('Btc0004_Shd_4'),
('Btc_BwwwQAZtc0605'),
('Btc_Bwwwwe12541edddddtc0605'),
('QARTa1b2c3d4e5');

select * from @MyTable 
order by (dbo.udf_ExtratcNumbersOnly(col1))

<强> Demo.

答案 4 :(得分:0)

您可以使用计数表/数字表来获取每个字符并仅查找数字,然后组合数字以形成字符串(可以将其转换为bigint)。然后你可以根据这个字符串订购。

See working demo

; with numbers as (
    select top 10000
        r= row_number() over( order by (select null))
    from sys.objects o1 
        cross join sys.objects o2
   )

, onlynumbers as
(
    select * from t 
    cross apply
    ( select part =substring(num,r,1),r
      from numbers where r<=len(num)
     )y
    where part  like '[0-9]' 
)

, finalorder as
(
    select num,cast(replace(stuff
    ((
        select ','+part
        from onlynumbers o2 
        where o2.num=o1.num
        order by o2.r
        for xml path('')
        ),1,1,''),',','') as bigint) b
  from onlynumbers o1
  group by num
   )
 select num from finalorder order by b asc