文字列をつなぐ
1
SELECT first_name || ' ' || last_name AS customer,
country
FROM customers
LIMIT 10;
結果 — 10 行のうち先頭 5 行
| customer | country |
|---|---|
| Liam Fischer | ES |
| Mateo Costa | FR |
| Yuki Jang | DE |
| Ines Meyer | AU |
| Leo Novak | ES |
ブラウザの別タブで Planche が開き、サンプルデータベースを読み込んだ状態でこのクエリが入ります。このページの例はどれも同じタブを使い回します。F9 を押すまで実行はされません。
The same expression elsewhere
-- PostgreSQL, Oracle: identical to SQLite.
SELECT first_name || ' ' || last_name AS customer FROM customers;
-- MySQL / MariaDB: || means OR unless PIPES_AS_CONCAT is set.
SELECT CONCAT(first_name, ' ', last_name) AS customer FROM customers;
-- SQL Server: + concatenates, and CONCAT() exists from 2012.
SELECT first_name + ' ' + last_name AS customer FROM customers;
SELECT CONCAT(first_name, ' ', last_name) AS customer FROM customers;
The same expression elsewhere — 比較のために載せています。ブラウザ内のサンプルは SQLite なので、この文はここでは実行できません。
|| 演算子が標準で、PostgreSQL、Oracle、SQLite はこれを使います。MySQL は || に独自の意味、つまり論理和を割り当てているので(サーバーが PIPES_AS_CONCAT で動いていない限り)、MySQL ではこの式は失敗せずに 0 か 1 を返します。SQL Server は + を使いますが、こちらは逆向きに曖昧で、数値列2つなら足し算になります。
CONCAT() は MySQL、MariaDB、PostgreSQL、SQL Server(2012以降)で利用できます。この4つのエンジン間では、もっとも移植しやすい形です。ただし、NULL の扱いは演算子とは異なります。
綴りだけでなく、挙動が違うものが2つあります。
- 演算子では NULL が伝播します。
'a' || NULLは PostgreSQL と SQLite で NULL、'a' + NULLは SQL Server で NULL です。ミドルネームが1つ欠けているだけで列全体が空になります。CONCAT()は NULL を空文字として扱います。Oracle の||も NULL を無視しますが、それは次の点が理由です。 - Oracle は空文字を NULL として扱います。そこでは
'' IS NULLが真で、他のどのエンジンでも偽です。MySQL では行が見つかるWHERE name = ''が、Oracle では1行も返しません。
日付を書式化する
3
SELECT order_number,
ordered_at,
strftime('%Y-%m', ordered_at) AS month,
strftime('%d/%m/%Y', ordered_at) AS day_first
FROM orders
LIMIT 10;
結果 — 10 行のうち先頭 5 行
| order_number | ordered_at | month | day_first |
|---|---|---|---|
| ORD-2022-000001 | 2022-03-09 16:12:52 | 2022-03 | 09/03/2022 |
| ORD-2023-000002 | 2023-12-13 00:59:50 | 2023-12 | 13/12/2023 |
| ORD-2024-000003 | 2024-06-01 20:22:14 | 2024-06 | 01/06/2024 |
| ORD-2023-000004 | 2023-04-12 22:22:30 | 2023-04 | 12/04/2023 |
| ORD-2025-000005 | 2025-07-15 11:11:56 | 2025-07 | 15/07/2025 |
Formatting a date elsewhere
-- MySQL / MariaDB
SELECT DATE_FORMAT(ordered_at, '%Y-%m') AS month FROM orders;
-- PostgreSQL, Oracle
SELECT to_char(ordered_at, 'YYYY-MM') AS month FROM orders;
-- SQL Server (2012+)
SELECT FORMAT(ordered_at, 'yyyy-MM') AS month FROM orders;
-- ...or, much faster in bulk, and the usual choice for grouping:
SELECT CONVERT(char(7), ordered_at, 126) AS month FROM orders;
Formatting a date elsewhere — 比較のために載せています。ブラウザ内のサンプルは SQLite なので、この文はここでは実行できません。
関数名は4つ、パターン言語は3種類です。strftime は C 風の %Y-%m、DATE_FORMAT は同じパーセント記号でも一部の文字の意味が違い、to_char は YYYY-MM、FORMAT は .NET の yyyy-MM を使います。最後のものは大文字小文字が意味を持ちます。mm は分です。
表示ではなく集計に使うなら、そのエンジンが安く計算できる形を選んでください。SQL Server では行数が多いとき FORMAT より CONVERT(char(7), col, 126) のほうがずっと速く、PostgreSQL では date_trunc('month', col) が to_char より速いうえ結果が日付のままです。そしてインデックスのレッスンを思い出してください。列をこれらで包めば、その列のインデックスは使えなくなります。
Planche は、どのエンジンが返したものでも表示を揃えます。ドライバは日付を YYYY-MM-DD HH:MM:SS[.mmm] で描画してグリッドを統一し、Oracle ドライバは NLS_DATE_FORMAT をそれに合わせるので、自分で書いた TO_CHAR の結果と、その隣のグリッドの表示が食い違いません。
2つの日付の差
5
SELECT o.order_number,
o.ordered_at,
s.delivered_at,
ROUND(julianday(s.delivered_at) - julianday(o.ordered_at), 1) AS days_to_deliver
FROM orders o
JOIN shipments s ON s.order_id = o.id
WHERE s.delivered_at IS NOT NULL
ORDER BY days_to_deliver DESC
LIMIT 15;
結果 — 15 行のうち先頭 5 行
| order_number | ordered_at | delivered_at | days_to_deliver |
|---|---|---|---|
| ORD-2023-000152 | 2023-11-27 00:00:56 | 2023-12-07 22:33:00 | 10.9 |
| ORD-2025-001288 | 2025-10-18 04:20:05 | 2025-10-28 19:54:56 | 10.6 |
| ORD-2024-001816 | 2024-01-23 03:41:56 | 2024-02-02 18:40:52 | 10.6 |
| ORD-2023-002976 | 2023-05-23 02:17:37 | 2023-06-02 15:33:53 | 10.6 |
| ORD-2022-001345 | 2022-05-02 07:54:46 | 2022-05-12 19:12:56 | 10.5 |
Days between two dates elsewhere
-- MySQL / MariaDB: whole days, first argument minus second.
SELECT DATEDIFF(delivered_at, ordered_at) AS days FROM ...;
-- PostgreSQL: subtracting two timestamps gives an interval.
SELECT delivered_at - ordered_at AS elapsed FROM ...;
SELECT EXTRACT(DAY FROM delivered_at - ordered_at) AS days FROM ...;
-- SQL Server: the unit comes first, and the arguments are the other way round.
SELECT DATEDIFF(day, ordered_at, delivered_at) AS days FROM ...;
-- Oracle: subtracting two DATEs gives a number of days.
SELECT delivered_at - ordered_at AS days FROM ...;
Days between two dates elsewhere — 比較のために載せています。ブラウザ内のサンプルは SQLite なので、この文はここでは実行できません。
違うのは名前だけではありません。MySQL の DATEDIFF(a, b) は2つの引数を取り、a - b を日数で返します。SQL Server の DATEDIFF(day, a, b) は単位を先に書き、b - a を返します。引数の順序が逆なので、片方からコピーしたクエリでは結果の符号が反転することがあります。PostgreSQL と Oracle は引き算を使い、それぞれ interval と日数を返します。
SQL Server 版はさらに、経過時間ではなく境界をまたいだ回数を数えます。DATEDIFF(day, '2025-01-01 23:00', '2025-01-02 01:00') は、2時間しか離れていなくても 1 です。
NULL を置き換える
7
SELECT id,
first_name,
phone,
COALESCE(phone, '(none)') AS portable,
IFNULL(phone, '(none)') AS sqlite_and_mysql
FROM customers
WHERE phone IS NULL
LIMIT 10;
結果 — 10 行のうち先頭 5 行
| id | first_name | phone | portable | sqlite_and_mysql |
|---|---|---|---|---|
| 2 | Mateo | NULL | (none) | (none) |
| 9 | Sven | NULL | (none) | (none) |
| 21 | Layla | NULL | (none) | (none) |
| 25 | Freya | NULL | (none) | (none) |
| 28 | Priya | NULL | (none) | (none) |
Replacing a NULL elsewhere
-- Everywhere, and the one to reach for:
SELECT COALESCE(phone, '(none)') FROM customers;
-- MySQL / MariaDB / SQLite
SELECT IFNULL(phone, '(none)') FROM customers;
-- SQL Server. Note ISNULL, not IFNULL, and it takes exactly two arguments.
SELECT ISNULL(phone, '(none)') FROM customers;
-- Oracle
SELECT NVL(phone, '(none)') FROM customers;
Replacing a NULL elsewhere — 比較のために載せています。ブラウザ内のサンプルは SQLite なので、この文はここでは実行できません。
COALESCE は標準で、どこにでもあり、引数をいくつでも取って最初の非 NULL を返します。エンジン固有のものはどれもちょうど2引数で、しかも2つは取り違えやすい名前です。MySQL と SQLite が IFNULL、SQL Server が ISNULL。新しいコードで COALESCE より優先する理由はありません。
まとめると
クエリが複数のエンジンで動く必要があるなら、CONCAT()、COALESCE()、CAST() でだいたい間に合います。日付の書式化は移植できません。ユーザーのロケールとタイムゾーンが手元にあるアプリ側でやったほうが、たいていうまくいきます。