问题
In the answers on this case
it was suggested that I should not use cursor because of performance reasons. What are the best practices to loop over the update data in an update trigger ?
UPDATE:
The following is the TSQL for the creation of that update trigger.
CREATE TRIGGER [dbo].[trAfterUpdateInfoDoc]
ON [dbo].[InfoDocs]
AFTER UPDATE
AS
BEGIN
DECLARE @infodoctemplateid INT;
DECLARE @infodocid INT;
DECLARE @requireccount FLOAT(2);
DECLARE @filledcount FLOAT(2);
DECLARE @pcnt FLOAT(2);
DECLARE c CURSOR FOR
SELECT id
FROM InfoDocs ifd
WHERE exists (SELECT 1 FROM Inserted AS i WHERE i.id = ifd.id)
OPEN c
FETCH NEXT FROM c INTO @infodocid
WHILE @@Fetch_Status = 0
BEGIN
SELECT @infodoctemplateid = InfoDocTemplateId
FROM InfoDocs
WHERE id = @infodocid;
SELECT @requireccount = COUNT(*)
FROM InfoDocTemplateFields
WHERE InfoDocTemplateId = @infodoctemplateid
AND IsRequired = 1;
IF (@requireccount = 0)
BEGIN
set @pcnt = 100;
END
ELSE
BEGIN
select @filledcount = count(*) from InfoDocFields
where InfoDocId = @infodocid
and InfoDocTemplateFieldId in (select id from InfoDocTemplateFields where InfoDocTemplateId = @infodoctemplateid and IsRequired = 1)
and (BooleanValue is not null or (StringValue is not null and StringValue <> '') or IntValue is not null or DateValue is not null)
set @pcnt = @filledcount / @requireccount * 100.0;
END
update InfoDocs set PercentageCompleted = @pcnt Where id = @infodocid;
Fetch next From c into @infodocid
End
Close c
Deallocate c
END
回答1:
I've tried translating your cursor into a set based code, however there is no way for me to test if my solution is correct, and I didn't get much sleep last night so I might have missed some things here and there - and it probably can be a shorter and more efficient code than what I've written, but it should give you a good place to start:
CREATE TRIGGER [dbo].[trAfterUpdateInfoDoc]
ON [dbo].[InfoDocs]
AFTER UPDATE
AS
BEGIN
WITH CTE1 AS
(
SELECT ifd.Id,
SUM(CASE WHEN IsRequired = 1 THEN 1 ELSE 0 END) As RequiredCount,
(
select count(*)
from InfoDocFields
where InfoDocFields.InfoDocId = ifd.Id,
and InfoDocTemplateFieldId in (
select id
from InfoDocTemplateFields
where InfoDocTemplateId = idtf.InfoDocTemplateId
and IsRequired = 1
)
and
InfoDocFields.BooleanValue is not null
or (InfoDocFields.StringValue is not null and InfoDocFields.StringValue <> '')
or InfoDocFields.IntValue is not null
or InfoDocFields.DateValue is not null
) As Filledcount
FROM InfoDocs ifd
JOIN InfoDocTemplateFields idtf
ON ifd.InfoDocTemplateId = idtf.InfoDocTemplateId
WHERE exists (SELECT 1 FROM Inserted AS i WHERE i.id = ifd.id)
GROUP BY ifd.Id, idtf.InfoDocTemplateId
), CTE2 AS
(
SELECT ifd.Id,
CASE WHEN RequiredCount = 0 THEN
100
ELSE
Filledcount / RequiredCount * 100.0
END As Completed
FROM CTE1
)
UPDATE docs
SET PercentageCompleted = Completed
FROM InfoDocs docs
JOIN cte2
ON docs.id = cte2.Id
END
回答2:
You can get rid of the cursor by doing an update with a join.
E.g.
UPDATE t1
SET Col2 = t2.Col2,
Col3 = t2.Col3
FROM Table1 t1
INNER JOIN Table2 t2 ON t1.Col1 = t2.Col1
WHERE t1.Col1 IN (21, 31)
This will get you the best possible performance. And the code will be more compact and easier to understand.
来源:https://stackoverflow.com/questions/53222867/tsql-alternative-for-cursor-to-loop-over-update-trigger-data