How can I get rid of an unhelpful parallel branch when unpivoting a single row?NUMA Nodes - MAXDOP - PLESQL...
Examples of non trivial equivalence relations , I mean equivalence relations without the expression " same ... as" in their definition?
Why does nature favour the Laplacian?
How to reduce LED flash rate (frequency)
How could Tony Stark make this in Endgame?
What's the polite way to say "I need to urinate"?
How to verbalise code in Mathematica?
What software provides a code editing environment on iPad?
How can Republicans who favour free markets, consistently express anger when they don't like the outcome of that choice?
Will tsunami waves travel forever if there was no land?
The Defining Moment
What does the "ep" capability mean?
How much cash can I safely carry into the USA and avoid civil forfeiture?
A Note on N!
Mjolnir's timeline from Thor's perspective
Size of electromagnet needed to replicate Earth's magnetic field
a sore throat vs a strep throat vs strep throat
Do I have to worry about players making “bad” choices on level up?
Is there really no use for MD5 anymore?
Real-world applications of fields, rings and groups in linear algebra.
Map of water taps to fill bottles
Interpret a multiple linear regression when Y is log transformed
Is this homebrew Wind Wave spell balanced?
Do I have an "anti-research" personality?
Does Gita support doctrine of eternal cycle of birth and death for evil people?
How can I get rid of an unhelpful parallel branch when unpivoting a single row?
NUMA Nodes - MAXDOP - PLESQL Server thread statuswhy sql server has high Worker threads?Query memory grant and tempdb spillHow does PARALLEL_MIN_PERCENT affect parallel execution?SQL Server Threads and Degree Of ParallelismIs it possible to see which SPID uses which scheduler (worker thread)?SQL Server instance running out of worker threadsWhat's the easiest and most accurate way to visualize parallel thread usage in SQL Server?MAX worker thread in SQL server 2012/14/16
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ margin-bottom:0;
}
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:

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:

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;
sql-server execution-plan sql-server-2017 parallelism
add a comment |
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:

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:

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;
sql-server execution-plan sql-server-2017 parallelism
Have you tried batch mode, or are there reasons that's not practical on the real-world example? For your toy example, if I introduce a dummy columnstore index into the original unpivot query (e.g., addWHERE NOT EXISTS (SELECT * FROM dbo.batchMode WHERE 0=1)with an emptydbo.batchModetable with columnstore index), the query plan reserved 5 threads and completes in about 1/3 the CPU time and elapsed time of the original query. The query does use a bit more memory though.
– Geoff Patterson
yesterday
@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
20 hours ago
add a comment |
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:

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:

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;
sql-server execution-plan sql-server-2017 parallelism
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:

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:

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;
sql-server execution-plan sql-server-2017 parallelism
sql-server execution-plan sql-server-2017 parallelism
edited yesterday
Paul White♦
54.4k14288461
54.4k14288461
asked yesterday
Joe ObbishJoe Obbish
22.4k43494
22.4k43494
Have you tried batch mode, or are there reasons that's not practical on the real-world example? For your toy example, if I introduce a dummy columnstore index into the original unpivot query (e.g., addWHERE NOT EXISTS (SELECT * FROM dbo.batchMode WHERE 0=1)with an emptydbo.batchModetable with columnstore index), the query plan reserved 5 threads and completes in about 1/3 the CPU time and elapsed time of the original query. The query does use a bit more memory though.
– Geoff Patterson
yesterday
@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
20 hours ago
add a comment |
Have you tried batch mode, or are there reasons that's not practical on the real-world example? For your toy example, if I introduce a dummy columnstore index into the original unpivot query (e.g., addWHERE NOT EXISTS (SELECT * FROM dbo.batchMode WHERE 0=1)with an emptydbo.batchModetable with columnstore index), the query plan reserved 5 threads and completes in about 1/3 the CPU time and elapsed time of the original query. The query does use a bit more memory though.
– Geoff Patterson
yesterday
@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
20 hours ago
Have you tried batch mode, or are there reasons that's not practical on the real-world example? For your toy example, if I introduce a dummy columnstore index into the original unpivot query (e.g., add
WHERE NOT EXISTS (SELECT * FROM dbo.batchMode WHERE 0=1) with an empty dbo.batchMode table with columnstore index), the query plan reserved 5 threads and completes in about 1/3 the CPU time and elapsed time of the original query. The query does use a bit more memory though.– Geoff Patterson
yesterday
Have you tried batch mode, or are there reasons that's not practical on the real-world example? For your toy example, if I introduce a dummy columnstore index into the original unpivot query (e.g., add
WHERE NOT EXISTS (SELECT * FROM dbo.batchMode WHERE 0=1) with an empty dbo.batchMode table with columnstore index), the query plan reserved 5 threads and completes in about 1/3 the CPU time and elapsed time of the original query. The query does use a bit more memory though.– Geoff Patterson
yesterday
@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
20 hours ago
@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
20 hours ago
add a comment |
2 Answers
2
active
oldest
votes
I am able to get the desired plan shape with a serial loop join when all of the following are true:
- An
APPLYorCROSS JOINis used instead ofUNPIVOT
- The
APPLYcontains no outer references - The source of rows in the
APPLYis 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:

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.
add a comment |
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:

The top-level execution plan is:

add a comment |
Your Answer
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "182"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f236732%2fhow-can-i-get-rid-of-an-unhelpful-parallel-branch-when-unpivoting-a-single-row%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
I am able to get the desired plan shape with a serial loop join when all of the following are true:
- An
APPLYorCROSS JOINis used instead ofUNPIVOT
- The
APPLYcontains no outer references - The source of rows in the
APPLYis 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:

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.
add a comment |
I am able to get the desired plan shape with a serial loop join when all of the following are true:
- An
APPLYorCROSS JOINis used instead ofUNPIVOT
- The
APPLYcontains no outer references - The source of rows in the
APPLYis 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:

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.
add a comment |
I am able to get the desired plan shape with a serial loop join when all of the following are true:
- An
APPLYorCROSS JOINis used instead ofUNPIVOT
- The
APPLYcontains no outer references - The source of rows in the
APPLYis 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:

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.
I am able to get the desired plan shape with a serial loop join when all of the following are true:
- An
APPLYorCROSS JOINis used instead ofUNPIVOT
- The
APPLYcontains no outer references - The source of rows in the
APPLYis 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:

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.
answered yesterday
Joe ObbishJoe Obbish
22.4k43494
22.4k43494
add a comment |
add a comment |
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:

The top-level execution plan is:

add a comment |
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:

The top-level execution plan is:

add a comment |
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:

The top-level execution plan is:

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:

The top-level execution plan is:

edited yesterday
answered yesterday
Paul White♦Paul White
54.4k14288461
54.4k14288461
add a comment |
add a comment |
Thanks for contributing an answer to Database Administrators Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f236732%2fhow-can-i-get-rid-of-an-unhelpful-parallel-branch-when-unpivoting-a-single-row%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Have you tried batch mode, or are there reasons that's not practical on the real-world example? For your toy example, if I introduce a dummy columnstore index into the original unpivot query (e.g., add
WHERE NOT EXISTS (SELECT * FROM dbo.batchMode WHERE 0=1)with an emptydbo.batchModetable with columnstore index), the query plan reserved 5 threads and completes in about 1/3 the CPU time and elapsed time of the original query. The query does use a bit more memory though.– Geoff Patterson
yesterday
@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
20 hours ago