自動化無しに生活無し

WEB開発関係を中心に備忘録をまとめています

OracleDBのSQLまとめ

thumbnail

dockerにインストールしたOracle−XEを使用している。

UbuntuのDockerでOracle DB SQL Silverの試験勉強の環境を整える

HR スキーマを搭載した。

雇用日時をもとに絞り込みをする

【非推奨】文字列を与えると、暗黙的にDATE型に変換する

例えば、2015年1月1日以降に雇用した従業員を出力するには、

SELECT * FROM employees WHERE hire_date >= '2015-01-01' ;

そして、新人を上に表示させるため、降順に並び替える。

SELECT * FROM employees WHERE hire_date >= '2015-01-01' ORDER BY hire_date DESC;

ただし、このように文字列を指定する方法はあまり好ましくない。

【推奨】TO_DATEを使うことで安全にDATE変換をして比較をする

このようにTO_DATE関数を使って型を変換して比較をしたほうが良いだろう。

SELECT * FROM employees WHERE hire_date >= TO_DATE('2015-01-01', 'YYYY-MM-DD');
SELECT * FROM employees WHERE hire_date >= TO_DATE('2015-01-01', 'YYYY-MM-DD') ORDER BY hire_date DESC;

このようにする。

【EXTRACT】年だけで絞り込みをするには?

EXTRACT 関数を使って対応する。戻り値は数値になるため、シングルクオートはつけない。

-- 年だけで絞り込みをするには、EXTRACT 関数を使う
SELECT * FROM employees WHERE EXTRACT(YEAR FROM hire_date) >= 2016 ORDER BY hire_date DESC;

MONTHを指定すれば月も取得できる。

-- 月だけで絞り込みをする( 2017年以降 の 下半期に入社した人 )
SELECT * FROM employees WHERE 
    EXTRACT(YEAR FROM hire_date) >= 2017 AND
    EXTRACT(MONTH FROM hire_date) >= 7
    ORDER BY hire_date DESC;

【BETWEEN】期間を指定して絞り込みをするには?

EXTRACTを使って年だけ取り出し、比較をしている。

-- 2013年から 2015年に入社した人
SELECT * FROM employees WHERE
    EXTRACT(YEAR FROM hire_date ) BETWEEN 2013 AND 2015
    ORDER BY hire_date DESC;

このBETWEENは

BETWEEN a AND b 

によりa以上b以下という表現になる。そのため、BETWEEN文を使った時点で未満の表現は実現できない。

また、この方法だと、データ量が多い場合遅くなってしまう。

更に、インデックスが張られている場合は通用しない。

そこで、WHERE 文のANDと TO_DATE を使う。

SELECT * FROM employees WHERE
    hire_date >= TO_DATE('2013-01-01', 'YYYY-MM-DD') AND
    hire_date < TO_DATE('2016-01-01', 'YYYY-MM-DD')
    ORDER BY hire_date DESC;

これで更に柔軟に表現をすることができる。

BETWEEN a AND b を使えば簡潔に表現はできるが、柔軟性にかける点で注意が必要。

【注意】BETWEEN の文字列問題

BETWEEN で文字列を扱う場合。

例えば、名前がAからCまでの人を取り出したい場合。

SELECT * FROM employees WHERE last_name BETWEEN 'A' AND 'C';

としてしまうと、AとBから始まる人は取れるが、Cから始まる人は取れない。

例えば、ChenはCよりも大きいとみなされ取れない。

そのため このように、WHERE と LIKE を使って対応する。

-- last_name がAからCまでの人。
SELECT * FROM employees WHERE
    last_name LIKE 'A%' OR
    last_name LIKE 'B%' OR
    last_name LIKE 'C%'
    ORDER BY last_name;

簡潔に書く場合、SUBSTRを使う。ただしSUBSTR はデータ量が多い場合は遅いため注意。

SELECT * FROM employees WHERE
    SUBSTR(last_name, 1, 1) IN ('A','B','C')
    ORDER BY last_name; 

SUBSTR(取り出す列名(もしくは文字列), 開始位置, 何文字分取り出すか) で指定する。

以上から、BETWEENは簡潔に表現できる反面、ANDへ書き直す際の手間、未満の表現ができない問題なども含め、使用を禁じているケースもある。

部署を結合して取り出す。

INNER JOIN を使って外部キーに紐付いているデータを結合して出す。

-- department_id で紐付いているデータを取り出す。
-- ※ ただしemployees の department_id がNULL、departments に存在しない場合 の社員は出てこない。
-- ※ INNER JOIN は JOIN と略して書いても良い

SELECT * FROM employees
    JOIN departments ON employees.department_id = departments.department_id
    ORDER BY employee_id;

ただし、このINNER JOIN(JOIN)は department_id がNULL であったり、departments テーブルに存在しないidが指定されていた場合は表示しないようになっている。

LEFT JOIN を使って 紐付かないデータも表示する。

そこでLEFT JOINを使う。

-- employees の department_id がNULL の社員を出すには LEFT JOIN を使う。

SELECT * FROM employees
    LEFT JOIN departments ON employees.department_id = departments.department_id
    ORDER BY employee_id;

これにより紐付かないデータも表示できる。

多段 JOIN で更に紐付いているデータを結合する。

-- locations テーブルの国id とcountries の国idで結合
-- countries の地域idとregions の地域idで結合。

SELECT * FROM locations l
    LEFT JOIN countries c  ON l.country_id = c.country_id
    LEFT JOIN regions r ON c.region_id = r.region_id;

更に、 スペースを空けてテーブル名のエイリアスを作っておくと、短く表現できる。このようにASは省略できる。

テーブルのエイリアスにはASは使わないことのほうが多い。カラムのエイリアスにはASを使ったほうがよい。

【サブクエリ】条件に一致するデータだけ、結合する。

条件に一致するデータだけ結合をする。

結合した後に絞り込んでもよいが、それでは無駄に結合されてしまう。

そのため、先に絞り込みをしてから結合する。サブクエリを使う。

-- 日本に一致する国だけ結合。( countries で絞ってから結合する。) 
SELECT * FROM locations l 
LEFT JOIN ( SELECT * FROM countries WHERE country_name = 'Japan' ) c  ON l.country_id = c.country_id 
LEFT JOIN regions r ON c.region_id = r.region_id;

ここで、もしlocationsの時点で絞り込みをしたい場合は、FROM の時点でサブクエリを動かす。

-- location で country_id が JP のデータを絞った上で結合する。
SELECT * FROM ( SELECT * FROM locations WHERE country_id = 'JP' ) l 
LEFT JOIN countries c  ON l.country_id = c.country_id 
LEFT JOIN regions r ON c.region_id = r.region_id;

RIGHT JOINは?

RIGHT JOINは右側のデータを必ず残したい場合に使う。しかし、

SELECT * FROM employees e
    RIGHT JOIN departments d 
    ON e.department_id = d.department_id;

このRIGHT JOINと

SELECT * FROM departments d
    LEFT JOIN employees e 
    ON e.department_id = d.department_id;

このLEFT JOINは等価である。順序を逆にすれば良いだけなので、現場では基本LEFT JOINが使われる。

Oracleの関数まとめ

【NVL】NULL値の値を置き換える

SELECT NVL(manager_id, 'なし') FROM employees;

こうすれば、manager_id は ‘なし’ として扱われ、NULLがそのままになってしまうことはない。

【COALESCE】NULL値の置き換えを複数指定する。

SELECT COALESCE(phone_number, mobile_number, '連絡先なし') AS primary_contact
FROM employees;

phone_number があればそれを、なければ mobile_numberを、両方なければ ‘連絡先なし’ を表示する。

ちなみに、このCOALESCEはANSI基準であり、Oracleでしか使えないNVLとは異なる。

【TO_CHAR】日付や数値を文字列に変換する

SELECT TO_CHAR(hire_date, 'YYYY-MM') FROM employees;
-- 例: '2024-07'

【TO_DATE】文字列を日付に変換する

SELECT * FROM employees WHERE hire_date >= TO_DATE('2015-01-01', 'YYYY-MM-DD') ORDER BY hire_date DESC;

【TRIM】前後の空白、もしくは指定文字を削除する

SELECT TRIM('  Hello  ') FROM dual; -- → 'Hello'
SELECT TRIM('0' FROM '000123000') FROM dual; -- → '123'

【LENGTH】文字列の長さを測る

LENGTH('abc') -- → 3

【SUBSTR】文字列の部分取得

SELECT * FROM employees WHERE
    SUBSTR(last_name, 1, 1) IN ('A','B','C')
    ORDER BY last_name;

SUBSTR(文字列, 開始位置, 長さ)

【UPPER()とLOWER()】大文字化と小文字化

UPPER('abc') → 'ABC'
LOWER('ABC') → 'abc'

関数の実用例

NVLでNULL対策をして、必要なデータだけトリミングして表示する。

SELECT TO_CHAR(NVL(hire_date, SYSDATE), 'YYYY-MM-DD') AS 入社日
    FROM employees;
スポンサーリンク