【问题标题】:Triggers on TSQL inserting a non-identity table插入非标识表的 TSQL 触发器
【发布时间】:2010-02-08 22:37:11
【问题描述】:

我正在开发一个触发器,它需要在另一个表中重新插入其数据。

目标表有一个主键 INT NOT NULL WITHOUT Identity,所以我有 2 个选择:

  1. 计算最大值并从此处插入。
  2. 从序列表中获取最大值。

我过去总是创建一个具有标识的表变量,并从之前计算的值插入移位。

CREATE TRIGGER trg
   ON  [dbo].[table] 
   AFTER INSERT
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @t TABLE (sec INT IDENTITY(1, 1), id INT)
    DECLARE @ini INT

    SELECT @ini = ISNULL(MAX(id), 0) FROM tableDest
    -- SELECT @ini = value FROM sequencesTable WHERE seqId = 987

    INSERT INTO @t (id) SELECT id FROM inserted

    INSERT INTO tableDest 
        (id, field1, field2) 
    SELECT @ini + t.sec, field1, field2
    FROM @t t
        JOIN inserted ON t.id = inserted.id


    -- SELECT @ini = @ini + MAX(t.sec) FROM @t
    -- UPDATE sequencesTable SET value = @ini WHERE seqId = 987
END

有没有更好的方法来做到这一点?

提前致谢。

【问题讨论】:

  • 你需要确定另一个表中那个int PK的意图和意义是什么。这是您可以确保为其提供正确键值的唯一方法。

标签: sql-server tsql


【解决方案1】:

假设 SQL 2005+,你可以使用ROW_NUMBER():

CREATE TRIGGER trg
   ON  [dbo].[table] 
   AFTER INSERT
AS
BEGIN
    SET NOCOUNT ON;

    INSERT INTO tableDest
        (id, field1, field2)
    SELECT
        Seed.Value + ROW_NUMBER() OVER(ORDER BY Id), 
        field1, 
        field2
    FROM Inserted
    CROSS JOIN (
       SELECT MAX(id) as Value
       FROM tableDest
    ) as Seed
END

使用CROSS JOIN 而不是直接获取种子值可以使您免于MAX(Id) 在获取值和插入值之间切换的并发问题。否则,您需要一个 SERIALIZABLE 事务来防止新行在读取后插入到 tableDest 中。

【讨论】:

  • 然后仍然对种子值进行 CROSS JOIN,但您必须使用表变量来获取增量值。
【解决方案2】:

澄清一下,如果我运行这个语句,它是否总是在表 Foo 中给我一个唯一的 Bar,即使同时并行执行许多同时的语句?

INSERT INTO Foo (Bar)
  SELECT
    CASE WHEN Bar = -1 
    THEN (SELECT Seed.Value + ROW_NUMBER() OVER(ORDER BY AutoId) 
          FROM inserted CROSS JOIN (SELECT MAX(Bar) as Value FROM Foo) as Seed)
    ELSE Bar
    END

在这种情况下,是什么让语句并发安全?是否因为为 Bar 生成新值的 select 嵌套在 insert 语句中?或者是防止同时读取表 Foo 的交叉连接?那case语句呢,对结果有影响吗,在并发方面?

谢谢!

【讨论】:

    【解决方案3】:

    tableDest 真的需要人工主键吗?

    另外,并发性:在计算@ini 的值和实际执行插入之间,tableDest 上是否有可能发生其他插入?

    【讨论】:

    • 嗯,我考虑过通过包围 BEGIN TRAN/COMMIT 来解决并发问题...
    • 使用 begin tran/commit 在触发器中包装代码不会给您带来任何好处。触发器是一种隐式事务。
    • CTE 会更好吗?像 WITH ini (offset) AS SELECT MAX(id) AS offset FROM tableDest INSERT INTO tableDest (id, field1, field2) SELECT ini.offset + inserted.sec,inserted.field1,inserted.field2 FROM ini CROSS JOIN inserted 我不不确定 CTE 是否保证是原子的。不过,我最初的问题仍然存在:您真的需要一个唯一的 ID 列,还是因为一些愚蠢的“每个表都必须有一个 ID 字段”规则而需要提供一个?
    • 呃,为最后的道歉:我没有意识到 cmets 没有像问题一样很好地格式化!
    • Ken,他是 2000 年的,所以 CTE 不可用
    【解决方案4】:

    我想我会问一个显而易见的问题,为什么不在目标表上设置一个标识值? (注意,如果您这样做,请确保您没有使用 @@identity 从源表中获取标识值,因为这不是您将获得的标识值 - 您将从目标表中获取标识。 )

    或者您可以使用源表中的标识作为另一个表的标识。这让我觉得设计的初衷。否则,可能很难将这些记录与原始数据匹配。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-24
      • 1970-01-01
      相关资源
      最近更新 更多