Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sql-server/22.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
解析sql中的不规则分隔符_Sql_Sql Server - Fatal编程技术网

解析sql中的不规则分隔符

解析sql中的不规则分隔符,sql,sql-server,Sql,Sql Server,我有一个不规则的分隔符@uid='1585-1586--1-5417-2347-8865'由7个破折号组成;然而,当SQL遇到“-1”时,我需要它返回“-1” 换句话说,我需要这个结果: 而不是: @uid='1585-1586--1-5417-2347-8865'由7个破折号组成。但是,我需要SQL在看到“-1”时返回“-1”,如下面的屏幕截图所示 这可以通过下面的脚本实现,该脚本依赖于UDF declare @uid nvarchar(100) set @uid = '1585-1586

我有一个不规则的分隔符@uid='1585-1586--1-5417-2347-8865'由7个破折号组成;然而,当SQL遇到“-1”时,我需要它返回“-1”

换句话说,我需要这个结果:

而不是:

@uid='1585-1586--1-5417-2347-8865'由7个破折号组成。但是,我需要SQL在看到“-1”时返回“-1”,如下面的屏幕截图所示

这可以通过下面的脚本实现,该脚本依赖于UDF

declare @uid nvarchar(100)
set @uid = '1585-1586--1-5417-2347-8865'

select
(select  data from   [dbo].[fnSplit] (REPLACE ((REPLACE (@uid, '-', '|')),'||', '|-'), '|') where id = 1) as [Col_A],
(select  data from   [dbo].[fnSplit] (REPLACE ((REPLACE (@uid, '-', '|')),'||', '|-'), '|') where id = 2) as [Col_B],
(select  data from   [dbo].[fnSplit] (REPLACE ((REPLACE (@uid, '-', '|')),'||', '|-'), '|') where id = 3) as [Col_C],
(select  data from   [dbo].[fnSplit] (REPLACE ((REPLACE (@uid, '-', '|')),'||', '|-'), '|') where id = 4) as [Col_D],
(select  data from   [dbo].[fnSplit] (REPLACE ((REPLACE (@uid, '-', '|')),'||', '|-'), '|') where id = 5) as [Col_E],  
(select  data from   [dbo].[fnSplit] (REPLACE ((REPLACE (@uid, '-', '|')),'||', '|-'), '|') where id = 6) as [Col_F]


您的答案在这里对您有一点帮助,但是,没有定义
[dbo].[fnSplit]
对其他任何人都没有帮助

如果我们可以假设数据定义良好(有6列),那么我们可以“垃圾邮件”一些
CHARINDEX
函数来执行此操作。如答案所示,您需要替换所有分隔符,然后重新插入双分隔符的值
-

DECLARE @UID varchar(30) = '1585-1586--1-5417-2347-8865';

DECLARE @Delimiter char(1) = '-';

SELECT SUBSTRING(ca.FixedUID,1,C1.I-1) AS Col1,
       SUBSTRING(ca.FixedUID,C1.I+1, C2.I-C1.I-1) AS Col2,
       SUBSTRING(ca.FixedUID,C2.I+1, C3.I-C2.I-1) AS Col3,
       SUBSTRING(ca.FixedUID,C3.I+1, C4.I-C3.I-1) AS Col4,
       SUBSTRING(ca.FixedUID,C4.I+1, C5.I-C4.I-1) AS Col5,
       SUBSTRING(ca.FixedUID,C5.I+1, LEN(ca.FixedUID)-C5.I) AS Col6
FROM (VALUES(@UID))V([UID])
     CROSS APPLY (VALUES(REPLACE(REPLACE(V.UID,@Delimiter,'|'),'||','|' + @Delimiter)))ca(FixedUID)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID)))C1(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C1.I+1)))C2(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C2.I+1)))C3(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C3.I+1)))C4(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C4.I+1)))C5(I);
当然,如果您有一个“空”值,那么这将失败:

DECLARE @UID varchar(30) = '1585--71-5417-2347-8865';

DECLARE @Delimiter char(1) = '-';

SELECT SUBSTRING(ca.FixedUID,1,C1.I-1) AS Col1,
       SUBSTRING(ca.FixedUID,C1.I+1, C2.I-C1.I-1) AS Col2,
       SUBSTRING(ca.FixedUID,C2.I+1, C3.I-C2.I-1) AS Col3,
       SUBSTRING(ca.FixedUID,C3.I+1, C4.I-C3.I-1) AS Col4,
       SUBSTRING(ca.FixedUID,C4.I+1, C5.I-C4.I-1) AS Col5,
       SUBSTRING(ca.FixedUID,C5.I+1, LEN(ca.FixedUID)-C5.I) AS Col6
FROM (VALUES(@UID))V([UID])
     CROSS APPLY (VALUES(REPLACE(REPLACE(V.UID,@Delimiter,'|'),'||','|' + @Delimiter)))ca(FixedUID)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID)))C1(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C1.I+1)))C2(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C2.I+1)))C3(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C3.I+1)))C4(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C4.I+1)))C5(I);
传递给LEFT或SUBSTRING函数的长度参数无效

因此,不应使用数据中可能出现的分隔符(但是,我们可以希望,由于这些分隔符都是整数值,因此不存在
NULL
值,而是存在
0
'1585-0-71-5417-2347-8865'

如果使用类似的字符串拆分器,则可以很好地透视(并取消Pivot)数据,但上述示例中的值将位于错误的位置:

SELECT MAX(CASE DS.ItemNumber WHEN 1 THEN DS.Item END) AS Col1,
       MAX(CASE DS.ItemNumber WHEN 2 THEN DS.Item END) AS Col2,
       MAX(CASE DS.ItemNumber WHEN 3 THEN DS.Item END) AS Col3,
       MAX(CASE DS.ItemNumber WHEN 4 THEN DS.Item END) AS Col4,
       MAX(CASE DS.ItemNumber WHEN 5 THEN DS.Item END) AS Col5,
       MAX(CASE DS.ItemNumber WHEN 6 THEN DS.Item END) AS Col6
FROM (VALUES(@UID))V([UID])
     CROSS APPLY (VALUES(REPLACE(REPLACE(V.UID,@Delimiter,'|'),'||','|' + @Delimiter)))ca(FixedUID)
     CROSS APPLY dbo.DelimitedSplit8K_LEAD(ca.FixedUID,'|') DS;
这将导致以下结果:

Col1 Col2 Col3 Col4 Col5 Col6
---- ---- ---- ---- ---- ----
1585 -71  5417 2347 8865 NULL

基本上我所做的是从6到1的递归cte。每次迭代我都会删除最后一个分隔的数字,并将其移动到
col\u val
列。 我决定使用
reverse
,这样我就可以使用
patindex
找到连字符,然后找到数字。这样做可以得到负值。相反,字符串看起来像1--6851-5851-0,然后
patindex('%-[0-9]]')
返回2,因为我使用了字符串0-1585-1586--1的
right
函数,它将返回-1

我将
'0-'
添加到
delim_列的开头
,因为我想使用
patindex
,而不必考虑最后一个分隔的列

col\u val
重复上述所有内容,但不是使用
@uid
而是使用
delim\u列

以下是每个迭代的外观:

col_num     delim_column                col_val     loc
6           0-1585-1586--1-5417-2347    8865        4
5           0-1585-1586--1-5417         2347        4
4           0-1585-1586--1              5417        4
3           0-1585-1586                 -1          2
2           0-1585                      1586        4
1           0                           1585        4
然后我使用一个简单的choose函数来旋转这些列。这将使列名变得干净

DECLARE
    @uid VARCHAR(MAX) = '1585-1586--156-5417-2347-8865',
    @delim_count INT = 0

--First, count the number of delimiters. We do this by temporarily replacing '--' with a single '-'
--and then count the difference in lengths of the two strings (one with '-' and one without)

SELECT @delim_count = LEN(REPLACE(@uid, '--', '-')) - LEN(REPLACE(REPLACE(@uid, '--', '-'), '-','')) - IIF(@uid LIKE '-%', 1, 0)

--next a recursive cte that will lop off the last number each iteration and move the last value to col_val
;WITH fnsplit(col_num, delim_column, col_val, loc)
AS
(
    SELECT 
        @delim_count+1    --start with 6 and then go to 1. remove the +1 and replace col_num > 0 for a zero index
        ,'0-'+SUBSTRING(@uid,0, LEN(@uid) - LEN(RIGHT(@uid, PATINDEX('%-[0-9]%', reverse(@uid)) - 1)) )
        ,RIGHT(@uid, PATINDEX('%-[0-9]%', REVERSE(@uid)) - 1)
        ,PATINDEX('%-[0-9]%', REVERSE(@uid)) - 1
    UNION ALL
    SELECT 
         col_num - 1
        ,SUBSTRING(delim_column,0, LEN(delim_column) - LEN(RIGHT(delim_column, PATINDEX('%-[0-9]%', REVERSE(delim_column)) - 1)) )
        ,RIGHT(delim_column, PATINDEX('%-[0-9]%', REVERSE(delim_column)) - 1)
        ,PATINDEX('%-[0-9]%', REVERSE(delim_column)) - 1
    FROM 
        fnsplit
    WHERE 
        col_num > 1
)

--select * from fnsplit   -- uncomment here and comment all below to see the recursion 

SELECT 
    *
FROM
(
    SELECT 
         column_name
        ,col_val
    FROM
        fnsplit
    CROSS APPLY
        (SELECT CHOOSE(col_num, 'Col_A','Col_B','Col_C', 'Col_D', 'Col_E', 'Col_F')) tbl(column_name)
)PVT
    PIVOT
    (
        MAX(col_val)
            FOR column_name IN ([Col_A], [Col_B], [Col_C], [Col_D], [Col_E], [Col_F])
    ) PVT1

什么是
fnSplit
?@Larnu我相信它是来自某个包的UDF,基本上与SQL Server最新版本中的
STRING\u SPLIT
做相同的事情。我猜,@TimBiegeleisen,但没有定义,这个答案对未来的读者来说是无用的。我们可以使用R或Python解析脚本来代替自定义的
fnspilt
。它可能比这个答案需要更少的行数,并且R在所有当前SQL Server版本(即2016年和更高版本,我认为即使是2014年也不受主流支持)中都可以使用
,而
,@user716255,这是拆分字符串SQL Server的一种非常好的方式。使用迭代方法的速度要快得多。使用也可以出现在数据中的分隔符通常会导致问题。选择不同的分隔符可以解决此问题。首先不要将非规范化数据存储在数据库中。在将数据插入数据库之前解析并清理数据。在这种情况下,您不能仅仅拆分文本来获取数据,您需要一个正则表达式或一个简单的解析器来处理这两个注释,但我将假设这是一个数据转储,OP请求一个用于ETL目的的查询。;-)@LarryB最好由ETL工具来完成,而不是SQL。这也是首字母缩略词的意思,提取转换,然后加载。即使OP希望先加载,然后转换(ELT),使用其他方法解析也要容易得多languages@PanagiotisKanavos最近我想起了你的评论。同样,我完全同意,但我假设OP意识到,由于时间限制和资源有限,他们无法获得最佳方案。我从未在面试中看到过表格结构和数据示例。我倾向于看到的评论是“照常”的,因为我们应该教育并提供解决方案,这很酷。我从来没有在一家数据没有非规范化或其他愚蠢的公司工作过。清理它需要官僚主义,到那时我将把我的简历提交到别处。
DECLARE
    @uid VARCHAR(MAX) = '1585-1586--156-5417-2347-8865',
    @delim_count INT = 0

--First, count the number of delimiters. We do this by temporarily replacing '--' with a single '-'
--and then count the difference in lengths of the two strings (one with '-' and one without)

SELECT @delim_count = LEN(REPLACE(@uid, '--', '-')) - LEN(REPLACE(REPLACE(@uid, '--', '-'), '-','')) - IIF(@uid LIKE '-%', 1, 0)

--next a recursive cte that will lop off the last number each iteration and move the last value to col_val
;WITH fnsplit(col_num, delim_column, col_val, loc)
AS
(
    SELECT 
        @delim_count+1    --start with 6 and then go to 1. remove the +1 and replace col_num > 0 for a zero index
        ,'0-'+SUBSTRING(@uid,0, LEN(@uid) - LEN(RIGHT(@uid, PATINDEX('%-[0-9]%', reverse(@uid)) - 1)) )
        ,RIGHT(@uid, PATINDEX('%-[0-9]%', REVERSE(@uid)) - 1)
        ,PATINDEX('%-[0-9]%', REVERSE(@uid)) - 1
    UNION ALL
    SELECT 
         col_num - 1
        ,SUBSTRING(delim_column,0, LEN(delim_column) - LEN(RIGHT(delim_column, PATINDEX('%-[0-9]%', REVERSE(delim_column)) - 1)) )
        ,RIGHT(delim_column, PATINDEX('%-[0-9]%', REVERSE(delim_column)) - 1)
        ,PATINDEX('%-[0-9]%', REVERSE(delim_column)) - 1
    FROM 
        fnsplit
    WHERE 
        col_num > 1
)

--select * from fnsplit   -- uncomment here and comment all below to see the recursion 

SELECT 
    *
FROM
(
    SELECT 
         column_name
        ,col_val
    FROM
        fnsplit
    CROSS APPLY
        (SELECT CHOOSE(col_num, 'Col_A','Col_B','Col_C', 'Col_D', 'Col_E', 'Col_F')) tbl(column_name)
)PVT
    PIVOT
    (
        MAX(col_val)
            FOR column_name IN ([Col_A], [Col_B], [Col_C], [Col_D], [Col_E], [Col_F])
    ) PVT1