「SQL Server」タグアーカイブ

SQL:NOT IN と NOT EXISTS の違いとパフォーマンス比較

SQLでサブクエリを使って除外条件を指定する際に利用される「NOT IN」と「NOT EXISTS」。両者の動作の違いやNULLの扱い、パフォーマンス差を実例付きで徹底解説します。

EXISTSANSI SQL(国際標準SQL)に含まれる構文 のため、
ほぼすべてのリレーショナルデータベースで利用できます。
古いバージョンの一部DBを除き、標準構文として移植性が非常に高いのが特徴です。

1. NOT IN と NOT EXISTS の基本構文

構文例説明
NOT INSELECT * FROM A WHERE ID NOT IN (SELECT ID FROM B);サブクエリの結果に含まれないIDを抽出
NOT EXISTSSELECT * FROM A WHERE NOT EXISTS (SELECT 1 FROM B WHERE A.ID = B.ID);Bに同じIDが存在しない場合のみAを取得

ポイント:

  • 両者とも「除外」目的だが、評価タイミングとNULL処理が異なる。


2. 動作の違い(NULLの扱いに注目)

条件NOT INの結果NOT EXISTSの結果
サブクエリにNULLが含まれるすべての行が除外される正常に比較できる
サブクエリが空(0件)全件取得される全件取得される

理由:
NOT IN は内部的に「A.ID <> B.ID」を繰り返すような処理を行うため、NULLが含まれると比較結果がUNKNOWNとなり、全体が評価されなくなる。
一方、NOT EXISTS行ごとに存在チェックを行うため、NULLの影響を受けない。


3. 実行結果の比較例

以下の例を見てみましょう。

テーブルA

ID NAME
1 田中
2 鈴木
3 佐藤

テーブルB

ID
1
NULL

4. パフォーマンスの違い

比較項目NOT INNOT EXISTS
NULLの影響受ける受けない
実行計画(最適化)インデックス利用されにくい場合あり最適化されやすい
大量データ時の効率遅くなるケースありより安定して高速
Oracleの最適化傾向半結合(Anti-Join)に変換されることあり同様に最適化される

実測例(概略)

件数NOT IN所要時間NOT EXISTS所要時間
1万件0.25秒0.20秒
10万件3.1秒1.8秒

※ 実測環境:Oracle 19c、インデックスあり、CPU 4コア相当


5. どちらを使うべきか

条件推奨句
サブクエリにNULLが含まれる可能性ありNOT EXISTS
データが小規模でNULLなしどちらでも可
大規模データ・実行計画を重視NOT EXISTS(推奨)
可読性を優先NOT EXISTS のほうが誤動作が少ない

6. まとめ

観点内容
ANSI SQL対応○(どのDBでも使用可能)
実行パフォーマンスDBごとに最適化される(MySQL 8以降で特に改善)
推奨度高い(NOT INより安全で移植性が高い)
注意点MySQL 5.x 以前では最適化が弱いケースがある

✔ 結論:
除外条件を指定する場合は、基本的に「NOT EXISTS」を使う方が安全で高速です。
ただし、NULLが確実に存在しないことが保証される小規模データではNOT INも選択肢になります。

SQL便利技:PIVOTとUNPIVOTで自由自在に表を変換する方法

SQLを使ってデータを扱うとき、表の形を「横持ち」や「縦持ち」に変換したい場面は多々あります。
例えば、月ごとの売上を列ごとに並べたい、あるいはアンケート結果を1列にまとめたいなど。

こうした「表の回転」に便利なのが PIVOTUNPIVOT です。
本記事では、それぞれの使い方と、主要なDBMSごとの違いを整理します。


PIVOTとは?

PIVOTは 縦持ちデータを横持ちに変換する 機能です。
例:月ごとの売上を集計して列化する。

サンプルデータ
商品売上
A1月100
A2月150
B1月200
B2月180

PIVOTのイメージ

商品1月売上2月売上
A100150
B200180


UNPIVOTとは?

UNPIVOTは 横持ちデータを縦持ちに変換する 機能です。
例:上記の「商品×月売上表」を再び「商品・月・売上」の縦持ちに戻す。


各DBMSでの書き方比較

1. SQL Server

SQL Serverはネイティブで PIVOT / UNPIVOT をサポート。

 
-- PIVOT
SELECT 商品, [1月] AS 売上1月, [2月] AS 売上2月
FROM (
  SELECT 商品, 月, 売上
  FROM Sales
) AS src
PIVOT (
  SUM(売上) FOR 月 IN ([1月], [2月])
) AS pvt;

-- UNPIVOT
SELECT 商品, 月, 売上
FROM SalesPivot
UNPIVOT (
  売上 FOR 月 IN ([1月売上], [2月売上])
) AS unpvt;

2. Oracle

Oracleは PIVOT / UNPIVOT が標準で利用可能。

-- PIVOT
SELECT *
FROM Sales
PIVOT (
  SUM(売上) FOR 月 IN ('1月' AS "売上1月", '2月' AS "売上2月")
);
-- UNPIVOT
SELECT 商品, 月, 売上
FROM SalesPivot
UNPIVOT (
  売上 FOR 月 IN (売上1月 AS '1月', 売上2月 AS '2月')
);

3. PostgreSQL

PostgreSQLはPIVOT句を持たないため、crosstab関数(tablefunc拡張) を使う。

-- PIVOT (crosstab)
SELECT *
FROM crosstab(
  'SELECT 商品, 月, 売上 FROM sales ORDER BY 1,2',
  'SELECT DISTINCT 月 FROM sales ORDER BY 1'
) AS ct(商品 text, "1月" int, "2月" int);
-- UNPIVOTは UNION ALL で対応
SELECT 商品, '1月' AS 月, 売上1月 AS 売上 FROM sales_pivot
UNION ALL
SELECT 商品, '2月', 売上2月 FROM sales_pivot;

 


4. MySQL

MySQLには PIVOT 句はなく、CASE式 + GROUP BY を使う。

-- PIVOT
SELECT
  商品,
  SUM(CASE WHEN 月 = '1月' THEN 売上 END) AS 売上1月,
  SUM(CASE WHEN 月 = '2月' THEN 売上 END) AS 売上2月
FROM Sales
GROUP BY 商品;
UNPIVOTも標準構文がないので、PostgreSQL同様 UNION ALL を用いる。
-- UNPIVOT (UNION ALL)
SELECT 商品, '1月' AS 月, 売上1月 AS 売上 FROM sales_pivot
UNION ALL
SELECT 商品, '2月', 売上2月 FROM sales_pivot;

 


DBMS比較表

DBMSPIVOT対応UNPIVOT対応代替手段
SQL Serverネイティブネイティブそのまま使用可
Oracleネイティブネイティブそのまま使用可
PostgreSQLなしなしcrosstab関数 / UNION ALL
MySQLなしなしCASE式 + GROUP BY / UNION ALL

 

まとめ

  • SQL Server / Oracle → PIVOT/UNPIVOTがシンプルに使える。

  • PostgreSQL / MySQL → 標準ではなく、関数やCASE式で工夫が必要。

「集計を横に展開したい」あるいは「フラットに戻したい」とき、
DBMSに応じた方法を覚えておくと、データ整形がぐっと楽になります。

SQL:全角文字と半角文字を判定する方法

SQLで全角文字と半角文字を判定するにはLENGTHBやOCTET_LENGTH関数で取得したバイト数とLENGTH関数で取得した文字数を比較することで判断することができます。

使用例

  • サンプルテーブル「goods」
  • クエリー(SQL)

    ORACLEの場合はOCTET_LENGTHをLENGTHBへ変更すれば同様の結果を得られます。

    SELECT SELECT name 
    FROM goods 
    WHERE name is NOT NULL 
     AND LENGTH(name) <> OCTET_LENGTH(name);
  • 出力結果

補足

1. 判定ロジックの背景と利用ケース

  • 全角文字・半角文字を判定する目的として、データ品質管理(例:ユーザー登録時の文字種チェック)、システム移行時の文字コード整合性確認、レポートや印刷用データの整形などが考えられます。

  • 特にマルチバイト文字(日本語)環境では、1文字あたりのバイト長が異なるため、バイト長と文字数のずれを利用した判定が有効です。

  • 本記事で紹介されているように、例えば PostgreSQL の LENGTH()OCTET_LENGTH() の比較を使う方法はシンプルでクロスプラットフォームにも応用可能です。

2. 各データベースでの関数違い・注意点

  • PostgreSQL:文字数を返す LENGTH(text)、バイト長を返す OCTET_LENGTH(text) を使う。
    例:WHERE length(col) <> octet_length(col)

  • Oracle:Oracle には直接 OCTET_LENGTH がないが、LENGTHB() がバイト長を、LENGTH() が文字数を返す(ただしCHAR型/NCHAR型、CHARSET設定によって挙動が異なる)ので、LENGTHB(col) <> LENGTH(col) という書き方が使えます。

  • MySQL:CHAR_LENGTH(col) が文字数、LENGTH(col) がバイト数を返すので、CHAR_LENGTH(col) <> LENGTH(col) といった判定が可能。

  • SQL Server:LEN() が文字数(末尾の空白はカウントされない)、DATALENGTH() がバイト長(文字コード依存)を返すので、環境次第では LEN(col) * 2 <> DATALENGTH(col) などと併用するケースがあります。

  • 注意点として、文字コード(UTF-8、UTF‐16、Shift_JIS 等)が環境によって異なると「半角=1バイト」「全角=2バイト」という前提が崩れる場合があります。特にUTF-8では日本語全角文字が3バイトある場合もありますので、バイト数の比較ロジックを適用する際は対象の文字コードを意識してください。

3. 実運用的な工夫・パフォーマンス面の注意

  • 大規模テーブルでこのようなチェックを行う場合、たとえば WHERE LENGTH(col) <> OCTET_LENGTH(col) のような関数を大量の行に対して実行するとインデックスが効かず、全表スキャンになる可能性があります。

    • 解決策:バッチ処理的に時間帯を分けて実施、あるいはチェック専用にサマリテーブルを用意する。

    • また、事前に「文字種/文字コード/想定バイト数」などのデータ仕様を設け、そもそも混在しないように制御するのが望ましいです。

  • 判定だけでなく、誰がいつどのレコードを修正したか(=トレーサビリティ)を残すなら、更新日時・更新者カラムを活用して「チェック済み/要修正」などのフラグを設けると管理しやすくなります。

  • 将来的に正規表現(REGEXP)や文字列関数(例えば Unicode プロパティを利用した判定)を使って「半角カタカナ」「全角英数字」「漢字のみ」などより細かく制御したい場合も多いため、可能ならその準備もしておくと良いでしょう。

4. よくある誤り・ハマりどころ

  • 前提として「文字数とバイト数が異なる=全角含む」という仮定をしているため、 半角カタカナ絵文字(マルチバイト4バイトなど) が含まれていると誤検知される可能性があります。たとえば、UTF-8環境で絵文字が4バイトなので「文字数1、バイト数4」→「異なる」と判定されてしまう。

  • 文字コード設定の違い:テーブル/カラムごとに異なる照合順序・文字セットが指定されていると、バイト数や文字数の挙動が予想と異なることがあります。例えば MySQL で utf8mb4 を使っているなら日本語全角文字が3バイトではなく4バイトになるケースあり。

  • 更新系の処理で「半角に変換された/全角に変換された」履歴を残していないため、修正したデータが「元はどちらだったか」が分からなくなるという運用上のリスク。必要に応じて「修正前の値」保持やログ出力を検討するべきです。

5. 具体的な応用例・コードスニペット

PostgreSQL での運用例

-- 全角文字を含む可能性のあるレコードを抽出
SELECT id, name
FROM goods
WHERE name IS NOT NULL
  AND length(name) <> octet_length(name);

MySQL での運用例

-- 半角/全角混在検知
SELECT id, name
FROM goods
WHERE name IS NOT NULL
  AND char_length(name) <> length(name);

Oracle での運用例

-- 全角文字を含む可能性のあるレコード(VARCHAR2、CHARセット依存)
SELECT id, name
FROM goods
WHERE name IS NOT NULL
  AND LENGTHB(name) <> LENGTH(name);

6. フォローアップ可能な内容・発展トピック

  • 正規表現を用いて「全角ひらがな」「全角カタカナ」「全角英数字」「半角英数字」などを分類・抽出する方法。

  • 文字種に応じて別テーブルへアーカイブ・除外といったワークフロー設計。

  • BI/レポート用途で「文字種別カウント」を可視化する方法(例:Excel/Tableau/BIツールを用いた文字種分布グラフ化)。

  • 外部システム(CSV/Excel)からデータインポート時に「文字種チェック+自動整形(全角→半角、半角→全角)」を組み込むETL(Extract-Transform-Load)設計。

  • 将来的に多言語対応を視野に入れた「Unicodeカテゴリ判定」(たとえば、CJK文字・ラテン文字・Emoji など)を含めた文字種チェック。