查询花费太长时间在where子句中使用标量函数执行

时间:2016-01-20 22:26:05

标签: sql-server sql-server-2008 sql-server-2008-r2

以下是我的标量函数:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER FUNCTION [CheckClients]
(
    @UserId Varchar(3),
    @DbrNo varchar(10),
    @V_DBR_CLIENT varchar(6)
)
RETURNS int
AS
BEGIN
     Declare @Flag int
     set @Flag=1

     if(@V_DBR_CLIENT='XXXXXX')
     BEGIN
         if((select COUNT(USR_CLI) 
             from USRAGYCLI 
             inner join DBR on DBR_CLIENT = USR_CLI 
             where USR_CODE = @UserId and DBR_SERIES like @DbrNo +'T') <> 
            (select COUNT(DBR_CLIENT) 
             from DBR 
             where DBR_SERIES like @DbrNo + 'T') OR 
            (select COUNT(DBR_CLIENT) 
             from DBR 
             where DBR_SERIES like @DbrNo +'T') <= 0)
         BEGIN
             set @Flag=0
         END
      END

      RETURN @Flag
END

这是我的存储过程:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE [SEL_CLI]
    @V_USER_ID VARCHAR(3),
    @V_NUMBER_OF_ROWS INT,
    @V_STARTS_WITH INT  
AS
BEGIN
    CREATE TABLE #tmpDbrNo 
    (
         Code VARCHAR(10),
         Name VARCHAR(100),
         NumberOfDebtors int,
         rownum int 
    )   

    ;WITH Temp AS
    (
        SELECT 
            CLT_NO AS Code,
            CLT_NAME AS Name,
            COUNT(DBR_NO) AS NumberOfDebtors
        FROM
            DBR 
        JOIN 
            USRAGYCLI ON DBR_CLIENT = USR_AGY_CLI
        JOIN 
            CLT ON DBR_CLIENT = CLT_NO
        WHERE       
            AND USR_CODE = @V_USER_ID           
            AND 1 = CheckClients(@V_USER_ID, DBR_NO, DBR_CLIENT)
        GROUP BY        
            CLT_NO, CLT_NAME
    )               
    INSERT INTO #tmpDbrNo   
        SELECT 
            Code, Name, NumberOfDebtors, 
            ROW_NUMBER() OVER (ORDER by Code) rownum 
        FROM
            Temp

    SELECT 
        Code, Name, NumberOfDebtors  
    FROM
        #tmpDbrNo 
    WHERE
        rownum BETWEEN @V_STARTS_WITH AND @V_STARTS_WITH + @V_NUMBER_OF_ROWS
END

上面的查询执行大约需要25秒,等待时间太长。如果我在where子句中注释掉我调用标量函数的行,则执行查询需要0秒。

任何人都可以提出更好的方法来执行查询吗?我试图在下面的情况下调用函数,但没有成功。

AND 1 = CASE WHEN DBR_CLIENT='XXXXXX' THEN CheckClients(@V_USER_ID,DBR_NO,DBR_CLIENT) ELSE 1 END

2 个答案:

答案 0 :(得分:0)

您可以优化标量函数查询以减少多次读取。像:

ALTER FUNCTION [CheckClients] (
    @UserId VARCHAR(3),
    @DbrNo VARCHAR(10),
    @V_DBR_CLIENT VARCHAR(6)
    )
RETURNS INT
AS
BEGIN
    DECLARE @Flag INT

    SET @Flag = 1

    IF (@V_DBR_CLIENT = 'XXXXXX')
    BEGIN

        DECLARE @Count INT = ISNULL((
                SELECT COUNT(DBR_CLIENT)
                FROM DBR
                WHERE DBR_SERIES LIKE @DbrNo + 'T'
            ), 0);

        IF (
                (ISNULL((
                    SELECT COUNT(USR_CLI)
                    FROM USRAGYCLI
                    INNER JOIN DBR ON DBR_CLIENT = USR_CLI
                    WHERE USR_CODE = @UserId
                        AND DBR_SERIES LIKE @DbrNo + 'T'
                ), 0) <> @Count)
                OR (@Count <= 0)
            )
        BEGIN
            SET @Flag = 0
        END

    END

    RETURN @Flag
END

此外,您需要研究查询的执行计划,以找出查询执行时间较长的位置。并在必要时创建非聚集索引。

- 以后编辑 -

非可疑问题(调用标量函数):

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE [SEL_CLI]
    @V_USER_ID VARCHAR(3),
    @V_NUMBER_OF_ROWS INT,
    @V_STARTS_WITH INT  
AS
BEGIN
    CREATE TABLE #tmpDbrNo 
    (
         Code VARCHAR(10),
         Name VARCHAR(100),
         NumberOfDebtors int,
         rownum int 
    )   

    ;WITH Temp AS
    (
        SELECT 
            CLT_NO AS Code,
            CLT_NAME AS Name,
            COUNT(DBR_NO) AS NumberOfDebtors
        FROM
            DBR 
        JOIN 
            USRAGYCLI ON DBR_CLIENT = USR_AGY_CLI
        JOIN 
            CLT ON DBR_CLIENT = CLT_NO
        WHERE       
            USR_CODE = @V_USER_ID           
            AND 1 = 
            (CASE 
                WHEN (@V_DBR_CLIENT = 'XXXXXX') THEN 
                    (CASE 
                        WHEN (
                                ISNULL((
                                    SELECT COUNT(USR_CLI)
                                    FROM USRAGYCLI
                                    INNER JOIN DBR ON DBR_CLIENT = USR_CLI
                                    WHERE USR_CODE = @UserId
                                        AND DBR_SERIES LIKE @DbrNo + 'T'
                                ), 0) <> ISNULL((
                                    SELECT COUNT(DBR_CLIENT)
                                    FROM DBR
                                    WHERE DBR_SERIES LIKE @DbrNo + 'T'
                                ), 0)
                            )
                            OR (ISNULL((
                                    SELECT COUNT(DBR_CLIENT)
                                    FROM DBR
                                    WHERE DBR_SERIES LIKE @DbrNo + 'T'
                                ), 0) <= 0)            
                        THEN 0 
                        ELSE 1 
                    END)
                ELSE 1 
            END)--CheckClients(@V_USER_ID, DBR_NO, DBR_CLIENT)
        GROUP BY        
            CLT_NO, CLT_NAME
    )               
    INSERT INTO #tmpDbrNo   
        SELECT 
            Code, Name, NumberOfDebtors, 
            ROW_NUMBER() OVER (ORDER by Code) rownum 
        FROM
            Temp

    SELECT 
        Code, Name, NumberOfDebtors  
    FROM
        #tmpDbrNo 
    WHERE
        rownum BETWEEN @V_STARTS_WITH AND @V_STARTS_WITH + @V_NUMBER_OF_ROWS
END

正如您所看到的,标量函数可以包含在同一查询中,但是如果您很好地研究函数,那么标量函数中的查询显然不完全依赖于存储过程中的查询。它正在计数,并且每次都会重新读取和操作表格中的数据。

因此,使用这种类型的查询使得Sargable的非Sargable不会提高性能。该问题的可能解决方案是

  1. 要先在表格中添加所需数据,然后从那里查看。
  2. 研究您的查询计划(设计和执行)并相应地对其进行优化。

答案 1 :(得分:0)

这只是一个黑暗的镜头,因为我们没有提供任何ddl或很多工作。我想我正确地解释了标量函数中的现有逻辑。作为一般规则,您应该避免使用标志。这是一个非常古老的学校思维方式,根本不适合关系数据。我怀疑通过了解实际需求可以大大改善这一点,但这是我能用有限的细节做的最好的。

CREATE FUNCTION [CheckClients]
(
    @UserId Varchar(3),
    @DbrNo varchar(10),
    @V_DBR_CLIENT varchar(6)
)
RETURNS table as return

with RowCounts as
(
    select
    (
        select COUNT(DBR_CLIENT) 
        from DBR 
        where DBR_SERIES like @DbrNo + 'T'
    ) as ClientCount
    , 
    (
        select COUNT(USR_CLI) 
        from USRAGYCLI u
        inner join DBR d on d.DBR_CLIENT = u.USR_CLI 
        where u.USR_CODE = @UserId 
            and d.DBR_SERIES like @DbrNo +'T'
    ) as UserCount
)

select case 
    when @V_DBR_CLIENT = 'XXXXXX' then
        Case when rc.UserCount <> rc.ClientCount then 0
            when rc.ClientCount < 0 then 0
            else 1
        end
    else 1
    end as Flag
from RowCounts rc