Convirtió una función escalar en una función TVF para ejecución paralela: todavía se ejecuta en modo serie

10

Una de mis consultas se estaba ejecutando en modo de ejecución en serie después de un lanzamiento y noté que se usaron dos nuevas funciones en una vista a la que se hace referencia en la consulta LINQ to SQL generada desde la aplicación. Así que convertí esas funciones ESCALAR en funciones TVF, pero la consulta aún se ejecuta en modo serie.

Anteriormente hice la conversión de Scalar a TVF en algunas otras consultas y resolvió el problema de la ejecución en serie forzada.

Aquí está la función escalar:

CREATE FUNCTION [dbo].[FindEventReviewDueDate]
(
       @EventNumber VARCHAR(20),
       @EventID VARCHAR(25),
          @EventIDDate BIT
)

RETURNS DateTime
AS
BEGIN

DECLARE @CurrentEventStatus VARCHAR(20)
DECLARE @EventDateTime DateTime
DECLARE @ReviewDueDate DateTime


SELECT @CurrentEventStatus = (SELECT cis.EventStatus
                                 FROM CurrentEventStatus cis 
                                 INNER JOIN Event1 r WITH (NOLOCK) ON (cis.Event1Id = r.Id)
                                 WHERE (r.EventNumber = @EventNumber) AND r.EventID = @EventID)

SELECT @EventDateTime = (SELECT EventDateTime FROM Event1 r 
                          WHERE (r.EventNumber = @EventNumber) AND r.EventID = @EventID)

IF @CurrentEventStatus IN ('0','6') AND EventIDDate = 1
BEGIN

       SET @ReviewDueDate = DATEADD(DAY, 30, @EventDateTime)

       WHILE @ReviewDueDate < getdate() 
             SET @ReviewDueDate = DATEADD(DAY, 30, @ReviewDueDate)

       DECLARE @EventDateJournalDate DateTime

       SELECT @EventDateJournalDate = (SELECT TOP 1 ij.Date
                                       FROM EventPage_EventJournal ij 
                                       INNER JOIN EventJournalPages p ON ij.PageId = p.Id 
                                       INNER JOIN Journal f ON p.FormId = f.Id 
                                       INNER JOIN Event1 r WITH (NOLOCK) ON (f.Event1Id = r.Id)
                                       WHERE (r.EventNumber = @EventNumber AND r.EventID = @EventID) AND ij.ReviewType = 'Supervisor Monthly Review' ORDER BY ij.Date DESC)

      IF(DATEADD(DAY, 30, @EventDateTime) < getdate() AND
           (@EventDateJournalDate is null OR DATEADD(DAY, 30, @EventDateJournalDate) < getdate()) AND
              DATEADD(DAY, 14, @ReviewDueDate) > DATEADD(DAY, 30, getdate()))
                  SET @ReviewDueDate = DATEADD(DAY, -30, @ReviewDueDate)
         ELSE IF((@EventDateJournalDate is not null ) AND (DATEADD(DAY, 30, @EventDateJournalDate) >= @ReviewDueDate))
                  SET @ReviewDueDate = DATEADD(DAY, 30, @ReviewDueDate)

END
RETURN @ReviewDueDate

END

Aquí está la función TVF convertida.

CREATE FUNCTION [dbo].[FindEventReviewDueDate_test]
(
       @EventNumber VARCHAR(20),
       @EventID VARCHAR(25),
          @EventIDDate BIT
)

RETURNS @FunctionResultTableVairable TABLE (
 CurrentEventStatus varchar(20),
 Event1DateTime DateTime,
 ReviewDueDate DateTime
 )
AS 
BEGIN

DECLARE @CurrentEventStatus VARCHAR(20)
DECLARE @EventDateTime DateTime
DECLARE @ReviewDueDate DateTime


SELECT @CurrentEventStatus = (SELECT cis.EventStatus
                                 FROM CurrentEventStatus cis 
                                 INNER JOIN Event1 r WITH (NOLOCK) ON (cis.Event1Id = r.Id)
                                 WHERE (r.EventNumber = @EventNumber) AND r.EventID = @EventID)

SELECT @EventDateTime = (SELECT EventDateTime FROM Event1 r 
                          WHERE (r.EventNumber = @EventNumber) AND r.EventID = @EventID)

IF @CurrentEventStatus IN ('0','6') AND EventIDDate = 1
BEGIN

       SET @ReviewDueDate = DATEADD(DAY, 30, @EventDateTime)

       WHILE @ReviewDueDate < getdate() 
             SET @ReviewDueDate = DATEADD(DAY, 30, @ReviewDueDate)

       DECLARE @EventDateJournalDate DateTime

       SELECT @EventDateJournalDate = (SELECT TOP 1 ij.Date
                                       FROM EventPage_EventJournal ij 
                                       INNER JOIN EventJournalPages p ON ij.PageId = p.Id 
                                       INNER JOIN Journal f ON p.FormId = f.Id 
                                       INNER JOIN Event1 r WITH (NOLOCK) ON (f.Event1Id = r.Id)
                                       WHERE (r.EventNumber = @EventNumber AND r.EventID = @EventID) AND ij.ReviewType = 'Supervisor Monthly Review' ORDER BY ij.Date DESC)

      IF(DATEADD(DAY, 30, @EventDateTime) < getdate() AND
           (@EventDateJournalDate is null OR DATEADD(DAY, 30, @EventDateJournalDate) < getdate()) AND
              DATEADD(DAY, 14, @ReviewDueDate) > DATEADD(DAY, 30, getdate()))
                  SET @ReviewDueDate = DATEADD(DAY, -30, @ReviewDueDate)
         ELSE IF((@EventDateJournalDate is not null ) AND (DATEADD(DAY, 30, @EventDateJournalDate) >= @ReviewDueDate))
                  SET @ReviewDueDate = DATEADD(DAY, 30, @ReviewDueDate)
                   insert into @FunctionResultTableVairable
      select @CurrentEventStatus,@EventDateTime,@ReviewDueDate          

END
return;
END

GO

¿Hay algún problema con mi implementación de la función TVF que impide que la consulta se ejecute en modo paralelo?

Yo uso la función TVF en la consulta de la siguiente manera;

select ReviewDueDate from dbo.FunctionResultTableVairable('a','b','c')

Mi consulta real que usa la vista es bastante compleja y si comento la parte de la función en la vista y al ejecutarla, la consulta se ejecuta en paralelo, por lo que es la función la que obliga a la consulta a ejecutarse en paralelo.

Mi consulta real está en el siguiente formato.

select 
dv.column1,
dv.column2,
---------
---------
--------
(select ReviewDueDate from dbo.FunctionResultTableVairable('a','b','c')) AS 'Columnx'
from
DemoView dv
Where 
condition1
conditon 2

Cualquier ayuda es apreciada.

usuario9516827
fuente
3
¿Qué dice el plan de consulta?
David Browne - Microsoft
2
Además de que hay una gran diferencia entre un TVF en línea y un TVF de múltiples instrucciones, si su TVF devuelve la misma fila para cada fila en la consulta externa (ya que solo toma constantes), y solo le importa una columna de salida , ¿por qué lo pones en una subconsulta en la lista de selección? Esto solo hace posible ejecutar una y otra vez sin ninguna razón. Asigne el resultado a una variable y luego use la variable en su consulta.
Aaron Bertrand

Respuestas:

5

¿Es posible convertir mi función escalar a Inline TVF?

Si. Algo como lo siguiente lo haría.

Todavía es bastante fuerte y si se correlacionara probablemente sería bastante ineficiente. Como Aaron señala en los comentarios, está llamando a esto con valores constantes, aunque esperamos que el plan de consulta refleje esto y solo lo ejecute una vez.

CREATE FUNCTION [dbo].[FindEventReviewDueDateInline] (@EventNumber VARCHAR(20),
                                                      @EventID     VARCHAR(25),
                                                      @EventIDDate BIT)
RETURNS TABLE
AS
    RETURN
      WITH X
           AS (SELECT cis.EventStatus AS CurrentEventStatus,
                      r.EventDateTime
               FROM   CurrentEventStatus cis
                      INNER JOIN Event1 r
                              ON cis.Event1Id = r.Id
               WHERE  r.EventNumber = @EventNumber
                      AND r.EventID = @EventID
                      AND cis.EventStatus IN ( '0', '6' )
                      AND @EventIDDate = 1)
      SELECT X.CurrentEventStatus,
             X.EventDateTime,
             CA4.ReviewDueDate
      FROM   X
             --SET @ReviewDueDate = DATEADD(DAY, 30, @EventDateTime)
             CROSS APPLY(VALUES(DATEADD(DAY, 30, X.EventDateTime))) CA1(ReviewDueDate)
             -- WHILE @ReviewDueDate < getdate() 
             --       SET @ReviewDueDate = DATEADD(DAY, 30, @ReviewDueDate)
             CROSS APPLY(VALUES( IIF(CA1.ReviewDueDate >= GETDATE(), CA1.ReviewDueDate, DATEADD(DAY, 30 * CEILING(( IIF(CAST(GETDATE() AS TIME) > CAST(CA1.ReviewDueDate AS TIME), 1, 0)
                                                                                                           + DATEDIFF(DAY, CA1.ReviewDueDate, GETDATE()) ) / 30.0), CA1.ReviewDueDate)))) CA2(ReviewDueDate)
             --SELECT @EventDateJournalDate = ....
             CROSS APPLY(SELECT TOP 1 ij.Date
                         FROM   EventPage_EventJournal ij
                                INNER JOIN EventJournalPages p
                                        ON ij.PageId = p.Id
                                INNER JOIN Journal f
                                        ON p.FormId = f.Id
                                INNER JOIN Event1 r WITH (NOLOCK)
                                        ON ( f.Event1Id = r.Id )
                         WHERE  ( r.EventNumber = @EventNumber
                                  AND r.EventID = @EventID )
                                AND ij.ReviewType = 'Supervisor Monthly Review'
                         ORDER  BY ij.Date DESC) CA3(EventDateJournalDate)
             -- IF(DATEADD(DAY, 30, @EventDateTime) < getdate()
             CROSS APPLY(VALUES ( CASE
                          WHEN ( DATEADD(DAY, 30, X.EventDateTime) < GETDATE()
                                 AND ( CA3.EventDateJournalDate IS NULL
                                        OR DATEADD(DAY, 30, CA3.EventDateJournalDate) < GETDATE() )
                                 AND DATEADD(DAY, 14, CA2.ReviewDueDate) > DATEADD(DAY, 30, GETDATE()) )
                            THEN DATEADD(DAY, -30, CA2.ReviewDueDate)
                          WHEN( ( CA3.EventDateJournalDate IS NOT NULL )
                                AND ( DATEADD(DAY, 30, CA3.EventDateJournalDate) >= CA2.ReviewDueDate ) )
                            THEN DATEADD(DAY, 30, CA2.ReviewDueDate)
                          ELSE CA2.ReviewDueDate
                        END )) CA4(ReviewDueDate); 
Martin Smith
fuente
11

Forrest tiene razón en su mayoría , pero los detalles más finos son:

SQL Server no puede paralelizar modificaciones a las variables de tabla, que utiliza su función.

Antes de la ejecución intercalada de SQL Server 2017 , las estimaciones de filas de las funciones valoradas en la tabla de varias instrucciones eran muy bajas.

Un efecto secundario de esto es que los planes tuvieron un costo muy bajo en el extremo inferior y, a menudo, no romperían el umbral de costo para el paralelismo.

Erik Darling
fuente
1
Bien, ¿puede sugerir alguna solución alternativa para que mi consulta principal pueda ejecutarse en modo paralelo? Mirando mi función escalar, ¿es posible convertir mi función escalar a Inline TVF?
user9516827
1
@ user9516827 probablemente podría encadenar algunos CTE juntos para hacer algo similar, pero no tengo idea si iría en paralelo, funcionaría mejor, etc. Probarlo depende de usted.
Erik Darling
@MartinSmith: Mi consulta real es una consulta muy compleja con muchas combinaciones y combinaciones para vistas, etc. Esta función se usa para una columna de selección en la consulta principal y es por eso que estaba tratando de arreglar eso. También es un LINQ -SQL (exec sp_executesql form) generó una consulta y tengo la limitación de no hackear el código .net.Gracias
user9516827
10

SQL Server no puede paralelizar TVF de múltiples declaraciones, que es lo que es suyo. Solo los TVF en línea se pueden paralelizar.

Para descanso
fuente
1
Como tengo variables allí, ¿no puedo convertir la función escalar a Inline TVF?
user9516827