MySQLで集合差を出す

  • 52
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

MySQL方言にはMINUSとかEXCEPTが無いので、集合の差分を表現するのが意外と面倒くさい。大抵の場合、相関サブクエリでNOT EXISTSを使ったSQLを書くことになる。

一般従業員を示すemployeeとリーダー職を示すleaderという二種類のテーブルがあるとする。

CREATE TABLE employee (
  id INT NOT NULL,
  PRIMARY KEY(id)
);
CREATE TABLE leader (
  id INT NOT NULL,
  PRIMARY KEY(id)
);

employeeにはleaderの中身も含まれているとする。さて問題。employeeではあるが、leaderではないidを洗い出したい。

employee集合からleader集合を引くことができれば、該当のid群を洗い出せる。

-- イメージ的にはこうしたいんよ
employee EXCEPT leader

NOT IN (非推奨)

MySQLにEXCEPTのような機能はない。そこで素直に書くならNOT INになるけれどNOT INは遅いことが多い。

SELECT employee.id
  FROM employee
 WHERE employee.id NOT IN (
   SELECT leader.id
     FROM leader
)

NOT EXISTS + 相関サブクエリ

そこで相関サブクエリの形に修正すると多少緩和される。

SELECT employee.id
  FROM employee
 WHERE NOT EXISTS (
   SELECT leader.id
     FROM leader
    WHERE leader.id = employee.id
    LIMIT 1
)

同じように複数の集合を引きたいのなら、AND NOT EXISTS ...というように続けていけば計算できる。

LEFT JOIN + IS NULL

コメント欄で指摘いただいた方法。indexが使える場合、NOT EXISTSよりも更に速いことが多い。

SELECT employee.id
  FROM employee
  LEFT JOIN leader
         ON leader.id = employee.id
 WHERE leader.id IS NULL

leader側もデータがないものだけがleader.id IS NULLになる。つまり集合差になる。

基本的にLEFT JOIN + IS NULL方式、indexがない場合は NOT EXISTS方式、という感じだろうか。なんにせよ実行前にexplainするのが基本ですね