できない.dev

MySQL で only_full_group_by のエラーが出て GROUP BY が通らない

GROUP BY に無い列を選ぶと、どの行の値を返すか一意に決まらないため MySQL は ERROR 1055 で拒否する。
集約関数で包むか GROUP BY に足すか、値を問わないと宣言する ANY_VALUE() を使う。

公開:

要約

このエラーは、SELECT 句の列が GROUP BY によって一意に決まらないときに出る。
グループが複数行を含むのに、その中のどの行の値を返せばよいか SQL として決まらないため、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

only_full_group_byServer SQL Modes(新しいタブで開く) が示すとおり MySQL 8.4 の既定の sql_mode に含まれる。
古い設定から移ってきたクエリが、ここで初めて弾かれることが多い。

よくある原因

  1. 非集約列の混入: GROUP BY name に対して address を選ぶと、同じ name の中で address が複数あり得る。
    値が 1 つに定まらないので拒否される。
  2. HAVING / ORDER BY での参照: 制約は SELECT 句だけではない。MySQL Handling of GROUP BY(新しいタブで開く)HAVINGORDER BY も同じ条件で検査すると説明している。
  3. GROUP BY を書いていない集約: SELECT name, MAX(age) FROM t; のように GROUP BY 無しで集約関数を使うと、全体が 1 グループになり name が定まらない。
    この場合は ERROR 1140 になる。
  4. 環境差: 開発環境で sql_mode から外していると通り、本番の既定設定で落ちる。
    逆にマネージドサービスへ移行して急に出ることもある。
  5. ORM の生成クエリ: 集計用のスコープに、一覧表示用の列選択が残っていると混入する。
    生成された SQL をログで確認しないと気づきにくい。

解決策

1. 集約関数で包む

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

「代表値を 1 つ選ぶ」という意図が SQL 上に現れるため、後から読む人にも判断が伝わる。

2. GROUP BY に追加する

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

ただしグループの粒度が変わる。name ごとの集計が欲しかったのなら結果の意味が変わってしまうので、意図と合うか確認する。

3. 主キーでグループ化する

SELECT u.id, u.name, COUNT(o.id) AS orders
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id;

u.id が主キーなら u.name はそこから一意に決まる。
MySQL はこの関数従属を認識するので、u.nameGROUP BY に足さなくても通る。

4. ANY_VALUE() で明示する

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

ANY_VALUE() は「どの行の値でも構わない」と宣言する関数で、その列についてだけ検査を抑止する。sql_mode を丸ごと変えるより影響範囲が小さい。

5. 設定を確認する

SELECT @@SESSION.sql_mode;
SELECT @@GLOBAL.sql_mode;

環境ごとに値が違うなら、まずそこを揃える。only_full_group_by を外す対処は、これまで気づかずに済んでいた不定なクエリをそのまま通してしまうので、最後の手段にする。

この記事は役立ちましたか?