web-dev-qa-db-ja.com

計算式で予期しない結果を返すグループ化セット

ここでは、_grouping sets_を使用した2つの類似したクエリがあり、SELECT句には、集計で計算されたいくつかの式が含まれています。

_SELECT RN10, RN10 / 10, COUNT(*) FROM 
(
       SELECT RN, RN/10 AS RN10, RN/100 AS RN100 FROM 
       (
               SELECT RN = -1 + ROW_NUMBER() OVER (ORDER BY 1/0) 
               FROM master..spt_values
       ) A
) B
GROUP BY GROUPING SETS ((RN10), (RN10 / 10), ())
ORDER BY 1, 2
_

それの計画はここにあります: 最初のクエリ計画

そして

_SELECT RN10, SUBSTRING(RN,3,99), COUNT(*) FROM 
(
       SELECT RN, SUBSTRING(RN,2,99) AS RN10 FROM 
       (
               SELECT RN = CAST(-1 + ROW_NUMBER() OVER (ORDER BY 1/0) AS VARCHAR(99)) 
               FROM master..spt_values
       ) A
) B
GROUP BY GROUPING SETS ((RN10), (SUBSTRING(RN,3,99)), ())
ORDER BY 1, 2
_

対応するプランはここにあります: 2番目のクエリプラン

両方のクエリは、最初に何らかの集計式を計算します。最初の場合は_RN10 / 10_で、2番目の場合はSUBSTRING(RN,3,99)です。次に、同じ式がSELECT句で使用されますが、最初のプランではそれが再最初のクエリで計算され、2番目のクエリではありません。

その結果、最初の結果セットには、予想外のNULLsが含まれています。

results

最初のクエリが計算を2回(集計で1回、最後のselectでもう1回)実行するのに対し、2番目のクエリでは1回だけ計算する理由を誰かが説明できますか?

7
sepupic

期待される結果が何であるかが明確である、より簡単な例を使用します。

_CREATE TABLE Queen
(
   FirstName        VARCHAR(7),
   Surname          VARCHAR(7)
); 

INSERT INTO Queen
    (FirstName, Surname)
VALUES
    ('Brian',   'May'),
    ('Freddie', 'Mercury'),
    ('John',    'Deacon'),
    ('Roger',   'Taylor')
;
_

クエリ1

_SELECT Surname,
       NULL AS SurnameInitial,
       COUNT(*) AS Count
FROM   Queen
GROUP  BY Surname
UNION ALL
SELECT NULL AS Surname,
       LEFT(Surname,1) AS SurnameInitial,
       COUNT(*) AS Count
FROM   Queen
GROUP  BY LEFT(Surname,1)
_

クエリ1の結果

_+---------+----------------+-------+
| Surname | SurnameInitial | Count |
+---------+----------------+-------+
| Deacon  | NULL           |     1 |
| May     | NULL           |     1 |
| Mercury | NULL           |     1 |
| Taylor  | NULL           |     1 |
| NULL    | D              |     1 |
| NULL    | M              |     2 |
| NULL    | T              |     1 |
+---------+----------------+-------+
_

クエリ2

_SELECT Surname,
       LEFT(Surname,1) AS SurnameInitial,
       COUNT(*) AS Count
FROM   Queen
GROUP  BY GROUPING SETS ( ( Surname ), (LEFT(Surname,1)) ) 
ORDER BY SurnameInitial, Surname
_

クエリ2の結果

_ORDER BY SurnameInitial_とNULLがSQL Serverで最初にソートされるという事実にもかかわらず、SurnameInitialNULLである行は最後に並べられます。

_+---------+----------------+-------+
| Surname | SurnameInitial | Count |
+---------+----------------+-------+
| Deacon  | D              |     1 |
| May     | M              |     1 |
| Mercury | M              |     1 |
| Taylor  | T              |     1 |
| NULL    | NULL           |     1 |
| NULL    | NULL           |     2 |
| NULL    | NULL           |     1 |
+---------+----------------+-------+
_

クエリ1および2 shouldは同じ結果を返します。問題は、SQL Serverが次のSQLのように扱うことを決定することです。

_WITH GrpSets AS
(
SELECT Surname,
       COUNT(*) AS Count
FROM   Queen
GROUP  BY Surname
UNION ALL
SELECT NULL AS Surname,
       COUNT(*) AS Count
FROM   Queen
GROUP  BY LEFT(Surname,1)
)
SELECT Surname,
       LEFT(Surname,1) AS SurnameInitial,
       Count
FROM GrpSets
_

これは私にとってバグのように見えます(トレースフラグ8605は、最初のクエリツリー表現で損傷が既に行われていることを示しています)。 バグレポート

クエリ3

_SELECT Surname,
       LEFT(FirstName,1) AS FirstNameInitial,
       COUNT(*) AS Count
FROM   Queen
GROUP  BY GROUPING SETS ( ( Surname ), (LEFT(FirstName,1)) ) 
_

クエリ3の結果

_+---------+------------------+-------+
| Surname | FirstNameInitial | Count |
+---------+------------------+-------+
| NULL    | B                |     1 |
| NULL    | F                |     1 |
| NULL    | J                |     1 |
| NULL    | R                |     1 |
| Deacon  | NULL             |     1 |
| May     | NULL             |     1 |
| Mercury | NULL             |     1 |
| Taylor  | NULL             |     1 |
+---------+------------------+-------+
_

Query3は、列とその列を参照する式のグループ化の問題のあるパターンを満たしていません。とにかくここで同じ問題が発生することは不可能です

_SELECT Surname,
       NULL AS FirstNameInitial,
       COUNT(*) AS Count
FROM   Queen
GROUP  BY Surname
UNION ALL
SELECT NULL AS Surname,
       LEFT(FirstName,1) AS FirstNameInitial,
       COUNT(*) AS Count
FROM   Queen
GROUP  BY LEFT(FirstName,1)
_

これは、FirstName列全体を上流に渡さない(またはcouldが渡されることが保証された一意のFirstName列を持っている)ため、その上で計算されるLEFT(FirstName,1)式。

同じ理由で、_(RN10), (SUBSTRING(RN,3,99))_の問題は発生しません。

@ i-one おそらくコメントにある理由

正規化(代数化)のバグ。これには、_GROUP BY_のメンバー内のSELECTリスト内の集計されていない列と式の一致を見つけるロジックがあります。同じロジックにより、たとえば次のように書くことができます

_SELECT Surname, LEFT(Surname, 1), COUNT(*)
FROM   Queen
GROUP BY Surname
_

以下のように計算式を明示的に追加する必要なし

_GROUP BY Surname, LEFT(Surname, 1)
_

または別の例は

_SELECT Surname,
       LEFT(Surname,1) AS SurnameInitial,
       LEFT(Surname,2) AS SurnamePrefix,
       COUNT(*) AS Count
FROM   Queen
GROUP  BY GROUPING SETS ( ( Surname ), (LEFT(Surname,1)) ) 
_

この場合、LEFT(Surname,2)が許可され、LEFT(Surname,1)の場合に問題となる方法で計算することが唯一の計算方法になります。

11
Martin Smith