SQL Server MERGE语句中的性能调用标量函数

时间:2019-05-05 12:07:54

标签: sql sql-server performance function

我有一个带有MERGE语句的代码,该语句调用了标量函数,但性能却很差。

这段代码可能要花费我30分钟甚至更长的时间,有时甚至会在中间失败。

我的代码:

MERGE INTO Table1 AS md
USING (SELECT * FROM Table2 edb
       WHERE edb.FileId = @FileId
         AND edb.IsRowError = 0) edbTable ON dbo.GetId(edbTable.Filed1, edbTable.Filed2, edbTable.Filed3, edbTable.Filed4, edbTable.Filed5, edbTable.Filed6) = md.Id
                                          AND md.IsActive = 1

WHEN MATCHED THEN 
    UPDATE 
        SET .
         .
         .

功能:

ALTER FUNCTION [dbo].[GetId] 
    (@Filed1 NVARCHAR(9),
     @Filed2 NVARCHAR(9),
     @Filed3 NVARCHAR(13),
     @Filed4 INT,
     @Filed5 DATE,
     @Filed6 INT)
RETURNS INT
AS
BEGIN
    DECLARE @Id INT = NULL

    IF ((@Filed5 IS NULL) OR (@Filed6 = 0))
    BEGIN
        SET @Id = (SELECT TOP 1 md.Id 
                   FROM Table1 md with(NOLOCK)
                   JOIN Table3 mb with(NOLOCK) ON md.Table3Id = mb.Id
                   WHERE (mb.Filed1 = @Filed1 AND mb.Filed2 = @Filed2 
                          OR mb.Filed1 = @Filed2 AND mb.Filed2 = @Filed1)
                     AND mb.Filed7 = 1
                     AND @Filed3 = md.Filed3 
                     AND @Filed4 = md.Filed4 
                   ORDER BY md.LastUpdateDate DESC)

        IF @Id IS NOT NULL
             RETURN @Id 
     ELSE 
         SET @Id = (SELECT TOP 1 md.Id 
                    FROM Table1 md with(NOLOCK)
                    JOIN Table3 mb with(NOLOCK) ON md.Table3Id = mb.Id
                    WHERE (mb.Filed1 = @Filed1 OR mb.Filed1 = @Filed1)
                      AND mb.Filed7 = 1
                      AND @Filed3 = md.Filed3 
                      AND Filed4 = md.Filed4 
                      AND @Filed1 > 0
                    ORDER BY  md.LastUpdateDate DESC)

         IF @Id IS NOT NULL
             RETURN @Id 
         ELSE 
             set @Id =(select top 1 md.Id from Table1 md with(NOLOCK)
                                            join Table3 mb with(NOLOCK) on md.Table3Id = mb.Id
                                            where (mb.Filed1 = @Filed2 
                                                or mb.Filed2 = @Filed2 )
                                              and mb.Filed7 = 1
                                              and @Filed3 = md.Filed3 
                                              and @Filed4 = md.Filed4 
                                              and @Filed2 >0
                                            order by md.LastUpdateDate desc)
 end
 else
 begin
   set @Id = (select top 1 md.Id from Table1 md with(NOLOCK)
                                            join Table3 mb with(NOLOCK) on md.Table3Id = mb.Id
                                            where (mb.Filed1 = @Filed1 and mb.Filed2 = @Filed2 
                                               or mb.Filed1 = @Filed2 and mb.Filed2 = @Filed1 )
                                              and mb.Filed7 = 1
                                              and @Filed3 = md.Filed3 
                                              and @Filed4 = md.Filed4 
                                              and (@Filed5 = md.ContractDate or (@Filed5 is null and md.ContractDate is null))
                                              and @Filed6 = md.PurchasePrice)
   if @Id is not NULL
      RETURN @Id 
   else
      set @Id =(select top 1 md.Id from Table1 md with(NOLOCK)
                                            join Table3 mb with(NOLOCK) on md.Table3Id = mb.Id
                                            where (mb.Filed1 = @Filed1 
                                                or mb.Filed2 = @Filed1 )
                                              and mb.Filed7 = 1
                                              and @Filed3 = md.Filed3 
                                              and @Filed4 = md.Filed4 
                                              and (@Filed5 = md.ContractDate or (@Filed5 is null and md.ContractDate is null))
                                              and @Filed6  = md.PurchasePrice
                                              and @Filed1 >0)
   if @Id is not NULL
      RETURN @Id 
   else
      set @Id =(select top 1 md.Id from Table1 md with(NOLOCK)
                                            join Table3 mb with(NOLOCK) on md.Table3Id = mb.Id
                                            where (mb.Filed1 = @Filed2 
                                                or mb.Filed2 = @Filed2 )
                                              and mb.Filed7 = 1
                                              and @Filed3 = md.Filed3 
                                              and @Filed4 = md.Filed4 
                                              and (@Filed5 = md.ContractDate or (@Filed5 is null and md.ContractDate is null))
                                              and @Filed6 = md.PurchasePrice
                                              and @Filed2 >0)
   if @Id is not NULL
      RETURN @Id 
  end


    RETURN @Id 

END

我需要更改什么? 如何更改代码?

我需要所有If状态,因为对于要检索的记录有一个优先选择,所以我无法将其转换为join。 因为如果有一些join,他们会给我一个肯定的答案,那么它将给我列出所有来自该人群的人的名单,因此,重要的是要知道这是由于第一次加入还是第二次或第三次加入,因为我有优先选择权。

如果我不是将if改为join,是否可以提取出第一行?就是说:如果我加入,并且第一个没有结果,第二个有结果,而在第三个中,则仅返回第二个而不是第三个的结果-有这样的事情吗?

1 个答案:

答案 0 :(得分:0)

好的,这是您逻辑的精确复制,但是作为内联表值函数。在必要条件相同的情况下,很有可能进一步削减这一比例。例如,mb.File7 = 1 AND @Filed3 = md.Filed3 AND @Filed4 = md.Filed4出现在每个 WHEN子句中,因此应将其从所有WHEN子句中移出并放入WHERE。这样可能会使查询的性能更高,也更容易阅读。

无论如何,这执行相同的逻辑。我在代码中添加了一些注释,不过您可能想看看:

ALTER FUNCTION dbo.GetID (@Filed1 nvarchar(9), --Honestly, give these better names. These parameters don't tell you anything about what they are
                                               --which'll make it harder for people who don't know your function to use it.
                          @Filed2 nvarchar(9),
                          @Filed3 nvarchar(13),
                          @Filed4 int,
                          @Filed5 date,
                          @Filed6 int)
RETURNS TABLE AS RETURN

    SELECT TOP 1 --This may be a bad idea, we'll see
           CASE WHEN @Filed5 IS NULL OR @Filed6 = 0 THEN CASE WHEN (mb.Filed1 = @Filed1 AND mb.Filed2 = @Filed2 
                                                                OR  mb.Filed1 = @Filed2 AND mb.Filed2 = @Filed1)
                                                               AND mb.Filed7 = 1
                                                               AND @Filed3 = md.Filed3 
                                                               AND @Filed4 = md.Filed4 THEN md.Id
                                                              WHEN mb.Filed1 = @Filed1
                                                               AND mb.Filed7 = 1
                                                               AND @Filed3 = md.Filed3 
                                                               AND @Filed4 = md.Filed4 
                                                               AND @Filed1 > 0 THEN md.ID
                                                              WHEN @filed2 IN (mb.Filed1, mb.Filed2)
                                                               AND mb.Filed7 = 1
                                                               AND @Filed3 = md.Filed3 
                                                               AND @Filed4 = md.Filed4 
                                                               AND @Filed2 >0 THEN md.ID
                                                         END
                     --None of these in your original had an ORDER BY clause, was this intentional?
                     --If so, I assume you want a random row, instead. So you'll need a further
                     --CASE expression in your ORDER BY for these, and then the return either NEWID or md.LastUpdateDate
                ELSE CASE WHEN (mb.Filed1 = @Filed1   --Your parenthesis seem wrong here
                           AND  mb.Filed2 = @Filed2   --YOu have an OR in the middle
                            OR  mb.Filed1 = @Filed2   --So I doubt this does what you want
                           AND  mb.Filed2 = @Filed1 ) --But I haven't correct, as I don't know what it should do
                           AND mb.Filed7 = 1
                           AND @Filed3 = md.Filed3 
                           AND @Filed4 = md.Filed4 
                           AND (@Filed5 = md.ContractDate
                            OR  (@Filed5 IS NULL AND md.ContractDate IS))
                           AND @Filed6 = md.PurchasePrice THEN md.ID
                          WHEN (mb.Filed1 = @Filed1 
                            OR  mb.Filed2 = @Filed1)
                           AND mb.Filed7 = 1
                           AND @Filed3 = md.Filed3 
                           AND @Filed4 = md.Filed4 
                           AND (@Filed5 = md.ContractDate
                            OR (@Filed5 IS NULL AND md.ContractDate IS NULL))
                           AND @Filed6  = md.PurchasePrice
                           AND @Filed1 >0 THEN md.Id
                          WHEN (mb.Filed1 = @Filed2 
                            OR  mb.Filed2 = @Filed2)
                           AND mb.Filed7 = 1
                           AND @Filed3 = md.Filed3 
                           AND @Filed4 = md.Filed4 
                           AND (@Filed5 = md.ContractDate
                            OR  (@Filed5 IS NULL AND md.ContractDate IS NULL))
                           AND @Filed6 = md.PurchasePrice
                           AND @Filed2 >0 THEN md.Id
                     END
           END AS Id                         
    FROM dbo.Table1 md --Why md when the table is called "Table1". I assume this is anonymised
         JOIN dbo.Table3 md ON md.Table3Id = mb.Id
    ORDER BY md.LastUpdateDate DESC;
GO