TSQL alternative for cursor to loop over Update-tr

2019-08-27 19:13发布

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

2条回答
时光不老,我们不散
2楼-- · 2019-08-27 19:41

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
查看更多
叛逆
3楼-- · 2019-08-27 19:45

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.

查看更多
登录 后发表回答