【问题标题】:Creating a SQL Server trigger to transition from a natural key to a surrogate key创建 SQL Server 触发器以从自然键转换为代理键
【发布时间】:2011-04-25 16:41:12
【问题描述】:

背景故事

在我们计划在我们的一个主表中弃用自然键列的工作中。该项目包含 100 多个链接到此表/列的应用程序; 400 多个直接引用此列的存储过程;以及这些应用程序之间的大量公用表也引用了此列。

Big Bang 和 Start from Scratch 方法已经不存在了。我们将一次弃用本专栏的一个应用程序,验证更改,然后继续下一个……我们制定了一个长期目标,以使这项工作切实可行。

我遇到的问题是很多这些应用程序都有共享存储过程和表。如果我完全转换应用程序 A 的所有表/存储过程,应用程序 B 和 C 将被破坏,直到转换。这些反过来可能会破坏应用程序 D、E、F...等。我已经为代码类和存储过程实现了一个策略,我坚持的部分是数据库的转换状态。

这是我们所拥有的基本示例:

Users
---------------------------
Code          varchar(32) natural key

Access
---------------------------
UserCode      varchar(32) foreign key
AccessLevel   int

我们现在的目标只是像这样的过渡状态:

Users
---------------------------
Code          varchar(32) 
Id            int         surrogate key

Access
---------------------------
UserCode      varchar(32)   
UserID        int         foreign key      
AccessLevel   int

在过渡阶段的想法是,未迁移的应用程序和存储过程仍然能够访问所有适当的数据,并且新的数据可以开始推送到正确的列——一旦所有存储过程和应用程序的迁移完成我们终于可以删除多余的列了。

我想使用 SQL Server 的触发器来自动拦截任何新的插入/更新,并对每个受影响的表执行以下操作:

CREATE TRIGGER tr_Access_Sync
ON Access
INSTEAD OF INSERT(, UPDATE)
AS
BEGIN
  DIM @code as Varchar(32)
  DIM @id as int

  SET @code = (SELECT inserted.code FROM inserted)
  SET @id = (SELECT inserted.code FROM inserted)

  -- This is a migrated application; find the appropriate legacy key
  IF @code IS NULL AND @id IS NOT NULL
     SELECT Code FROM Users WHERE Users.id = @id

  -- This is a legacy application; find the appropriate surrogate key
  IF @id IS NULL AND @code IS NOT NULL
     SELECT Code FROM Users WHERE Users.id = @id

  -- Impossible code:
  UPDATE inserted SET inserted.code=@code, inserted.id=@id
END

问题

到目前为止,我遇到的两个大问题是:

  1. 我不能执行“AFTER INSERT”,因为 NULL 约束会使插入失败。
  2. 我提到的“不可能的代码”是我希望如何干净地代理原始查询;如果原始查询中有 x、y、z 列或只有 x,理想情况下我希望使用相同的触发器来执行这些操作。如果我添加/删除另一列,我希望触发器保持功能。

任何人都有一个可能的代码示例,或者甚至在只有一个值传递给 SQL 时也能保持这些列正确填充的替代解决方案?

【问题讨论】:

  • 此外,如果您要添加 ID 列,请不要将它们命名为 ID,这是一种不好的做法,可能会导致错误(您可能会在复杂查询中意外加入错误的列)和报告困难。使用 tablenameID。

标签: sql-server database tsql triggers refactoring-databases


【解决方案1】:

在解决这个问题之后,这似乎是我在 SQL 语法中能想到的最通用/可重用的解决方案。即使两列都有 NOT NULL 约束,它也能正常工作,即使您在插入时根本没有引用“其他”列。

CREATE TRIGGER tr_Access_Sync
ON Access
INSTEAD OF INSERT
AS 
BEGIN

    /*-- Create a temporary table to modify because "inserted" is read-only */
    /*-- "temp" is actually "#temp" but it throws off stackoverflow's syntax highlighting */
    SELECT * INTO temp FROM inserted

    /*-- If for whatever reason the secondary table has it's own identity column */
    /*-- we need to get rid of it from our #temp table to do an Insert later with identities on */
    ALTER TABLE temp DROP COLUMN oneToManyIdentity

    UPDATE temp 
    SET 
        UserCode = ISNULL(UserCode, (SELECT UserCode FROM Users U WHERE U.UserID = temp.UserID)),
        UserID = ISNULL(UserID, (SELECT UserID FROM Users U WHERE U.UserCode = temp.UserCode))

    INSERT INTO Access SELECT * FROM temp

END

【讨论】:

    【解决方案2】:

    棘手的业务......

    好的,首先:这个触发器在许多情况下都起作用:

    SET @code = (SELECT inserted.code FROM inserted)
    SET @id = (SELECT inserted.code FROM inserted)
    

    可以使用Inserted 伪表中的一组行来调用触发器 - 你要在这里选择哪一个?您需要以这样一种方式编写您的触发器,即使您在Inserted 表中获得 10 行,它也能正常工作。如果一条 SQL 语句插入 10 行,您的触发器将不会被触发 10 次 - 每行触发一次 - 但整个批次只有 一次 - 您需要将其纳入帐户!

    第二点:我会尝试设置 ID 的 IDENTITY 字段 - 然后它们总是会得到一个值 - 即使对于“旧版”应用程序也是如此。那些“旧”应用程序应该提供旧密钥 - 所以你应该没问题。我看到并且不知道您如何处理这些问题的唯一问题是来自已转换应用程序的插入 - 它们是否也提供“旧式”旧密钥?如果没有 - 您需要多快才能拥有这样的钥匙?

    我正在考虑的是一个“清理工作”,它将在表上运行并使用 NULL 旧键获取所有行,然后为其提供一些有意义的值。使它成为一个常规的存储过程并执行它,例如一天,四小时,三十分钟 - 任何适合您的需求。这样您就不必处理触发器及其所具有的所有限制。

    【讨论】:

      【解决方案3】:

      难道不能让架构更改“大爆炸”,但在那些“隐藏”更改的表的顶部创建视图吗?

      我认为您可能会发现您只是将损坏推迟到以后的时间点:“我们将一次弃用此列一个应用程序” - 这可能是我的天真,但我看不出如何这永远行得通。

      当不同的应用程序以不同的方式做事时,肯定会出现更糟糕的情况吗?

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-07-15
      • 1970-01-01
      • 2011-09-11
      • 1970-01-01
      • 2014-02-02
      • 2019-04-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多