触发在另一个 table 中创建或更新记录

Trigger to create or update records in another table

编辑: 这个问题基本解决了。我仍在尝试做的事情可能需要不同的方法。

我正在向遗留数据库 table 添加触发器,以便为相关 table 自动创建新记录。这不是家庭作业。这是我在 SQL Server 2017 上的数据库 Fiddle。https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=ed2ed585606da9d69cb63402ea5c0807

例如,这是 parent table 的样子。

新记录将从 kids 列创建,即 comma-separated(不幸的是,它是旧数据库,无法更改)。我正在使用 SQL Server 2016 中的 string_split 函数将其分隔成行。例如。当我在上面插入这 5 条记录时,由于我的触发器,这是我在 kids table 中得到的结果,正如预期的那样。

编辑: 我修复了前两个问题。此外,代码现在包含在下面。

CREATE OR ALTER TRIGGER TR_Update_Kids ON [dbo].[parents] AFTER INSERT, UPDATE AS
BEGIN
   SET NOCOUNT ON;
   IF TRIGGER_NESTLEVEL(OBJECT_ID('TR_Update_Kids','TR')) > 1 RETURN
   
   MERGE INTO [dbo].[kids] AS KidsTable USING
      (SELECT CONVERT(VARCHAR(32), HashBytes('SHA2_256', CONCAT(INSERTED.id,'-',TRIM([value]))), 2) AS kidCode,
               INSERTED.surname AS surname,
               TRIM([value]) AS name,
               INSERTED.address AS address,
               INSERTED.sports AS sports,
               INSERTED.team AS team,
               INSERTED.id AS parentID
        FROM INSERTED
        CROSS APPLY string_split(INSERTED.kids, ',')
      ) AS KidsInfo
   ON KidsTable.kidCode=KidsInfo.kidCode
   WHEN MATCHED THEN UPDATE SET
        KidsTable.surname = KidsInfo.surname,
        KidsTable.address = KidsInfo.address,
        KidsTable.sports = KidsInfo.sports,
        KidsTable.team = KidsInfo.team
   WHEN NOT MATCHED THEN INSERT
        (kidCode,surname,name,address,sports,team,age,class,teacher,parentID,join_date)
        VALUES
        (KidsInfo.kidCode,KidsInfo.surname,KidsInfo.name,KidsInfo.address,KidsInfo.sports,KidsInfo.team,NULL,NULL,NULL,KidsInfo.parentID,GETDATE());
   print ('Added trigger to insert/update kids when parents data changes')
END

触发器似乎可以满足我的某些需要,即当您创建新记录时没问题。当您更新地址、运动或团队时,也没有问题。但是,我还有 3 2 个问题。

  1. 没有形成新的孩子行。当您将新的 child 添加到 parent 的逗号分隔字段时,不会为该 child 形成一个新行。 请参阅fiddle。现已解决。
  2. 姓氏没有更新当您更新 parent 的姓氏时,新姓氏会添加新记录,但旧记录会保留还有。 请参阅fiddle。现已解决。
  3. Two-way 需要更新记录 需要two-way 更新,所以我需要为孩子们​​创建第二个(类似的)触发器table,因此当 nameaddresssportsteam 字段更新时,它会传播回 parents table. surname 永远不会从 kids table 更新,并且 parentsID 永远不会因为任何原因而更新。 ageclassteacher 的更新将独立于 parents 我不确定如何开始第二个触发器以及您的帮助将不胜感激。 这个 是结合我在很多 Whosebug 帖子 中读到的结果,我什至不确定我是否这样做了新触发器正确。

如果第三期值得单独提问,请告诉我。然后我会接受前两个的答案,然后再问第三个新问题。谢谢!

即使您想要创建 kids table 的标准化版本,您也不需要重复额外的数据,例如在 kidsparents 中存储相同的 address 是多余的,可能会导致问题。举个简单的例子,如果我 运行:

UPDATE dbo.Kids
SET Address = 'A new Address'
WHERE Name = 'John'
AND Surname = 'Adams';

这应该如何更新 parent 记录?这个房子有3个人child人,如果一个人可以换地址,另外两个人不能,那么这个地址只属于kids人,不属于parent人。或者如果 parents 和 children 都需要一个地址,但不要求他们住在同一个地址,那么你可能每个都有一个地址列,但这是不可能的提供任何自动同步性。或者,如果(正如我怀疑的那样)parent 地址是相关的,那么如果您需要 child 的地址,则无需针对每个人存储它 child (或任何其他公共字段),只需加入 parent,例如

SELECT k.*, p.address, p.surname, p.sports, p.team 
FROM Kids AS k 
JOIN Parents AS p ON p.Id = k.ParentId;

这应该可以解决您的第三个问题。要么你的双向触发器是多余的(如果你不复制数据),要么它不是 possible/sensible 由于将多行组合成一行,可能导致冲突。

要回答您的第一个问题,即为什么没有为 Taylor domer 创建记录,那是因为您实际上拥有以下伪代码:

If any record already exists in kids
    -  then update the existing record
else
    - add a new record.

既然 Samuel Domer 已经存在,那么您只能进行更新,而永远不会进行插入。你根本不需要 IF 语句,如果你接受我关于不复制数据的建议,那么你也不需要更新(因为你只更新 tables), 所以你只需要一个 insert

INSERT INTO dbo.kids(KidCode, Name, ParentId, join_date)
SELECT  kidCode = CONVERT(VARCHAR(32), HASHBYTES('SHA2_256', CONCAT(INSERTED.id, '-', TRIM(s.value))), 2),
        TRIMname = (value),
        parentID = INSERTED.id,
        join_date = GETDATE()
FROM    INSERTED
        CROSS APPLY string_split(INSERTED.kids, ',') AS s
WHERE   NOT EXISTS (SELECT 1  FROM dbo.Kids AS k WHERE k.ParentId = INSERTED.id AND k.name = TRIM(s.value));

您可能还想考虑如果从 parent 中删除 child 会发生什么情况,可以按如下方式处理:

DELETE  k
FROM    dbo.Kids AS k
WHERE   EXISTS
(
    SELECT  1
    FROM    DELETED AS d
            CROSS APPLY string_split(d.kids, ',') AS s
    WHERE   d.id = k.ParentID
    AND     TRIM(s.value) = k.name
    AND     NOT EXISTS 
            (   SELECT 1 
                FROM    INSERTED AS i 
                        CROSS APPLY string_split(i.kids, ',') AS s2 
                WHERE   i.id = d.id 
                AND     TRIM(s2.value) = TRIM(s.value)
            )
);

Complete demo on db<>fiddle