web-dev-qa-db-ja.com

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

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

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

round robin

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

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

enter image description here

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;
9
Joe Obbish

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

  • APPLYまたはCROSS JOINUNPIVOTの代わりに使用されます
  • 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を取得します。

enter image description here

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

8
Joe Obbish

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

実行が開始 計画の左端 。ネストされたループブランチは、テーブルスキャンブランチの実行中に実行されます(データを開いて待機しています)。これは 避けられない です。両方のブランチが同時にアクティブであるため、SQL Serverはreserve2 *このプランの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;

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

Function plan

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

Top-level query

8
Paul White 9