単一の行をアンピボットするときに、役に立たない並列分岐をどのようにして取り除くことができますか?


9

少数のスカラー集計をアンピボットする次のクエリを考えてみます。

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);

SQL Server 2017で、2つの並列ブランチを持つプランを取得します。左の平行枝は私には場違いだと感じます。オプティマイザーは、グローバルスカラー集約からの単一の行出力のみが存在することを保証しますが、その親オペレーターはラウンドロビンパーティション化を使用した分散ストリームです。

ラウンドロビン

クエリを実行すると、期待どおりにすべての行が1つのスレッドに移動します。このクエリにはパフォーマンスの問題はありませんが、クエリはMAXDOPが4に設定された8つの並列スレッドを予約しています。繰り返しますが、これは適切ではないと感じています。両方の並列ブランチを同時に実行することは不可能です。TF 2467を有効にして、スケジューリングアルゴリズムを変更し、スケジューラごとのワーカースレッドの数を調べるため、不要なワーカースレッドの予約を回避したいと思います。

クエリを書き換えて、テーブルスキャンとローカル集計を含む並列ブランチを1つだけにすることはできますか?たとえば、ネストされたループをシリアルゾーンで実行することを除いて、以下の一般的な形状で問題ありません。

ここに画像の説明を入力してください

Application Reasons™の場合、このクエリを分割して分割しないことを強く推奨します。必要に応じて、実際のクエリプランをここで表示できます。自宅で遊んでみたい場合は、クエリで使用するテーブルを作成するT-SQLを次に示します。

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;

回答:


8

次のすべてが当てはまる場合、シリアルループ結合を使用して目的のプラン形状を取得できます。

  • APPLYまたはCROSS JOIN代わりに使用されていますUNPIVOT
  • APPLY何の外部参照が含まれていません
  • の行のソースAPPLYは、テーブルではなくテーブル値コンストラクタです

たとえば、これを行う1つの方法を次に示します。

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);

並列ブランチが1つだけあると主張されているように、目的のプランplan shapeを取得します。

ここに画像の説明を入力してください

うまくいかなかった他の多くのことを試しました。この回答は、なぜ機能するのかわからず、SQL Serverの将来のバージョンでは機能しない可能性があるという点で満足できませんが、私の問題は解決しました。


8

両方の並列ブランチを同時に実行することは不可能です。

実行はプランの左端から始まります。ネストされたループブランチは、テーブルスキャンブランチの実行中に実行されます(データを開いて待機しています)。これは避けられない。両方のブランチが同時にアクティブになるため、SQL Serverはこのプラン用に2 * DOPワーカーを予約します。

堅牢なソリューションの場合、ピボットをテーブル値関数に配置できます。

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;

次に、クエリを次のように書き換えます。

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;

この関数は、必要に応じて単一のブランチで並列処理を使用します。

機能計画

トップレベルの実行プランは次のとおりです。

トップレベルのクエリ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.