Oracle SQL 文字列結合の全手順|初心者向け


Oracle SQL 文字列結合の全手順|初心者向け

はじめに

SQL(Structured Query Language)は、データベースを操作するための標準的な言語です。Oracleデータベースを使う上で、SQLの知識は不可欠です。SQLでデータを取得する際、多くの場合、複数の情報を組み合わせて表示したり、特定の形式に整えたりする必要があります。ここで登場するのが「文字列結合」です。

例えば、社員の「姓」と「名」を組み合わせて「フルネーム」として表示したい、商品の「価格」に「円」という単位を付けたい、複数のスキルをカンマ区切りで一覧表示したい、といったケースで文字列結合は活躍します。

Oracle SQLにおける文字列結合には、いくつかの方法があります。初心者の方にとっては、どれを使えばいいのか、それぞれの違いは何なのか、NULL値はどう扱うのかなど、疑問に思うことが多いかもしれません。

この記事では、Oracle SQLにおける文字列結合の基本的な方法から、応用、NULLの扱い、複数行の結合、パフォーマンスに関する考慮事項まで、初心者の方にも分かりやすく、そして詳細に解説します。この記事を読めば、Oracle SQLでの文字列結合を自信を持って行えるようになるでしょう。約5000語にわたる詳細な解説を通して、あなたのSQLスキルアップを徹底的にサポートします。

さあ、Oracle SQLの文字列結合の世界へ一緒に踏み出しましょう!

1. 文字列結合の基本概念

1.1 なぜ文字列結合が必要なのか?

データベースには、様々な情報が分割されて格納されています。これは、データの重複を防ぎ、効率的に管理するためです。しかし、ユーザーが最終的に必要とする情報は、これらの分割されたデータを組み合わせて表示することがほとんどです。

例:
* 氏名の結合: (LAST_NAME) と (FIRST_NAME) を組み合わせて フルネーム (FULL_NAME) として表示する。
* 住所の結合: 都道府県市区町村番地 を組み合わせて完全な 住所 (ADDRESS) として表示する。
* 表示形式の整形: 商品の価格 (PRICE) と 通貨単位 (‘円’) を組み合わせて ‘1500円’ のように表示する。
* 報告書作成: 複数の列の情報を組み合わせて、見やすい形式のレポートを作成する。
* 条件指定: 結合した文字列に対してLIKE演算子などで検索を行う。

このように、文字列結合は、データベースから取得したデータをユーザーフレンドリーな形式で表示したり、より複雑な処理を行うための準備として非常に重要です。

1.2 SQLにおける文字列とは?

SQLにおける文字列は、一連の文字の並びです。Oracle SQLでは、シングルクォート(')で囲んで表現します。

例:
* 'Hello, World!'
* '東京都新宿区'
* '商品名'
* 'YYYY/MM/DD' (日付書式モデルも文字列として扱われる)

データベースのテーブルの列に格納されているデータのうち、データ型が VARCHAR2, CHAR, NVARCHAR2, NCHAR, CLOB などであるものは、基本的に文字列として扱われます。数値型 (NUMBER) や日付型 (DATE, TIMESTAMP) のデータも、文字列結合の際には多くの場合、文字列に変換されてから結合されます。

1.3 Oracle SQLでの文字列結合方法の概要

Oracle SQLで文字列結合を行うための主要な方法は以下の2つです。

  1. || (パイプパイプ) 演算子:
    最も一般的で柔軟性の高い方法です。複数の文字列を連結できます。

  2. CONCAT() 関数:
    ANSI SQL標準に近い関数ですが、Oracleの実装では2つの文字列しか一度に結合できません。

どちらの方法も結果として得られる文字列は同じですが、使い勝手やNULLの扱いに違いがあります。これらの方法について、次に詳しく解説します。

2. 主要な文字列結合方法

2.1 || (パイプパイプ) 演算子

|| 演算子は、Oracle SQLで文字列を結合する際に最も広く使われる方法です。複数の文字列、列、関数呼び出しの結果などを柔軟に結合できます。

構文:

sql
string1 || string2 || string3 || ...

string1, string2, string3, … は、結合したい文字列リテラル、列名、または文字列値を返す式です。

使い方と基本的な例:

  • 固定文字列と列の結合:
    sql
    SELECT '社員番号: ' || employee_id AS 社員情報
    FROM employees
    WHERE employee_id = 100;

    この例では、固定文字列 '社員番号: 'employees テーブルの employee_id 列の値を結合しています。結果は '社員番号: 100' のようになります(employee_idが数値型の場合でも文字列に変換されます)。

  • 複数列の結合:
    sql
    SELECT first_name || ' ' || last_name AS フルネーム
    FROM employees
    WHERE employee_id = 100;

    ここでは、first_name 列、スペースリテラル ' 'last_name 列の3つを結合しています。これにより、名と姓の間にスペースが入ったフルネームが得られます。結果は 'Steven King' のようになります。

  • 複数の文字列を結合:
    sql
    SELECT 'プロジェクト名: ' || project_name || ' (担当: ' || manager_name || ')' AS プロジェクト情報
    FROM projects
    WHERE project_id = 1;

    この例では、複数の固定文字列と複数の列を組み合わせて、より複雑な表示形式を作成しています。結果は 'プロジェクト名: Alpha Project (担当: John Smith)' のようになります。

|| 演算子の NULL の扱い:

|| 演算子の大きな特徴は、NULL 値を無視する という点です。NULL と他の文字列を結合しても、NULL は結果の文字列に含まれません。

例:
sql
SELECT 'Hello' || NULL || 'World' AS combined_string FROM dual;

結果は 'HelloWorld' となります。NULL が完全に無視されています。

もし列の値が NULL の場合でも、その部分は単にスキップされます。

sql
-- 仮に従業員の中にはミドルネームがNULLの人がいるとする
SELECT first_name || ' ' || middle_name || ' ' || last_name AS フルネーム
FROM employees
WHERE employee_id = 100; -- middle_name が NULL の社員

middle_name が NULL の場合、結果は first_name || ' ' || ' ' || last_name となり、first_namelast_name の間に2つのスペースが入る可能性があります(NULL の代わりに何も入らないため)。正確には、first_name || ' ' || last_name と同じ結果になります。NULL と結合された ' ' も結果的に無視されます。

これは、予期せぬ形でスペースが詰まったり、逆に不要な区切り文字が入ったりする原因になることがあるため、NULLを考慮した結合を行う場合は注意が必要です(後述のNULLの扱いセクションで詳しく解説します)。

数値や日付との結合(暗黙の型変換):

|| 演算子を使って、数値型や日付型の列、あるいは数値や日付の値を文字列と結合しようとすると、Oracleは自動的にそれらを文字列に変換してから結合を行います。これを「暗黙の型変換」と呼びます。

例:
“`sql
SELECT ‘価格: ‘ || 1500 AS price_info FROM dual;
— 結果: ‘価格: 1500’

SELECT ‘登録日: ‘ || SYSDATE AS register_date_info FROM dual;
— 結果: ‘登録日: 23-OCT-23’ (日付のデフォルト書式による)
``
この暗黙の型変換は便利ですが、日付のデフォルト書式は環境によって異なったり、期待する形式と違ったりすることがあります。また、数値の場合も、桁区切りや通貨記号などを付けたい場合は、明示的な型変換関数(
TO_CHAR`)を使う方が安全で柔軟です(後述の型変換セクションで詳しく解説します)。

注意点:

  • 結合順序: || 演算子は左から右へ評価されます。複数の文字列を連続して結合できます。
  • 可読性: 長い文字列を多数の || で結合すると、SQL文が読みにくくなることがあります。適宜改行するなどして、読みやすいように工夫しましょう。
  • データ型: 原則として文字列リテラルはシングルクォート ' で囲みます。数値を結合する場合でも、Oracleが自動変換するため通常は問題ありませんが、混乱を避けるためには明示的な型変換が望ましい場合が多いです。

|| 演算子は非常に強力で柔軟なため、Oracle SQLでの文字列結合の主要な手段となります。まずはこの || 演算子の使い方をしっかりとマスターしましょう。

2.2 CONCAT() 関数

CONCAT() 関数は、2つの文字列を結合するための関数です。ANSI SQL標準にも含まれており、他のデータベースシステムでも似たような関数が存在します。

構文:

sql
CONCAT(string1, string2)

string1string2 は、結合したい文字列リテラル、列名、または文字列値を返す式です。注意点として、OracleのCONCAT関数は引数を2つしか取れません。

使い方と基本的な例:

  • 2つの文字列を結合:
    sql
    SELECT CONCAT('Hello', 'World') AS combined_string FROM dual;
    -- 結果: 'HelloWorld'
  • 固定文字列と列の結合:
    sql
    SELECT CONCAT('社員番号: ', employee_id) AS 社員情報
    FROM employees
    WHERE employee_id = 100;

    これも || 演算子と同様に、employee_id は自動的に文字列に変換されてから結合されます。結果は '社員番号: 100' のようになります。

  • 複数列の結合 (2つだけ):
    sql
    SELECT CONCAT(first_name, last_name) AS フルネーム
    FROM employees
    WHERE employee_id = 100;

    結果は 'StevenKing' のようになります。名と姓の間にスペースを入れたい場合は、そのままでは CONCAT 関数だけではできません。

CONCAT() 関数を使った複数の文字列結合(ネスト):

CONCAT() 関数は2つの引数しか取れませんが、関数をネスト(入れ子)にすることで、実質的に3つ以上の文字列を結合できます。

例:
sql
-- first_name, スペース, last_name を結合したい場合
SELECT CONCAT(first_name, CONCAT(' ', last_name)) AS フルネーム
FROM employees
WHERE employee_id = 100;

この例では、まず CONCAT(' ', last_name) でスペースと姓を結合し、その結果と first_name を外側の CONCAT 関数で結合しています。結果は 'Steven King' のようになります。

さらに多くの文字列を結合する場合は、どんどんネストを深くしていく必要があります。
sql
-- 'プロジェクト名: ' || project_name || ' (担当: ' || manager_name || ')' を CONCAT で実現
SELECT CONCAT('プロジェクト名: ', CONCAT(project_name, CONCAT(' (担当: ', CONCAT(manager_name, ')')))) AS プロジェクト情報
FROM projects
WHERE project_id = 1;

このように、CONCAT 関数で複数の文字列を結合しようとすると、非常に複雑で読みにくいSQL文になってしまう傾向があります。

CONCAT() 関数と || 演算子の比較:

特徴 || 演算子 CONCAT() 関数
結合できる数 2つ以上 (無制限) 2つのみ (ネストすれば3つ以上可能)
NULLの扱い NULLを無視する (NULL
可読性 複数の結合が比較的容易に記述できる 複数の結合にはネストが必要で読みにくい
ANSI SQL Oracle独自の拡張 ANSI SQLに準拠

CONCAT() 関数と NULL の扱い:

CONCAT() 関数は、引数のどちらか、または両方が NULL の場合、結果は NULL になります。

例:
sql
SELECT CONCAT('Hello', NULL) FROM dual; -- 結果: NULL
SELECT CONCAT(NULL, 'World') FROM dual; -- 結果: NULL
SELECT CONCAT(NULL, NULL) FROM dual; -- 結果: NULL

この NULL の扱いは || 演算子とは異なります。CONCAT 関数は、結合対象に一つでも不明な値(NULL)が含まれていれば、結果も不明(NULL)とするという、より厳密な NULL の伝播ルールに従います。

どちらの NULL 扱いが適切かは、実現したいロジックによります。NULL 値を含むデータに対して、そのNULLを無視して他の値を結合したい場合は || 演算子が適しています。一方、一つでも NULL が含まれていれば結果全体を NULL にしたい場合は CONCAT() 関数が適しているかもしれません。しかし、Oracleでは || 演算子の方が一般的で、NULLの扱いを制御したい場合は NVLCOALESCE と組み合わせるのが一般的です(後述)。

注意点:

  • 引数の数: 最も重要な制限は、引数が2つまでであることです。3つ以上の結合にはネストが必要です。
  • 可読性: ネストが深くなるとSQL文が非常に読みにくくなります。多くの文字列を結合する場合は、通常 || 演算子を使う方が推奨されます。
  • パフォーマンス: 非常に多数のCONCAT関数をネストすると、|| 演算子を連続して使う場合に比べてわずかにパフォーマンスが劣る可能性が指摘されることもありますが、ほとんどのケースでは無視できる差です。

結論として、Oracle SQLでの文字列結合の主流は || 演算子です。CONCAT() 関数は2つの文字列結合に限定されるため、多くの場面で || 演算子の方が便利で直感的です。ただし、他のデータベースシステムとの互換性を特に重視する場合などには CONCAT() 関数も選択肢に入り得ます。

3. 応用的な文字列結合

基本的な || 演算子や CONCAT 関数を使って文字列を結合する方法を理解したところで、より実践的な応用テクニックを見ていきましょう。

3.1 スペース、記号、改行などを挿入する

結合したい文字列と文字列の間に、スペース、カンマ、ハイフン、括弧、改行コードなどの特定の文字を挿入することはよくあります。これらは単なる文字列リテラルとして結合することで実現できます。

  • スペースの挿入:
    ' ' という文字列リテラルを結合します。
    sql
    SELECT first_name || ' ' || last_name AS フルネーム FROM employees;
    -- 結果例: 'Steven King'

  • 区切り記号の挿入:
    カンマ、ハイフン、スラッシュなど、任意の記号を挿入できます。
    “`sql
    SELECT department_name || ‘-‘ || location_id AS 部署所在地 FROM departments;
    — 結果例: ‘Sales-1700’

    SELECT product_code || ‘/’ || version AS 製品バージョン FROM products;
    — 結果例: ‘P101/V2.0’

    SELECT item1 || ‘, ‘ || item2 || ‘, ‘ || item3 AS アイテムリスト FROM my_table;
    — 結果例: ‘Apple, Banana, Cherry’ (カンマとスペースで区切る)
    “`

  • 括弧などの装飾:
    値の前後に括弧を付けたり、特定の情報を強調したりできます。
    sql
    SELECT product_name || ' (' || price || '円)' AS 商品価格表示 FROM products;
    -- 結果例: 'Laptop (120000円)'

  • 改行コードの挿入:
    レポート出力などで、意図的に改行を挿入したい場合があります。改行コードはOSによって異なりますが、Oracleでは CHR() 関数を使って文字コードで指定するのが一般的です。

    • LF (Line Feed): CHR(10) – UNIX/Linux、macOS での改行
    • CR (Carriage Return): CHR(13) – 古いMacintoshでの改行
    • CRLF (Carriage Return + Line Feed): CHR(13) || CHR(10) – Windows での改行

    環境に依存しない、または複数環境での利用を考慮する場合、改行コードの選択には注意が必要ですが、一般的には CHR(10) がよく使われます。

    例:住所を複数行で表示する場合(例として、address1 に番地、address2 に建物名が入っているとする)
    sql
    SELECT address1 || CHR(10) || address2 AS 整形済み住所 FROM addresses WHERE address_id = 1;
    -- 結果例 (ツールや表示環境による):
    -- 東京都新宿区1-1-1
    -- 新宿ビル5F

    レポートツールやSQLクライアントの表示設定によっては、CHR(10) が改行として認識されない場合もあります。

3.2 条件付き結合 (CASE文)

特定の条件に基づいて、結合する文字列を変更したり、結合自体を行うかどうかを制御したりしたい場合があります。このような要件には CASE 文と文字列結合を組み合わせるのが効果的です。

CASE 文は、条件に応じて異なる値を返す式です。これを文字列結合の一部として利用します。

構文(CASE文):

sql
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
...
ELSE result_else
END

使い方と例:

  • 列の値によって表示を変える:
    例えば、商品の在庫数 (STOCK) が0なら「(在庫なし)」と表示を付け加えたい場合。
    sql
    SELECT product_name ||
    CASE
    WHEN stock = 0 THEN ' (在庫なし)'
    ELSE '' -- 在庫がある場合は何も付け加えない
    END AS 商品在庫情報
    FROM products;

    この例では、stock が0の場合にのみ ' (在庫なし)' という文字列が結合されます。それ以外の場合は '(空文字列)が結合されるため、表示に変化はありません。

  • NULL 値がある場合に代替の表示を挿入する:
    後述の NVLCOALESCE も使えますが、より複雑な条件でNULLの場合の表示を制御したい場合に CASE 文が役立ちます。例えば、役職 (JOB_TITLE) が NULL の場合は「(役職未設定)」と表示したいが、役職がある場合はそのまま表示したい、というケース。

    sql
    -- 従業員リストを表示する際に、役職がNULLの場合は代替文字列を表示
    SELECT last_name || ', ' || first_name ||
    CASE
    WHEN job_title IS NULL THEN ' (役職未設定)'
    ELSE ' (' || job_title || ')' -- 役職がある場合はカッコ付きで表示
    END AS 従業員名簿
    FROM employees;

    この例では、job_title IS NULL の場合は ' (役職未設定)' が、NULLでない場合は ' (' || job_title || ')' という文字列が生成され、名前の後ろに結合されます。

  • 複数の条件で結合内容を変える:
    例えば、注文の状態 (STATUS) に応じて、表示するメッセージを結合したい場合。
    sql
    SELECT order_id || ': ' || item_name ||
    CASE status
    WHEN 'Pending' THEN ' - 処理待ち'
    WHEN 'Shipped' THEN ' - 出荷済み'
    WHEN 'Delivered' THEN ' - 配達完了'
    ELSE ' - 状態不明'
    END AS 注文詳細
    FROM orders;

    CASE status ... END の部分が、status の値に応じて異なる文字列を返し、それが order_iditem_name と結合されます。

CASE 文を使うことで、データの値に基づいた動的な文字列結合が可能になり、より表現力豊かなクエリを作成できます。これは、レポート作成やデータの表示整形において非常に強力なテクニックです。

4. NULL値の扱い

Oracle SQLで文字列結合を行う際に、NULL 値の扱いは非常に重要です。意図しない結果を避けるためには、NULL 値がどのように処理されるかを理解しておく必要があります。

4.1 Oracle SQLにおけるNULLの特性

NULL は「値がない」「不明である」ことを示します。数値のゼロや空文字列 ('') とは異なります。NULL との算術演算は常に NULL を返し、NULL との比較(=<>) は常に UNKNOWN を返します(IS NULL または IS NOT NULL を使って比較する必要があります)。

4.2 || 演算子と NULL

前述したように、|| 演算子は NULL を無視します。これは、NULL が文字列結合の操作において、あたかもそこに何も存在しないかのように振る舞うことを意味します。

  • 'abc' || NULL'abc' になります。
  • NULL || 'xyz''xyz' になります。
  • 'abc' || NULL || 'xyz''abcxyz' になります。
  • NULL || NULLNULL になります(NULLとNULLを結合しても結果はNULLになる)。

この特性は、例えばオプションの項目(ミドルネームなど)が NULL であっても、他の項目との結合が中断されないという利点があります。しかし、ミドルネームが NULL の場合に、first_name || ' ' || middle_name || ' ' || last_name のように結合すると、middle_name が NULL の部分がスキップされ、first_name || ' ' || ' ' || last_name となり、名と姓の間に不要なスペースが一つ余分に入ってしまうことがあります。正確には、Oracleの||演算子はNULLを結合した結果はNULLにならないため、first_name || ' ' || NULL || ' ' || last_namefirst_name || ' ' || ' ' || last_name と同じ結果になり、スペースが一つ余分になる可能性があります(この挙動はOracleのバージョンや設定による場合もありますが、基本的にはNULL部分をスキップすると理解しておけば良いでしょう)。

4.3 CONCAT() 関数と NULL

一方、CONCAT() 関数は、引数のどちらか一方でも NULL であれば、結果は NULL になります。

  • CONCAT('abc', NULL)NULL になります。
  • CONCAT(NULL, 'xyz')NULL になります。
  • CONCAT(NULL, NULL)NULL になります。

これは、NULL が含まれる結合は結果が不明となる、という標準的なデータベースの NULL 伝播ルールに従った挙動です。もし結合したい列の中に一つでも NULL が含まれる可能性がある場合、CONCAT を使うと結合結果全体が NULL になってしまう可能性があるため注意が必要です。

4.4 NULL を回避・置換して結合する (NVL, COALESCE)

文字列結合で NULL 値による影響(||での不要なスペース、CONCATでの結果NULL化)を避けたい場合、NVL() 関数や COALESCE() 関数を使って NULL を他の値(空文字列や代替テキスト)に置き換えてから結合を行うのが一般的です。

  • NVL(expression, substitute) 関数:
    expression が NULL でない場合は expression の値を返し、NULL の場合は substitute の値を返します。引数は2つだけです。

    例:ミドルネームが NULL の場合に空文字列に置き換えて結合する (|| 演算子の場合)
    sql
    SELECT first_name || ' ' || NVL(middle_name, '') || ' ' || last_name AS フルネーム
    FROM employees;

    もし middle_name が NULL であれば NVL(middle_name, '') は空文字列 '' を返します。結果として first_name || ' ' || '' || ' ' || last_name のように結合され、名と姓の間にスペースが一つだけ入るようになります。middle_name に値があれば、それがそのまま使われます。

    例:CONCAT を使いつつ、NULL による結果 NULL 化を防ぐ
    sql
    -- CONCAT(string1, string2) の場合、string1 または string2 が NULL なら結果は NULL
    -- これを防ぐために NVL を使う
    SELECT CONCAT(NVL(prefix, ''), NVL(base_string, '')) AS combined
    FROM my_table;

    このように、結合する各要素に対して個別に NVL を適用することで、要素が NULL であっても結合処理が NULL に中断されるのを防ぎ、代わりに空文字列などが結合されるように制御できます。

  • COALESCE(expression1, expression2, ..., expressionN) 関数:
    引数のリストを左から順に評価し、最初に見つかった NULL でない値 を返します。NVL と異なり、引数を3つ以上指定できます。

    例:ミドルネームが NULL なら代わりに空文字列を使う (|| 演算子の場合)
    sql
    SELECT first_name || ' ' || COALESCE(middle_name, '') || ' ' || last_name AS フルネーム
    FROM employees;

    この使い方は NVL と同様の結果になります。COALESCE の真価は複数の代替候補がある場合に発揮されます。

    例:担当者名として、まず manager_name を試し、それが NULL なら contact_person を使い、それも NULL なら「担当者未定」と表示する
    sql
    SELECT '担当者: ' || COALESCE(manager_name, contact_person, '担当者未定') AS 担当者情報
    FROM projects;

    この例では、COALESCEmanager_namecontact_person の順に NULL かチェックし、最初に見つかった NULL でない値を返します。どちらも NULL の場合は '担当者未定' が使われます。その結果が '担当者: ' と結合されます。

NVL vs COALESCE:

  • NVL はシンプルで引数が2つ固定です。
  • COALESCE は複数の代替候補を指定できます (NVL(expr, val)COALESCE(expr, val) と同じです)。
  • COALESCE は NULL でない値が見つかった時点で評価を停止するため、パフォーマンス面で有利な場合があります(特に引数に関数が含まれる場合)。
  • COALESCE は ANSI SQL 標準であり、他のデータベースシステムとの互換性が高いです。

どちらを使うかは状況によりますが、単に1つの値を代替したい場合は NVL、複数の代替候補を順に試したい場合は COALESCE が適しています。どちらを使うにしても、文字列結合において NULL による予期せぬ結果を防ぐために、NVLCOALESCE との組み合わせは非常に一般的で重要なテクニックです。

5. 数値、日付、その他の型との結合

文字列結合は、文字データ同士だけでなく、数値データ、日付データ、その他のデータ型と組み合わせて行うことも頻繁にあります。

5.1 暗黙的な型変換の挙動

Oracle SQLでは、文字列結合 (|| 演算子または CONCAT 関数) の際に、数値型や日付型などの非文字列型のデータが文字列型に自動的に変換される「暗黙の型変換」が行われます。

例:
sql
SELECT '商品のIDは ' || product_id AS ProductInfo FROM products WHERE product_id = 10; -- product_idがNUMBER型でも文字列に変換される
SELECT '注文日: ' || order_date AS OrderInfo FROM orders WHERE order_id = 101; -- order_dateがDATE型でも文字列に変換される

この自動変換は便利ですが、変換される文字列の形式はOracleのデフォルト設定(NLS_DATE_FORMAT, NLS_NUMBER_FORMAT など)に依存します。

  • 日付のデフォルト書式は DD-MON-RR (例: 23-OCT-23) であることが多いですが、環境によって異なります。
  • 数値のデフォルト書式は、桁区切りや通貨記号が付かないシンプルな形式であることが多いです (例: 150000)。

デフォルト書式では、期待する表示形式(例: ‘2023/10/23’、’¥150,000’)にならないことがほとんどです。また、デフォルト設定に依存すると、環境が変わった場合に表示形式が変わってしまうという問題もあります。

5.2 明示的な型変換の推奨 (TO_CHAR())

デフォルト書式に依存せず、常に期待通りの表示形式を得るためには、文字列と結合する前に、数値や日付を 明示的に TO_CHAR() 関数を使って文字列に変換することを強く推奨します。

TO_CHAR() 関数は、数値や日付を、指定した書式モデルに従って文字列に変換します。

構文:

  • 日付の場合: TO_CHAR(date_expression, 'format_model')
  • 数値の場合: TO_CHAR(number_expression, 'format_model')

日付の TO_CHAR():

日付の書式モデルには様々な要素があります。

  • YYYY: 年 (4桁)
  • MM: 月 (2桁)
  • DD: 日 (2桁)
  • HH24: 時 (24時間表記)
  • MI: 分
  • SS: 秒
  • DY: 曜日 (短縮形, 例: 月)
  • FM: 書式モデルの先頭に置くと、先頭のゼロや末尾のスペースを除去 (例: FMYYYY/MM/DD2023/1/5 のように表示)

例:
“`sql
SELECT ‘注文日: ‘ || TO_CHAR(order_date, ‘YYYY/MM/DD HH24:MI’) AS 整形済み注文日
FROM orders
WHERE order_id = 101;
— 結果例: ‘注文日: 2023/10/23 14:30’

SELECT ‘今日の日付は ‘ || TO_CHAR(SYSDATE, ‘FMMonth DD, YYYY (DY)’) AS formatted_date FROM dual;
— 結果例: ‘今日の日付は October 23, 2023 (月)’ (FMがないと ‘October 23, 2023 (MON)’ のようになる)
“`

数値の TO_CHAR():

数値の書式モデルにも様々な要素があります。

  • 9: 数値を表示 (必要に応じて空白を詰める)
  • 0: 数値を表示 (必要に応じて先頭にゼロを埋める)
  • FM: 先頭/末尾の空白やゼロを除去
  • .: 小数点
  • ,: 桁区切り記号
  • L: 地域通貨記号 (NLS_CURRENCYに基づき ‘¥’ など)
  • MI: マイナス記号を末尾に付ける
  • PR: マイナス値を < > で囲む

例:
“`sql
SELECT ‘価格: ‘ || TO_CHAR(price, ‘FM999,999,990’) || ‘円’ AS 整形済み価格
FROM products
WHERE product_id = 50;
— price が 150000 の場合の結果例: ‘価格: 150,000円’
— price が 5000 の場合の結果例: ‘価格: 5,000円’
— price が 150.5 の場合の結果例: ‘価格: 151円’ (小数点以下をどう扱うかは書式による)

SELECT ‘金額: ‘ || TO_CHAR(amount, ‘FML999G999D00’) AS 整形済み金額 FROM sales WHERE sale_id = 20;
— amount が 123456.78 の場合の結果例 (日本語環境): ‘金額: ¥123,456.78’ (Lは通貨記号、Gは桁区切り、Dは小数点)

SELECT ‘在庫数: ‘ || TO_CHAR(stock, ‘FM000’) AS 整形済み在庫数 FROM products WHERE product_id = 30;
— stock が 50 の場合の結果例: ‘在庫数: 050’ (先頭にゼロを埋める)
``
数値の書式モデルは非常に多岐にわたります。Oracleドキュメントで詳細な書式モデルを確認してください。
FM` を使うことで、数値の先頭に不要な空白が入るのを防ぎ、文字列結合の結果を整形する上で非常に便利です。

5.3 なぜ明示的な型変換が良いか?

  • 予測可能性: 常に同じ書式で変換されるため、どの環境で実行しても結果が変わりません。
  • 柔軟性: TO_CHAR の書式モデルを使うことで、表示形式を細かく制御できます。
  • エラー回避: 暗黙の型変換でエラーが発生するケースは稀ですが、明示的に変換することでより安全になります。
  • 可読性: SQL文を読む人が、どのような形式で文字列に変換されるかを一目で理解できます。

日付や数値を文字列と結合する際は、特別な理由がない限り TO_CHAR 関数を使って明示的に変換することをお勧めします。

6. 複数行の文字列を結合する(集約関数)

これまでは、1つの行内にある複数の列や文字列を結合する方法を見てきました。しかし、データベースのデータは、関連する情報が複数行に分かれて格納されていることもよくあります。例えば、1人の顧客が複数のスキルを持っている場合に、それらのスキルが顧客IDごとに複数行に格納されている、といったケースです。

このような場合、「同じグループに属する複数行の値を、1つの文字列にまとめて結合する」という処理が必要になります。これは「文字列集約」や「グループ連結」と呼ばれる処理です。

Oracle SQLでは、主に LISTAGG() 関数を使ってこの処理を実現します。

6.1 LISTAGG() 関数 (Oracle 11g Release 2 以降)

LISTAGG() は、指定したグループ内で、指定した列の値を連結して1つの文字列を生成する集約関数です。非常に頻繁に使われる関数です。

構文:

sql
LISTAGG(measure_column [, delimiter]) WITHIN GROUP (ORDER BY order_by_clause)

  • measure_column: 連結したい値が含まれる列を指定します。
  • delimiter: オプションで、連結する値の間に挿入する区切り文字を指定します。デフォルトは NULL(区切り文字なし)ですが、通常は ,, などを指定します。
  • WITHIN GROUP (ORDER BY order_by_clause): これは必須です。グループ内のどの順序で値を連結するかを指定します。order_by_clausecolumn_name [ASC|DESC], ... の形式で指定します。連結順序を指定しないと、結果が不定になります。

使い方と基本的な例:

  • 同じ部署に属する従業員の名前をカンマ区切りで一覧表示する:
    employees テーブルに department_id, first_name, last_name 列があるとします。

    sql
    SELECT department_id,
    LISTAGG(first_name || ' ' || last_name, ', ') WITHIN GROUP (ORDER BY last_name, first_name) AS department_members
    FROM employees
    GROUP BY department_id
    ORDER BY department_id;

    このクエリは、department_id でグループ化し、各部署ごとに所属する従業員のフルネーム(first_name || ' ' || last_name で結合)を , を区切り文字として連結します。WITHIN GROUP (ORDER BY last_name, first_name) は、同じ部署内の従業員を姓、名の順にソートして連結することを指定しています。

    結果例:
    DEPARTMENT_ID | DEPARTMENT_MEMBERS
    --------------|-------------------------------------
    10 | Jennifer Whalen
    20 | Michael Hartstein, Pat Fay
    30 | Alexander Khoo, Bruce Ernst, ...
    ... | ...

  • 顧客が持っているスキルをカンマ区切りで一覧表示する:
    customer_skills テーブルに customer_id, skill_name 列があるとします。

    sql
    SELECT customer_id,
    LISTAGG(skill_name, ', ') WITHIN GROUP (ORDER BY skill_name) AS customer_skills_list
    FROM customer_skills
    GROUP BY customer_id;

    顧客ごとに持っているスキル名をカンマ区切りで連結し、スキル名のアルファベット順に並べ替えています。

DISTINCT キーワードの利用:

Oracle 12c Release 2 以降では、LISTAGG(DISTINCT measure_column, ...) のように DISTINCT キーワードを使って、重複する値を排除してから連結することができます。

例:同じ部署内で重複なく役職名の一覧を表示する
sql
-- 仮に employees テーブルに job_title 列があり、同じ部署に同じ役職の人が複数いる場合
SELECT department_id,
LISTAGG(DISTINCT job_title, ', ') WITHIN GROUP (ORDER BY job_title) AS distinct_job_titles
FROM employees
GROUP BY department_id;

これにより、例えば部署30に ‘Sales Representative’ が3人いても、連結結果には ‘Sales Representative’ は一度だけ登場します。

ON OVERFLOW clause (Oracle 12c Release 2 以降):

LISTAGG の結果文字列が非常に長くなる場合、最大長(VARCHAR2の最大長など)を超えてしまいエラー(ORA-01489: result of string concatenation is too long)となる可能性があります。Oracle 12c Release 2 以降では、ON OVERFLOW 句を使ってこの問題を回避できます。

構文例:
sql
LISTAGG(measure_column, delimiter) WITHIN GROUP (ORDER BY order_by_clause)
ON OVERFLOW { ERROR | TRUNCATE [WITH 'truncation_string'] [COUNT] }

* ERROR: (デフォルト) 結果が長すぎるとエラーになります。
* TRUNCATE: 結果を切り詰めます。
* [WITH 'truncation_string']: 切り詰めた部分の代わりに表示する文字列を指定できます (例: '...')。デフォルトは何も表示しません。
* [COUNT]: 切り詰められた要素の数を末尾に表示します。

例:結果が長すぎる場合に末尾を「…(+N件)」のように表示する
sql
SELECT department_id,
LISTAGG(first_name || ' ' || last_name, ', ') WITHIN GROUP (ORDER BY last_name, first_name)
ON OVERFLOW TRUNCATE WITH '... (+)' COUNT AS department_members_truncated
FROM employees
GROUP BY department_id
ORDER BY department_id;

結果例 (長い場合): Steven King, Neena Kochhar, ... (+15)

LISTAGG は非常に便利ですが、結果の長さには制限があることを覚えておきましょう。特に多くの要素を結合する場合や、結合する文字列自体が長い場合は、ON OVERFLOW 句を使うか、他の方法を検討する必要があります。

6.2 その他の方法 (XMLAGG, SYS_CONNECT_BY_PATH)

Oracleの古いバージョン(11gR2より前)や、特定の要件(非常に長い文字列を結合したいなど)の場合、LISTAGG 以外の方法が使われることもありました。

  • XMLAGG(): XML DB機能の一部として提供される関数で、XML要素を連結できます。これを文字列連結に応用するテクニックがありました。LISTAGG よりも文字列長制限が緩い場合があります。
  • 階層クエリ (SYS_CONNECT_BY_PATH): 疑似的に階層構造を作り出し、SYS_CONNECT_BY_PATH 関数でパスを連結していくことで文字列集約を実現する複雑な方法です。

これらの方法は LISTAGG に比べて構文が複雑で直感的でないため、Oracle 11gR2以降を使用している場合は、特別な理由がない限り LISTAGG を使うのが一般的かつ推奨される方法です。初心者の方はまず LISTAGG をマスターすることに注力すれば良いでしょう。

7. パフォーマンスに関する考慮事項

文字列結合は、特に大量のデータを扱う場合や、結合処理を頻繁に行うクエリでは、パフォーマンスに影響を与える可能性があります。

  • 処理負荷: 文字列のコピーや新しい文字列の生成にはCPUリソースが消費されます。結合する文字列が長くなったり、結合回数が多くなったりすると、その負荷は増加します。
  • メモリ使用量: 結合結果の文字列はメモリ上に生成されます。特に LISTAGG の結果が非常に長くなると、メモリを圧迫する可能性があります。

パフォーマンスを改善するための考慮事項:

  • 不必要な結合を避ける: 必要な場合にのみ文字列結合を行いましょう。表示のためだけに結合するのであれば、SELECT句で行い、WHERE句やJOIN句の条件に結合結果を使うのは避けましょう。条件指定には元の列を使うべきです。
  • TO_CHAR を適切に使う: 日付や数値を結合する際に TO_CHAR を使うことは書式制御のために推奨されますが、書式変換自体にもコストがかかります。必要な形式への変換のみを行いましょう。
  • LISTAGG の結果長に注意: 大量のデータを LISTAGG で結合する場合、結果文字列が長くなりすぎるとエラーになったり、処理が遅くなったりします。必要に応じて ON OVERFLOW TRUNCATE を使うか、集約するデータの件数を制限する、あるいはアプリケーション側で結合を行うなどの対策を検討する必要があります。
  • 関数ベースインデックス: 結合結果の文字列に対して検索(例: WHERE full_name LIKE '...')を頻繁に行う場合、文字列結合の式に対して関数ベースインデックスを作成することで、検索パフォーマンスを向上できる可能性があります。ただし、インデックスの維持にコストがかかる点、特定の条件でしか利用されない点に注意が必要です。

“`sql
— 例: フルネームの結合結果に対してインデックスを作成
CREATE INDEX idx_employees_fullname ON employees (first_name || ‘ ‘ || last_name);

— インデックスが利用される可能性があるクエリ
SELECT * FROM employees WHERE first_name || ‘ ‘ || last_name LIKE ‘Steven%’;
“`
インデックスが有効に機能するかどうかは、WHERE句の条件や実行計画を確認する必要があります。

  • SQLチューニング: パフォーマンスが問題となる場合は、実行計画を確認し、遅延の原因となっている部分を特定して集中的にチューニングを行います。文字列結合だけでなく、JOINの方法やインデックスの使用状況なども含めて総合的に評価することが重要です。

ほとんどの一般的な用途では、文字列結合のパフォーマンスが問題になることは少ないです。しかし、大規模なデータ処理や、応答性能が求められるオンライン処理では、これらの点に注意を払うことが必要になります。

8. よくあるエラーとその対処法

文字列結合を行う際に、初心者の方が遭遇しやすいエラーとその対処法を見ていきましょう。

  • ORA-00904: invalid identifier (識別子が無効です)

    • 原因: 存在しない列名や関数名を指定している、あるいはスペルミスがある。
    • 対処法: 指定した列名や関数名が正しいか、テーブルにその列が存在するか、スペルミスがないかを確認します。大文字小文字の区別(通常Oracleでは列名などは大文字小文字を区別しないことが多いが、稀にダブルクォートで囲んで作成された場合は区別が必要)も確認します。
  • ORA-00923: FROM keyword not found where expected (FROMキーワードが指定位置にありません)

    • 原因: SELECT句の項目リストとFROM句の間にカンマなどの構文上の誤りがある。文字列結合の式自体に構文エラー(演算子の誤用など)がある場合も、このエラーが表示されることがあります。
    • 対処法: SELECT文の構文を最初から見直します。特にSELECT句の各項目(列名や式)がカンマで正しく区切られているか、文字列結合の式が閉じているかなどを確認します。
  • ORA-01722: invalid number (数値が無効です)

    • 原因: 文字列を数値に変換しようとして失敗した場合に発生します。文字列結合自体は通常、非文字列型を文字列に変換するためこのエラーは直接は引き起こしにくいですが、結合結果の文字列を後続の処理で数値として扱おうとした場合などに発生する可能性があります。あるいは、文字列結合の式の中で、数値変換を伴う関数を誤って使っている場合など。
    • 対処法: 文字列結合の前後で、意図しない型変換が発生していないか確認します。特に、文字列型の列に数値以外のデータが入っている場合、それを数値として扱おうとするとエラーになります。
  • ORA-01489: result of string concatenation is too long (文字列結合の結果が長すぎます)

    • 原因: || 演算子や LISTAGG 関数などによる文字列結合の結果が、Oracleが許容する文字列の最大長(通常はVARCHAR2のサイズ制限、例えば4000バイトや32767バイトなど)を超えた場合に発生します。
    • 対処法:
      • 結合するデータ量を減らす。
      • LISTAGG の場合は Oracle 12c R2 以降であれば ON OVERFLOW TRUNCATE 句を使用する。
      • 結合する文字列の長さを確認し、データ型や変数サイズを見直す(PL/SQLの場合など)。
      • 結合をクライアントアプリケーション側で行うことも検討する。
      • CLOB型など、より大きな文字列を扱えるデータ型を使用することも検討する(ただし、関数によってはCLOBを直接扱えない場合もある)。
  • NULLによる意図しない結果:

    • 原因: || 演算子のNULL無視特性や CONCAT 関数のNULL伝播特性を考慮せずにクエリを作成した場合、期待と異なる結果になることがあります(例: 不要なスペース、結果がNULLになる)。
    • 対処法: NULLが含まれる可能性のある列を結合する場合は、NVL()COALESCE() 関数を使って NULL を適切な代替値(空文字列や特定のテキスト)に置き換えてから結合します。

エラーメッセージは、問題解決のための重要なヒントです。エラーメッセージをよく読み、どの部分で問題が発生しているかを特定することから始めましょう。Oracleのエラーコード(ORA-XXXXX)で検索すると、詳細な情報や解決策が見つかることが多いです。

9. 練習問題

ここまで学んだ内容を定着させるために、簡単な練習問題をいくつか解いてみましょう。以下の問題に挑戦してみてください。解答例は後述します。

前提として、以下の構造を持つ employees テーブルと products テーブルがあるものとします。

employees テーブル:
employee_id NUMBER (主キー)
first_name VARCHAR2(50)
last_name VARCHAR2(50)
job_title VARCHAR2(50) NULLABLE
salary NUMBER
department_id NUMBER

products テーブル:
product_id NUMBER (主キー)
product_name VARCHAR2(100)
price NUMBER
stock NUMBER

問題 1: フルネームの表示
employees テーブルから、各従業員のフルネームを「姓 名」の形式で表示してください。姓と名の間にはスペースを1つ入れてください。

問題 2: 従業員と役職情報の表示
employees テーブルから、各従業員の「姓, 名 (役職)」という形式で情報を表示してください。ただし、job_title が NULL の場合は「姓, 名 (役職未設定)」と表示してください。

問題 3: 整形済み商品価格の表示
products テーブルから、各商品の「商品名: 価格円」という形式で情報を表示してください。価格は桁区切り(カンマ)を付けて表示し、末尾に「円」を付けてください。例:「ノートPC: 150,000円」

問題 4: 在庫情報の表示
products テーブルから、各商品の「商品名 (在庫数: N)」という形式で情報を表示してください。ただし、stock が 0 の場合は「商品名 (在庫なし)」と表示してください。

問題 5: 同じ部署の従業員リスト
employees テーブルから、部署IDごとに、その部署に所属する全従業員のフルネームをカンマとスペース , 区切りで一覧表示してください。従業員は姓、名の昇順で並べてください。


練習問題 解答例:

問題 1:
sql
SELECT last_name || ' ' || first_name AS FullName
FROM employees;

または
sql
SELECT CONCAT(CONCAT(last_name, ' '), first_name) AS FullName
FROM employees;

問題 2:
sql
SELECT last_name || ', ' || first_name ||
CASE
WHEN job_title IS NULL THEN ' (役職未設定)'
ELSE ' (' || job_title || ')'
END AS EmployeeJobInfo
FROM employees;

または NVLCOALESCE を使ってより簡潔に書くこともできます。役職がある場合とない場合で括弧の有無が異なるため、このケースでは CASE が最も柔軟です。もし「姓, 名 (役職)」で、役職がNULLなら「姓, 名 ()」でもよければ…
sql
-- (役職未設定)のように代替文字列を入れるなら CASE が良い
-- NULLの場合は括弧の中身が空で良いなら NVL/COALESCE と || で実現可能
SELECT last_name || ', ' || first_name || ' (' || NVL(job_title, '') || ')' AS EmployeeJobInfo
FROM employees;
-- ただし、job_titleがNULLの場合 '姓, 名 ()' となってしまう

やはり CASE 文で完全に制御するのが最も正確です。

問題 3:
sql
SELECT product_name || ': ' || TO_CHAR(price, 'FM999,999,990') || '円' AS FormattedPrice
FROM products;

数値書式モデル FM999,999,990 は、FM で先頭/末尾の空白を詰め、, で桁区切り、9 で数値を表示(必要なら空白)、0 でゼロを必ず表示(小数点以下がない場合は .00 などにしないため 0 を使う方がシンプル)。

問題 4:
sql
SELECT product_name ||
CASE
WHEN stock = 0 THEN ' (在庫なし)'
ELSE ' (在庫数: ' || stock || ')'
END AS StockInfo
FROM products;

CASE 文で stock の値によって結合する文字列を切り替えています。

問題 5:
sql
SELECT department_id,
LISTAGG(last_name || ' ' || first_name, ', ') WITHIN GROUP (ORDER BY last_name, first_name) AS DepartmentMembers
FROM employees
GROUP BY department_id;

department_id ごとにグループ化し、LISTAGG でフルネームを連結しています。WITHIN GROUP (ORDER BY ...) で連結順序を指定しています。

これらの練習問題を通して、基本的な文字列結合、NULLや条件による制御、型変換、複数行結合といったテクニックを実践的に確認できたかと思います。

10. まとめ

この記事では、Oracle SQLにおける文字列結合について、初心者向けに基本から応用までを網羅して解説しました。

  • 文字列結合の必要性: データを整形し、見やすく表示するために不可欠な処理です。
  • 主要な方法:
    • || 演算子: 複数結合に柔軟で一般的です。NULLは無視されます。
    • CONCAT() 関数: 2つの文字列を結合します。どちらかがNULLだと結果もNULLになります。ネストすれば複数結合も可能ですが、可読性が低下します。
  • 応用テクニック:
    • スペース、記号、改行コード (CHR(10)) を挿入することで、表示形式を細かく制御できます。
    • CASE 文と組み合わせることで、条件に応じた動的な文字列結合が可能です。
  • NULLの扱い: ||CONCAT でNULLの挙動が異なる点を理解し、NVL()COALESCE() 関数を使ってNULLを回避・置換して結合する方法を学びました。これにより、予期せぬ結果を防ぐことができます。
  • 型変換: 数値や日付を文字列と結合する際は、暗黙の型変換に頼らず、TO_CHAR() 関数を使って明示的に変換し、表示形式を制御することが推奨されます。
  • 複数行の結合: 同じグループに属する複数行の値を1つの文字列にまとめるには、集約関数 LISTAGG() (Oracle 11g R2以降) が非常に便利です。結果長制限や、Oracle 12c R2以降で利用可能な DISTINCTON OVERFLOW 句についても学びました。
  • パフォーマンスとエラー: 大量データでのパフォーマンスへの影響や、よくあるエラーとその対処法についても触れました。

これらの知識があれば、Oracle SQLでの文字列結合に関して、様々な要件に対応できるようになるはずです。

文字列結合は、SQLクエリの結果を見やすく、利用しやすくするための基本的ながら強力な手段です。特にレポート作成やアプリケーションへのデータ表示において、このスキルは欠かせません。

11. 次のステップ

この記事でOracle SQLの文字列結合の基礎を固めましたが、SQLの世界はさらに奥深く、学ぶべきことはたくさんあります。

  • より高度な文字列操作関数: SUBSTR, INSTR, LENGTH, REPLACE, TRIM, LPAD, RPAD など、Oracleには文字列を操作するための豊富な関数が用意されています。これらを学ぶことで、さらに複雑な文字列処理が可能になります。
  • 正規表現関数: Oracle 10g から正規表現関数 (REGEXP_LIKE, REGEXP_INSTR, REGEXP_SUBSTR, REGEXP_REPLACE) が導入され、非常に強力なパターンマッチングや文字列置換ができるようになりました。
  • 分析関数: LISTAGG も一種の分析関数的な使い方ができますが、ウィンドウ関数としての分析関数は、集計やランキングなど、より複雑なデータ分析に役立ちます。
  • PL/SQL: ストアドプロシージャや関数などの手続き型拡張言語であるPL/SQLを学ぶと、SQLだけでは難しい複雑なロジックやループ処理などを記述できるようになり、より高度な文字列処理もPL/SQL内で行うことができます。

これらのトピックに挑戦することで、あなたのOracle SQLスキルはさらに向上するでしょう。

まずは、この記事で学んだ文字列結合のテクニックを、実際の業務や学習で積極的に活用してみてください。実際に手を動かすことが、理解を深める一番の方法です。

この記事が、あなたのOracle SQL学習の一助となれば幸いです。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール