【SQL】指定した項目が数字以外のデータを抽出する方法|REGEXP・ISNUMERIC・REGEXP_LIKEをRDBMS別に解説

データベースのVARCHAR列には、本来数値が入るべき項目に「abc」「N/A」「-」などの数字以外の値が混入していることがあります。CSVインポートや手入力によるデータ不備は実務で頻繁に発生し、集計や型変換でエラーの原因になります。

この記事では、SQLで指定した項目が数字以外のデータを抽出する方法を、MySQL・PostgreSQL・SQL Server・Oracleの4つのRDBMSに対応した構文で体系的に解説します。正規表現パターンの詳細、ISNUMERICの落とし穴、NULL/空文字列の扱い、データクレンジングへの活用まで、実務で必要な知識を網羅しています。

この記事で学べること

  • 「数字以外のデータ」とは何か(VARCHAR列の混在データ問題)
  • MySQL:REGEXP / NOT REGEXP で数字以外を抽出する方法
  • PostgreSQL:~ 演算子・SIMILAR TO で非数値を判定する方法
  • SQL Server:TRY_CAST / ISNUMERIC / PATINDEX の使い分け
  • Oracle:REGEXP_LIKE で非数値データを検出する方法
  • 整数・小数・負数・指数表記に対応した正規表現パターン
  • ISNUMERICの落とし穴と安全な代替手段
  • NULL・空文字列の扱い方
  • 数字のみ抽出する逆パターン
  • RDBMS間の構文比較テーブル
  • データクレンジング・インポートデータ検証への活用
スポンサーリンク

サンプルデータの準備

以下のテーブルを使って、各RDBMSでの抽出方法を解説します。price 列はVARCHAR型で、数値と非数値が混在しています。

CREATE TABLE + INSERT文(クリックで展開)
CREATE TABLE products (
    id       INT PRIMARY KEY,
    name     VARCHAR(50),
    price    VARCHAR(20),  -- あえてVARCHAR型
    category VARCHAR(20)
);

INSERT INTO products VALUES
(1,  '商品A', '1500',    '食品'),
(2,  '商品B', 'abc',     '飲料'),
(3,  '商品C', '2400',    '食品'),
(4,  '商品D', 'N/A',     '日用品'),
(5,  '商品E', '3200',    '飲料'),
(6,  '商品F', '---',     '食品'),
(7,  '商品G', '980.5',   '飲料'),
(8,  '商品H', '未定',    '日用品'),
(9,  '商品I', '-500',    '食品'),
(10, '商品J', '',       '飲料'),
(11, '商品K', NULL,      '日用品'),
(12, '商品L', '1.5e3',   '食品');
id name price category
1 商品A 1500 食品
2 商品B abc 飲料
3 商品C 2400 食品
4 商品D N/A 日用品
5 商品E 3200 飲料
6 商品F 食品
7 商品G 980.5 飲料
8 商品H 未定 日用品
9 商品I -500 食品
10 商品J (空文字) 飲料
11 商品K NULL 日用品
12 商品L 1.5e3 食品

注意:price列はVARCHAR型です。数値が入るべき列をVARCHAR型で定義してしまうと、数値以外の値が格納できてしまいます。赤字の行が「数字以外のデータ」として抽出される対象です。

「数字以外のデータ」とは何か

VARCHAR(文字列型)列に格納されたデータのうち、数値として解釈できないものを「数字以外のデータ」と呼びます。具体的には以下のようなケースです。

パターン 数値?
英字のみ abc, hello No
記号を含む N/A, ---, $100 No
日本語を含む 未定, 確認中 No
空文字列 ''(長さ0の文字列) No
整数 1500, 0 Yes
小数 980.5, 3.14 Yes
負数 -500, -3.14 Yes
指数表記 1.5e3, 2E10 場合による

ポイント:「数字以外」の定義はビジネス要件によって変わります。整数のみを数値とするか、小数・負数・指数表記も含めるかで、使う正規表現パターンが異なります。

MySQL:REGEXP / NOT REGEXP で数字以外を抽出する

MySQLでは REGEXP(正規表現マッチ)演算子を使って、数値パターンに一致しないデータを抽出できます。

基本:整数以外のデータを抽出する

最もシンプルなパターンです。^[0-9]+$ は「先頭から末尾まで数字のみ」にマッチするため、NOT を付けることで数字以外を抽出します。

MySQL – 整数以外のデータを抽出
SELECT *
FROM products
WHERE price NOT REGEXP ';

実行結果

+----+-------+-------+----------+
| id | name  | price | category |
+----+-------+-------+----------+
|  2 | 商品B | abc   | 飲料     |
|  4 | 商品D | N/A   | 日用品   |
|  6 | 商品F | ---   | 食品     |
|  7 | 商品G | 980.5 | 飲料     |
|  8 | 商品H | 未定  | 日用品   |
|  9 | 商品I | -500  | 食品     |
| 10 | 商品J |       | 飲料     |
| 12 | 商品L | 1.5e3 | 食品     |
+----+-------+-------+----------+

このクエリは「整数のみ」を数値とみなすため、小数(980.5)や負数(-500)も「数字以外」として抽出されます。NULLの行(id=11)は REGEXP の結果が NULL になるため、結果に含まれない点に注意してください。

応用:小数・負数も数値として扱う

小数点や負号を含む値も数値として扱いたい場合は、正規表現パターンを拡張します。

MySQL – 小数・負数も数値とみなす
SELECT *
FROM products
WHERE price NOT REGEXP ';

実行結果

+----+-------+-------+----------+
| id | name  | price | category |
+----+-------+-------+----------+
|  2 | 商品B | abc   | 飲料     |
|  4 | 商品D | N/A   | 日用品   |
|  6 | 商品F | ---   | 食品     |
|  8 | 商品H | 未定  | 日用品   |
| 10 | 商品J |       | 飲料     |
| 12 | 商品L | 1.5e3 | 食品     |
+----+-------+-------+----------+

パターン ^-?[0-9]+(\.[0-9]+)?$ の各部分の意味は以下の通りです。

パターン 意味
^ 文字列の先頭
-? マイナス記号が0個または1個(負数対応)
[0-9]+ 1桁以上の数字
(\.[0-9]+)? 小数点+数字が0回または1回(小数対応)
$ 文字列の末尾

MySQL 8.0+:REGEXP_LIKE 関数

MySQL 8.0以降では、REGEXP_LIKE() 関数も使用できます。第3引数でフラグを指定でき、より柔軟な制御が可能です。

MySQL 8.0+ – REGEXP_LIKE関数
SELECT *
FROM products
WHERE NOT REGEXP_LIKE(price, ');

PostgreSQL:~ 演算子・SIMILAR TO で数字以外を抽出する

PostgreSQLでは、POSIX正規表現の ~ 演算子 または SIMILAR TO を使って非数値データを抽出できます。

~ 演算子を使う方法(推奨)

~ はPOSIX正規表現マッチ演算子で、!~ はその否定形です。

PostgreSQL – ~ 演算子で整数以外を抽出
-- 整数以外を抽出
SELECT *
FROM products
WHERE price !~ ';

-- 小数・負数も数値とみなす
SELECT *
FROM products
WHERE price !~ ';

SIMILAR TO を使う方法

SIMILAR TO はSQL標準に近い正規表現マッチです。構文がやや異なります。

PostgreSQL – SIMILAR TO
SELECT *
FROM products
WHERE price NOT SIMILAR TO ';

ポイント:SIMILAR TO では暗黙的に文字列全体がマッチ対象になるため、^$ は不要です。一方、~ 演算子では部分マッチなので ^$ で全体一致を指定する必要があります。

PostgreSQLの型キャスト方式

正規表現の代わりに、型キャストの成功/失敗で判定する方法もあります。

PostgreSQL – キャストで判定(安全な方法)
-- カスタム関数を作成
CREATE OR REPLACE FUNCTION is_numeric(text) RETURNS BOOLEAN AS $$
BEGIN
    PERFORM $1::NUMERIC;
    RETURN TRUE;
EXCEPTION WHEN others THEN
    RETURN FALSE;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- 数値以外を抽出
SELECT *
FROM products
WHERE NOT is_numeric(price);

この方法は指数表記(1.5e3)も正しく数値と判定できる利点があります。

SQL Server:TRY_CAST / ISNUMERIC / PATINDEX で数字以外を抽出する

SQL Serverには複数の判定方法がありますが、それぞれ特性が異なります。

方法1:TRY_CAST / TRY_CONVERT(推奨)

SQL Server 2012以降で使える TRY_CAST は、変換に失敗すると NULL を返します。最も安全で推奨される方法です。

SQL Server – TRY_CAST(推奨)
SELECT *
FROM products
WHERE TRY_CAST(price AS DECIMAL(18,2)) IS NULL
  AND price IS NOT NULL;

実行結果

+----+-------+-------+----------+
| id | name  | price | category |
+----+-------+-------+----------+
|  2 | 商品B | abc   | 飲料     |
|  4 | 商品D | N/A   | 日用品   |
|  6 | 商品F | ---   | 食品     |
|  8 | 商品H | 未定  | 日用品   |
| 10 | 商品J |       | 飲料     |
+----+-------+-------+----------+

TRY_CAST は指数表記(1.5e3)も FLOAT として変換可能です。DECIMAL に変換する場合は失敗する可能性があるため、用途に応じて FLOAT または DECIMAL を使い分けてください。

方法2:ISNUMERIC 関数

ISNUMERIC() は値が数値に変換可能なら 1 を返す関数ですが、注意点が多い 関数です。

SQL Server – ISNUMERIC(注意が必要)
SELECT *
FROM products
WHERE ISNUMERIC(price) = 0;

ISNUMERICの落とし穴:以下の値は ISNUMERIC = 1(数値と判定)になりますが、実際には数値として使えないことがあります。

ISNUMERIC 実際の扱い
$100 1(数値) CAST(… AS INT) でエラー
\100 1(数値) CAST(… AS INT) でエラー
,(カンマ単体) 1(数値) 数値として無意味
.(ピリオド単体) 1(数値) 数値として無意味
1e5 1(数値) CAST(… AS INT) でエラー
+, -(符号のみ) 1(数値) 数値として無意味

このため、ISNUMERIC の代わりに TRY_CAST を使うことを強く推奨します。

方法3:PATINDEX(正規表現の代替)

SQL ServerにはREGEXPがありませんが、PATINDEX でパターンマッチが可能です。

SQL Server – PATINDEX
-- 数字以外の文字を含むデータを抽出
SELECT *
FROM products
WHERE PATINDEX('%[^0-9]%', price) > 0;

PATINDEX('%[^0-9]%', price) は「数字以外の文字が含まれる位置」を返します。0 より大きければ非数値文字が存在するということです。ただし、この方法ではマイナス記号や小数点も「数字以外」と判定されるため、整数チェック向きです。

Oracle:REGEXP_LIKE で数字以外を抽出する

Oracleでは REGEXP_LIKE 関数を使って正規表現マッチングを行います。

基本:整数以外のデータを抽出する

Oracle – REGEXP_LIKE で整数以外を抽出
SELECT *
FROM products
WHERE NOT REGEXP_LIKE(price, ');

応用:小数・負数・指数表記にも対応する

Oracle – 小数・負数・指数にも対応
-- 小数・負数に対応
SELECT *
FROM products
WHERE NOT REGEXP_LIKE(price, ');

-- 指数表記にも対応(完全版)
SELECT *
FROM products
WHERE NOT REGEXP_LIKE(price, ');

Oracleの TO_NUMBER 方式

Oracleでは DEFAULT ... ON CONVERSION ERROR(Oracle 12c R2以降)を使った安全なキャスト方法もあります。

Oracle 12c R2+ – DEFAULT ON CONVERSION ERROR
SELECT *
FROM products
WHERE TO_NUMBER(price DEFAULT NULL ON CONVERSION ERROR) IS NULL
  AND price IS NOT NULL;

正規表現パターン詳細:用途別まとめ

「何を数値とみなすか」によって使う正規表現パターンが異なります。以下に用途別のパターンをまとめます。

用途 正規表現パターン マッチ例 非マッチ例
正の整数のみ ^[0-9]+$ 123, 0, 999 -1, 3.14, abc
整数(負数含む) ^-?[0-9]+$ 123, -456, 0 3.14, abc
小数も含む ^-?[0-9]+(\.[0-9]+)?$ 123, -3.14, 0.5 abc, .5
先頭の小数点も許容 ^-?([0-9]+\.?[0-9]*|\.[0-9]+)$ 123, .5, -.3 abc, .
指数表記も含む ^-?[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?$ 1.5e3, -2E10 abc, e5
符号付き(+/-) ^[+-]?[0-9]+(\.[0-9]+)?$ +100, -3.14 abc, +-1

ポイント:実務では「小数・負数を含む」パターン(^-?[0-9]+(\.[0-9]+)?$)が最も汎用的です。指数表記はビジネスデータでは稀なので、通常は不要です。

NULL・空文字列の扱い

REGEXP や ISNUMERIC で判定する際、NULL空文字列の挙動はRDBMSによって異なります。意図しない結果を避けるために、明示的に処理することが重要です。

RDBMS NULLのREGEXP結果 空文字のREGEXP結果 NOT REGEXP に含まれる?
MySQL NULL 0(不一致) NULL: 含まれない / 空文字: 含まれる
PostgreSQL NULL FALSE NULL: 含まれない / 空文字: 含まれる
SQL Server (ISNUMERIC: 0) (ISNUMERIC: 0) NULL: 含まれない / 空文字: 含まれる
Oracle NULL 空文字 = NULL扱い NULL・空文字とも含まれない

NULLも含めて抽出する安全なクエリ

MySQL – NULLも数字以外として抽出
SELECT *
FROM products
WHERE price IS NULL
   OR price = ''
   OR price NOT REGEXP ';

実行結果

+----+-------+-------+----------+
| id | name  | price | category |
+----+-------+-------+----------+
|  2 | 商品B | abc   | 飲料     |
|  4 | 商品D | N/A   | 日用品   |
|  6 | 商品F | ---   | 食品     |
|  8 | 商品H | 未定  | 日用品   |
| 10 | 商品J |       | 飲料     |
| 11 | 商品K | NULL  | 日用品   |
| 12 | 商品L | 1.5e3 | 食品     |
+----+-------+-------+----------+

注意:Oracleでは空文字列がNULLとして扱われます。他のRDBMSでは空文字列とNULLは別物です。RDBMS間でクエリを移植する際は、この違いに注意してください。

逆パターン:数字のみのデータを抽出する

「数字以外を抽出」の逆パターンとして、数字のみ(数値として有効なデータ)を抽出する方法も押さえておきましょう。

RDBMS別 – 数値のみ抽出
-- MySQL
SELECT * FROM products
WHERE price REGEXP ';

-- PostgreSQL
SELECT * FROM products
WHERE price ~ ';

-- SQL Server
SELECT * FROM products
WHERE TRY_CAST(price AS DECIMAL(18,2)) IS NOT NULL;

-- Oracle
SELECT * FROM products
WHERE REGEXP_LIKE(price, ');

実行結果(MySQL)

+----+-------+-------+----------+
| id | name  | price | category |
+----+-------+-------+----------+
|  1 | 商品A | 1500  | 食品     |
|  3 | 商品C | 2400  | 食品     |
|  5 | 商品E | 3200  | 飲料     |
|  7 | 商品G | 980.5 | 飲料     |
|  9 | 商品I | -500  | 食品     |
+----+-------+-------+----------+

RDBMS間比較テーブル

各RDBMSで「数字以外のデータを抽出する」ための構文を比較します。

RDBMS 正規表現 型キャスト 推奨
MySQL NOT REGEXP
REGEXP_LIKE() (8.0+)
CAST(col AS SIGNED)
(エラー時0)
REGEXP
PostgreSQL !~ 演算子
NOT SIMILAR TO
カスタム関数
::NUMERIC
~ 演算子
SQL Server PATINDEX
LIKE
TRY_CAST
ISNUMERIC
TRY_CAST
Oracle REGEXP_LIKE() TO_NUMBER
DEFAULT ON CONVERSION ERROR
REGEXP_LIKE

実務活用:データクレンジングへの応用

数字以外のデータを抽出するテクニックは、データクレンジング(データ品質の改善)に直結します。ここでは実務でよくあるパターンを紹介します。

パターン1:不正データの件数を把握する

MySQL – 数値/非数値のカウント
SELECT
    COUNT(*) AS total_rows,
    SUM(CASE WHEN price REGEXP '
         THEN 1 ELSE 0 END) AS numeric_count,
    SUM(CASE WHEN price NOT REGEXP '
              OR price IS NULL
         THEN 1 ELSE 0 END) AS invalid_count
FROM products;

実行結果

+------------+---------------+---------------+
| total_rows | numeric_count | invalid_count |
+------------+---------------+---------------+
|         12 |             5 |             7 |
+------------+---------------+---------------+

パターン2:インポートデータの検証

CSVファイルを一時テーブルにインポートした後、本テーブルに格納する前に検証するパターンです。

MySQL – インポートデータの検証と修正
-- 1. 不正データの特定
SELECT id, name, price,
    CASE
        WHEN price IS NULL THEN 'NULL値'
        WHEN price = '' THEN '空文字'
        WHEN price NOT REGEXP '
        THEN '非数値'
        ELSE 'OK'
    END AS status
FROM products;

-- 2. 不正データをデフォルト値に置換
UPDATE products
SET price = '0'
WHERE price IS NULL
   OR price = ''
   OR price NOT REGEXP ';

パターン3:カテゴリ別の不正データ率を算出

MySQL – カテゴリ別の不正データ率
SELECT
    category,
    COUNT(*) AS total,
    SUM(CASE WHEN price NOT REGEXP '
              OR price IS NULL
         THEN 1 ELSE 0 END) AS invalid,
    ROUND(
        SUM(CASE WHEN price NOT REGEXP '
                  OR price IS NULL
             THEN 1 ELSE 0 END) * 100.0
        / COUNT(*), 1
    ) AS invalid_pct
FROM products
GROUP BY category;

実行結果

+----------+-------+---------+-------------+
| category | total | invalid | invalid_pct |
+----------+-------+---------+-------------+
| 食品     |     5 |       2 |        40.0 |
| 飲料     |     4 |       1 |        25.0 |
| 日用品   |     3 |       2 |        66.7 |
+----------+-------+---------+-------------+

応用:英数字以外のデータを抽出する

数字だけでなく、英数字(アルファベット + 数字)以外のデータを抽出したい場合もあります。文字コード体系のバリデーションに有用です。

RDBMS別 – 英数字以外を抽出
-- MySQL
SELECT * FROM products
WHERE name NOT REGEXP ';

-- PostgreSQL
SELECT * FROM products
WHERE name !~ ';

-- SQL Server
SELECT * FROM products
WHERE PATINDEX('%[^a-zA-Z0-9]%', name) > 0;

-- Oracle
SELECT * FROM products
WHERE NOT REGEXP_LIKE(name, ');

よくあるエラーと対処法

エラー・問題 原因 対処法
NULLが結果に含まれない NULL との比較は常に NULL を返す OR col IS NULL を追加
小数が「数字以外」として抽出される ^[0-9]+$ は整数のみにマッチ 小数対応パターンに変更
ISNUMERIC で誤判定される 通貨記号等も数値と判定される TRY_CAST に置き換え
SQL Serverで REGEXP が使えない SQL Server は REGEXP 非対応 PATINDEX または TRY_CAST を使用
前後にスペースがあるデータ 見た目は数値でも空白を含む TRIM(col) で前処理
全角数字が判定されない [0-9] は半角数字のみ 全角を半角に変換してから判定

前後スペースへの対処

MySQL – TRIM で前処理してから判定
-- スペースを除去してから判定
SELECT *
FROM products
WHERE TRIM(price) NOT REGEXP '
   OR TRIM(price) = ''
   OR price IS NULL;

まとめ

SQLで指定した項目が数字以外のデータを抽出する方法を、4つのRDBMSに対応した形で解説しました。

やりたいこと 推奨する方法
MySQLで非数値を抽出 NOT REGEXP '^-?[0-9]+(\\.[0-9]+)?$'
PostgreSQLで非数値を抽出 !~ '^-?[0-9]+(\\.[0-9]+)?$'
SQL Serverで非数値を抽出 TRY_CAST(col AS DECIMAL) IS NULL
Oracleで非数値を抽出 NOT REGEXP_LIKE(col, '^-?[0-9]+(\\.[0-9]+)?$')
NULLも含めて抽出 OR col IS NULL を追加
不正データの件数を把握 SUM(CASE WHEN ... THEN 1 ELSE 0 END)
スペース混入への対処 TRIM(col) で前処理

数字以外のデータの抽出は、データクレンジングやインポートデータの品質チェックで欠かせないテクニックです。正規表現パターンはビジネス要件に応じて「整数のみ」「小数含む」「負数含む」と段階的に拡張できます。ISNUMERICの落とし穴に注意し、SQL Serverでは TRY_CAST を使うことを推奨します。

関連記事:WHERE句の使い方と複数条件の書き方LIKE演算子でのパターンマッチDISTINCTで重複データを削除する方法