web-dev-qa-db-ja.com

NULLを含む列でのGROUP BY WITH ROLLUPの使用

次の表があります( SQL Fiddleで確認してください )(問題を分解するために作成しました)。

_| ID | Word    |
----------------
| 5  | "Hello" |
| 6  |  NULL   |
| 7  | "World" |
| 8  | "World" |
_

ここで、_GROUP BY Word WITH ROLLUP_を使用して、各Wordの出現回数をカウントしたいと思います。 ROLLUPによって生成された行のWordカラムのNULLは、「total」に置き換える必要があります。

_SELECT
  ID,
  ifnull(Word, "total") as Word,
  count(*) as occurrences
FROM test
GROUP BY Word WITH ROLLUP;
_

問題は、レコード内のNULLも、wordsがNULLである行の数で置き換えることです。

_| ID |  Word | occurrences |
|----|-------|-------------|
|  6 | total |           1 | <- Here lies the problem
|  5 | Hello |           1 |
|  7 | world |           2 |
|  7 | total |           4 |
_

それで、私はcount(Word)を使用して、それがデータからのNULLであるか、ROLLUPによって作成されたNULLであるかを区別して、「空」または「合計」で置き換えることができます。

_SELECT
  ID,
  if(count(Word) = 0, "empty", ifnull(Word, "total")) as Word,
  count(*) as occurrences
FROM test
group by Word with ROLLUP;
_

これは私のユースケースでは機能しますが、まだ欠陥があります。Wordがすべての行でNULLの場合、count(Word)も合計を示す最後の行で0になります:( SQL Fiddle異なるデータ(レコード#6のみ)

_| ID |  Word | occurrences |  
|----|-------|-------------|  
|  6 | empty |           1 |  
|  6 | empty |           1 |  <- Word should be "total"
_

次の文は私が望む結果をもたらします:

_SELECT
  ID,
  ifnull(Word, "total") as Word,
  count(*) as occurrences
FROM
(
SELECT
  ID,
  ifnull(Word, "empty") as Word
FROM test
) tmp
GROUP BY Word WITH ROLLUP
_

しかし、ここでのほとんどの回答では、サブクエリの使用は推奨されていないようなので、もっと良い解決策があるかどうか疑問に思っています。

5
Adrian Aulbach

あなたには「解決策」があるので、

より良い解決策があるかどうか私は思っています。

それについてコメントしたいと思います。いくつかの側面に焦点を当てましょう:

  1. SELECT ID [...] GROUP BY Wordは、WITH ROLLUPの有無に関係なく間違っています。 値が不明なフィールドを選択しています。特に、MySQLを使用すると、ランダムな値を返すことが公開されます。または、厳密モード(推奨、および最新バージョンのMySQLではデフォルト)を使用している場合、クエリが失敗します: 'db_9_b0ff7.test.ID' is n't in GROUP BY

  2. 正式かどうかに関係なく、WITH ROLLUPMySQL実装を参照)は、テーブルの二重再スキャンを回避できるため、場合によっては便利ですが、製品コードには多くはありません。 。何も問題はありませんが、SQL1999に実装されるまではMSSQL専用の機能であり、当時は広く利用できなかったか、MySQLの場合のように、限られた実装。参照:---(https://www.percona.com/blog/2007/09/17/using-group-by-with-rollup-for-reporting-performance-optimization/

  3. 原則として、最終的なクエリに問題はありません。両方のクエリ(IDなし)で説明を実行すると、次のようになります。

    MariaDB [test]> EXPLAIN SELECT   if(count(Word) = 0, "empty", ifnull(Word, "total")) as Word,   count(*) as occurrences FROM test group by Word with ROLLUP\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: test
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 4
            Extra: Using filesort
    1 row in set, 1 warning (0.00 sec)
    
    Warning (Code 1052): Column 'Word' in group statement is ambiguous
    MariaDB [test]> EXPLAIN SELECT   ifnull(Word, "total") as Word,   count(*) as occurrences FROM ( SELECT   ifnull(Word, "empty") as Word FROM test ) tmp GROUP BY Word WITH ROLLUP\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: test
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 4
            Extra: Using filesort
    1 row in set, 1 warning (0.01 sec)
    

    通常、MySQLに対する憎しみは、MySQLの初期バージョンに対する制限のためです。私はそれが今完璧であると言っているわけではありません。MySQLの開発は、これまでと同じように、本格的なリレーショナルエンジンではなく、高速で簡単にインストールできる簡単なRDBMSに焦点を当てているため、依然として問題があります。 、ただし、適切な場所のサブクエリは問題ありません(結局のところ、一部のクエリでは、何があってもサブクエリが必要になります。使用方法は問題ありません-要約された行をもう一度調べるオーバーヘッドのみがあり、実際の取引では、フルセットと比較して重要ではないと思います。

    ここの私のインスタンスでは、サブクエリは表示されていませんが、5.6のマテリアライズドサブクエリで確認できます: http://sqlfiddle.com/#!9/b0ff7/7 (It問題なく内側のクエリを実行してから、外側の部分を実行します)。インデックスは(filesortにより)間違いなくここで役立ちますが、それは最終的なレコード数によって異なります。インデックス(現在は存在しない)を使用できないため、サブクエリがパフォーマンスを低下させる可能性があります。特定のmysqlバージョンをテストし、そのようなインデックスを作成する必要があります。

  4. あなたの問題はデータモデルにあると思います-SQLの世界(Oracle以外)では、NULLは不明/無効な値です。それを正しくない「空」に関連付けているようです-Wordが存在しないことがわかっている場合は、 ''(NULLではなく空の文字列)である必要があります。あなたがそれを変更できるかどうかはわかりませんが、現在のモデルは好きではありません。何も言えないかもしれませんので、その点については触れません。 NULL単語を数えますか? NULLの代わりに ''を使用するその他の理由。
  5. 最後の質問は、MySQLでサブクエリを使用せずにそれを実行することは可能ですか? 私は本当に方法がわかりません-結合を行うことはできましたが、それでもサブクエリに対するものです。私は 代替 と考えることができます:

    SELECT * FROM (
        SELECT   Word,   
        count(*) as occurrences 
        FROM test 
        GROUP BY Word with rollup) 
    tmp 
    ORDER BY occurrences;
    

    これにより、最後の行に概要が含まれるようになります(他にnullがある場合は、実際のnull値です)。順序は、null値のみの場合でも発生します。逆に、順序付けが必要なため、さらに遅くなる可能性があり、ここでもインデックスが必要であり、そのインデックスが使用される場合と使用されない場合があります。

4
jynus

サブクエリを回避し、このクエリを使用して入力した文字を最適化できます。

SELECT
  ifnull(Word, "empty") as Word,
  count(*) as occurrences
FROM test
GROUP BY ifnull(Word, "empty") WITH ROLLUP
;

これは基本的に、グループ化が実行される前に、nullの値を「空の」マーカーに置き換えます。

  • グループ化列に「空」の値を含む行にnullカウントが見つかる
  • 合計数は、グループ化列にnull値を含む行にあります

PostgreSQLにも同様のアプローチを使用できますが、特別なGROUPING関数を提供しているため、SQL Serverには必要ありません。

1
chris544