4

背景故事

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

生活大爆炸和从头开始的方法不在图片中。我们将一次弃用本专栏一个应用程序,验证更改,然后继续下一个……我们有一个长期的目标来使这项工作切实可行。

我遇到的问题是很多这些应用程序都有共享的存储过程和表。如果我完全转换应用程序 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,也可以使用另一种解决方案来保持这些列正确填充?

4

3 回答 3

2

棘手的业务...

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

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

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

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

我正在考虑的是一个“清理工作”,它将在表上运行并使用 NULL 遗留键获取所有行,然后为其提供一些有意义的值。使它成为一个常规的存储过程,并每天、4 小时、30 分钟执行一次——只要适合您的需要。这样您就不必处理触发器及其所具有的所有限制。

于 2010-10-15T05:26:34.280 回答
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
于 2010-10-15T13:16:57.293 回答
1

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

我认为您可能会发现您只是将损坏推迟到以后的时间点:“我们将一次弃用此列一个应用程序”-这可能是我的天真,但我看不出这是怎么回事去工作。

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

于 2010-10-14T23:44:00.310 回答