SQL を使ってよく求められるに検索があります。会員を検索したり、予約を検索したりとかです。この検索機能を実装する際、しばしば埋め込まれてしまう不具合が行の重複です。これが埋め込まれたまま実運用まで行くとお客様から〇〇さんのデータが二重に見える、などとお問い合わせいただくことなってしまいます。データを破壊する様な不具合ではないので致命傷とまで言いませんが、機能不全や業務を妨げる事態を起こす不具合ですので望ましくありません。これの防御方法の一つを紹介します。
実行環境は MySQL 8.0 です。ドキュメントを見るにより古いバージョンでも同じ挙動をします。主に使うのは GROUP BY と sql_mode の ONLY_FULL_GROUP_BY です。具体的に何を行うのかというと次です。
[mysqld] # ONLY_FULL_GROUP_BY をあらかじめ設定ファイルに記述 sql-mode="ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"
SELECT * FROM members LEFT JOIN member_access_logs on members.member_id = member_access_logs.member_id GROUP BY members.member_id; # 検索対象の一行単位になってほしいテーブルの主キーで GROUP BY
この様な検索対象の一行単位になってほしいテーブルの主キーで GROUP BY された SQL 文が発行される様にします。この様にすると重複行が現れる様なクエリは軒並みエラーになります。
何が起きているかというと ONLY_FULL_GROUP_BY により集計しなければ複数行になりうるクエリがエラーになり、GROUP BY 主キー
により 1 主キーにつき 1 行が守られるようになります。
MySQL は、関数従属性の検出を実装しています。 ONLY_FULL_GROUP_BY SQL モードが有効な場合 (デフォルト)、MySQL は、選択リスト、HAVING 条件または ORDER BY リストが GROUP BY 句で名前が付けられておらず、機能的に依存していない非集計カラムを参照するクエリーを拒否します。
MySQL :: MySQL 8.0 リファレンスマニュアル :: 12.20.3 MySQL での GROUP BY の処理
単に GROUP BY のみでも見かけ上は一行になるのですが、奇妙な検索結果を防止するために ONLY_FULL_GROUP_BY をつけます。
GROUP BY で余分な処理が入って、ない場合よりも実行速度が遅くなるという懸念がありますが、主キーの場合滅多にあてはまりません(経験的なので explain 必須です)。また真に遅くなる場合は開発中のみ有効にして本番やステージングでは解除する、といった手もあります。