T-SQL 触发器触发“列名称或提供值的数量与表定义不匹配”错误
题
这是我无法解决的问题,我已经查看了 到处. 。也许这里有人会知道!
我有一个名为 dandb_raw 的表,特别包含三列:dunsId (PK)、名称和搜索名称。我还有一个作用于该表的触发器:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dandb_raw_searchNames]
ON [dandb_raw]
FOR INSERT, UPDATE
AS
SET NOCOUNT ON
select dunsId, name into #magic from inserted
UPDATE dandb
SET dandb.searchName = company_generateSearchName(dandb.name)
FROM (select dunsId, name from #magic) i
INNER JOIN dandb_raw dandb
on i.dunsId = dandb.dunsId
--Add new search matches
SELECT c.companyId, dandb.dunsId
INTO #newMatches
FROM dandb_raw dandb
INNER JOIN (select dunsId, name from #magic) a
on a.dunsId = dandb.dunsId
INNER JOIN companies c
ON dandb.searchName = c.searchBrand
--avoid url matches that are potentially wrong
AND (lower(dandb.url) = lower(c.url)
OR dandb.url = ''
OR c.url = ''
OR c.url is null)
INSERT INTO #newMatches (companyId, dunsId)
SELECT c.companyId, max(dandb.dunsId) dunsId
FROM dandb_raw dandb
INNER JOIN
(
select
case when charindex('/',url) <> 0 then left(url, charindex('/',url)-1)
else url
end urlMatch, * from companies
) c
ON dandb.url = c.urlMatch
where subsidiaryOf = 1 and isReported = 1 and dandb.url <> ''
and c.companyId not in (select companyId from #newMatches)
group by companyId
having count(dandb.dunsId) = 1
UPDATE cd
SET cd.dunsId = nm.dunsId
FROM companies_dandb cd
INNER JOIN #newMatches nm
ON cd.companyId = nm.companyId
GO
触发器导致插入失败:
insert into [dandb_raw](dunsId, name)
select 3442355, 'harper'
union all
select 34425355, 'har 466per'
update [dandb_raw] set name ='grap6767e'
出现此错误:
Msg 213, Level 16, State 1, Procedure companies_contactInfo_updateTerritories, Line 20
Insert Error: Column name or number of supplied values does not match table definition.
最奇怪的是触发器中的每个单独的语句都是独立工作的。这几乎就像插入是一个一次性表,如果您尝试将插入移动到临时表之一,它就会感染临时表。
那么是什么原因导致触发器失败呢?怎么才能阻止呢?
解决方案
我认为大卫和切尔沃联合起来解决了这个问题。
我很确定所发生的部分原因是我们在多个触发器中使用了#newMatches。当一个触发器更改某些行时,它将触发另一个触发器,该触发器将尝试使用连接作用域#newMatches。
因此,它会尝试找到已经存在的具有不同模式的表,然后终止并生成上面的消息。一项有利的证据:inserts 是否使用堆栈样式作用域(嵌套触发器有自己的 inserts?)
不过仍在猜测 - 至少现在一切似乎都在发挥作用!
其他提示
什么是 Companies_contactInfo_updateTerritories?实际参考文献提到了过程“companies_contactInfo_updateTerritories”,但我在给出的代码中没有看到它。我也看不到它在哪里被调用。除非它是来自您的应用程序调用 SQL,因此无关......
如果您测试了所有内容并且它有效,但现在它不起作用,那么一定有什么不同。需要考虑的一件事是安全性。我注意到您只是将表命名为 [dandb_raw] 而不是 [dbo].[dandb_raw]。因此,如果用户有一个同名的表 [user].[dandb_raw],则该表将用于检查定义,而不是您的表。此外,触发器还会创建临时表。但是,如果某些临时表由于某种原因已经存在但具有不同的定义,这也可能是一个问题。
我在代码中没有看到任何明显的问题。
“选择 ..INTO”是弱功夫。尝试显式创建临时表定义:
CREATE TABLE #newMatches
(
CompanyID int PRIMARY KEY,
DunsID int
)
当你完成 #newMatches 后,你应该删除它,以便稍后可以再次创建它(临时表是连接范围的!!)
DROP TABLE #newMatches
触发器代码(因为它必须在每次更新数据时运行)必须高效并且必须考虑多个记录插入。你第二次成功了,但第一次却没有成功。您使这变得过于复杂,并且使用了诸如 Not in 语句之类的东西,这些东西通常不如使用左连接有效。临时表在这里是不必要的(我永远不会考虑在触发器中使用临时表),因为它们会增加触发器的低效率。没有理由不从插入i而不是从(选择dunsid,来自#magic的名称)i的理由写作。
第一个可能更快,并且更易于阅读和维护。
这里:JOIN ( select case when charindex('/',url) <> 0 then left(url, charindex('/',url)-1) else url end urlMatch, * 来自公司 ) c ON dandb.url = c.urlMatch
您正在选择表中的所有字段,即使您看起来只使用一个字段。为什么?您还在公司的所有记录上运行该案例陈述,即使您加入后可能不需要所有这些记录。
一般来说,我会避免使用 select * 但特别是在触发器中。假设您要插入到另一个表中,并且使用 select * from some table join 来插入或删除。向该表添加列将导致触发器失败并停止所有数据更改,直到修复为止。
您还使用了触发器中的函数。如果你有一个大的插入件,这可能会非常慢。我建议您通过更新大量记录来测试这一点,看看会发生什么。所有数据更改不仅仅从用户界面发生,一次一条记录。有时会从 Management Studio 中的临时查询更新一个字段(当所有价格需要调整 10% 时,这是想到的最简单的例子。)您的触发器需要能够处理这些类型,如果更新以及您期待的更新。我将运行一个更新 100000 行的测试用例,看看这个触发器会减慢多少速度。
也许这并不能真正回答你的问题,但我不得不说,触发器远非最佳。