在不使用游标的情况下影响多行时,在SQL Server触发器中拆分字段并插入行

zqry0prt  于 2023-02-15  发布在  SQL Server
关注(0)|答案(3)|浏览(151)

我有一个tableINSERT触发器,其中table的一个字段包含一个用逗号分隔的键值对列表,这些键值对用:分隔
我可以使用下面的语句轻松地将包含两个值的字段选择到临时表中:

-- SAMPLE DATA FOR PRESENTATION ONLY
    DECLARE @messageIds VARCHAR(2000) = '29708332:55197,29708329:54683,29708331:54589,29708330:54586,29708327:54543,29708328:54539,29708333:54538,29708334:62162,29708335:56798';
    
    SELECT
            SUBSTRING(value, 1,CHARINDEX(':', value) - 1)AS MessageId,
            SUBSTRING(value, CHARINDEX(':', value) + 1, LEN(value)-SUBSTRING(value,0,CHARINDEX(value,':'))) AS DeviceId
            INTO #temp_messages
            FROM STRING_SPLIT(@messageIds, ',')
            SELECT * FROM #temp_messages 
            DROP TABLE #temp_messages

结果将如下所示

29708332    55197
29708329    54683
29708331    54589
29708330    54586
29708327    54543
29708328    54539
29708333    54538
29708334    62162
29708335    56798

从这里我可以join的临时table到其他表和insert的一些结果到第三个table
在触发器内部,我可以使用简单的SELECT语句获取messageIds,如下所示

DECLARE @messageIds VARCHAR(2000) = (SELECT ProcessMessageIds FROM INSERTED)

现在,我创建临时table(如上所述)并处理我的

INSERT INto <new_table> SELECT col1, col1, .. FROM #temp_messages
JOIN <another_table> ON ...

不幸的是,这只适用于单行插入,只要有多行插入,我的SELECT ProcessMessageIds FROM INSERTED就会失败,因为INSERTED表中有多行。
我可以处理CURSOR中的行,但据我所知,CURSORS是触发器中的禁忌,我应该尽可能避免使用它们。
因此,我的问题是,是否有另一种方法可以在trigger中不使用CURSOR来完成此操作?

lnvxswe2

lnvxswe21#

在讨论解决方案的细节之前,让我指出,如果您规范化了数据库,就不会有这样的问题,正如@Larnu在您的问题的评论部分所指出的那样。
你的

DECLARE @messageIds VARCHAR(2000) = (SELECT ProcessMessageIds FROM INSERTED)

语句假定将有单个值赋给@messageIDs,但正如您所指出的,这不一定正确。

    • 溶液1**:与INSERTED联接而不是将其加载到变量中
INSERT INTO t1
SELECT ...
FROM t2
JOIN T3
ON ...
JOIN INSERTED
ON ...

然后您可以毫无问题地到达INSERTED.ProcessMessageIds。这将不再假设使用了单个值。

    • 溶液2**:游标

正如您已经指出的,您可以使用CURSOR,但是在触发器内使用游标并不是一个很好的主意,请参见https://social.msdn.microsoft.com/Forums/en-US/87fd1205-4e27-413d-b040-047078b07756/cursor-usages-in-trigger-in-sql-server?forum=aspsqlserver

    • 溶液3**:一次插入一行

虽然这不需要修改trigger,但需要修改插入方式,并且会增加必要的数据库请求数量,所以我建议您不要选择这种方法。

    • 溶液4**:正规化

参见https://www.simplilearn.com/tutorials/sql-tutorial/what-is-normalization-in-sql
如果您有一个合适的表而不是一个复合值表,就不会有这样的问题,并且通常处理消息id会容易得多。

    • 摘要**

最好规范化您的表并执行以后需要的重构。现在这是一项巨大的工作,但您将享受其成果。如果这不是一个选项,您可以"当作它已被规范化"并选择解决方案1。

j5fpnvbx

j5fpnvbx2#

你甚至可以比所有讨厌的CHARINDEX调用更整洁一点,恕我直言:

DECLARE @messageIds VARCHAR(2000) = '29708332:55197,29708329:54683,29708331:54589,29708330:54586,29708327:54543,29708328:54539,29708333:54538,29708334:62162,29708335:56798';
    
    SELECT MessageId = MAX(CASE WHEN j.[key] = 0 THEN j.value END),
           DeviceId  = MAX(CASE WHEN j.[key] = 1 THEN j.value END)           
        FROM
          STRING_SPLIT(@messageIds, ',') AS x
        CROSS APPLY 
          OPENJSON(CONCAT('["', REPLACE(x.value,':', '","'), '"]"')) AS j
        GROUP BY x.value;

把它转换成你的触发器

INSERT dbo.Destination(MessageId, DeviceId)
SELECT MessageId, DeviceId
FROM
(
  SELECT MessageId = MAX(CASE WHEN j.[key] = 0 THEN j.value END),
         DeviceId  = MAX(CASE WHEN j.[key] = 1 THEN j.value END)           
  FROM inserted AS i
  CROSS APPLY STRING_SPLIT(i.ProcessMessageIds, ',') AS x
  CROSS APPLY 
  OPENJSON(CONCAT('["', REPLACE(x.value,':', '","'), '"]"')) AS j
  GROUP BY x.value
) AS src;

没有局部变量,没有inherent assumptions about single-row inserts,也没有#temp表。

o2g1uqev

o2g1uqev3#

正如答案中所指出的,使用INSERTED表进行连接解决了我的问题。

SELECT INTAB.Id,
    SUBSTRING(value, 1,CHARINDEX(':', value) - 1)AS MessageId,
    SUBSTRING(value, CHARINDEX(':', value) + 1, LEN(value)-SUBSTRING(value,0,CHARINDEX(value,':'))) AS DeviceId
    FROM INSERTED AS INTAB
    CROSS APPLY  STRING_SPLIT(ProcessMessageids,',')

我以前从来没有用过“交叉应用”,谢谢。

相关问题