如何在INSTEAD OF INSERT触发器中唯一标识记录?

时间:2015-11-05 14:54:53

标签: sql-server triggers

好的,这是一个挑战。我可能会错过这个显而易见的事情,但到目前为止,我一直在绞尽脑汁但却无法找到一个合适的解决方案。 (我有一些“解决方法”,但我也不喜欢)

我们正在使用更多字段扩展现有表t_table_x。该表有一个充当PK的标识列和许多(功能上)可以包含“双精度”的数据列。问题是,表的字段已占据PAGE的可用8000字节的很大一部分,添加所有新字段将导致(某些)记录超过此限制。解决方案似乎很简单。我们简单地添加一个新表t_table_y,它共享相同的标识值,然后从y到x共享FK。为了便于使用,我们然后添加一个视图,它连接两个表并返回它们就好像它们位于一个大表中一样。到现在为止还挺好。 如果用户还可以直接将信息(ETL)加载到视图中,那么再次考虑可用性将非常好,因此不必首先将前半部分插入t_table_x然后将另一半插入t_table_y中。 1}}。起初我持怀疑态度,因为这意味着inserted(伪)表需要能够支持每条记录超过8000个字节。结果,这完美无瑕! 但后来麻烦开始了。当触发器在t_table_x中插入相关列时,会生成IDENTITY值,我们需要使用这些值来插入t_table_y中的其他列。然而,我遇到了一个问题,我无法知道什么身份值适合[插入]中的原始记录。

我可以尝试How to write an INSTEAD OF INSERT trigger on a multi table view that works with identities?,但问题是#inserted因为8k限制而无效。一个(坏菜)解决方法是使所有字段varchar(max),以便数据超出页面。可能会有一些性能受到打击,但是很好......

我提出的另一个选择是使用%%physloc%%,但似乎这对inserted无效。

通过使用游标,确实可行的是RBAR,但是......好吧......而不是=)

或者我可以创建#table_x和#table_y临时表,插入两个表中,然后再次通过[inserted]开始匹配它们。因为整个地方都可能有双打(在x和y中),这可能是一个相当繁重的操作(将涉及大量数据;我无法将任何索引添加到伪表中;等等...这里的示例是简化的,我实际上是使用解释的逻辑添加4个额外的表。)

任何拥有更优雅解决方案的人?

IF OBJECT_ID('v_test') IS NOT NULL DROP VIEW v_test
IF OBJECT_ID('t_table_y') IS NOT NULL DROP TABLE t_table_y
IF OBJECT_ID('t_table_x') IS NOT NULL DROP TABLE t_table_x

GO

CREATE TABLE t_table_x ( row_id  int           NOT NULL IDENTITY(1, 1) 
                                PRIMARY KEY,
                         value_a varchar(3000) NOT NULL,
                         value_b varchar(3000) NOT NULL )


CREATE TABLE t_table_y ( row_id  int           NOT NULL 
                                PRIMARY KEY,
                                FOREIGN KEY (row_id) REFERENCES t_table_x (row_id),
                         value_c varchar(3000) NOT NULL,
                         value_d varchar(3000) NOT NULL )

GO

CREATE VIEW v_test
AS 
SELECT x.row_id,
       x.value_a,
       x.value_b,
       y.value_c,
       y.value_d
  FROM t_table_x x
  JOIN t_table_y y
    ON y.row_id = x.row_id

GO

DECLARE @row_id int

INSERT t_table_x (value_a, value_b) VALUES (Replicate('A', 2500), Replicate('B', 2500))
SELECT @row_id = SCOPE_IDENTITY()

INSERT t_table_y (row_id, value_c, value_d) VALUES (@row_id, Replicate('C', 2500), Replicate('D', 2500))
GO
SELECT * FROM v_test

GO
-- this won't work
INSERT v_test (value_a, value_b, value_c, value_d)
SELECT t.name, Convert(varchar, t.object_id), c.name, Convert(varchar, c.column_id)
  FROM sys.tables t
  JOIN sys.columns c
    ON c.object_id = t.object_id

GO
-- so we build an INSTEAD OF INSERT trigger
CREATE TRIGGER tr1_v_test
ON v_test
INSTEAD OF INSERT
AS
    -- simply return results for now
    SELECT * FROM t_entity

GO
-- test
INSERT v_test (value_a, value_b, value_c, value_d)
SELECT t.name, Convert(varchar, t.object_id), c.name, Convert(varchar, c.column_id)
  FROM sys.tables t
  JOIN sys.columns c
    ON c.object_id = t.object_id

-- as we can see, it works now, but (logically) we don't have a value in row_id (yet) =/
GO
ALTER TRIGGER tr1_v_test
ON v_test
INSTEAD OF INSERT
AS
    -- how to find/add a row-identifier to [inserted]

    -- not allowed:
    UPDATE [inserted] SET row_id ...

    -- not available
    SELECT *, %%physloc%% FROM inserted

    -- not an option (max size of a record = 8000 bytes (PAGE))
    SELECT row_id = IDENTITY(int, 1, 1), value_a, value_b, value_c, value_d 
      INTO #numbered_temp_table
      FROM [inserted]

    -- ???

更新:在输入时我一直在搜索并发现:TSQL is expecting the identity column to be inserted when using an instead of insert trigger归结为:让ETL在插入内部找出唯一的row_id值。既然我们有ROW_NUMBER()可用,那么问这个问题应该不是那么多。除非有人提出更好的解决方案,否则我可能会选择这个解决方案。

1 个答案:

答案 0 :(得分:1)

您可以使用MERGE语句。关于它的一个好处是它可以从源输出一些额外的列以及插入的值。这是一个例子:

CREATE TABLE table1(ID int IDENTITY, col1 INT)
GO

CREATE TABLE table2(ID int, col2 INT)
GO

CREATE TABLE table3(ID int, col3 INT)
GO

ALTER VIEW vtable
AS

SELECT t1.ID, t1.col1, t2.col2, t3.col3 
FROM table1 t1
JOIN table2 t2 ON t2.ID = t1.ID
JOIN table3 t3 ON t3.ID = t1.ID
GO


CREATE TRIGGER trvtable ON dbo.vtable
INSTEAD OF INSERT
AS
BEGIN
   DECLARE @t TABLE(ID int, col2 INT, col3 INT)

    MERGE dbo.table1 t
    USING Inserted s ON t.ID = s.ID
    WHEN NOT MATCHED THEN INSERT(col1) VALUES(s.col1)
    OUTPUT inserted.ID, s.col2, s.col3 INTO @t;

    INSERT INTO dbo.table2 SELECT ID, col2 FROM @t
    INSERT INTO dbo.table3 SELECT ID, col3 FROM @t

END
GO


INSERT INTO dbo.vtable( ID, col1, col2, col3 ) VALUES  
(NULL, 1, 2, 3),
(NULL, 4, 5, 6)

SELECT * FROM dbo.vtable

输出:

ID  col1    col2    col3
1   1       2       3
2   4       5       6

请注意我是如何插入NULL VALUES ( NULL, -- ID - int的。这很重要。