我有两张桌子:
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
上执行插入,出了什么问题?
答案 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
表具有相同的结构 - 并且该列age
是INT
列,无法将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个字符!您还应始终为varchar
和nvarchar
列提供明确的长度 .....
答案 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但你将丢失年龄中包含的数据
希望有所帮助,
马库斯