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



好吧,这里有一点挑战。我可能错过了显而易见的东西,但到目前为止,我一直在绞尽脑汁,但没能想出一个像样的解决方案。(我有一些"变通办法",但我都不喜欢)

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

我可以尝试如何在使用标识的多表视图上编写INSTEAD OF INSERT触发器?但这里的问题是,由于8k的限制,#inserted无法工作。一个(糟糕的问题)解决方案是使所有字段都变为varchar(max),这样数据就出了页面。可能会有一些表演上的成功,但哦,好吧。。。

我提出的另一个替代方案是使用%%physloc%%,但这似乎对inserted不起作用。

肯定会起作用的是使用光标进行RBAR,但是。。。好而不是=)

或者,我可以创建#table_x和#table_y临时表,插入到这两个表中,然后再次通过inserted]开始匹配它们。因为可能到处都是doubles(在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]
    -- ???

update:在键入此项时,我一直在四处搜索,还发现:TSQL在使用非插入触发器时希望插入标识列,这归结为:让ETL在插入中计算出一个唯一的row_id值。既然我们有ROW_NUMBER()可用,就不需要问imho那么多了。除非有人拿出更好的解决方案,否则我可能会选择这个。

您可以为此使用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的。这很重要。

最新更新