乱数と結合タイプを使用した予期しない結果


16

4つの乱数(1から4)を取得し、結合して一致するdatabase_id番号を取得する単純なスクリプトがあります。LEFT JOINを使用してスクリプトを実行すると、毎回4行が返されます(予想される結果)。ただし、INNER JOINを使用して実行すると、行の数が変化します(2行、8行の場合もあります)。

論理的には、sys.databasesにdatabase_ids 1〜4の行が存在することがわかっているため、違いはありません。また、(結合するのではなく)4つの行を持つ乱数テーブルから選択しているため、4行以上が返されることはありません。

これはSQL Server 2012と2014の両方で発生します。INNERJOINがさまざまな行数を返す原因は何ですか?

/* Works as expected -- always four rows */

SELECT rando.RandomNumber, d.database_id
FROM 
  (SELECT 1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber 
   FROM sys.databases WHERE database_id <= 4) AS rando
LEFT JOIN sys.databases d ON rando.RandomNumber = d.database_id;


/* Returns a varying number of rows */

SELECT rando.RandomNumber, d.database_id
FROM 
  (SELECT 1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber 
   FROM sys.databases WHERE database_id <= 4) AS rando
INNER JOIN sys.databases d ON rando.RandomNumber = d.database_id;

/* Also returns a varying number of rows */

WITH rando AS (
  SELECT 1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber
  FROM sys.databases WHERE database_id <= 4
)

SELECT r.RandomNumber, d.database_id
FROM rando AS r
INNER JOIN sys.databases d ON r.RandomNumber = d.database_id;

3
常に4行を取得する別の方法:SELECT TOP (4) d.database_id FROM sys.databases AS d CROSS JOIN (VALUES (1),(2),(3),(4)) AS multi (i) WHERE d.database_id <= 4 ORDER BY CHECKSUM(NEWID()) ;非決定的関数の値に結合がないため、正常に機能すると思います。
ypercubeᵀᴹ

回答:


9

追加のSELECTを追加することにより、計算スカラー評価をプランの奥深くにプッシュし、結合述語を提供します。上部の計算スカラーは、前のスカラーを参照します。

SELECT rando.RandomNumber, d.database_id
FROM 
  (SELECT ( SELECT 1 + ABS(CHECKSUM(NEWID())) % (4)) AS RandomNumber 
   FROM sys.databases WHERE database_id <= 4) AS rando
INNER JOIN sys.databases d ON rando.RandomNumber = d.database_id

|--Compute Scalar(DEFINE:([Expr1071]=[Expr1070]))

|--Compute Scalar(DEFINE:([Expr1070]=(1)+abs(checksum(newid()))%(4)))

まだ遅すぎる理由を掘り下げていますが、現在ポール・ホワイトによるこの投稿を読んでいます(https://sql.kiwi/2012/09/compute-scalars-expressions-and-execution-plan-performance.html) 。おそらく、NEWIDが決定論的ではないという事実と関係があるのでしょうか?


12

これは、サイトの賢い人の一人が鳴り響くまで、いくらかの洞察を与えるかもしれません。

ランダムな結果を一時テーブルに入れ、結合タイプに関係なく一貫して4つの結果を取得します。

/* Works as expected -- always four rows */

DECLARE @Rando table
(
    RandomNumber int
);

INSERT INTO
    @Rando
(
    RandomNumber
)
-- This generates 4 random numbers from 1 to 4, endpoints inclusive
SELECT
    1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber
FROM
    sys.databases
WHERE
    database_id <= 4;

SELECT
    *
FROM
    @Rando AS R;

SELECT
    rando.RandomNumber
,   d.database_id
FROM 
    @Rando AS rando
    LEFT JOIN 
        sys.databases d 
        ON rando.RandomNumber = d.database_id
ORDER BY 1,2;


/* Returns a varying number of rows */

SELECT rando.RandomNumber, d.database_id
FROM 
    @Rando AS rando
    INNER JOIN 
        sys.databases d 
        ON rando.RandomNumber = d.database_id
ORDER BY 1,2;

/* Also returns a varying number of rows */

WITH rando AS 
(
    SELECT * FROM @Rando AS rando
)
SELECT r.RandomNumber, d.database_id
FROM 
    rando AS r
    INNER JOIN 
        sys.databases d 
        ON r.RandomNumber = d.database_id
ORDER BY 1,2;

2番目のクエリとテーブル変数を使用したバリエーションのクエリプランを比較すると、2つの間に明確な違いがあることがわかります。赤いXはNo Join Predicate、私の穴居人の開発者の脳にとって本当に奇妙に思えます

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

クエリのランダムビットを定数に削除すると 1 % (4)、私の計画は良く見えますが、Compute Scalarは削除されたので、私は近くに見えるようになりました

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

結合後の乱数の式を計算しています。それが予想されるかどうかにかかわらず、私はまだサイトの内部ウィザードに任せますが、少なくともそれがあなたの参加で可変的な結果を得ている理由です。

2014

自宅で一緒に遊ぶ人のために、上記のクエリプランは2008 R2インスタンスから生成されました。2014年の計画は異なって見えますが、結合後もCompute Scalar操作は残ります。

これは、定数式を使用した2014のクエリプランです

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

これは、newid式を使用した2014インスタンスのクエリプランです。

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

これは明らかに設計によるものであり、接続の問題はこちらです。その存在を知ってくれた@paulWhiteに感謝します。


1
そう、まさに-それが起こっていることですが、それは間違いなく予期されていません。結果は、渡されるT-SQLと一致しないため、質問に一致しません。
ブレントオザー14年

でも、静的1と乱数を交換すると述語参加していないとオペレータに参加できます
ジェームズ・アンダーソン

何かに取り組んでいるようです。でも、OPTION(FORCE ORDER)を使用して動作を変更しません-乱数はまだ最後に計算された...
エレミヤPeschka

sys.databases TVFを削除すると、同じプランが作成されます。gist.github.com
Jeremiah Peschka 14年

これはオペレーターの優先順位の問題のように聞こえます
ジェームスアンダーソン14年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.