このシリーズでは共通のサンプルデータベースを使用します。初回(第1回)でCREATE TABLE文を掲載しています。
はじめに
SQL では、SELECT や WHERE の中で関数を使うことで、データの整形・変換・条件分岐を柔軟に行えます。第7回では、実務で頻出する 文字列関数・日付関数・CASE 式・NULL 処理関数 を体系的に学びます。
文字列関数
代表的な文字列関数をまとめます(MySQL 基準)。
| 関数 | 説明 | 例 |
|---|---|---|
CONCAT(s1, s2, ...) |
文字列を結合する |
CONCAT('A', 'B') → 'AB'
|
SUBSTRING(s, pos, len) |
部分文字列を取得する |
SUBSTRING('ABCDE', 2, 3) → 'BCD'
|
LENGTH(s) |
バイト数を返す |
LENGTH('あ') → 3(UTF-8) |
CHAR_LENGTH(s) |
文字数を返す |
CHAR_LENGTH('あいう') → 3
|
UPPER(s) / LOWER(s)
|
大文字/小文字に変換する |
UPPER('abc') → 'ABC'
|
TRIM(s) |
前後の空白を除去する |
TRIM(' A ') → 'A'
|
REPLACE(s, from, to) |
文字列を置換する |
REPLACE('ABC', 'B', 'X') → 'AXC'
|
LEFT(s, n) / RIGHT(s, n)
|
左/右から n 文字を取得する |
LEFT('ABCDE', 3) → 'ABC'
|
LPAD(s, len, pad) / RPAD(s, len, pad)
|
指定文字で左/右埋めする |
LPAD('5', 3, '0') → '005'
|
PostgreSQL との違い: PostgreSQL では
LENGTH()が文字数を返します(MySQL のCHAR_LENGTH()に相当)。バイト数を取得するにはOCTET_LENGTH()を使います。
日付関数
| 関数 | 説明 | 例 |
|---|---|---|
NOW() |
現在の日時を返す | 2025-04-01 10:30:00 |
CURDATE() |
現在の日付を返す | 2025-04-01 |
DATE_FORMAT(date, fmt) |
日付を書式指定で文字列化する |
DATE_FORMAT('2025-04-01', '%Y年%m月%d日') → '2025年04月01日'
|
YEAR(date) / MONTH(date) / DAY(date)
|
年/月/日を取得する |
YEAR('2025-04-01') → 2025
|
DATEDIFF(d1, d2) |
日数差(d1 - d2)を返す |
DATEDIFF('2025-04-01', '2025-03-01') → 31
|
TIMESTAMPDIFF(unit, d1, d2) |
指定単位で差を返す |
TIMESTAMPDIFF(YEAR, '2020-04-01', '2025-04-01') → 5
|
DATE_ADD(date, INTERVAL n unit) |
日付に加算する |
DATE_ADD('2025-04-01', INTERVAL 3 MONTH) → '2025-07-01'
|
DATE_SUB(date, INTERVAL n unit) |
日付から減算する |
DATE_SUB('2025-04-01', INTERVAL 1 YEAR) → '2024-04-01'
|
EXTRACT(unit FROM date) |
日付要素を抽出する |
EXTRACT(MONTH FROM '2025-04-01') → 4
|
PostgreSQL との違い: PostgreSQL では
DATE_FORMATの代わりにTO_CHAR(date, 'YYYY"年"MM"月"DD"日"')を使います。DATEDIFFは使えず、date1 - date2で日数差を得ます。TIMESTAMPDIFFも使えないため、EXTRACT(YEAR FROM AGE(d2, d1))などで代替します。
CASE 式
CASE 式は SQL における条件分岐です。2 つの書き方があります。
単純 CASE 式
CASE expression
WHEN value1 THEN result1
WHEN value2 THEN result2
ELSE default_result
END
特定のカラムの値と定数を比較する場合に使います。
検索 CASE 式
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
ELSE default_result
END
不等号や複合条件など、任意の条件式を書けます。実務ではこちらを使うことが多いです。
注意: CASE 式は上から順に評価され、最初に条件が真になった
THENの値が返ります。どの条件にも合致しない場合はELSEの値(省略時は NULL)が返ります。
NULL 処理関数
| 関数 | 説明 |
|---|---|
COALESCE(v1, v2, ...) |
最初の非 NULL 値を返す(SQL 標準) |
IFNULL(v1, v2) |
v1 が NULL なら v2 を返す(MySQL 独自) |
NULLIF(v1, v2) |
v1 = v2 なら NULL を返す |
推奨: 可搬性を考慮して
COALESCEを優先的に使いましょう。PostgreSQL でもCOALESCEはそのまま使えます。IFNULLは MySQL 独自関数です。
練習問題
問題 1 ⭐(基本)― 社員情報の整形表示
employees テーブルと departments テーブルを結合し、各社員の情報を以下のフォーマットで表示してください。department_id が NULL の社員は「未所属」と表示します。
表示形式: 社員番号: 001 - 田中太郎(営業部)
- 社員番号は
LPADを使って 3 桁ゼロ埋めにする -
CONCATで文字列を結合する -
employee_idの昇順で並べる
期待結果:
+----------------------------------------------+
| employee_display |
+----------------------------------------------+
| 社員番号: 001 - 田中太郎(営業部) |
| 社員番号: 002 - 佐藤花子(開発部) |
| 社員番号: 003 - 鈴木一郎(営業部) |
| 社員番号: 004 - 高橋美咲(人事部) |
| 社員番号: 005 - 伊藤健太(開発部) |
| 社員番号: 006 - 渡辺さくら(開発部) |
| 社員番号: 007 - 山本大輔(経理部) |
| 社員番号: 008 - 中村真理(営業部) |
| 社員番号: 009 - 小林誠(人事部) |
| 社員番号: 010 - 加藤優子(未所属) |
+----------------------------------------------+
模範解答
SELECT
CONCAT(
'社員番号: ',
LPAD(e.employee_id, 3, '0'),
' - ',
e.employee_name,
'(',
COALESCE(d.department_name, '未所属'),
')'
) AS employee_display
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
ORDER BY e.employee_id;
解説:
-
LPAD(e.employee_id, 3, '0')で社員番号を 3 桁ゼロ埋め(1→001)にします。 -
LEFT JOINでdepartmentsを結合し、department_idが NULL の社員(加藤優子)も結果に含めます。 -
COALESCE(d.department_name, '未所属')で部署名が NULL の場合に「未所属」を表示します。
問題 2 ⭐(基本)― 顧客名の文字数
customers テーブルから、顧客名(customer_name)とその文字数を取得してください。文字数の多い順に並べてください。
期待結果:
+--------------------------+--------+
| customer_name | 文字数 |
+--------------------------+--------+
| DEFコーポレーション | 11 |
| JKLテクノロジー | 9 |
| 株式会社ABC | 7 |
| MNOサービス | 7 |
| GHI商事 | 5 |
+--------------------------+--------+
計算根拠(CHAR_LENGTH は文字数を返す):
DEFコーポレーション: D, E, F, コ, ー, ポ, レ, ー, シ, ョ, ン → 11文字JKLテクノロジー: J, K, L, テ, ク, ノ, ロ, ジ, ー → 9文字MNOサービス: M, N, O, サ, ー, ビ, ス → 7文字株式会社ABC: 株, 式, 会, 社, A, B, C → 7文字GHI商事: G, H, I, 商, 事 → 5文字
模範解答
SELECT
customer_name,
CHAR_LENGTH(customer_name) AS 文字数
FROM customers
ORDER BY CHAR_LENGTH(customer_name) DESC, customer_id;
解説:
-
CHAR_LENGTH()は文字数を返します。日本語の全角文字も 1 文字としてカウントされます。 -
LENGTH()を使うとバイト数が返るため、UTF-8 環境では日本語 1 文字が 3 バイトとしてカウントされ、意図した結果になりません。 - 同じ文字数の場合は
customer_idの昇順で並べています。MNOサービス(id=5)と株式会社ABC(id=1)はともに 7 文字ですが、customer_id昇順により株式会社ABC(id=1)が先に来ます。
問題 3 ⭐⭐(応用)― 勤続年数の計算
employees テーブルから、各社員の勤続年数(基準日: '2025-04-01')を計算してください。TIMESTAMPDIFF 関数を使い、完了した年数(端数切り捨て)を求めます。勤続年数の長い順に並べてください。
期待結果:
+-----------------+------------+--------------+
| employee_name | hire_date | years_worked |
+-----------------+------------+--------------+
| 高橋美咲 | 2018-09-01 | 6 |
| 佐藤花子 | 2019-07-15 | 5 |
| 田中太郎 | 2020-04-01 | 5 |
| 山本大輔 | 2020-11-15 | 4 |
| 鈴木一郎 | 2021-01-10 | 4 |
| 中村真理 | 2021-08-01 | 3 |
| 伊藤健太 | 2022-03-20 | 3 |
| 渡辺さくら | 2023-06-01 | 1 |
| 加藤優子 | 2023-10-01 | 1 |
| 小林誠 | 2024-01-15 | 1 |
+-----------------+------------+--------------+
計算根拠(基準日: 2025-04-01、TIMESTAMPDIFF は完了した年数を返す):
- 高橋美咲: 2018-09-01 → 2025-04-01 … 6年完了(次の記念日 2025-09-01 は未到達)
- 佐藤花子: 2019-07-15 → 2025-04-01 … 5年完了(次の記念日 2025-07-15 は未到達)
- 田中太郎: 2020-04-01 → 2025-04-01 … ちょうど5年
- 山本大輔: 2020-11-15 → 2025-04-01 … 4年完了(次の記念日 2025-11-15 は未到達)
- 鈴木一郎: 2021-01-10 → 2025-04-01 … 4年完了(2025-01-10 で4年到達済み)
- 中村真理: 2021-08-01 → 2025-04-01 … 3年完了(次の記念日 2025-08-01 は未到達)
- 伊藤健太: 2022-03-20 → 2025-04-01 … 3年完了(2025-03-20 で3年到達済み)
- 渡辺さくら: 2023-06-01 → 2025-04-01 … 1年完了(次の記念日 2025-06-01 は未到達)
- 加藤優子: 2023-10-01 → 2025-04-01 … 1年完了(次の記念日 2025-10-01 は未到達)
- 小林誠: 2024-01-15 → 2025-04-01 … 1年完了(2025-01-15 で1年到達済み)
模範解答
SELECT
employee_name,
hire_date,
TIMESTAMPDIFF(YEAR, hire_date, '2025-04-01') AS years_worked
FROM employees
ORDER BY years_worked DESC, hire_date;
解説:
-
TIMESTAMPDIFF(YEAR, hire_date, '2025-04-01')は、hire_dateから'2025-04-01'までの完了した年数を返します。端数は切り捨てられます。 - 例えば、佐藤花子の入社日
2019-07-15から2025-04-01は 5 年 8 か月余りですが、TIMESTAMPDIFF(YEAR, ...)は5を返します。 - 実務では
CURDATE()を使って現在日付を基準にすることが多いです。ここではテスト可能にするため固定日を使っています。 -
PostgreSQL の場合:
EXTRACT(YEAR FROM AGE('2025-04-01'::date, hire_date))を使います。
問題 4 ⭐⭐(応用)― CASE 式で給与ランク分類
employees テーブルの各社員を、給与額に応じて以下のランクに分類してください。
| ランク | 条件 |
|---|---|
| A | 40 万円以上 |
| B | 30 万円以上 40 万円未満 |
| C | 30 万円未満 |
各ランクの人数もカウントし、社員一覧とランク別集計の 2 つのクエリ を書いてください。
期待結果(社員一覧・給与の降順):
+-----------------+--------+-------------+
| employee_name | salary | salary_rank |
+-----------------+--------+-------------+
| 伊藤健太 | 450000 | A |
| 佐藤花子 | 420000 | A |
| 高橋美咲 | 380000 | B |
| 田中太郎 | 350000 | B |
| 山本大輔 | 330000 | B |
| 中村真理 | 310000 | B |
| 鈴木一郎 | 300000 | B |
| 加藤優子 | 290000 | C |
| 渡辺さくら | 280000 | C |
| 小林誠 | 270000 | C |
+-----------------+--------+-------------+
期待結果(ランク別集計):
+-------------+-------+
| salary_rank | count |
+-------------+-------+
| A | 2 |
| B | 5 |
| C | 3 |
+-------------+-------+
計算根拠:
- A(40万以上): 伊藤健太(450000), 佐藤花子(420000) → 2名
- B(30万以上40万未満): 高橋美咲(380000), 田中太郎(350000), 山本大輔(330000), 中村真理(310000), 鈴木一郎(300000) → 5名
- C(30万未満): 加藤優子(290000), 渡辺さくら(280000), 小林誠(270000) → 3名
模範解答
-- クエリ1: 社員一覧
SELECT
employee_name,
salary,
CASE
WHEN salary >= 400000 THEN 'A'
WHEN salary >= 300000 THEN 'B'
ELSE 'C'
END AS salary_rank
FROM employees
ORDER BY salary DESC;
-- クエリ2: ランク別集計
SELECT
CASE
WHEN salary >= 400000 THEN 'A'
WHEN salary >= 300000 THEN 'B'
ELSE 'C'
END AS salary_rank,
COUNT(*) AS count
FROM employees
GROUP BY
CASE
WHEN salary >= 400000 THEN 'A'
WHEN salary >= 300000 THEN 'B'
ELSE 'C'
END
ORDER BY salary_rank;
解説:
-
CASE式は上から順に評価され、最初に条件が真になったTHENの値が返ります。そのため 2 番目のWHEN salary >= 300000は「300000 以上かつ 400000 未満」を意味します(400000 以上は先の条件でマッチ済みのため)。 -
GROUP BYにCASE式を直接書くことで、ランク別の集計ができます。 - MySQL 8.0 以降では
GROUP BY salary_rank(エイリアスで指定)も動作しますが、SQL 標準ではGROUP BYにエイリアスは使えないため、CASE式をそのまま書く方が可搬性が高いです。
問題 5 ⭐(基本)― 日付の日本語フォーマット
employees テーブルから、入社日(hire_date)を YYYY年MM月DD日 形式で表示してください。employee_id の昇順で並べてください。
期待結果:
+-----------------+------------------+
| employee_name | hire_date_jp |
+-----------------+------------------+
| 田中太郎 | 2020年04月01日 |
| 佐藤花子 | 2019年07月15日 |
| 鈴木一郎 | 2021年01月10日 |
| 高橋美咲 | 2018年09月01日 |
| 伊藤健太 | 2022年03月20日 |
| 渡辺さくら | 2023年06月01日 |
| 山本大輔 | 2020年11月15日 |
| 中村真理 | 2021年08月01日 |
| 小林誠 | 2024年01月15日 |
| 加藤優子 | 2023年10月01日 |
+-----------------+------------------+
模範解答
SELECT
employee_name,
DATE_FORMAT(hire_date, '%Y年%m月%d日') AS hire_date_jp
FROM employees
ORDER BY employee_id;
解説:
-
DATE_FORMATの主な書式指定子:-
%Y→ 4 桁の年(例:2020) -
%m→ 2 桁の月(例:04)— ゼロ埋めあり -
%d→ 2 桁の日(例:01)— ゼロ埋めあり -
%e→ ゼロ埋めなしの日(例:1) -
%c→ ゼロ埋めなしの月(例:4)
-
-
PostgreSQL の場合:
TO_CHAR(hire_date, 'YYYY"年"MM"月"DD"日"')を使います。
問題 6 ⭐(基本)― NULL メールアドレスの補完
customers テーブルから、顧客名とメールアドレスを取得してください。メールアドレスが NULL の場合は '未登録' と表示します。customer_id の昇順で並べてください。
期待結果:
+--------------------------+------------------+
| customer_name | email_display |
+--------------------------+------------------+
| 株式会社ABC | abc@example.com |
| DEFコーポレーション | def@example.com |
| GHI商事 | ghi@example.com |
| JKLテクノロジー | 未登録 |
| MNOサービス | mno@example.com |
+--------------------------+------------------+
模範解答
SELECT
customer_name,
COALESCE(email, '未登録') AS email_display
FROM customers
ORDER BY customer_id;
解説:
-
COALESCE(email, '未登録')はemailが NULL でなければemailを、NULL なら'未登録'を返します。 - MySQL では
IFNULL(email, '未登録')でも同じ結果になりますが、COALESCEは SQL 標準関数であり、PostgreSQL などでもそのまま使えます。 -
COALESCEは 3 つ以上の引数を取れる点もIFNULLとの違いです:COALESCE(a, b, c)は a → b → c の順に最初の非 NULL 値を返します。
問題 7 ⭐⭐(応用)― メールアドレスからドメイン抽出
customers テーブルから、メールアドレスがある顧客について、@ より後ろのドメイン部分を抽出してください。customer_id の昇順で並べてください。
期待結果:
+--------------------------+-----------------+-------------+
| customer_name | email | domain |
+--------------------------+-----------------+-------------+
| 株式会社ABC | abc@example.com | example.com |
| DEFコーポレーション | def@example.com | example.com |
| GHI商事 | ghi@example.com | example.com |
| MNOサービス | mno@example.com | example.com |
+--------------------------+-----------------+-------------+
計算根拠: メールアドレスの
@以降を抽出します。
- abc@example.com → example.com
- def@example.com → example.com
- ghi@example.com → example.com
- JKLテクノロジー は email が NULL のため除外
- mno@example.com → example.com
模範解答
SELECT
customer_name,
email,
SUBSTRING_INDEX(email, '@', -1) AS domain
FROM customers
WHERE email IS NOT NULL
ORDER BY customer_id;
解説:
-
SUBSTRING_INDEX(email, '@', -1)は@をデリミタとして文字列を分割し、最後の部分(右側)を返します。- 第 3 引数が
-1: 最後のデリミタより後ろを返す → ドメイン部分 - 第 3 引数が
1: 最初のデリミタより前を返す → ローカル部分(例:abc)
- 第 3 引数が
-
WHERE email IS NOT NULLでJKLテクノロジー(email が NULL)を除外しています。 -
PostgreSQL の場合:
SUBSTRING_INDEXは使えません。代わりにSPLIT_PART(email, '@', 2)を使います。 - 別解(SUBSTRING + LOCATE を使用):
SELECT
customer_name,
email,
SUBSTRING(email, LOCATE('@', email) + 1) AS domain
FROM customers
WHERE email IS NOT NULL
ORDER BY customer_id;
LOCATE('@', email) で @ の位置を取得し、その次の文字から末尾までを SUBSTRING で切り出します。
問題 8 ⭐⭐(応用)― 年度別の注文集計
orders テーブルの注文を 会計年度(4 月始まり) で分類し、年度ごとの注文件数を集計してください。会計年度は「4 月〜翌年 3 月」を 1 年度とし、年度の表示は開始年とします(例: 2024 年 7 月 → 2024 年度)。
期待結果:
+-------------+--------------+
| fiscal_year | order_count |
+-------------+--------------+
| 2024 | 10 |
+-------------+--------------+
計算根拠:
全注文の日付: 2024-07-01, 2024-07-05, 2024-07-10, 2024-08-01, 2024-08-15, 2024-09-01, 2024-09-10, 2024-10-01, 2024-10-15, 2024-11-01
すべて 2024年4月〜2025年3月の範囲内 → 全 10 件が 2024 年度
模範解答
SELECT
CASE
WHEN MONTH(order_date) >= 4 THEN YEAR(order_date)
ELSE YEAR(order_date) - 1
END AS fiscal_year,
COUNT(*) AS order_count
FROM orders
GROUP BY
CASE
WHEN MONTH(order_date) >= 4 THEN YEAR(order_date)
ELSE YEAR(order_date) - 1
END
ORDER BY fiscal_year;
解説:
- 会計年度(4 月始まり)の判定ロジック:
- 月が 4 以上(4〜12月)→ その年が年度
- 月が 3 以下(1〜3月)→ 前年が年度
- サンプルデータでは全注文が 2024 年 7〜11 月の範囲なので、すべて 2024 年度となります。
- 1〜3 月のデータがある場合を想定して正しいロジックを書いておくことが重要です。
- 別解(DATE_SUB を使う方法):
SELECT
YEAR(DATE_SUB(order_date, INTERVAL 3 MONTH)) AS fiscal_year,
COUNT(*) AS order_count
FROM orders
GROUP BY YEAR(DATE_SUB(order_date, INTERVAL 3 MONTH))
ORDER BY fiscal_year;
日付から 3 か月引くことで 4 月→1 月に揃え、YEAR() で年度を取得します。簡潔に書けますが、可読性の面で CASE 式の方が意図が明確です。
問題 9 ⭐⭐⭐(チャレンジ)― 部署別・給与ランク別の条件付き集計
employees テーブルと departments テーブルを結合し、部署ごとに給与ランク(A: 40 万以上、B: 30 万以上 40 万未満、C: 30 万未満)の人数を 横持ち(列方向に展開) で表示してください。department_id が NULL の社員は除外します。部署を department_id の昇順で並べてください。
期待結果:
+-----------------+--------+--------+--------+
| department_name | rank_A | rank_B | rank_C |
+-----------------+--------+--------+--------+
| 営業部 | 0 | 3 | 0 |
| 開発部 | 2 | 0 | 1 |
| 人事部 | 0 | 1 | 1 |
| 経理部 | 0 | 1 | 0 |
+-----------------+--------+--------+--------+
計算根拠:
- 営業部(dept_id=1): 田中太郎(350000→B), 鈴木一郎(300000→B), 中村真理(310000→B) → A:0, B:3, C:0
- 開発部(dept_id=2): 佐藤花子(420000→A), 伊藤健太(450000→A), 渡辺さくら(280000→C) → A:2, B:0, C:1
- 人事部(dept_id=3): 高橋美咲(380000→B), 小林誠(270000→C) → A:0, B:1, C:1
- 経理部(dept_id=4): 山本大輔(330000→B) → A:0, B:1, C:0
- 加藤優子(dept_id=NULL) → INNER JOIN により除外
模範解答
SELECT
d.department_name,
SUM(CASE WHEN e.salary >= 400000 THEN 1 ELSE 0 END) AS rank_A,
SUM(CASE WHEN e.salary >= 300000 AND e.salary < 400000 THEN 1 ELSE 0 END) AS rank_B,
SUM(CASE WHEN e.salary < 300000 THEN 1 ELSE 0 END) AS rank_C
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id
GROUP BY d.department_id, d.department_name
ORDER BY d.department_id;
解説:
-
条件付き集計(Conditional Aggregation) は
SUM(CASE WHEN ... THEN 1 ELSE 0 END)のパターンで実現します。条件に合致する行を1、それ以外を0として合計するため、結果的に条件を満たす行数になります。 -
COUNT(CASE WHEN ... THEN 1 END)でも同じ結果になります(ELSE を省略すると NULL が返り、COUNTは NULL をカウントしないため)。 -
INNER JOINを使うことでdepartment_idが NULL の社員(加藤優子)は自動的に除外されます。 - このテクニックはクロス集計(ピボット) と呼ばれ、行方向のデータを列方向に展開する際の定番手法です。
問題 10 ⭐⭐⭐(チャレンジ)― 商品名の加工と在庫状況ラベル
products テーブルから、以下の加工を行って商品情報を表示してください。
-
REPLACEで商品名中のPCをパソコンに置換する - 在庫数に応じて在庫状況を表示する:
- 30 以下:
在庫少 - 31〜100:
通常 - 101 以上:
豊富
- 30 以下:
- 価格を
¥付き・3 桁カンマ区切りでフォーマットする
product_id の昇順で並べてください。
期待結果:
+----+----------------------------+-----------+--------+--------------+
| id | product_name_replaced | category | stock | stock_status |
+----+----------------------------+-----------+--------+--------------+
| 1 | ノートパソコン Pro | パソコン | 50 | 通常 |
| 2 | ワイヤレスマウス | 周辺機器 | 200 | 豊富 |
| 3 | USBハブ 7ポート | 周辺機器 | 150 | 豊富 |
| 4 | 4Kモニター 27インチ | モニター | 30 | 在庫少 |
| 5 | メカニカルキーボード | 周辺機器 | 80 | 通常 |
| 6 | ノートパソコン Light | パソコン | 100 | 通常 |
| 7 | Webカメラ HD | 周辺機器 | 120 | 豊富 |
| 8 | ゲーミングモニター | モニター | 20 | 在庫少 |
+----+----------------------------+-----------+--------+--------------+
計算根拠:
- id=1:
ノートPC Pro→ノートパソコン Pro、stock=50 → 通常(31〜100)- id=2:
ワイヤレスマウス→ 変更なし、stock=200 → 豊富(101以上)- id=3:
USBハブ 7ポート→ 変更なし、stock=150 → 豊富(101以上)- id=4:
4Kモニター 27インチ→ 変更なし、stock=30 → 在庫少(30以下)- id=5:
メカニカルキーボード→ 変更なし、stock=80 → 通常(31〜100)- id=6:
ノートPC Light→ノートパソコン Light、stock=100 → 通常(31〜100)- id=7:
Webカメラ HD→ 変更なし、stock=120 → 豊富(101以上)- id=8:
ゲーミングモニター→ 変更なし、stock=20 → 在庫少(30以下)
模範解答
SELECT
product_id AS id,
REPLACE(product_name, 'PC', 'パソコン') AS product_name_replaced,
category,
stock,
CASE
WHEN stock <= 30 THEN '在庫少'
WHEN stock <= 100 THEN '通常'
ELSE '豊富'
END AS stock_status
FROM products
ORDER BY product_id;
解説:
-
REPLACE(product_name, 'PC', 'パソコン')で商品名中のPCをパソコンに置換します。PCを含まない商品名はそのまま返ります。 -
CASE式で在庫状況を 3 段階に分類しています。WHEN stock <= 100は「31〜100」を意味します(30 以下は先の条件でマッチ済みのため)。 - 価格のカンマ区切りフォーマットは
FORMAT関数で実現できます:
CONCAT('¥', FORMAT(price, 0)) AS price_formatted
-- 例: 198000 → '¥198,000'
FORMAT(数値, 小数桁数) は 3 桁ごとにカンマを挿入します。CONCAT で先頭に ¥ を付けます。
PostgreSQL の場合: TO_CHAR(price, '¥FM999,999,999') を使います。
参考
- MySQL 8.0 リファレンスマニュアル — 文字列関数
- MySQL 8.0 リファレンスマニュアル — 日付および時間関数
- MySQL 8.0 リファレンスマニュアル — フロー制御関数(CASE, IF, IFNULL, NULLIF)
- MySQL 8.0 リファレンスマニュアル — 比較関数(COALESCE)
- PostgreSQL ドキュメント — 文字列関数
- PostgreSQL ドキュメント — 日付/時刻関数
@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!