Запрос занимает слишком много времени для выполнения со скалярной функцией в предложении where

Ниже моя скалярная функция:

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

person Mayur Pawar    schedule 20.01.2016    source источник
comment
Это потому, что скалярные функции ужасно неэффективны. И когда вы помещаете его в предложение where, это делает запрос невозможным для SARG. Другими словами, необходимо вычислить значение вашей функции для каждой отдельной строки в таблице. Вероятно, вы можете преобразовать вашу скалярную функцию во встроенную функцию с табличным значением, и производительность будет почти такой же быстрой, как и при исключении функции, которую вы видите сейчас.   -  person Sean Lange    schedule 21.01.2016
comment
Вероятно, вы можете заменить использование функции на SQL EXISTS и значительно повысить производительность.   -  person Nick.McDermaid    schedule 21.01.2016
comment
обязательно использовать функцию, а не использовать внутренний подзапрос   -  person Ajay2707    schedule 21.01.2016


Ответы (2)


Это просто выстрел в темноту, потому что нам не предоставили ни 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
person Sean Lange    schedule 21.01.2016
comment
Спасибо за ответ. Попробовали вашу функцию, но, похоже, производительность не изменилась. - person Mayur Pawar; 22.01.2016
comment
Вы должны измерять производительность, а не просто запускать ее и смотреть, сколько времени это займет. Посмотрите на план выполнения и выясните, где находится узкое место. Функция, которую я разместил здесь, будет выполнять скалярную функцию и является более гибкой. Но проблема может заключаться в запросах внутри функции. У нас нет определений таблиц или плана выполнения, чтобы увидеть, что происходит на самом деле. Мы просто предполагаем, в чем проблема, скорее всего. - person Sean Lange; 22.01.2016

Вы можете оптимизировать запрос скалярной функции, чтобы уменьшить многократное чтение. Нравиться:

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. Чтобы изучить ваши планы запросов (дизайн и выполнение) и соответствующим образом оптимизировать их.
person Aasish Kr. Sharma    schedule 21.01.2016
comment
Это все еще скалярная функция, и OP вызывает ее из предложения where. Это будет быстрее, но едва ли. Это по-прежнему скалярная функция, не поддерживающая SARG. - person Sean Lange; 21.01.2016
comment
Это верно, но я думаю, что скалярная функция здесь не большая проблема. Проблема заключается в операторе LIKE, который используется здесь. Запрос просто подсчитывает запись, поэтому он может работать лучше, если у него есть индекс для DBR_SERIES. - person Aasish Kr. Sharma; 22.01.2016
comment
Мы не знаем, есть ли индекс в этом столбце. Проблема может заключаться в операторе LIKE, но мы даже не знаем, что передается в качестве значений. Если это ведущий подстановочный знак, никакая индексация не поможет. И, конечно же, пока вы придерживаетесь скалярной функции, индексация бессмысленна, потому что она не поддерживает SARG. - person Sean Lange; 22.01.2016
comment
Я добавил еще несколько вещей, я думаю, что это станет решением. - person Aasish Kr. Sharma; 26.01.2016