文字列連結の書き方 - 文字列連結の方言 (|| / CONCAT / +)
文字列連結の演算子は RDBMS で異なる。標準の || / MySQL の CONCAT / SQL Server の + と NULL の扱い
概念図
構文
sql
a || b / CONCAT(a, b) / a + bサンプル
フルネーム結合の 3 パターン
sql
-- 標準 / PostgreSQL / SQLite / Oracle
SELECT first_name || ' ' || last_name AS full_name FROM users;
-- MySQL (|| は論理 OR と解釈される)
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM users;
-- SET sql_mode = 'PIPES_AS_CONCAT'; を設定すれば || も使える
-- SQL Server
SELECT first_name + ' ' + last_name AS full_name FROM users;
-- または
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM users;文字列連結演算子の歴史
ANSI SQL 標準は || を文字列連結演算子と定めています。PostgreSQL / SQLite / Oracle / DB2 はこれに従っていますが、MySQL は歴史的事情で || を論理 OR として扱い、SQL Server は加算と同じ + を連結に転用しています。
近年はほぼ全ての RDBMS で CONCAT() 関数がサポートされたため、移植性重視なら CONCAT() が現実的な選択肢です。
RDBMS 別比較表
| RDBMS | 演算子 | 関数 | NULL を含むとき |
|---|---|---|---|
| PostgreSQL | || | CONCAT() | || は NULL 伝播、CONCAT() は NULL を空文字扱い |
| MySQL | || は OR (sql_mode 次第) | CONCAT() | CONCAT() は 1 つでも NULL なら結果 NULL |
| SQLite | || | なし (3.44+ で concat()) | || は NULL 伝播 |
| SQL Server | + | CONCAT() (2012+) | + は NULL 伝播、CONCAT() は NULL を空文字扱い |
| Oracle | || | CONCAT() (2 引数のみ) | || も NULL を空文字扱い (独自仕様) |
NULL の扱いの違いが一番の罠
ほとんどの RDBMS で「NULL を含む連結は NULL」になります。つまり middle_name が NULL のユーザーは first || ' ' || middle || ' ' || last で全体が NULL になってしまいます。
対策としては COALESCE(middle, '') で空文字に落とすか、NULL を空文字扱いしてくれる CONCAT()(PostgreSQL / SQL Server)や CONCAT_WS()(区切り文字付き・MySQL / PostgreSQL / SQL Server)を使います。
sql
-- NULL 安全な例 (PostgreSQL / MySQL / SQL Server)
SELECT CONCAT_WS(' ', first_name, middle_name, last_name) AS full_name
FROM users;
-- 古典的な書き方 (全 RDBMS)
SELECT first_name
|| COALESCE(' ' || middle_name, '')
|| ' ' || last_name AS full_name
FROM users;SQL Server で + が暗黙の型変換で壊れる
SQL Server の + は文字列でも数値でも使える多重定義演算子です。両辺が数値型だと加算になってしまい、'Order ' + 42 のような書き方は「文字列 + int」で暗黙変換エラーになります。
明示的に CAST(42 AS VARCHAR) するか、型を気にしなくてよい CONCAT() を使うのが安全です。
sql
-- NG: 暗黙変換エラー
SELECT 'Order ' + 42;
-- OK
SELECT 'Order ' + CAST(42 AS VARCHAR(10));
SELECT CONCAT('Order ', 42);