SQL 服务器 - 将字段数据解析为单独列的函数

SQL Server - Function to parse field data to separate columns

我看过 ~8 个与此类似的线程,但 none 满足了我的确切需求(此处所讨论的列中缺乏分隔符一致性),所以请没有完全阅读和理解我的要求,请不要标记为可能的重复项。

Azure SQL 服务器 2019:

我继承了一个名为 dbo.Table 的 table,它有数百万条记录,如下所示:

Id   Body
1    Status: Completed
     Successful actions count: 106
     Page load count: 105

2    Status: Failed
     Successful actions count: 91
     Page load count: 90

3    Status: Completed
     Successful actions count: 44
     Page load count: 32

我知道(并且对此感到恼火)这个结构不是最佳的。我需要修复它,如果有任何正确方向的建议,我将不胜感激:

我在 table 中添加了三列:StatusSuccessful_Actions_CountPage_Load_Count

将数据从 Body 列解析到三个新列的最佳方法是什么,既适用于现有数据,也适用于未来的插入?

我不是在找人为我编写存储过程。而是类似于 SQL 我可以使用的服务器功能可以促进这一点,最好是通过现有的存储过程和未来的触发器来实现这一点?

我正在查看 STRING_SPLIT,但它似乎适合逗号分隔的字符串(或其他一些分隔符)。以我有限的 SQL 技能,我面临的挑战是没有一致的分隔符可供使用 - space 显然行不通。 SQL 专家的任何想法都将不胜感激。

编辑 1: 我已经使用下面的@Zhorov 解决方案 display 结果以我们需要的方式显示,但这在触发器内部不起作用,因此我们实际上可以将结果写入记录进来了。

这是触发器创建语句 - 我仅使用其中一列对其进行测试:

CREATE TRIGGER [dbo].[BodyParseTrigger] on [dbo].[MailArchive]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
INSERT INTO dbo.MailArchive (
 Status
)
SELECT 
j.*
FROM INSERTED
CROSS APPLY OPENJSON (CONCAT('{"', REPLACE(REPLACE(Mail_Body, ': ', '":"'), CHAR(10), '","'), '"}')) 
WITH (
   Status varchar(100) '$.Status'
     ) j
SET NOCOUNT OFF
END

创建命令成功完成。但只要对 table 进行 INSERT 操作,我就会收到一条错误消息,完全阻止 INSERT。消息是:

JSON text is not properly formatted. Unexpected character "'" is found at position 10.

这是使用触发器要克服的第一个问题 - 一旦我弄明白了,我还需要了解如何将多个 OPENJSON 语句放入其中以涵盖所有 3 列。将它写成@Zhorov 的答案使其仅作为一个值插入一个项目,而触发器需要 3 个项目。

而不是 string_split() 和条件聚合并且结构是一致的,还有另一种选择......有点 XML

例子

Select A.ID 
      ,Status  = stuff(Pos1,1,charindex(':',Pos1),'')
      ,Action  = try_convert(int,stuff(Pos2,1,charindex(':',Pos2),''))
      ,PageCnt = try_convert(int,stuff(Pos3,1,charindex(':',Pos3),''))
 From YourTable A
 Cross Apply (
                Select Pos1 = ltrim(rtrim(xDim.value('/x[1]','varchar(max)')))
                      ,Pos2 = ltrim(rtrim(xDim.value('/x[2]','varchar(max)')))
                      ,Pos3 = ltrim(rtrim(xDim.value('/x[3]','varchar(max)')))
                From  ( values (cast('<x>' + replace((Select replace(Body,char(13)+char(10),'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml)))  A(xDim)
             ) B 

Returns

ID   Status     Action  PageCnt
1    Completed  106     105

原回答:

另一种可能的方法是 JSON 转换(如评论中提到的@PanagiotisKanavos)。您需要将 Body 数据转换为有效的 JSON 对象并使用 OPENJSON() 和显式模式解析此对象:

Table:

CREATE TABLE Data (
   Id int,
   Body varchar(max) 
)
INSERT INTO Data
   (Id, Body)
VALUES
   (1, 'Status: Completed' + CHAR(13) + CHAR(10) + 'Successful actions count: 106' + CHAR(13) + CHAR(10) + 'Page load count: 105'),
   (2, 'Status: Failed' + CHAR(13) + CHAR(10) + 'Successful actions count: 91' + CHAR(13) + CHAR(10) + 'Page load count: 90'),
   (3, 'Status: Completed' + CHAR(13) + CHAR(10) + 'Successful actions count: 40' + CHAR(13) + CHAR(10) + 'Page load count: 44')

声明:

SELECT j.*
FROM Data d
CROSS APPLY OPENJSON (CONCAT('{"', REPLACE(REPLACE(d.Body, ': ', '":"'), CHAR(13) + CHAR(10), '","'), '"}')) WITH (
   Status varchar(100) '$.Status',
   Successful_Actions_Count int '$."Successful actions count"',
   Page_Load_Count int '$."Page load count"'
) j

结果:

-------------------------------------------------------
Status      Successful_Actions_Count    Page_Load_Count
-------------------------------------------------------
Completed   106                         105
Failed      91                          90
Completed   40                          44         

如果您在 Body 列中有 NULL 个值,您可以尝试这样:

SELECT d.Id, j.*
FROM Data d
OUTER APPLY OPENJSON (
   CASE 
      WHEN d.Body IS NULL THEN '{}'
      ELSE CONCAT('{"', REPLACE(REPLACE(d.Body, ': ', '":"'), CHAR(13) + CHAR(10), '","'), '"}')
   END   
) WITH (
   Status varchar(100) '$.Status',
   Successful_Actions_Count int '$."Successful actions count"',
   Page_Load_Count int '$."Page load count"'
) j

如果 Body 列中的数据以新行结尾,您需要添加额外的 key:value 对 ("x": "0") 以使 JSON有效:

SELECT d.Id, j.*
FROM Data d
OUTER APPLY OPENJSON (
   CASE 
      WHEN d.Body IS NULL THEN '{}'
      ELSE CONCAT('{"', REPLACE(REPLACE(d.Body, ': ', '":"'), CHAR(13) + CHAR(10), '","'), 'x": "0"}')
   END   
) WITH (
   Status varchar(100) '$.Status',
   Successful_Actions_Count int '$."Successful actions count"',
   Page_Load_Count int '$."Page load count"'
) j

更新:

如果您想实现触发器(我认为您需要一种不同类型的触发器),下一行代码可能会有所帮助。

Table 和触发器:

CREATE TABLE MailArchive (
   Id int,
   Mail_Body varchar(max),
   Status varchar(100),
   Successful_actions_count int,
   Page_load_count int
);
CREATE TRIGGER BodyParseTrigger ON MailArchive INSTEAD OF INSERT
AS BEGIN
   INSERT INTO MailArchive (ID, Mail_Body, Status, Successful_Actions_Count, Page_Load_Count)
   SELECT i.ID, i.Mail_Body, j.Status, j.Successful_Actions_Count, j.Page_Load_Count
   FROM Inserted i
   OUTER APPLY OPENJSON (CONCAT('{"', REPLACE(REPLACE(i.Mail_Body, ': ', '":"'), CHAR(13) + CHAR(10), '","'), '"}')) 
   WITH (
      Status varchar(100) '$.Status',
      Successful_Actions_Count int '$."Successful actions count"',
      Page_Load_Count int '$."Page load count"'
   ) j
END

声明:

INSERT INTO MailArchive
   (Id, Mail_Body)
VALUES
   (1, 'Status: Completed' + CHAR(13) + CHAR(10) + 'Successful actions count: 106' + CHAR(13) + CHAR(10) + 'Page load count: 105')

SELECT *
FROM MailArchive

结果:

---------------------------------------------------------------------------------------
Id  Mail_Body                     Status      Successful_actions_count  Page_load_count
---------------------------------------------------------------------------------------
1   Status: Completed             Completed   106                         105
    Successful actions count: 106
    Page load count: 105    

如何删除额外的换行符:

如果您的 Mail_Body 列包含额外的换行符,您可以尝试更改转换以消除可能的 JSON 解析错误。现在,转换的结果将是一个 JSON 数组 (["Status: Completed", ...]),而不是一个 JSON 对象 ({"Status":"Completed", ...})。在这种情况下,您应该将 OPENJSON() 与默认架构一起使用(不带 WITH 子句)并使用 MAX() 以获得预期结果:

Table 和带有额外换行符的数据:

DECLARE @text1 varchar(max) = 
   'Status: Completed' + CHAR(13) + CHAR(10) + 
   'Successful actions count: 106' + CHAR(13) + CHAR(10) + 
   'Page load count: 105' + CHAR(13) + CHAR(10) +
   CHAR(13) + CHAR(10) +
   CHAR(13) + CHAR(10) +
   CHAR(13) + CHAR(10)
DECLARE @text2 varchar(max) = 
   'Agent did not meet defined success criteria on this run.' + CHAR(13) + CHAR(10) +
   CHAR(13) + CHAR(10) +
   'Status: Completed' + CHAR(13) + CHAR(10) + 
   'Successful actions count: 106' + CHAR(13) + CHAR(10) + 
   'Page load count: 105' + CHAR(13) + CHAR(10) +
   CHAR(13) + CHAR(10) +
   CHAR(13) + CHAR(10)

CREATE TABLE Data (
   Id int,
   Mail_Body varchar(max) 
)
INSERT INTO Data 
   (Id, Mail_Body)
VALUES 
   (1, @text1),
   (2, @text2)

声明:

SELECT d.Id, j.[Status], j.Successful_actions_count, j.Page_load_count
FROM Data d
OUTER APPLY (
   SELECT
      MAX(CASE WHEN CHARINDEX('Status:', [value]) = 1 THEN REPLACE([value], 'Status:', '') END) AS [Status],
      MAX(CASE WHEN CHARINDEX('Successful actions count:', [value]) = 1 THEN REPLACE([value], 'Successful actions count:', '') END) AS [Successful_actions_count],
      MAX(CASE WHEN CHARINDEX('Page load count:', [value]) = 1 THEN REPLACE([value], 'Page load count:', '') END) AS [Page_load_count]
   FROM OPENJSON(CONCAT('["', REPLACE(d.Mail_Body, CHAR(13) + CHAR(10), '","'), '"]'))
) j

结果:

-----------------------------------------------------------
Id   Status      Successful_actions_count   Page_load_count
-----------------------------------------------------------
1    Completed   106                        105
2    Completed   106                        105