使用T-SQL检索具有许多方案的字符串的一部分

时间:2018-10-11 16:10:11

标签: sql sql-server tsql

我需要一个T-SQL查询来获得涵盖所有情况的输出(以下在预期中指定)。我尝试这样做,但未成功:

select 
    code,
    substring(code, patindex('%[0-9]%', code),
                    case 
                       when patindex('%[. ,/-]%', substring(code, patindex('%[0-9]%', code), len(code))) <> 0
                          then patindex('%[. ,/-]%', substring(code, patindex('%[0-9]%', code), len(code))) - 1 
                          else patindex('%[. ,/-]%', substring(code, patindex('%[0-9]%', code), len(code)))
                     end) 
from 
    table

这是预期的输出

input                           output 
------------------------------------------------------
AB 123456.123                   123456
AB 123456/123                   123456
AB 123456-123                   123456
AB B0-23456.123                 0-23456
AB 1234 5678 9545 3214.123      1234 5678 9545 3214
AB 123456 123                   123456 
AB.123456 123                   123456 
AB..123456 123                  123456 
AB..1C23456 123                 1C23456 

规则

  1. 从数字的第一个出现开始
  2. 在特殊字符(,/.-)后将字符串切成有效的大小写
    2.1如果字符串具有-在3个数字后有效,例如:AB B0-23456.123 ---- 0-23456
    2.2如果字符串在有效的空格后有3个以上的数字,例如:AB 1234 5678 9545 3214.123 ---- 1234 5678 9545 3214

2 个答案:

答案 0 :(得分:2)

我相信这将满足您的所有条件,但请务必对照此处未介绍的极端情况进行检查,以确保它符合您的期望。

DECLARE @table as TABLE (code VARCHAR(40))

INSERT INTO @table
(code)
Values
('AB 123456.123'),
('AB 123456/123'),
('AB 123456-123'),
('AB B0-23456.123'),
('AB 1234 5678 954 3214.123'),
('AB 1234 5678 9545/3214.123'),
('AB 123456 123'),
('AB.123456 123'),
('AB..123456 123'),
('AB..1C23456 123')


SELECT 
code as [input],
SUBSTRING(code,PATINDEX('%[0-9]%', code),
CASE WHEN PATINDEX('%[ -][0-9][0-9][0-9][0-9]%',SUBSTRING(code,PATINDEX('%[0-9]%', code),LEN(code))) <> 0
THEN 
    CASE WHEN PATINDEX('%[.,/]%',SUBSTRING(code,PATINDEX('%[0-9]%', code),LEN(code))) < ISNULL(NULLIF(PATINDEX('%[ -][0-9][0-9][0-9][^0-9]%',SUBSTRING(code,PATINDEX('%[0-9]%', code),LEN(code))),0),LEN(code))
    THEN PATINDEX('%[.,/]%',SUBSTRING(code,PATINDEX('%[0-9]%', code),LEN(code)))-1
    ELSE ISNULL(NULLIF(PATINDEX('%[ -][0-9][0-9][0-9][^0-9]%',SUBSTRING(code,PATINDEX('%[0-9]%', code),LEN(code))),0),LEN(code))
    END
ELSE PATINDEX('%[. ,/-]%', SUBSTRING(code,PATINDEX('%[0-9]%', code),LEN(code)))-1
END
) as [output]
FROM @table

编辑:修改内部案例陈述以纠正注释中提到的边缘案例,只是为了证明这可以在不使用CTE的情况下完成。 :)

答案 1 :(得分:1)

说实话:这是一场噩梦。 T-SQL绝对是错误的工具!

仅因为我没有提供足够的答案(由于您的问题不足够),我还是被要求以某种方式解决此问题。这是体育精神的问题...

DECLARE @mockup TABLE(ID INT IDENTITY, YourString VARCHAR(1000));
INSERT INTO @mockup VALUES
 ('AB 123456.123')                 
,('AB 123456/123')                 
,('AB 123456-123')                 
,('AB B0-23456.123')               
,('AB 1234 5678 9545 3214.123')    
,('AB 123456 123')                 
,('AB.123456 123')                 
,('AB..123456 123')                
,('AB..1C23456 123')
,('AB 1234 5678 954 3214-12345.123');

-噩梦

WITH CutForRules AS
(
    SELECT t.ID
          ,t.YourString
          ,ROW_NUMBER() OVER(PARTITION BY t.ID ORDER BY (SELECT (NULL))) FragmentIndex
          ,c AsXml
          ,d.value('text()[1]','varchar(100)') Fragment
          ,ISNUMERIC(d.value('text()[1]','varchar(100)')) FragmentIsNum
          ,LEN(d.value('text()[1]','varchar(100)')) FragmentLength
          ,d.value('@dlmt','varchar(10)') Delimiter
    FROM @mockup t
    CROSS APPLY(SELECT REVERSE(SUBSTRING(t.YourString,PATINDEX('%[0-9]%',t.YourString),1000))) A(a) 
    CROSS APPLY(SELECT REVERSE(SUBSTRING(a,PATINDEX('%[ /.-]%',a)+1,1000))) B(b)    
    CROSS APPLY(SELECT CAST('<x>' + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(b,'/','|'),' ','</x><x dlmt=" ">'),'.','</x><x dlmt=".">'),'-','</x><x dlmt="-">'),'|','</x><x dlmt="/">') + '</x>' AS XML)) C(c)
    CROSS APPLY c.nodes('/x') D(d)
)
SELECT t1.ID
      ,t1.YourString
      ,(
        SELECT CONCAT(t2.Delimiter,t2.Fragment)
        FROM CutForRules t2
        WHERE t1.ID=t2.ID
          AND (t2.FragmentIndex<(SELECT MIN(t3.FragmentIndex) 
                                 FROM CutForRules t3 
                                 WHERE t3.ID=t1.ID
                                   AND t3.FragmentIndex>t2.FragmentIndex
                                   AND t3.Delimiter=' '
                                   AND t3.FragmentLength<4
                                   AND t3.FragmentIsNum=1)
               OR NOT EXISTS(SELECT 1 FROM CutForRules t4 WHERE t4.ID=t1.ID AND t4.Delimiter=' ' AND t4.FragmentLength<4)
              )
        ORDER BY t2.FragmentIndex
        FOR XML PATH('')
       )
FROM CutForRules t1
GROUP BY t1.ID,t1.YourString
ORDER BY t1.ID;

您可以放置​​一个SELECT * FROM CutForRules来查看我为此使用的中间结果集。

但是我很确定,您会想出哦,可以,但是还有另外一种情况……

只需弄清楚一点:我在这一点上;;)

更新:一些解释

CTE CutForRules 会为我的测试数据返回此设置:

+----+---------------------------------+---------+---+---+------+
|    | YourString                      |Fragment | N | L | Delm |
+----+---------------------------------+---------+---+---+------+
| 1  | AB 123456.123                   | 123456  | 1 | 6 | NULL |
+----+---------------------------------+---------+---+---+------+
| 2  | AB 123456/123                   | 123456  | 1 | 6 | NULL |
+----+---------------------------------+---------+---+---+------+
| 3  | AB 123456-123                   | 123456  | 1 | 6 | NULL |
+----+---------------------------------+---------+---+---+------+
| 4  | AB B0-23456.123                 | 0       | 1 | 1 | NULL |
+----+---------------------------------+---------+---+---+------+
| 4  | AB B0-23456.123                 | 23456   | 1 | 5 | -    |
+----+---------------------------------+---------+---+---+------+
| 5  | AB 1234 5678 9545 3214.123      | 1234    | 1 | 4 | NULL |
+----+---------------------------------+---------+---+---+------+
| 5  | AB 1234 5678 9545 3214.123      | 5678    | 1 | 4 |      |
+----+---------------------------------+---------+---+---+------+
| 5  | AB 1234 5678 9545 3214.123      | 9545    | 1 | 4 |      |
+----+---------------------------------+---------+---+---+------+
| 5  | AB 1234 5678 9545 3214.123      | 3214    | 1 | 4 |      |
+----+---------------------------------+---------+---+---+------+
| 6  | AB 123456 123                   | 123456  | 1 | 6 | NULL |
+----+---------------------------------+---------+---+---+------+
| 7  | AB.123456 123                   | 123456  | 1 | 6 | NULL |
+----+---------------------------------+---------+---+---+------+
| 8  | AB..123456 123                  | 123456  | 1 | 6 | NULL |
+----+---------------------------------+---------+---+---+------+
| 9  | AB..1C23456 123                 | 1C23456 | 0 | 7 | NULL |
+----+---------------------------------+---------+---+---+------+
| 10 | AB 1234 5678 954 3214-12345.123 | 1234    | 1 | 4 | NULL |
+----+---------------------------------+---------+---+---+------+
| 10 | AB 1234 5678 954 3214-12345.123 | 5678    | 1 | 4 |      |
+----+---------------------------------+---------+---+---+------+
| 10 | AB 1234 5678 954 3214-12345.123 | 954     | 1 | 3 |      |
+----+---------------------------------+---------+---+---+------+
| 10 | AB 1234 5678 954 3214-12345.123 | 3214    | 1 | 4 |      |
+----+---------------------------------+---------+---+---+------+
| 10 | AB 1234 5678 954 3214-12345.123 | 12345   | 1 | 5 | -    |
+----+---------------------------------+---------+---+---+------+

提供的SELECT将按ID,YourString分组。这意味着:每个ID 1行。

返回的列是分组列加上一个很大的计算列。

这是与相关的子查询。它将获取当前ID的所有行并进行处理。然后返回其结果FOR XML PATH,这是将所有结果连接在一起的一个技巧。

棘手的部分在WHERE中:如果在长度为<4的空格之后至少有一个数字片段,则字符串将不包括该片段和所有后续片段。

如何获取当前元素之前的元素

这又是一个关联子查询,它以比当前元素大FragmentIndex的方式提取ID组中的元素。