9

Consider the following query that unpivots a few handfuls of scalar aggregates:

SELECT A, B
FROM (
    SELECT 
      MAX(CASE WHEN ID = 1 THEN 1 ELSE 0 END) VAL1
    , MAX(CASE WHEN ID = 2 THEN 1 ELSE 0 END) VAL2
    , MAX(CASE WHEN ID = 3 THEN 1 ELSE 0 END) VAL3
    , MAX(CASE WHEN ID = 4 THEN 1 ELSE 0 END) VAL4
    , MAX(CASE WHEN ID = 5 THEN 1 ELSE 0 END) VAL5
    , MAX(CASE WHEN ID = 6 THEN 1 ELSE 0 END) VAL6
    , MAX(CASE WHEN ID = 7 THEN 1 ELSE 0 END) VAL7
    , MAX(CASE WHEN ID = 16 THEN 1 ELSE 0 END) VAL16
    FROM dbo.PARALLEL_ZONE_REPRO
) q
UNPIVOT(B FOR A IN (
    VAL1
    ,VAL2
    ,VAL3
    ,VAL4
    ,VAL5
    ,VAL6
    ,VAL7
    ,VAL16
)) U
OPTION (MAXDOP 4);

On SQL Server 2017, I get a plan with two parallel branches. The left parallel branch feels out of place to me. The optimizer has a guarantee that there will be only a single row output from the global scalar aggregate, yet the parent operator of it is a Distribute Streams with round robin partitioning:

round robin

When I execute the query all of the rows go to a single thread as expected. There's no performance problem with this query, but the query reserves 8 parallel threads with MAXDOP set to 4. Again, I feel that this is out of place. It's impossible for both parallel branches to execute at the same time. I want to avoid unnecessary worker thread reservation because I have TF 2467 enabled which changes the scheduling algorithm to look at the number of worker threads per scheduler.

Is it possible to rewrite the query to have exactly one parallel branch that contains the table scan and local aggregate? For example, I would be fine with the general shape below except that I want the nested loop to execute in a serial zone:

enter image description here

For Application Reasons™ I strongly prefer to avoid splitting this query up into parts. If desired, you can view the actual query plan here. If you'd like to play along at home, here is T-SQL to create the table used in the query:

DROP TABLE IF EXISTS dbo.PARALLEL_ZONE_REPRO;

CREATE TABLE dbo.PARALLEL_ZONE_REPRO (
    ID BIGINT,
    FILLER VARCHAR(100)
);

INSERT INTO dbo.PARALLEL_ZONE_REPRO WITH (TABLOCK)
SELECT
  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) % 15
, REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
  • @GeoffPatterson I can't get batch mode to stick with the real world example. If I get aggressive with a USE PLAN hint then I end up with really long compile times. On 2019 I do get batch mode plans without trying though. Details about the compile times: forrestmcdaniel.com/2018/07/31/use-plan-and-compile-time – Joe Obbish Apr 27 at 0:03
8

I am able to get the desired plan shape with a serial loop join when all of the following are true:

  • An APPLY or CROSS JOIN is used instead of UNPIVOT
  • The APPLY contains no outer references
  • The source of rows in the APPLY is a table value constructor as opposed to a table

For example, here is one way to do it:

SELECT A, B
FROM 
(
    SELECT A
    , MAX(
        CASE
            WHEN A = 'VAL1' THEN VAL1 
            WHEN A = 'VAL2' THEN VAL2
            WHEN A = 'VAL3' THEN VAL3
            WHEN A = 'VAL4' THEN VAL4
            WHEN A = 'VAL5' THEN VAL5
            WHEN A = 'VAL6' THEN VAL6
            WHEN A = 'VAL7' THEN VAL7
            WHEN A = 'VAL16' THEN VAL16
            ELSE NULL
        END
    ) B
    FROM (
         SELECT 
           MAX(CASE WHEN ID = 1 THEN 1 ELSE 0 END) VAL1
         , MAX(CASE WHEN ID = 2 THEN 1 ELSE 0 END) VAL2
         , MAX(CASE WHEN ID = 3 THEN 1 ELSE 0 END) VAL3
         , MAX(CASE WHEN ID = 4 THEN 1 ELSE 0 END) VAL4
         , MAX(CASE WHEN ID = 5 THEN 1 ELSE 0 END) VAL5
         , MAX(CASE WHEN ID = 6 THEN 1 ELSE 0 END) VAL6
         , MAX(CASE WHEN ID = 7 THEN 1 ELSE 0 END) VAL7
         , MAX(CASE WHEN ID = 16 THEN 1 ELSE 0 END) VAL16
         FROM dbo.PARALLEL_ZONE_REPRO
    ) q
    CROSS APPLY (
        VALUES ('VAL1'), ('VAL2'), ('VAL3'), ('VAL4'),
        ('VAL5'), ('VAL6'), ('VAL7'), ('VAL16') 
    ) ca (A)
    GROUP BY A
) q
WHERE q.B IS NOT NULL
OPTION (MAXDOP 4);

I get the desired plan plan shape as claimed with just one parallel branch:

enter image description here

I tried many other things that did not work. This answer is unsatisfactory in that I don't know why it works and it may not work in a future version of SQL Server, but it did solve my problem.

8

It's impossible for both parallel branches to execute at the same time.

Execution starts at the left edge of the plan. The nested loops branch is running (opening, waiting for data) when the table scan branch is running. This is unavoidable. Both branches are active at the same time, so SQL Server will reserve 2 * DOP workers for this plan.

For a robust solution, you could place the pivot in a table-valued function:

CREATE OR ALTER FUNCTION dbo.PivotPZR()
RETURNS @R table 
(
    VAL1 bigint NOT NULL, VAL2 bigint NOT NULL,
    VAL3 bigint NOT NULL, VAL4 bigint NOT NULL,
    VAL5 bigint NOT NULL, VAL6 bigint NOT NULL,
    VAL7 bigint NOT NULL, VAL16 bigint NOT NULL
)
WITH SCHEMABINDING AS
BEGIN
    DECLARE 
        @Val1 bigint, @Val2 bigint, @Val3 bigint, @Val4 bigint,
        @Val5 bigint, @Val6 bigint, @Val7 bigint, @Val16 bigint;

    -- Can use parallelism
    SELECT
        @Val1 = MAX(CASE WHEN PZR.ID = 1 THEN 1 ELSE 0 END),
        @Val2 = MAX(CASE WHEN PZR.ID = 2 THEN 1 ELSE 0 END),
        @Val3 = MAX(CASE WHEN PZR.ID = 3 THEN 1 ELSE 0 END),
        @Val4 = MAX(CASE WHEN PZR.ID = 4 THEN 1 ELSE 0 END),
        @Val5 = MAX(CASE WHEN PZR.ID = 5 THEN 1 ELSE 0 END),
        @Val6 = MAX(CASE WHEN PZR.ID = 6 THEN 1 ELSE 0 END),
        @Val7 = MAX(CASE WHEN PZR.ID = 7 THEN 1 ELSE 0 END),
        @Val16 = MAX(CASE WHEN PZR.ID = 16 THEN 1 ELSE 0 END)
    FROM dbo.PARALLEL_ZONE_REPRO AS PZR;

    -- Single result row
    INSERT @R
        (VAL1, VAL2, VAL3, VAL4, VAL5, VAL6, VAL7, VAL16)
    VALUES
        (@Val1, @Val2, @Val3, @Val4, @Val5, @Val6, @Val7, @Val16);

    RETURN;
END;

Then rewrite the query as:

SELECT
    U.A,
    U.B
FROM dbo.PivotPZR() AS PP
UNPIVOT
(
    B FOR A IN (VAL1, VAL2 ,VAL3 ,VAL4, VAL5 ,VAL6 ,VAL7 ,VAL16)
) AS U;

The function uses parallelism with a single branch as desired:

Function plan

The top-level execution plan is:

Top-level query

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.