MySQL WHERE句 AND/OR/IN | 複数条件指定の具体的な使い方 詳細解説
はじめに:なぜ複数条件指定は重要なのか
データベースから特定の情報を正確に取り出すことは、データ駆動型のアプリケーションや分析において最も基本的な操作の一つです。SQLのSELECT
文は、この情報検索の核心を担いますが、テーブル全体からすべてのデータを取り出すだけでは、多くの場合非効率的であり、目的の情報を見つけ出すことも困難です。ここで登場するのがWHERE
句です。
WHERE
句は、SELECT
文だけでなく、UPDATE
文やDELETE
文においても、対象となるレコード(行)を絞り込むために使用されます。これにより、「すべてのデータ」ではなく、「特定の条件を満たすデータだけ」を操作することができます。
しかし、現実世界のデータは単一の条件だけで絞り込めるほど単純ではありません。「この商品の価格が1000円以上 かつ 在庫が10個未満である」といったように、複数の条件を同時に考慮してデータを検索する必要が頻繁に発生します。このような複数条件の指定を可能にするのが、論理演算子であるAND
、OR
、そして特定の集合に含まれるかどうかを判定するIN
演算子です。
この記事では、MySQLのWHERE
句でこれらの演算子を使って複数条件を指定する方法について、それぞれの基本的な使い方から、組み合わせ、パフォーマンスへの影響、実践的なテクニックまで、詳細かつ具体的に解説します。約5000語をかけて、この重要なテーマを深く掘り下げていきましょう。
まずは、最も基本的な論理演算子であるAND
から見ていきます。
第1章:論理演算子 AND – すべての条件を満たすレコードを抽出する
AND
演算子は、指定された複数の条件がすべて真(TRUE)であるレコードを抽出したい場合に使用します。SQLにおいて、AND
は論理積を意味します。
1.1 基本的な使い方
AND
演算子は、2つの条件式の間で使用します。
sql
SELECT カラム名 FROM テーブル名 WHERE 条件1 AND 条件2;
この構文は、「条件1
が真であり、かつ 条件2
も真である」すべてのレコードを選択します。
具体例:
products
テーブルから、価格(price
)が1000円より大きく、かつ在庫数(stock
)が50未満の商品を検索します。
sql
SELECT product_name, price, stock
FROM products
WHERE price > 1000 AND stock < 50;
このクエリは、価格が1000円より大きく、同時に在庫数が50未満であるような商品のリストを返します。価格が1000円以下だが在庫が40個の商品や、価格が1500円だが在庫が100個の商品は、この結果に含まれません。
1.2 複数の AND を連ねる
AND
演算子は、3つ以上の条件を結合するためにも繰り返し使用できます。
sql
SELECT カラム名 FROM テーブル名 WHERE 条件1 AND 条件2 AND 条件3 ...;
具体例:
先ほどの例に加えて、カテゴリID(category_id
)が3であるという条件も追加します。
sql
SELECT product_name, price, stock, category_id
FROM products
WHERE price > 1000 AND stock < 50 AND category_id = 3;
このクエリは、価格が1000円より大きく、在庫数が50未満で、かつカテゴリIDが正確に3である商品のみを返します。すべての条件が満たされない限り、レコードは選択されません。
AND
は非常に直感的で使いやすい演算子です。複数の必須条件を指定する際に広く利用されます。
1.3 AND とインデックス
AND
句の条件に使われているカラムにインデックスが適切に設定されている場合、MySQLは効率的に該当するレコードを絞り込むことができます。例えば、WHERE price > 1000 AND stock < 50
という条件で、price
カラムとstock
カラムの両方、または結合インデックス(price
, stock
)が作成されている場合、パフォーマンスが大幅に向上する可能性があります。
MySQLは、複数の条件のうち、最も絞り込み効果が高い(インデックスが効きやすく、かつ該当レコード数が少ないと推定される)条件から評価を開始しようとすることがあります。ただし、これはオプティマイザの判断に依存します。
注意点:
AND
条件が使われている場合でも、インデックスが全く使われない場合や、一部のインデックスしか使われない場合もあります。特に、関数を使った条件式(例: WHERE YEAR(order_date) = 2023 AND ...
)や、比較演算子(例: <
や>
)によっては、インデックスがフルに活用されないこともあります。パフォーマンスの確認には、常にEXPLAIN
ステートメントを使うことを推奨します(これについては後述します)。
AND
は複数の条件を「かつ」で結びつけ、すべてを満たすレコードを抽出する際に不可欠です。次に、いずれかの条件を満たすレコードを抽出するOR
演算子を見ていきましょう。
第2章:論理演算子 OR – いずれかの条件を満たすレコードを抽出する
OR
演算子は、指定された複数の条件のうち、少なくとも一つが真(TRUE)であるレコードを抽出したい場合に使用します。SQLにおいて、OR
は論理和を意味します。
2.1 基本的な使い方
OR
演算子は、2つの条件式の間で使用します。
sql
SELECT カラム名 FROM テーブル名 WHERE 条件1 OR 条件2;
この構文は、「条件1
が真である、または 条件2
が真である」すべてのレコードを選択します。
具体例:
products
テーブルから、価格(price
)が500円未満の商品、または在庫数(stock
)が10個未満の商品を検索します。
sql
SELECT product_name, price, stock
FROM products
WHERE price < 500 OR stock < 10;
このクエリは、価格が500円未満であれば(在庫数に関わらず)結果に含め、あるいは在庫数が10個未満であれば(価格に関わらず)結果に含めます。両方の条件を満たす商品ももちろん含まれます。価格が500円以上かつ在庫が10個以上の商品は、この結果に含まれません。
2.2 複数の OR を連ねる
OR
演算子も、3つ以上の条件を結合するために繰り返し使用できます。
sql
SELECT カラム名 FROM テーブル名 WHERE 条件1 OR 条件2 OR 条件3 ...;
具体例:
カテゴリID(category_id
)が1、または2、または5である商品を検索します。
sql
SELECT product_name, category_id
FROM products
WHERE category_id = 1 OR category_id = 2 OR category_id = 5;
このクエリは、カテゴリIDが1、2、または5のいずれかであるすべての商品を返します。
2.3 OR と括弧 (優先順位の制御)
OR
演算子とAND
演算子を同じWHERE
句の中で使用する場合、SQLではデフォルトでAND
演算子がOR
演算子よりも高い優先順位を持ちます。これは、算数における乗算(*
)が加算(+
)よりも優先されるのと似ています。
この優先順位を制御し、意図した通りの論理を指定するためには、括弧 ()
を使用することが非常に重要です。括弧で囲まれた部分は、他の部分よりも先に評価されます。
sql
-- デフォルトの優先順位 (AND が OR より先に評価される)
-- WHERE 条件1 OR 条件2 AND 条件3; は WHERE 条件1 OR (条件2 AND 条件3); と同じ意味
具体例1(括弧なし – ANDが優先):
「カテゴリIDが1で、かつ価格が1000円未満の商品」または「在庫数が10個未満の商品」を検索したい場合。
sql
SELECT product_name, category_id, price, stock
FROM products
WHERE category_id = 1 AND price < 1000 OR stock < 10;
このクエリは、デフォルトの優先順位により、以下のように解釈されます。
WHERE (category_id = 1 AND price < 1000) OR stock < 10;
つまり、「カテゴリ1で価格1000円未満」の商品 あるいは 「在庫10個未満」の商品が選択されます。意図と異なる可能性があります。
具体例2(括弧あり – ORを優先):
意図が「カテゴリIDが1 または 2の商品で、かつ価格が1000円未満の商品」を検索したい場合。
sql
SELECT product_name, category_id, price
FROM products
WHERE (category_id = 1 OR category_id = 2) AND price < 1000;
このクエリでは、括弧によって (category_id = 1 OR category_id = 2)
の部分が先に評価されます。「カテゴリIDが1または2である」という条件が満たされた上で、さらに price < 1000
という条件を満たすレコードだけが選択されます。
このように、AND
とOR
を組み合わせて使う場合は、必ず括弧を使って評価の順序を明確にすることが強く推奨されます。これにより、クエリの意図が明確になり、バグを防ぐことができます。
2.4 OR とインデックス
OR
条件におけるインデックスの利用は、AND
条件と比較して少し複雑になることがあります。
- 同じカラムに対する OR:
WHERE category_id = 1 OR category_id = 2 OR category_id = 5
のように、同じカラムに対して複数の等価条件をOR
で結合する場合、MySQLはこれらの条件を最適化して、効率的にインデックスを利用できることがあります。これは、次に説明するIN
演算子が適している典型的なケースです。 - 異なるカラムに対する OR:
WHERE price < 500 OR stock < 10
のように、異なるカラムに対する条件をOR
で結合する場合、MySQLはそれぞれの条件に利用できるインデックスを個別にスキャンし、その結果をマージする「インデックス・マージ」という手法を使うことがあります。しかし、これが常に最も効率的であるとは限りません。場合によっては、フルテーブルスキャンの方が速いと判断されることもあります。 - OR と AND の組み合わせ:
(category_id = 1 OR category_id = 2) AND price < 1000
のような場合、インデックスがどのように利用されるかは、クエリの構造、利用可能なインデックス、データの分布に大きく依存します。複合インデックス(例:(category_id, price)
)が有効な場合もあれば、個別のインデックスが使われる場合もあります。
一般的に、OR
条件はAND
条件よりもインデックスの利用が難しく、パフォーマンスの問題を引き起こしやすい傾向があります。特に多くの条件をOR
で結合する場合や、異なるカラムに対してOR
を使う場合は、パフォーマンスに注意が必要です。EXPLAIN
を使って、実際にインデックスがどのように使われているかを確認することが不可欠です。
OR
は柔軟な条件指定を可能にしますが、特にAND
との組み合わせやパフォーマンス面では注意が必要です。次に、特定の集合に含まれるかどうかを簡単に判定できるIN
演算子を見ていきましょう。
第3章:IN 演算子 – 特定の値のリストに含まれるレコードを抽出する
IN
演算子は、あるカラムの値が、指定された値のリストの中に含まれているかどうかを判定するために使用します。これは、複数のOR
条件を簡潔に記述するのに非常に役立ちます。
3.1 基本的な使い方
IN
演算子は、カラム名の後にIN
キーワードを続け、その後に括弧で囲んだ値のリストを指定します。
sql
SELECT カラム名 FROM テーブル名 WHERE カラム名 IN (値1, 値2, 値3, ...);
この構文は、「カラム名
の値が、値1
、値2
、値3
… のいずれかに等しい」すべてのレコードを選択します。これは、WHERE カラム名 = 値1 OR カラム名 = 値2 OR カラム名 = 値3 OR ...
と全く同じ意味ですが、より読みやすく、記述も簡単です。
3.2 具体例
カテゴリID(category_id
)が1、または2、または5である商品を検索します。先ほどのOR
を使った例と同じ条件ですが、IN
を使うことでより簡潔に書けます。
sql
SELECT product_name, category_id
FROM products
WHERE category_id IN (1, 2, 5);
このクエリは、category_id
が1, 2, 5のいずれかである商品をすべて返します。
3.3 IN とサブクエリ
IN
演算子の強力な使い方の一つは、値のリストをサブクエリの結果セットとして指定することです。これにより、「あるテーブルのデータが、別のテーブルの特定の条件を満たすデータに関連しているか」といった複雑な条件を表現できます。
sql
SELECT カラム名 FROM テーブル名 WHERE カラム名 IN (サブクエリ);
サブクエリは、単一のカラムを持つ結果セットを返す必要があります。
具体例:
注文されたことのある商品(つまり、order_items
テーブルに登場する商品)をproducts
テーブルから検索します。
sql
SELECT product_name, product_id
FROM products
WHERE product_id IN (SELECT product_id FROM order_items);
このクエリは、order_items
テーブルからすべてのproduct_id
を取得し、そのリストに含まれるproduct_id
を持つproducts
テーブルのレコードをすべて選択します。
3.4 IN のパフォーマンスと代替手段
IN
演算子は、リストのサイズが小さい場合や、サブクエリが高速に実行される場合には非常に効率的です。特に、固定値のリストに対してインデックス付きのカラムでIN
を使う場合、MySQLは内部的に複数のOR
条件として処理する際に最適化を行い、効率的にインデックスを利用できることが多いです。
しかし、IN
リストが非常に大きい場合や、サブクエリの実行に時間がかかる場合、パフォーマンスの問題が発生することがあります。また、サブクエリの結果セットが大きすぎると、メモリを消費する可能性があります。
IN
(特にサブクエリを伴う場合)の代替手段としては、以下のようなものがあります。
- OR の連鎖:
IN (値1, 値2, ...)
と同じですが、リストが長くなると記述が煩雑になります。 -
JOIN: サブクエリの代わりに、
INNER JOIN
やLEFT JOIN
を使って関連するテーブルと結合し、WHERE
句で絞り込む方法です。多くのケースでIN
サブクエリよりもJOIN
の方がパフォーマンスが良いとされています。例:
IN (SELECT product_id FROM order_items)
の代わりにJOIN
を使う場合。sql
SELECT p.product_name, p.product_id
FROM products p
INNER JOIN order_items oi ON p.product_id = oi.product_id;
このクエリは、products
テーブルとorder_items
テーブルをproduct_id
で結合し、一致する行だけを返します。結果には重複が含まれる可能性があるため、重複を除去したい場合はDISTINCT
を使用するか、サブクエリでEXISTS
を使用する方法を検討します。 -
EXISTS:
IN
サブクエリと似ていますが、サブクエリが値を返すのではなく、「少なくとも1行が条件を満たすかどうか」を判定します。相関サブクエリと共に使用されることが多く、サブクエリが大きな結果セットを返す可能性がある場合には、IN
よりもEXISTS
の方が効率的になることがあります。例:
IN (SELECT product_id FROM order_items)
の代わりにEXISTS
を使う場合。sql
SELECT p.product_name, p.product_id
FROM products p
WHERE EXISTS (SELECT 1 FROM order_items oi WHERE oi.product_id = p.product_id);
このクエリは、products
テーブルの各行に対して、対応するproduct_id
を持つ行がorder_items
テーブルに存在するかどうかをチェックします。存在すれば、そのproducts
行を選択します。
どの方法が最も効率的かは、データの量、データの分布、利用可能なインデックス、MySQLのバージョンなどによって異なります。ここでもEXPLAIN
を使って比較検討することが重要です。一般的には、固定値リストの場合はIN
、関連テーブルとの条件指定の場合はJOIN
やEXISTS
が推奨される傾向があります。
3.5 IN と NULL
IN
演算子で指定するリストの中にNULL
が含まれている場合、または比較対象のカラムの値がNULL
である場合、結果は少し注意が必要です。
WHERE カラム名 IN (値1, 値2, NULL)
:
カラムの値が値1
または値2
と等しければTRUEになります。カラムの値がNULL
の場合、NULL
との比較は常にUnknownになるため、この条件全体としてはTRUEになりません。また、カラムの値がリスト中のNULL
以外のどの値とも等しくなく、かつカラムの値がNULL
である場合も、結果はUnknownとなり、そのレコードは選択されません。リスト中のNULL
は、実質的に無視されます。WHERE NULL IN (値1, 値2, 値3)
:
NULL
がリストに含まれているかどうかの判定は、常にUnknownになります。したがって、この条件はTRUEになることはありません。WHERE カラム名 IN (サブクエリ)
:
サブクエリの結果セットにNULL
が含まれていても、NULL
との比較はUnknownになるため、カラムの値がNULL
であるレコードは選択されません。ただし、カラムの値がサブクエリ結果セット中のNULL
以外の値と一致すれば選択されます。
NULL
を含む比較の挙動は、他の比較演算子と同様にUnknownを返す可能性があるため、期待通りの結果を得るためにはIS NULL
やIS NOT NULL
を明示的に使用することが必要になる場合があります(後述)。
3.6 IN とインデックス
IN (値1, 値2, ...)
のように固定値リストを指定する場合、もし対象のカラムにインデックスがあれば、MySQLは通常、インデックスを利用して高速にこれらの値を検索できます。これは、複数のインデックスルックアップを効率的に実行することによって実現されます。したがって、多くの固定値をOR
で繋げるよりもIN
を使う方が、インデックス利用の点で有利になることがよくあります。
IN (サブクエリ)
の場合、インデックスの利用効率はサブクエリの種類と結果セットのサイズ、そして外部クエリのカラムにインデックスがあるかどうかに依存します。非相関サブクエリであれば、サブクエリの結果がまずキャッシュされ、そのキャッシュされたリストに対して外部クエリのカラムのインデックスが使われる可能性があります。相関サブクエリの場合は、外部クエリの各行に対してサブクエリが実行されるため、サブクエリ内で適切にインデックスが利用されることが重要です。
IN
は、特定の候補リストからの選択をシンプルに記述できる強力なツールですが、特にパフォーマンスに配慮が必要な場合は、JOIN
やEXISTS
といった代替手段も視野に入れて検討すべきです。
ここまで、AND
、OR
、IN
それぞれの基本的な使い方を見てきました。次は、これらの演算子を組み合わせて、より複雑な条件を指定する方法を掘り下げます。
第4章:AND と OR の組み合わせ – 複雑な条件の構築
実際のクエリでは、複数のAND
とOR
を組み合わせて、より複雑なビジネスロジックに基づいた条件を指定することがよくあります。前述のように、この際には括弧を使って評価の順序を明確にすることが極めて重要です。
4.1 優先順位と括弧の再確認
SQLの論理演算子のデフォルトの優先順位は以下の通りです(高い順)。
NOT
AND
OR
したがって、括弧がない場合、AND
はOR
より先に評価されます。
A OR B AND C
はA OR (B AND C)
と同じA AND B OR C AND D
は(A AND B) OR (C AND D)
と同じ
しかし、デフォルトの優先順位に頼るのではなく、意図した論理構造に合わせて必ず括弧を使用することで、クエリの可読性を高め、間違いを防ぐことができます。
4.2 複雑な条件の例
様々な組み合わせの例を見てみましょう。
例1:「特定のカテゴリの商品で、価格が範囲内にあるもの」または「在庫数が少ない商品」
- 条件A:
category_id = 3 AND (price BETWEEN 1000 AND 5000)
- 条件B:
stock < 10
これらの条件をOR
で結びます。
sql
SELECT product_name, category_id, price, stock
FROM products
WHERE (category_id = 3 AND price BETWEEN 1000 AND 5000) OR stock < 10;
このクエリは、
1. カテゴリIDが3で、かつ価格が1000から5000の範囲にある商品
または
2. 在庫数が10個未満の商品
をすべて選択します。括弧がない場合、AND
が先に評価されてしまい、意図しない結果になる可能性があります。
例2:「特定の顧客からの注文で、高額なもの」または「特定の期間内のすべての注文」
- 条件A:
customer_id = 101 AND total_amount > 5000
- 条件B:
order_date BETWEEN '2023-01-01' AND '2023-03-31'
これらの条件をOR
で結びます。
sql
SELECT order_id, customer_id, total_amount, order_date
FROM orders
WHERE (customer_id = 101 AND total_amount > 5000) OR (order_date BETWEEN '2023-01-01' AND '2023-03-31');
このクエリは、
1. 顧客IDが101で、かつ合計金額が5000より大きい注文
または
2. 2023年1月1日から2023年3月31日までの期間に行われた注文
をすべて選択します。ここでも、各論理ブロックを括弧で囲むことで、AND
とOR
の評価順序を明確にしています。
例3:「複数の特定のカテゴリの商品で、かつ在庫が十分にあるもの」
- 条件A:
category_id IN (1, 2, 5)
- 条件B:
stock >= 100
これらの条件をAND
で結びます。
sql
SELECT product_name, category_id, stock
FROM products
WHERE category_id IN (1, 2, 5) AND stock >= 100;
このクエリは、カテゴリIDが1、2、または5のいずれかであり、かつ在庫数が100以上である商品をすべて選択します。IN
演算子は複数のOR
条件をまとめて表現しているため、実質的には(category_id = 1 OR category_id = 2 OR category_id = 5) AND stock >= 100
と同じ意味になります。この場合も、IN
の部分全体が先に評価されるように、理論的には括弧が必要です。しかし、IN
演算子は単一のカラムに対する複数の等価条件であるため、AND
よりも優先度が低いOR
の集まりであっても、AND
との組み合わせで意図通りに機能することが多いです。とはいえ、複雑なクエリでは明示的に括弧を使う方が安全です。
4.3 AND/OR 組み合わせとインデックス
AND
とOR
が組み合わされた複雑な条件の場合、MySQLがインデックスをどのように利用するかはさらに複雑になります。
(A AND B) OR (C AND D)
のような構造の場合、MySQLは条件A AND B
と条件C AND D
それぞれに対して利用可能なインデックスを評価し、結果をマージすることを試みる可能性があります。A AND (B OR C)
のような構造の場合、条件A
で絞り込んだ後、その絞り込まれた結果に対して(B OR C)
を評価するかもしれません。あるいは、B OR C
に利用できるインデックスを探すかもしれません。
インデックスの利用は、条件のカラム、条件の種類(等価、範囲、LIKEなど)、利用可能なインデックスの種類(シングルカラム、複合、全文など)、データの分布、そしてMySQLオプティマイザの判断に依存します。
複雑なWHERE
句のパフォーマンスを理解するには、EXPLAIN
ステートメントを使ってクエリの実行計画を確認することが不可欠です。EXPLAIN
の出力を見ることで、どのインデックスが使われているか、フルテーブルスキャンが発生しているか、どの程度効率的にレコードが絞り込まれているかなどを把握できます。これにより、インデックスの追加やクエリの書き換え(例: OR
をUNION
で分割するなど)といった最適化の方向性を判断できます。
第5章:NOT 演算子 – 条件を満たさないレコードを抽出する
NOT
演算子は、指定された条件が真(TRUE)ではないレコードを抽出したい場合に使用します。これは条件を反転させる論理否定です。
5.1 基本的な使い方
NOT
演算子は、反転させたい条件式の前に置きます。
sql
SELECT カラム名 FROM テーブル名 WHERE NOT 条件;
この構文は、「条件
が真ではない」すべてのレコードを選択します。つまり、条件
が偽(FALSE)またはUnknown(NULLの場合など)であるレコードです。
5.2 NOT と他の演算子の組み合わせ
NOT
はAND
、OR
、IN
といった他の演算子と組み合わせて使用できます。この場合も、括弧を使って評価の対象を明確にすることが推奨されます。
- NOT AND:
NOT (条件1 AND 条件2)
は、条件1と条件2の両方が真である場合に真にならない、つまり、条件1が偽または条件2が偽(またはその両方)である場合に真になります。これはド・モルガンの法則によりNOT 条件1 OR NOT 条件2
と等価です。 - NOT OR:
NOT (条件1 OR 条件2)
は、条件1または条件2のいずれか一方でも真である場合に真にならない、つまり、条件1も条件2も両方とも偽である場合に真になります。これはド・モルガンの法則によりNOT 条件1 AND NOT 条件2
と等価です。 - NOT IN:
カラム名 NOT IN (値1, 値2, ...)
は、カラムの値が指定されたリストに含まれていない場合に真になります。これはNOT (カラム名 = 値1 OR カラム名 = 値2 OR ...)
と等価です。
5.3 具体例
例1:特定のカテゴリではない商品
sql
SELECT product_name, category_id
FROM products
WHERE NOT category_id = 3; -- または WHERE category_id != 3;
これはカテゴリIDが3ではないすべての商品を返します。!=
や<>
演算子で否定等価条件を指定することもできますが、NOT
を使うことでより複雑な条件を否定できます。
例2:在庫数が10未満ではなく、かつ価格が1000円未満ではない商品
sql
SELECT product_name, stock, price
FROM products
WHERE NOT (stock < 10) AND NOT (price < 1000);
これは、在庫数が10以上(stock >= 10
)で、かつ価格が1000円以上(price >= 1000
)の商品を検索するのと論理的に同じです。後者の方が直感的で、インデックスの利用という点でも有利なことが多いです。一般的に、否定形(NOT
)は肯定形に書き換え可能であれば、肯定形の方が理解しやすくパフォーマンスも予測しやすい傾向があります。
例3:特定のカテゴリリストに含まれない商品
sql
SELECT product_name, category_id
FROM products
WHERE category_id NOT IN (1, 2, 5);
これは、カテゴリIDが1、2、5のいずれでもないすべての商品を返します。これはcategory_id != 1 AND category_id != 2 AND category_id != 5
と等価であり、NOT IN
を使う方がリストが長い場合に簡潔です。
5.4 NOT と NULL
NOT
演算子は、NULL
を扱う際に特に注意が必要です。SQLにおいて、条件式がUnknown(NULL
との比較など)を返した場合、NOT
を適用しても結果はUnknownのままです。
WHERE NOT (カラム名 IS NULL)
: カラムがNULL
でない場合に真となります。これはWHERE カラム名 IS NOT NULL
と等価であり、通常はこちらを使います。WHERE NOT (カラム名 > 10)
: カラムが10より大きくない場合に真となります。しかし、カラムがNULL
の場合、カラム名 > 10
はUnknownとなり、NOT (Unknown)
もUnknownとなるため、NULL
のレコードは選択されません。WHERE カラム名 NOT IN (値1, 値2)
: カラムの値が値1
でもなく、値2
でもない場合に真となります。しかし、カラムの値がNULL
の場合、NULL NOT IN (...)
はUnknownとなるため、NULL
のレコードは選択されません。NOT IN
リストにNULL
が含まれている場合も同様で、カラムの値がNULL
以外のリストの値と一致しない場合にUnknownを返す可能性があるため、NULL
のレコードは選択されません。
NULL
を含む比較において、特定の条件を満たさないレコード かつ NULL
でないレコードを選択したい場合は、NOT (条件) AND カラム名 IS NOT NULL
のようにIS NOT NULL
を明示的に追加する必要があります。あるいは、条件を肯定形で考え直し、IS NULL
やIS NOT NULL
を適切に組み合わせる方が、意図が明確になることが多いです。
NOT
演算子は条件を反転させますが、特にNULL
の扱いやインデックス利用においては、肯定形に書き換えることが推奨される場合が多いことを覚えておきましょう。
第6章:NULL 条件 (IS NULL, IS NOT NULL)
SQLにおけるNULL
は、「値が存在しない」「不明である」といった状態を表し、他の値(たとえNULL
自身であっても)との比較は、ほとんどの場合Unknownという特別な結果になります。したがって、通常の比較演算子(=
, !=
, >
, <
など)を使ってNULL
を判定することはできません。NULL
を判定するためには、専用のIS NULL
またはIS NOT NULL
を使用します。
6.1 IS NULL と IS NOT NULL の使い方
WHERE カラム名 IS NULL
: カラムの値がNULL
であるレコードを選択します。WHERE カラム名 IS NOT NULL
: カラムの値がNULL
ではないレコードを選択します。
具体例:
在庫数が不明(NULL
)の商品を検索します。
sql
SELECT product_name, stock
FROM products
WHERE stock IS NULL;
在庫数が不明ではない(NULL
ではない)商品を検索します。
sql
SELECT product_name, stock
FROM products
WHERE stock IS NOT NULL;
6.2 NULL と AND/OR/IN の組み合わせでの注意点
NULL
は、AND
やOR
、IN
といった論理演算子と組み合わせて使用する際に、Unknownという第三の値が存在するため、期待と異なる結果を返す可能性があります。
SQLの論理演算子は、TRUE、FALSE、Unknownの3つの値を扱います。
- AND: TRUE AND TRUE = TRUE; TRUE AND FALSE = FALSE; TRUE AND Unknown = Unknown; FALSE AND Unknown = FALSE; Unknown AND Unknown = Unknown
- OR: TRUE OR TRUE = TRUE; TRUE OR FALSE = TRUE; TRUE OR Unknown = TRUE; FALSE OR Unknown = Unknown; Unknown OR Unknown = Unknown
- NOT: NOT TRUE = FALSE; NOT FALSE = TRUE; NOT Unknown = Unknown
WHERE
句は、条件式の結果がTRUEであるレコードのみを選択します。UnknownやFALSEのレコードは選択されません。
例:
price
が1000より大きいか、またはstock
がNULL
である商品を検索したい。
sql
SELECT product_name, price, stock
FROM products
WHERE price > 1000 OR stock IS NULL;
これは意図通りに機能します。price > 1000
がTRUEであれば、OR
の規則により全体がTRUEになります。price > 1000
がFALSEまたはUnknownで、かつstock IS NULL
がTRUE(つまりstockがNULL)であれば、全体はTRUEになります。
例:
price
が1000より大きく、かつstock
がNULL
ではない商品を検索したい。
sql
SELECT product_name, price, stock
FROM products
WHERE price > 1000 AND stock IS NOT NULL;
これも意図通りに機能します。両方の条件がTRUEの場合のみ、全体がTRUEになります。
注意が必要なケース:
WHERE カラム名 = 値1 OR カラム名 = 値2 OR カラム名 IS NULL
のように、複数の等価条件に加えてIS NULL
条件をOR
で結合する場合、これは WHERE カラム名 IN (値1, 値2) OR カラム名 IS NULL
と書くことができます。
逆に、WHERE カラム名 NOT IN (値1, 値2)
は、NULL
のレコードを含みません。もしNULL
のレコードも含めたい場合は、WHERE (カラム名 NOT IN (値1, 値2)) OR カラム名 IS NULL
と明示的にOR カラム名 IS NULL
を追加する必要があります。
NULL
の扱いは、SQLにおけるWHERE句の落とし穴の一つです。通常の比較演算子はNULL
に対して期待通りに動作しないため、必ずIS NULL
またはIS NOT NULL
を使用し、論理演算子との組み合わせにおけるUnknownの挙動を理解しておくことが重要です。
第7章:その他の WHERE 句演算子(補足)
AND
、OR
、IN
は複数条件を指定するための論理演算子ですが、これらの演算子と組み合わせて使用される個々の条件式では、様々な比較演算子や特殊な演算子が使われます。ここでは、よく使われるものを簡単に補足します。
- 比較演算子:
=
(等しい)!=
または<>
(等しくない)>
(より大きい)<
(より小さい)>=
(より大きいか等しい)<=
(より小さいか等しい)
これらは数値、文字列、日付などの比較に使用されます。
- BETWEEN: 値が指定された範囲内にあるかどうかを判定します。
WHERE カラム名 BETWEEN 値1 AND 値2
はWHERE カラム名 >= 値1 AND カラム名 <= 値2
と等価です。境界値(値1
と値2
)を含みます。日付や数値の範囲指定に便利です。
例:WHERE order_date BETWEEN '2023-01-01' AND '2023-03-31'
- LIKE: 文字列が指定されたパターンに一致するかどうかを判定します。
%
(任意の文字列) と_
(任意の一文字) をワイルドカードとして使用します。
例:WHERE product_name LIKE 'SQL%'
(SQLで始まる文字列)
例:WHERE product_name LIKE '%Book'
(Bookで終わる文字列)
例:WHERE product_name LIKE '%data%'
(dataを含む文字列)
LIKE
検索、特に先頭がワイルドカードで始まる検索(例:LIKE '%...'
)は、通常インデックスを効率的に利用できません。 - REGEXP (または RLIKE): 文字列が正規表現に一致するかどうかを判定します。
LIKE
よりも複雑なパターンマッチングが可能です。パフォーマンスは一般的にLIKE
よりも低くなる傾向があります。
例:WHERE product_name REGEXP '^SQL'
(SQLで始まる文字列 – LIKEと同じ結果だが正規表現を使用)
これらの演算子は、AND
、OR
、IN
といった論理演算子と組み合わせて、より具体的で詳細な条件式を作成するために使用されます。例えば、WHERE category_id IN (1, 2) AND price > 1000 AND product_name LIKE '%Premium%'
のように組み合わせて使われます。
第8章:パフォーマンス考慮
大規模なデータベースからデータを検索する場合、WHERE
句のパフォーマンスは非常に重要です。非効率なWHERE
句は、クエリ全体の実行時間を大幅に増加させ、データベースサーバーに過負荷をかける可能性があります。ここでは、複数条件指定におけるパフォーマンスの考慮事項について掘り下げます。
8.1 インデックスの重要性
インデックスは、データベーステーブルからのデータ検索を高速化するための特殊なデータ構造です。書籍の索引のように、特定のカラムの値に基づいて目的の行を素早く見つけ出すことができます。WHERE
句で条件として指定されるカラムに適切なインデックスが設定されているかどうかは、クエリのパフォーマンスに絶大な影響を与えます。
インデックスがない場合、データベースはテーブル全体を最初から最後までスキャンして条件に一致する行を探す必要があります(フルテーブルスキャン)。これはテーブルのサイズに比例して時間がかかります。一方、インデックスがあれば、データベースはインデックスを使って直接、または非常に少ないステップで目的の行を見つけ出すことができます。
8.2 AND/OR/IN とインデックス利用
- AND:
WHERE 条件1 AND 条件2
の場合、もし条件1
と条件2
のどちらかのカラムにインデックスがあり、その条件が多くの行を絞り込める場合、MySQLはそのインデックスを利用して絞り込み、残りの条件を適用することがあります。または、条件1
と条件2
両方のカラムを含む複合インデックスがある場合、最も効率的にインデックスを利用できる可能性があります。複合インデックスは、その定義順序に沿って条件が指定されている場合に最も効果を発揮しやすいです(例:INDEX (col1, col2)
がある場合、WHERE col1 = 'A' AND col2 = 'B'
やWHERE col1 = 'A'
はインデックスを使えますが、WHERE col2 = 'B'
だけでは通常使えません)。 - OR:
WHERE 条件1 OR 条件2
の場合、インデックスの利用はより複雑になります。- 同じカラムに対する等価条件のOR (
col = val1 OR col = val2
) は、col IN (val1, val2)
と同等であり、col
にインデックスがあれば効率的に利用できます。 - 異なるカラムに対するOR (
col1 = val1 OR col2 = val2
) の場合、MySQLはインデックスマージ(各インデックスで検索して結果をマージ)を試みることがありますが、これが常に効率的とは限りません。多くの場合、このような条件はフルテーブルスキャンを引き起こしやすいです。もし可能であれば、このようなOR条件を避けるか、クエリの設計を見直すことを検討します。
- 同じカラムに対する等価条件のOR (
- IN:
WHERE col IN (値リスト)
の場合、col
にインデックスがあれば効率的に利用できます。これは内部的に複数の等価条件のORとして扱われ、インデックスを効率的にルックアップできるためです。値リストが大きい場合でも、インデックスがあればフルテーブルスキャンよりはるかに高速になることが多いです。
WHERE col IN (サブクエリ)
の場合、パフォーマンスはサブクエリの実行速度と、外部クエリのcol
にインデックスがあるかどうかに依存します。効率の悪いサブクエリや、col
にインデックスがない場合は遅くなる可能性があります。
8.3 EXPLAIN による実行計画の確認
クエリのパフォーマンスを分析する上で最も重要なツールは、MySQLのEXPLAIN
ステートメントです。クエリの前にEXPLAIN
を付けるだけで、MySQLがそのクエリをどのように実行するか(実行計画)を表示してくれます。
sql
EXPLAIN SELECT product_name, price FROM products WHERE price > 1000 AND stock < 50;
EXPLAIN
の出力で特に注目すべき列は以下の通りです。
type
: アクセスタイプ。ALL
(フルテーブルスキャン)は最も遅く、index
(フルインデックススキャン)、range
(インデックスを使った範囲検索)、ref
(非ユニークインデックスを使った等価検索)、eq_ref
(JOINにおけるユニークインデックスを使った等価検索)、const
/system
(定数)の順に効率が良くなります。目標はALL
やindex
を避け、より効率的なタイプにすることです。possible_keys
: MySQLがこのクエリの実行に利用可能だと考えたインデックス。key
: 実際にMySQLが実行計画で選択したインデックス。key_len
: 選択されたインデックスの長さ。ref
: インデックス検索で定数または他のカラムと比較された値。rows
: MySQLが条件を満たすために走査する必要があると推定した行数。この数が少ないほど効率的です。Extra
: 追加情報。例えば、Using index condition
(インデックスを使った条件評価)、Using where
(WHERE句による絞り込み)、Using index
(カバーリングインデックス – インデックスのみでクエリを完結)、Using temporary
(一時テーブルの使用 – 遅くなる要因)、Using filesort
(ファイルソート – ORDER BYなどがインデックスを使えない場合に発生し遅くなる要因) などが表示されます。
複雑なWHERE
句を持つクエリを最適化する際は、まずEXPLAIN
を実行し、現在の実行計画を理解することから始めます。type
がALL
になっている場合は、WHERE
句のカラムにインデックスを追加することを検討します。インデックスがあるにも関わらずALL
になっている場合は、インデックスが条件に合っていないか、データの分布上インデックスを使うよりフルスキャンの方が効率的だとオプティマイザが判断している可能性があります。
8.4 WHERE 句の条件の順序
MySQLのオプティマイザは賢いので、AND
条件の場合、WHERE 条件1 AND 条件2
と WHERE 条件2 AND 条件1
の間で実行計画が劇的に変わることは少ないです。しかし、インデックスの利用順序や、最も絞り込み効果が高い条件を先に評価させたいといった意図がある場合、条件の順序が影響を与えることも理論的にはあり得ます(ただし、これはオプティマイザのバージョンや状況に依存します)。
経験則として、最も絞り込み効果が高いと期待される条件(例えば、インデックスがあり、かつ該当するレコードが非常に少ない条件)を先に書くというプラクティスがありますが、これは必須ではなく、オプティマイザに任せても多くの場合問題ありません。それよりも、インデックスを適切に貼ること、そしてEXPLAIN
で実行計画を確認することの方が遥かに重要です。
OR
条件の場合、インデックスマージが発生するかどうかは、条件の順序よりもインデックスの有無とデータの分布に強く依存します。
パフォーマンス最適化は複雑なテーマであり、特定のケースでは上記以外の要因も影響します。しかし、WHERE
句で使われるカラムへのインデックス付けと、EXPLAIN
を使った実行計画の分析は、パフォーマンスチューニングの出発点として最も重要です。
第9章:実践的なテクニックと注意点
WHERE句の複数条件指定を効果的かつ安全に使うための実践的なテクニックと注意点をいくつか紹介します。
9.1 括弧の使い方による可読性と正確性の向上
前述の通り、AND
とOR
を組み合わせる際には、意図した論理構造を明確にするために必ず括弧を使用しましょう。たとえデフォルトの優先順位で意図通りになる場合でも、括弧を付けることでクエリの可読性が向上し、後からクエリを見直す際に混乱を防ぐことができます。複雑な条件では、括弧を適切に使うことがバグの予防に直結します。
“`sql
— 推奨しない (意図が不明瞭になる可能性がある)
WHERE category_id = 1 AND price < 1000 OR stock < 10;
— 推奨 (意図が明確)
WHERE (category_id = 1 AND price < 1000) OR stock < 10;
“`
9.2 複雑な条件の分解
非常に複雑な論理を持つWHERE
句は、単一のクエリに詰め込むと読みにくく、デバッグが困難になることがあります。場合によっては、複雑な条件をより単純な複数のクエリに分解し、一時テーブルや共通テーブル式 (CTE) を使用して段階的にデータを絞り込む方が、管理しやすくなることがあります。
例えば、「条件A AND 条件B AND (条件C OR 条件D OR 条件E) AND 条件F」のような複雑な条件は、WHERE
句全体が長くなりすぎると理解が難しくなります。この場合、一部の条件で一旦データを絞り込み、その結果に対してさらに条件を適用する、といったアプローチも検討できます。
9.3 データ型の整合性
WHERE
句で値を比較する場合、カラムのデータ型と比較する値のデータ型を一致させることが重要です。MySQLはある程度の型変換を自動で行いますが、これが常に意図通りに機能するとは限りませんし、型変換が発生するとインデックスが利用されなくなる原因になることもあります。
例えば、文字列型のカラムを数値として比較したり、日付型のカラムを文字列として比較したりする際には注意が必要です。日付や時刻の比較は、MySQLが提供する日付/時刻関数(DATE()
, TIME()
, YEAR()
, MONTH()
, DAY()
など)や、適切なフォーマットの文字列リテラルを使って行うのが安全です。
例: 日付カラムorder_date
がDATE
型の場合
“`sql
— 正しい(インデックスが利用されやすい)
WHERE order_date >= ‘2023-01-01’ AND order_date <= ‘2023-12-31’;
WHERE YEAR(order_date) = 2023; — 関数を使うとインデックスが効きにくい
— 正しくない可能性がある(意図通りに比較されない、インデックスが効かない)
WHERE order_date = ‘2023/01/01’; — MySQLは型変換を試みるが、フォーマットに注意
WHERE CAST(order_date AS CHAR) LIKE ‘2023%’; — 関数を使うとインデックスが効かない
“`
文字列の比較で大文字・小文字を区別するかどうかも、カラムの照合順序(collation)によって決まります。特定の比較方法が必要な場合は、BINARY
キーワードやCOLLATE
句を使用することも検討できます。
9.4 パラメータバインディングの重要性
アプリケーションからSQLを実行する際、ユーザーからの入力をWHERE
句の条件値として直接文字列結合でクエリに埋め込むのは、SQLインジェクションの脆弱性を生み出すため絶対に避けるべきです。代わりに、プレースホルダを使ったパラメータバインディング(プリペアドステートメント)を使用しましょう。
パラメータバインディングは、セキュリティ上の利点だけでなく、パフォーマンス上の利点もあります。MySQLはバインドされたクエリの実行計画を一度キャッシュし、パラメータの値が変わっても同じ実行計画を再利用できる場合があります。これにより、クエリ実行のオーバーヘッドを減らすことができます。
9.5 IN
リストのサイズ制限
IN
リストに含めることができる要素の数には、MySQLのmax_allowed_packet
設定や、SQLステートメントの長さ制限などの制約が関わってきます。非常に大きなリスト(数万件以上など)をIN
リストに含めることは、パフォーマンスやリソース消費の面で問題を引き起こす可能性があります。このような場合は、一時テーブルにリストを格納し、その一時テーブルとJOIN
する方法や、EXISTS
を使ったサブクエリに切り替える方法を検討します。
おわりに:複数条件指定のマスターに向けて
MySQLのWHERE
句におけるAND
、OR
、IN
演算子は、データベースから必要なデータを正確に、かつ効率的に抽出するための強力なツールです。これらの演算子を使いこなすことで、複雑なビジネスロジックに基づいた多様な検索条件を表現できるようになります。
この記事で解説した主要なポイントを改めてまとめます。
AND
はすべての条件が真であるレコードを選択します。複数の必須条件を指定する際に使用します。OR
はいずれかの条件が真であるレコードを選択します。複数の可能な条件を指定する際に使用します。IN
はカラムの値が指定されたリストに含まれるレコードを選択します。同じカラムに対する複数の等価条件を簡潔に記述できます。サブクエリと組み合わせることで、動的なリストに対する条件指定も可能です。AND
とOR
を組み合わせる場合は、評価順序を明確にするために必ず括弧()
を使用します。NOT
は条件を反転させますが、NULL
の扱いやパフォーマンスを考慮すると、可能な場合は肯定形で記述するのが望ましいです。NULL
値の判定には、通常の比較演算子ではなくIS NULL
またはIS NOT NULL
を使用します。論理演算子との組み合わせではUnknownの挙動に注意が必要です。- クエリのパフォーマンスは、
WHERE
句で使われるカラムへのインデックスの有無に大きく依存します。 - 複雑な
WHERE
句のパフォーマンス問題を解決するには、EXPLAIN
ステートメントを使って実行計画を分析することが不可欠です。 - 実践的なテクニックとして、括弧による可読性向上、条件の分解、データ型の整合性、パラメータバインディングの使用などが挙げられます。
これらの知識とテクニックを習得することで、データベース操作の効率と正確性を飛躍的に向上させることができます。実際の開発や分析作業では、これらの演算子を組み合わせて様々なシナリオに対応する必要があります。この記事が、MySQLのWHERE句を使った複数条件指定をマスターするための一助となれば幸いです。
学習の次のステップとしては、実際に様々なデータセットに対してこれらの演算子を使ったクエリを書き、EXPLAIN
を使って実行計画を確認する練習を重ねることをお勧めします。理論だけでなく、実践を通じて経験を積むことが、真の理解につながります。
Happy querying!