使用 SSIS OR T-SQL 将一列带引号和不带引号的逗号分隔值拆分为多列

Posted

技术标签:

【中文标题】使用 SSIS OR T-SQL 将一列带引号和不带引号的逗号分隔值拆分为多列【英文标题】:Using SSIS OR T-SQL Split a column of quoted & unquoted comma separated values into multiple columns 【发布时间】:2022-01-09 08:33:16 【问题描述】:

我在名为 C0 的列中有逗号分隔的数据。 C0 中的数据如下所示:

C0
"Pacey LLC.",213830ZZ,11/1/2017,11/1/2017,"297,311.74","2,371.40",0.00,"1,325.18",0.00,42.22,"123,986.56"
Mike The Miker,9814140VCD,12/1/2018,12/1/2018,"3,917,751.99","419,743.54","36,642.66","344,090.43",0.00,10.00,"2,434,671.06"

我希望它以这样的方式结束:

F1 F1 F3 F4 F5 F6 F7 F8 F9 F10 F11
"Pacey LLC." 213830ZZ 11/1/2017 11/1/2017 297,311.74 2,371.40 0.00 1,325.18 0.00 42.22 123,986.56
Mike The Miker 9814140VCD 12/1/2018 12/1/2018 3,917,751.99 419,743.54 36,642.66 344,090.43 0.00 10.00 2,434,671.06

我已经尝试过嵌套替换,但是如果没有正则表达式(即 T/SQL)就找不到可靠搜索的模式?我也试过TOKEN approach in SSIS by this feller,但都没有结果。

嵌套替换方法卡在小于 1,000(如 0.00)的货币字段上,而 SSIS TOKEN 方法假定所有字段都是引号分隔的,在我的示例中它们不是。

【问题讨论】:

根据问题指南,请展示您的尝试并告诉我们您发现了什么(在本网站或其他地方)以及为什么它不能满足您的需求。 这种事情在TSQL中是极其困难的。您可能会发现使用 Regex 的 SQLCLR 函数是您的最佳选择。故事的寓意:不要在同一列中存储多条信息 您不能可靠地对 CSV 数据使用正则表达式。 CSV 是一种流式传输协议,并通过状态机正确实现。我同意@Charlieface 的观点,你应该在 SQL CLR 中为此编写一个解析器,但不要尝试对其进行正则表达式,使用 RFC 4180 Common Format and MIME Type for Comma-Separated Values (CSV) Files 作为参考。 【参考方案1】:

正如您已经被告知的那样,TSQL 是错误的工具。尽管如此,这是可以做到的(至少对于给定的集合)。如果这是一次性操作,您可以尝试一下。如果这是在现实生活场景中重复发生的任务,我会尝试以适当的格式获取数据。

但是,这适用于给定的行:

DECLARE @t1 TABLE(ID INT IDENTITY, YourString NVARCHAR(1000));
INSERT INTO @t1 VALUES(N'"Pacey LLC.",213830ZZ,11/1/2017,11/1/2017,"297,311.74","2,371.40",0.00,"1,325.18",0.00,42.22,"123,986.56"')
                     ,(N'Mike The Miker,9814140VCD,12/1/2018,12/1/2018,"3,917,751.99","419,743.54","36,642.66","344,090.43",0.00,10.00,"2,434,671.06"');

--您的数据包含特定文化格式的日期(真的!很糟糕) --更好地切换到ISO8601 --设置日期格式会有所帮助,但不推荐

SET DATEFORMAT dmy;

--第一个 cte 将使用 APPLY 和计算的 TOP() --这将允许一个一个地获取每个字符。

WITH singleChars AS
(                    
SELECT t.ID
      ,A.Pos
      ,SUBSTRING(t.YourString,A.POs,1) AS CharOnPos
FROM @t1 t
CROSS APPLY(SELECT TOP (LEN(t.YourString)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values) A(Pos) --master..spt_values can be any table with sufficient rows
)

--我们继续 递归 cte --它将遍历字符串并查找我们是否在引用区域内

,recCTE AS
(
    SELECT *
          ,CASE WHEN CharOnPos='"' THEN 1 ELSE 0 END AS QuoteIsOpen
          ,CAST(CharOnPos AS NVARCHAR(MAX)) AS GrowingString
    FROM singleChars WHERE Pos=1

    UNION ALL

    SELECT sc.ID,sc.Pos,sc.CharOnPos
          ,A.QuoteIsStillOpen
          ,CONCAT(GrowingString,CASE WHEN sc.CharOnPos=N',' AND A.QuoteIsStillOpen=0 THEN N'$%&' ELSE sc.CharOnPos END)
    FROM singleChars sc
    INNER JOIN recCTE r ON sc.ID = r.ID AND sc.Pos=r.Pos+1 
    CROSS APPLY(VALUES(CASE WHEN sc.CharOnPos='"' THEN CASE WHEN r.QuoteIsOpen=1 THEN 0 ELSE 1 END ELSE r.QuoteIsOpen END )) A(QuoteIsStillOpen)
)

--此 CTE 使用 TOP 1 WITH TIESORDER BY 执行一个技巧,分区 ROW_NUMBER() --结果将包括ID递归的最终字符串

,newlySeparated AS
(
    SELECT TOP 1 WITH TIES * FROM recCTE
    ORDER BY ROW_NUMBER() OVER(PARTITION BY ID ORDER BY Pos DESC)
)

--最后的SELECT 使用一个技巧来拆分字符串位置和类型安全

SELECT A.*
FROM newlySeparated ns
CROSS APPLY OPENJSON(CONCAT(N'[["',REPLACE(REPLACE(ns.GrowingString,'"',''),'$%&','","'),N'"]]'))
WITH(Company    NVARCHAR(100)        '$[0]'
    ,Code1      NVARCHAR(100)        '$[1]'
    ,Date1      DATE                 '$[2]'
    ,Date2      DATE                 '$[3]'
    ,Decimal1   NVARCHAR(100)        '$[4]' --Using a numbers type might work here, this depends on your machine
    ,Decimal2   NVARCHAR(100)        '$[5]'
    ,Decimal3   NVARCHAR(100)        '$[6]'
    ,Decimal4   NVARCHAR(100)        '$[7]'
    ,Decimal5   NVARCHAR(100)        '$[8]'
    ,Decimal6   NVARCHAR(100)        '$[9]'
    ,Decimal7   NVARCHAR(100)        '$[10]') A
OPTION(MAXRECURSION 0);

结果

+----------------+------------+------------+------------+--------------+------------+-----------+------------+------+-------+--------------+
| Pacey LLC.     | 213830ZZ   | 2017-01-11 | 2017-01-11 | 297,311.74   | 2,371.40   | 0.00      | 1,325.18   | 0.00 | 42.22 | 123,986.56   |
+----------------+------------+------------+------------+--------------+------------+-----------+------------+------+-------+--------------+
| Mike The Miker | 9814140VCD | 2018-01-12 | 2018-01-12 | 3,917,751.99 | 419,743.54 | 36,642.66 | 344,090.43 | 0.00 | 10.00 | 2,434,671.06 |
+----------------+------------+------------+------------+--------------+------------+-----------+------------+------+-------+--------------+

【讨论】:

使用文化特定日期有什么危险?谢谢。 @MikeG 文化特定数据(甚至是 语言特定 数据)取决于执行机器上的切割设置。具有相同输入的相同代码可能会在另一台服务器上中断。有一些文化,11/1/2017 是 11 月 1 日。像31/1/2017 这样的日期会产生错误(实际上更好),而这个日期只会以错误的结果运行。与1-Dec-2017 相同,它会在德国服务器上中断,因为它在那里是“Dez”。与数字相同,因为10.123 可以超过 10k 或只有 10 个小数。明白我的意思了吗? @MikeG 其实我不知道,如果我的结果是正确的......我把你的日期定为 1 月 11 日和 1 月 12 日。再想一想,我假设您实际上想要 11 月和 12 月的第一个???这正是为什么... :-) @MikeG 用dmymdySET DATEFORMAT 之后尝试上述操作。 这对我来说在这些变化之前就解决了,因为我正在处理更下游的日期。

以上是关于使用 SSIS OR T-SQL 将一列带引号和不带引号的逗号分隔值拆分为多列的主要内容,如果未能解决你的问题,请参考以下文章

将 MySQL 导出为 CSV,一些列带引号,一些不带引号

应用T-SQL语言,如何将一列的只分成两列显示,这样的语句怎么写呢?

从 SSIS 生成 excel 但在每一列中都获得报价?

在 SSIS 数据源 T-SQL 中使用 WHERE ValX> ValY 时,结果包括 ValX = ValY 的列

从 T-SQL 运行 SSIS 包

制表分隔文件到熊猫