MySQL 8.0 リファレンスマニュアル


MySQL 8.0 リファレンスマニュアル  /  ...  /  MySQL での GROUP BY の処理

12.20.3 MySQL での GROUP BY の処理

SQL-92 以前では、選択リスト、HAVING 条件または ORDER BY リストが GROUP BY 句で指定されていない非集計カラムを参照するクエリーは許可されません。 たとえば、このクエリーは、選択リストの非集計 name カラムが GROUP BY に表示されないため、標準 SQL-92 では無効です:

SELECT o.custid, c.name, MAX(o.payment)
  FROM orders AS o, customers AS c
  WHERE o.custid = c.custid
  GROUP BY o.custid;

クエリーを SQL-92 で有効にするには、選択リストから name カラムを省略するか、GROUP BY 句で名前を指定する必要があります。

SQL:1999 以降では、GROUP BY カラムに機能的に依存している場合、オプション機能 T301 ごとにこのような非集計が許可されます: このような関係が namecustid の間に存在する場合、クエリーは有効です。 たとえば、これは custidcustomers の主キーであった場合です。

MySQL は、関数従属性の検出を実装しています。 ONLY_FULL_GROUP_BY SQL モードが有効な場合 (デフォルト)、MySQL は、選択リスト、HAVING 条件または ORDER BY リストが GROUP BY 句で名前が付けられておらず、機能的に依存していない非集計カラムを参照するクエリーを拒否します。

次の例に示すように、SQL ONLY_FULL_GROUP_BY モードが有効になっている場合、MySQL では、GROUP BY 句で指定されていない非集計カラムも許可されます:

mysql> CREATE TABLE mytable (
    ->    id INT UNSIGNED NOT NULL PRIMARY KEY,
    ->    a VARCHAR(10),
    ->    b INT
    -> );

mysql> INSERT INTO mytable
    -> VALUES (1, 'abc', 1000),
    ->        (2, 'abc', 2000),
    ->        (3, 'def', 4000);

mysql> SET SESSION sql_mode = sys.list_add(@@session.sql_mode, 'ONLY_FULL_GROUP_BY');

mysql> SELECT a, SUM(b) FROM mytable WHERE a = 'abc';
+------+--------+
| a    | SUM(b) |
+------+--------+
| abc  |   3000 |
+------+--------+

ONLY_FULL_GROUP_BY を使用する場合は、SELECT リストに複数の非集計カラムを含めることもできます。 この場合、次に示すように、このようなカラムはすべて WHERE 句の単一の値に制限する必要があり、このような制限条件はすべて論理 AND によって結合する必要があります:

mysql> DROP TABLE IF EXISTS mytable;

mysql> CREATE TABLE mytable (
    ->    id INT UNSIGNED NOT NULL PRIMARY KEY,
    ->    a VARCHAR(10),
    ->    b VARCHAR(10),
    ->    c INT
    -> );

mysql> INSERT INTO mytable
    -> VALUES (1, 'abc', 'qrs', 1000),
    ->        (2, 'abc', 'tuv', 2000),
    ->        (3, 'def', 'qrs', 4000),
    ->        (4, 'def', 'tuv', 8000),
    ->        (5, 'abc', 'qrs', 16000),
    ->        (6, 'def', 'tuv', 32000);

mysql> SELECT @@session.sql_mode;
+---------------------------------------------------------------+
| @@session.sql_mode                                            |
+---------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION |
+---------------------------------------------------------------+

mysql> SELECT a, b, SUM(c) FROM mytable
    ->     WHERE a = 'abc' AND b = 'qrs';
+------+------+--------+
| a    | b    | SUM(c) |
+------+------+--------+
| abc  | qrs  |  17000 |
+------+------+--------+

ONLY_FULL_GROUP_BY が無効になっている場合、GROUP BY の標準 SQL 使用に対する MySQL 拡張機能を使用すると、カラムが GROUP BY カラムに機能的に依存していなくても、選択リスト、HAVING 条件または ORDER BY リストで集計されていないカラムを参照できます。 これにより、MySQL は前述のクエリーを受け入れます。 この場合、サーバーは各グループから任意の値を自由に選択できるため、それらが同じでないかぎり、選択される値は決定的ではなく、必要な値ではない可能性があります。 さらに、ORDER BY 句を追加しても、各グループからの値の選択が影響を受ける可能性はありません。 結果セットのソートは値が選択された後に行われ、ORDER BY はサーバーが選択する各グループ内の値には影響しません。 ONLY_FULL_GROUP_BY の無効化は、主に、データの一部のプロパティのために、GROUP BY で指定されていない集計されていない各カラムのすべての値がグループごとに同じであることがわかっている場合に役立ちます。

ANY_VALUE() を使用して非集計カラムを参照することで、ONLY_FULL_GROUP_BY を無効にせずに同じ効果を得ることができます。

次の説明は、関数従属性、関数従属性が存在しない場合に MySQL が生成するエラーメッセージ、および関数従属性が存在しない場合に MySQL がクエリーを受け入れる方法を示しています。

選択リストの非集計 address カラムが GROUP BY 句で指定されていないため、ONLY_FULL_GROUP_BY が有効な場合、このクエリーは無効である可能性があります:

SELECT name, address, MAX(age) FROM t GROUP BY name;

このクエリーは、namet の主キーであるか、一意の NOT NULL カラムである場合に有効です。 このような場合、MySQL は、選択されたカラムが機能的にグループ化カラムに依存していることを認識します。 たとえば、name が主キーの場合、各グループには主キーの値が 1 つのみであるため、その値によって address の値が決まります。 そのため、グループ内の address 値の選択にランダム性はなく、クエリーを拒否する必要はありません。

namet の主キーまたは一意の NOT NULL カラムでない場合、クエリーは無効です。 この場合、関数従属性を推測できず、エラーが発生します:

mysql> SELECT name, address, MAX(age) FROM t GROUP BY name;
ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP
BY clause and contains nonaggregated column 'mydb.t.address' which
is not functionally dependent on columns in GROUP BY clause; this
is incompatible with sql_mode=only_full_group_by

それがわかっている場合、実際には特定のデータセットの各 name 値によって address 値が一意に決定され、address は事実上 name に依存します。 クエリーを受け入れるように MySQL に指示するには、ANY_VALUE() 関数を使用します:

SELECT name, ANY_VALUE(address), MAX(age) FROM t GROUP BY name;

または、ONLY_FULL_GROUP_BY を無効にします。

ただし、前述の例は非常に単純です。 特に、すべてのグループに 1 つの行のみが含まれるため、単一の主キーカラムでグループ化することはほとんどありません。 より複雑なクエリーでの関数従属性を示す追加の例は、セクション12.20.4「機能依存性の検出」 を参照してください。

クエリーに集計関数があり、GROUP BY 句がない場合、ONLY_FULL_GROUP_BY が有効になっている選択リスト、HAVING 条件または ORDER BY リストに非集計カラムを含めることはできません:

mysql> SELECT name, MAX(age) FROM t;
ERROR 1140 (42000): In aggregated query without GROUP BY, expression
#1 of SELECT list contains nonaggregated column 'mydb.t.name'; this
is incompatible with sql_mode=only_full_group_by

GROUP BY がない場合、単一のグループが存在し、どの name 値をグループに選択するかは非決定的です。 ここでも、MySQL が選択する name 値が重要でない場合は、ANY_VALUE() を使用できます:

SELECT ANY_VALUE(name), MAX(age) FROM t;

ONLY_FULL_GROUP_BY は、DISTINCT および ORDER BY を使用するクエリーの処理にも影響します。 次の行を含む c1c2 および c3 の 3 つのカラムを含むテーブル t の場合を考えてみます:

c1 c2 c3
1  2  A
3  4  B
1  2  C

次のクエリーを実行し、結果を c3 で順序付けするとします:

SELECT DISTINCT c1, c2 FROM t ORDER BY c3;

結果を並べ替えるには、最初に重複を排除する必要があります。 ただし、そのためには、最初の行または 3 番目の行を保持する必要がありますか。 この任意の選択は c3 の保持された値に影響し、これは順序付けに影響を与え、任意にすることもできます。 この問題を回避するために、いずれかの ORDER BY 式が次のいずれかの条件を満たさない場合、DISTINCT および ORDER BY を含むクエリーは無効として拒否されます:

  • 式が選択リストの式と等しい

  • 式によって参照され、クエリーで選択されたテーブルに属するすべてのカラムは、選択リストの要素です

標準 SQL に対する別の MySQL 拡張機能では、選択リスト内のエイリアス式への HAVING 句内の参照が許可されます。 たとえば、次のクエリーは、orders テーブルで 1 回だけ発生する name 値を返します。

SELECT name, COUNT(name) FROM orders
  GROUP BY name
  HAVING COUNT(name) = 1;

MySQL 拡張機能では、集計カラムの HAVING 句でエイリアスを使用できます:

SELECT name, COUNT(name) AS c FROM orders
  GROUP BY name
  HAVING c = 1;

標準 SQL では GROUP BY 句のカラム式のみが許可されるため、FLOOR(value/100) は非カラム式であるため、このようなステートメントは無効です:

SELECT id, FLOOR(value/100)
  FROM tbl_name
  GROUP BY id, FLOOR(value/100);

MySQL は、標準 SQL を拡張して GROUP BY 句でカラム以外の式を許可し、前述のステートメントが有効であるとみなします。

標準 SQL では、GROUP BY 句でエイリアスを使用することもできません。 MySQL は標準 SQL を拡張してエイリアスを許可するため、クエリーを記述する別の方法は次のとおりです:

SELECT id, FLOOR(value/100) AS val
  FROM tbl_name
  GROUP BY id, val;

エイリアス val は、GROUP BY 句のカラム式とみなされます。

GROUP BY 句にカラム以外の式が存在する場合、MySQL はその式と選択リスト内の式の等価性を認識します。 つまり、ONLY_FULL_GROUP_BY SQL モードが有効になっている場合、選択リストに同じ FLOOR() 式が出現するため、GROUP BY id, FLOOR(value/100) を含むクエリーは有効です。 ただし、MySQL は GROUP BY の非カラム式への関数従属性を認識しないため、3 番目に選択された式が id カラムと GROUP BY 句の FLOOR() 式の単純な式であっても、ONLY_FULL_GROUP_BY が有効な場合は次のクエリーは無効です:

SELECT id, FLOOR(value/100), id+FLOOR(value/100)
  FROM tbl_name
  GROUP BY id, FLOOR(value/100);

回避策は、導出テーブルを使用することです:

SELECT id, F, id+F
  FROM
    (SELECT id, FLOOR(value/100) AS F
     FROM tbl_name
     GROUP BY id, FLOOR(value/100)) AS dt;

関連キーワード:  関数, カラム, 選択, クエリー, 集計, リスト, FROM, ONLY, FULL, FLOOR