我的桌子上有一个触发器。
ALTER TRIGGER [dbo].[UpdateUniqueSubjectAfterInsertUpdate]
ON [dbo].[Contents]
AFTER INSERT,UPDATE
AS
BEGIN
-- Grab the Id of the row just inserted/updated
DECLARE @Id INT
SELECT @Id = Id
FROM INSERTED
END
每次插入或修改新条目时,我都希望更新单个字段(在此表中)。为了这个问题,想象一下我正在更新LastModifiedOn(日期时间)字段。
好的,所以我得到的是批量插入东西..
INSERT INTO [dbo].[Contents]
SELECT Id, a, b, c, d, YouDontKnowMe
FROM [dbo].[CrapTable]
现在所有行都已正确插入。 LastModifiedOn字段默认为null。因此,所有条目都为null - 除了第一行。
这是否意味着不会为插入表中的每一行调用触发器,而是在插入查询完成后,即。是否插入了所有行?这意味着,INSERTED表(在触发器中)没有一行,而是'n'行?!
如果是这样..呃.. :(这是否意味着我需要在这个触发器中使用光标?(如果我需要为每一行做一些独特的逻辑,我现在这样做。)
我将添加完整的触发器代码,以查看是否可以在没有光标的情况下执行此操作。
BEGIN
SET NOCOUNT ON
DECLARE @ContentId INTEGER,
@ContentTypeId TINYINT,
@UniqueSubject NVARCHAR(200),
@NumberFound INTEGER
-- Grab the Id. Also, convert the subject to a (first pass, untested)
-- unique subject.
-- NOTE: ToUriCleanText just replaces bad uri chars with a ''.
-- eg. an '#' -> ''
SELECT @ContentId = ContentId, @ContentTypeId = ContentTypeId,
@UniqueSubject = [dbo].[ToUriCleanText]([Subject])
FROM INSERTED
-- Find out how many items we have, for these two keys.
SELECT @NumberFound = COUNT(ContentId)
FROM [dbo].[Contents]
WHERE ContentId = @ContentId
AND UniqueSubject = @UniqueSubject
-- If we have at least one identical subject, then we need to make it
-- unique by appending the current found number.
-- Eg. The first instance has no number.
-- Second instance has subject + '1',
-- Third instance has subject + '2', etc...
IF @NumberFound > 0
SET @UniqueSubject = @UniqueSubject + CAST(@NumberFound AS NVARCHAR(10))
-- Now save this change.
UPDATE [dbo].[Contents]
SET UniqueSubject = @UniqueSubject
WHERE ContentId = @ContentId
END
答案 0 :(得分:8)
为什么不更改触发器来处理多行? 不需要游标或循环:这是SQL的全部内容......
UPDATE
dbo.SomeTable
SET
LastModifiedOn = GETDATE()
WHERE
EXIST (SELECT * FROM INSERTED I WHERE I.[ID] = dbo.SomeTable.[ID]
编辑:类似......
INSERT @ATableVariable
(ContentId, ContentTypeId, UniqueSubject)
SELECT
ContentId, ContentTypeId, [dbo].[ToUriCleanText]([Subject])
FROM
INSERTED
UPDATE
[dbo].[Contents]
SET
UniqueSubject + CAST(NumberFound AS NVARCHAR(10))
FROM
--Your original COUNT feels wrong and/or trivial
--Do you expect 0, 1 or many rows.
--Edit2: I assume 0 or 1 because of original WHERE so COUNT(*) will suffice
-- .. although, this implies an EXISTS could be used but let's keep it closer to OP post
(
SELECT ContentId, UniqueSubject, COUNT(*) AS NumberFound
FROM @ATableVariable
GROUP BY ContentId, UniqueSubject
HAVING COUNT(*) > 0
) foo
JOIN
[dbo].[Contents] C ON C.ContentId = foo.ContentId AND C.UniqueSubject = foo.UniqueSubject
编辑2:再次使用RANKING
UPDATE
C
SET
UniqueSubject + CAST(foo.Ranking - 1 AS NVARCHAR(10))
FROM
(
SELECT
ContentId, --not needed? UniqueSubject,
ROW_NUMBER() OVER (PARTITION BY ContentId ORDER BY UniqueSubject) AS Ranking
FROM
@ATableVariable
) foo
JOIN
dbo.Contents C ON C.ContentId = foo.ContentId
/* not needed? AND C.UniqueSubject = foo.UniqueSubject */
WHERE
foo.Ranking > 1
答案 1 :(得分:2)
对于INSERT INTO查询,触发器只运行一次。 INSERTED表将包含多行。
答案 2 :(得分:1)
0.1。创建了一个索引视图,表示需要清理的“主题”字段。这个领域必须是独一无二的......但在我们将它变得独一无二之前,我们需要将它分组。
-- Create the view.
CREATE VIEW ContentsCleanSubjectView with SCHEMABINDING AS
SELECT ContentId, ContentTypeId,
[dbo].[ToUriCleanText]([Subject]) AS CleanedSubject
FROM [dbo].[Contents]
GO
-- Index the view with three index's. Custered PK and a non-clustered,
-- which is where most of the joins will be done against.
-- Last one is because the execution plan reakons i was missing statistics
-- against one of the fields, so i added that index and the stats got gen'd.
CREATE UNIQUE CLUSTERED INDEX PK_ContentsCleanSubjectView ON
ContentsCleanSubjectView(ContentId)
CREATE NONCLUSTERED INDEX IX_BlahBlahSnipSnip_A ON
ContentsCleanSubjectView(ContentTypeId, CleanedSubject)
CREATE INDEX IX_BlahBlahSnipSnip_B ON
ContentsCleanSubjectView(CleanedSubject)
0.2。创建现在为什么的触发器代码
a)抓住所有“改变的”项目(没有新的/很难的)
b)命令所有插入的行,用干净的主题划分编号的行
c)在主更新子句中更新我们最多的单行。
这是代码......
ALTER TRIGGER [dbo].[UpdateUniqueSubjectAfterInsertUpdate]
ON [dbo].[Contents]
AFTER INSERT,UPDATE
AS
BEGIN
SET NOCOUNT ON
DECLARE @InsertRows TABLE (ContentId INTEGER PRIMARY KEY,
ContentTypeId TINYINT,
CleanedSubject NVARCHAR(300))
DECLARE @UniqueSubjectRows TABLE (ContentId INTEGER PRIMARY KEY,
UniqueSubject NVARCHAR(350))
DECLARE @UniqueSubjectRows TABLE (ContentId INTEGER PRIMARY KEY,
UniqueSubject NVARCHAR(350))
-- Grab all the records that have been updated/inserted.
INSERT INTO @InsertRows(ContentId, ContentTypeId, CleanedSubject)
SELECT ContentId, ContentTypeId, [dbo].[ToUriCleanText]([Subject])
FROM INSERTED
-- Determine the correct unique subject by using ROW_NUMBER partitioning.
INSERT INTO @UniqueSubjectRows
SELECT SubResult.ContentId, UniqueSubject = CASE SubResult.RowNumber
WHEN 1 THEN SubResult.CleanedSubject
ELSE SubResult.CleanedSubject + CAST(SubResult.RowNumber - 1 AS NVARCHAR(5)) END
FROM (
-- Order all the cleaned subjects, partitioned by the cleaned subject.
SELECT a.ContentId, a.CleanedSubject, ROW_NUMBER() OVER (PARTITION BY a.CleanedSubject ORDER BY a.ContentId) AS RowNumber
FROM ContentsCleanSubjectView a
INNER JOIN @InsertRows b ON a.ContentTypeId = b.ContentTypeId AND a.CleanedSubject = b.CleanedSubject
GROUP BY a.contentId, a.cleanedSubject
) SubResult
INNER JOIN [dbo].[Contents] c ON c.ContentId = SubResult.ContentId
INNER JOIN @InsertRows d ON c.ContentId = d.ContentId
-- Now update all the effected rows.
UPDATE a
SET a.UniqueSubject = b.UniqueSubject
FROM [dbo].[Contents] a INNER JOIN @UniqueSubjectRows b ON a.ContentId = b.ContentId
END
现在,子查询正确返回所有已清理的主题,正确分区并正确编号。我从不对'PARTITION'命令有所了解,所以这个技巧在这里是最重要的答案:)
然后我只使用父查询中正在更新的行加入子查询。行号是正确的,所以现在我只是做一个案例。如果这是第一次清理的主题存在(例如row_number = 1),请不要修改它。否则,将row_number减去1。这意味着同一主题的第二个实例,唯一主题将是=> cleansubject +'1'。
我认为我需要有一个索引视图的原因是因为如果我有两个非常相似的主题,当你已经剥离(即清理)所有坏字符(我已经确定是坏的) )..两个干净的科目可能是相同的。因此,我需要在cleaningSubject上进行所有连接,而不是主题。现在,对于我拥有的大量行,当我没有视图时,这是性能的废话。 :)
那么..这是过度设计的吗?
重构了触发器代码,因此它具有更高的性能。