【MySQL】検索結果の行の重複を予防する

  • 2021年11月22日
  • SQL

 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 必須です)。また真に遅くなる場合は開発中のみ有効にして本番やステージングでは解除する、といった手もあります。

>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG