SQL Server INSTEAD OF INSERT触发器失败

时间:2012-01-12 20:53:31

标签: sql sql-server sql-server-2008 tsql sql-server-2008-r2

我有两张桌子:

CREATE TABLE users
(
    id int PRIMARY KEY IDENTITY,
    firstname varchar(30),
    lastname varchar(30),
    age int
)

CREATE TABLE rejectedUsers
(
    id int PRIMARY KEY IDENTITY,
    firstname nvarchar,
    lastname nvarchar,
    age nvarchar
)

触发器:

CREATE TRIGGER checkNumeric
ON users
INSTEAD OF INSERT
AS
INSERT INTO users SELECT firstname, lastname, age FROM inserted WHERE ISNUMERIC(age) = 1;
INSERT INTO rejectedUsers SELECT firstname, lastname, age FROM inserted WHERE ISNUMERIC(age) = 0;

-- goes fine
INSERT INTO users
VALUES
('Vlad', 'P', 21)

-- fails

INSERT INTO users
VALUES
('Chuck', 'Norris', 'abc')

第二个语句引发了一个错误:

  

Msg 245,Level 16,State 1,Line 1
  将varchar值'abc'转换为数据类型int时,转换失败。

我希望触发器在rejectedUsers上执行插入,出了什么问题?

4 个答案:

答案 0 :(得分:6)

这是运行时错误,而不是编译器错误。在运行时将类型与基表进行比较,而不管您在一个而不是触发器中编码的任何辅助逻辑。插入的伪表是基于基表创建的,而不是基于传递给某个任意insert语句的变量类型。

我认为你不能通过简单地在它前面打一个触发器来提供类型透明度。如果你想允许人们编写临时查询,他们可以在那里抛出任何数据类型,你将需要首先在中间表中使用那些容纳垃圾的数据类型。或者做大多数人所做的事:让界面强制执行数据类型。如果你使用一个接受@age参数作为INT的存储过程,它们就不能在'abc'附近的任何地方附近......也没有年龄,因为明天或第二天它可能会改变。为什么不接受生日呢?

最后,请不要将列或变量定义为没有长度的varchar / nvarchar。在某些情况下,这些是1个字符,在其他情况下,它们是30个。要明确并告诉每个人你的真正含义。

答案 1 :(得分:4)

您的代码在这里:

INSERT INTO users
VALUES('Chuck', 'Norris', 'abc')

会尝试将abc插入到age类型的int列中 - 这真的是一个整数吗?我相信即使你有一个INSTEAD OF INSERT触发器,触发器内部的Inserted表将与Users表具有相同的结构 - 并且该列ageINT列,无法将abc作为值处理!

作为一般建议,我会始终明确指定您在插入中指的是哪些列 - 所以我将以上查询编写为:

INSERT INTO users(firstname, lastname, age)
VALUES('Chuck', 'Norris', 71)

我会改变这个:

INSERT INTO users 
    SELECT firstname, lastname, age 
    FROM inserted 
    WHERE ISNUMERIC(age) = 1;

到此:

INSERT INTO users(firstname, lastname, age)
    SELECT firstname, lastname, age 
    FROM inserted 
    WHERE ISNUMERIC(age) = 1;

这样一来,如果某些表被修改并突然得到一个新的列,而你的“通用”INSERT没有填写,你就不会有任何令人讨厌的意外......

作为旁注:我希望你知道通过指定:

CREATE TABLE rejectedUsers
(
    id int PRIMARY KEY IDENTITY,
    firstname nvarchar,
    lastname nvarchar,
    age nvarchar
)

您正在有效地创建一个包含大量nvarchar(1)列的表格,每个列可以容纳最多 1个字符!您还应始终varcharnvarchar列提供明确的长度 .....

答案 2 :(得分:1)

您无法在age中插入'abc',因为它是int字段。如果您确实要这样做,请将users中的年龄数据类型更改为VARCHAR(10)或类似内容,或者改为插入int

答案 3 :(得分:0)

只是一个疯狂的猜测,但也许尝试将拒绝的语法转换为以下

INSERT INTO rejectedUsers SELECT firstname, lastname, age FROM inserted WHERE ISNUMERIC(age) >= 0 and ISNUMERIC(age) > 120;

这可以防止人们进入年龄大于120岁或年龄小于0岁(或者您决定应该是最小/最大年龄)但理论上如果它允许将varchar传递给int也会推动'abc'也被拒绝了。

如果您仍有转换问题,请尝试以下操作:

INSERT INTO rejectedUsers SELECT firstname, lastname, case when age between 0 and 120 then age else 0 end as age FROM inserted WHERE ISNUMERIC(age) = 0;

如果我正确理解你的触发器使用情况(从未真正使用它们),这将再次检查年龄是否在范围内,否则发送0但你将丢失年龄中包含的数据

希望有所帮助,

马库斯