Сортировать по первому совпадающему номеру, затем по второму совпадающему номеру и т. Д. В SQL - PullRequest
0 голосов
/ 11 июня 2018

сортировка по первому совпадающему номеру, а затем по второму совпадающему номеру в 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 голосов
/ 11 июня 2018

Предполагая, что у вас будет максимум 2 числовых блока и каждое число будет не более 10 цифр, я создал для вас пример UDF CLR (DbProject - проект базы данных SQL CLR):

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);
    }
}

Я добавил это в базу данных 'test' следующим образом:

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

Примечание: SQL Server 2012 (в 2017 году возникла серьезная проблема безопасности, с которой вам нужно справиться).

Наконец протестировано с этим T-SQL:

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);

Вывод:

col1
QARTa1b2
Btc0004_Shd_4
Btc0007_Shd_01
Btc0007_Shd_6
Btc0007_Shd_7
Btc0504
MR_Tst_Btc0565
Btc_BwwwQAZtc0605
Btc_Bwwwwe12541edddddtc0605
0 голосов
/ 11 июня 2018

Вы можете использовать таблицу подсчета / таблицы чисел, чтобы получить каждый символ и найти только числа, а затем объединить числа, чтобы сформировать строку (которая может быть преобразована в bigint).Затем вы можете заказать на основе этой строки.

См. Рабочую демонстрацию

; 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
0 голосов
/ 11 июня 2018

Ниже запрос выполняет следующее: он использует функцию patindex для извлечения индекса в строке шаблона:

  1. во-первых, он извлекает начало числа, ища цифру.

  2. Во-вторых, извлекается конец числа в поисках цифры, за которой следует не цифра.

Сделав это, мы имеем все дляизвлечь число из строки и отсортировать его после преобразования (приведения) в целое число.

Попробуйте этот запрос:

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)
0 голосов
/ 11 июня 2018

Вначале я не рекомендую следующий подход с точки зрения производительности, вы должны исправить основную причину ваших данных.

Для обработки динамических входов, я думаю, вы должны создать UDFфункция для извлечения чисел только следующим образом: -

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))

Результат: -

Btc0004_Shd_4
Btc0007_Shd_6
Btc0007_Shd_7
Btc0504
MR_Tst_Btc0565
Btc_BwwwQAZtc0605
QARTa1b2c3d4e5
Btc_Bwwwwe12541edddddtc0605

Демо.

0 голосов
/ 11 июня 2018

Я начну свой ответ с того, что лучшим долгосрочным решением для вас является исправление вашей модели данных.Если вам нужно использовать различные части записи в запросах, для сортировки и т. Д., Рассмотрите возможность их хранения в отдельных достоверных столбцах.

Как говорится, одним из обходных путей является использование базовых строковых операций.чтобы извлечь два компонента, которые вы хотите использовать для сортировки.Обратите внимание, что мы должны привести их к числам, потому что иначе они не будут правильно сортироваться как текст.

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

Демо

...