
Oracle DB Skills は、Oracle Database向けの AIエージェント用ナレッジ集 / Skill集 です。
Codex や Claude Code のようなAIコーディングエージェントに「Oracle DBではこう考える・こう書く・こう確認する」という実務知識を読ませるための Markdown ガイド集です。
リポジトリの README では、SQL/PLSQL、パフォーマンス・チューニング、セキュリティ、管理、モニタリング、アーキテクチャ、DevOps、移行、SQLcl、ORDS などの領域に分かれた「100以上の実用ガイド」です。
今回、日本語で読みやすくする目的で Oracle Skills の Oracle Database 向けガイドをベースにした日本語版リポジトリ engchina/oracle-db-skills-ja を、Mac上のCodexにSkillとして導入して試します。
公式の Oracle Skills リポジトリでは、Oracle Database 向けの内容は db/ ドメインとして提供されています。
| カテゴリ | ファイル数 | パス |
|---|---|---|
| データベース設計とモデリング | 4 | skills/design/ |
| SQL開発 | 5 | skills/sql-dev/ |
| パフォーマンスとチューニング | 7 | skills/performance/ |
| アプリケーション開発 | 8 | skills/appdev/ |
| セキュリティ | 6 | skills/security/ |
| データベース管理 | 6 | skills/admin/ |
| モニタリングと診断 | 5 | skills/monitoring/ |
| アーキテクチャとインフラストラクチャ | 5 | skills/architecture/ |
| DevOpsとCI/CD | 5 | skills/devops/ |
| Oracleへの移行 | 14 | skills/migrations/ |
| PL/SQL開発 | 12 | skills/plsql/ |
| Oracle固有の機能 | 6 | skills/features/ |
| SQLcl | 8 | skills/sqlcl/ |
| ORDS (Oracle REST Data Services) | 10 | skills/ords/ |
最近、Codex や Claude Code のような AI コーディングエージェントを使うことで、コード作成だけでなく、設計レビューや運用手順の確認まで AI と一緒に進められるようになってきました。
一方で、Oracle Database のようなエンタープライズ向けデータベースでは、単に SQL を生成できるだけでは不十分で、実行計画の読み方、索引の使われ方、Predicate Information の access / filter の違いなど、実務で必要になる知識が多くあります。
そこで今回は、Oracle Database 向けの AI エージェント用 Skill 集である Oracle DB Skills を Codex に導入し、SQLcl 経由で Oracle Database の HR スキーマに接続して、スキーマ確認と SQL 実行計画レビューを試してみます。
特に今回は、Codex にデータベースのパスワードを直接渡さず、SQLcl の保存済み接続を利用する形にしています。これにより、AI エージェントから実DBを扱う場合でも、できるだけ安全に接続情報を扱いながら検証できるかを確認し、HR スキーマの構造確認、テーブル件数、制約・索引の確認に加えて、あえてチューニング要素を含む SQL を用意し、Oracle DB Skills の観点で実行計画をレビューしてみます。
ということで今回は、Codex + Oracle DB Skills + SQLcl を組み合わせて、AI エージェントに Oracle Database の実行計画レビューをしてもらう流れを試してみてみます。
なお、AWR / ASH / DBA_HIST_* を使う性能診断は行わず、HRユーザーで実行できるSELECTとDBMS_XPLAN.DISPLAYを中心に確認します。
■ Oracle DB Skills インストール
MacBook の Codex に Oracle DB Skills を Skill としてインストールします。
1) インストール
python3 $HOME/.codex/skills/.system/skill-installer/scripts/install-skill-from-github.py \
--repo engchina/oracle-db-skills-ja \
--path . \
--name oracle-db-skills-ja
shirok@macbook ~ % python3 $HOME/.codex/skills/.system/skill-installer/scripts/install-skill-from-github.py \
--repo engchina/oracle-db-skills-ja \
--path . \
--name oracle-db-skills-ja
Installed oracle-db-skills-ja to /Users/shirok/.codex/skills/oracle-db-skills-ja
2) インストール確認
インストール先は通常ここです。
SKILL.md や skills/ ディレクトリが見えればOKです。
このリポジトリはルート直下に SKILL.md があるので、--path . でリポジトリ全体を1つのskillとして入れる形になります。
shirok@macbook ~ % ls -l ~/.codex/skills/oracle-db-skills-ja
total 160
-rw-r--r-- 1 shirok staff 4827 May 28 22:14 AGENTS.md
-rw-r--r-- 1 shirok staff 1066 May 28 22:14 LICENSE
-rw-r--r-- 1 shirok staff 18558 May 28 22:14 README.md
-rw-r--r-- 1 shirok staff 4782 May 28 22:14 SKILL.md
-rw-r--r-- 1 shirok staff 16220 May 28 22:14 SKILLS.md
-rw-r--r-- 1 shirok staff 6854 May 28 22:14 SKILL_GENERATION_PROMPT.md
drwxr-xr-x 3 shirok staff 96 May 28 22:14 scripts
drwxr-xr-x 16 shirok staff 512 May 28 22:14 skills
-rw-r--r-- 1 shirok staff 13197 May 28 22:14 skills-index.md
3) Codex 確認
完了後、Codex を再起動してください。再起動すると oracle-db-skills-ja が skill として認識されます。
■ Codexへパスワードを渡さずSQLcl接続する方法
今回は SQLcl の保存済み接続を作成したうえで、Codexからも同じ接続名を使える状態にしています。以降の確認では、SQLcl で実行したSQL結果と、Codex + Oracle DB Skillsによるレビュー結果を組み合わせて確認します。
SQLcl MCP Serverから使うため、事前にSQLclの保存済み接続を作成しておきます。
SQLcl側に保存済み接続を作り、Codexには接続名だけ渡すことができます。
SQLcl MCP Serverは、~/.dbtools 配下のSQLcl接続ストアに保存された接続を使う仕組みで、MCPクライアントから使うには -savepwd でパスワードを安全に保存する必要があります。
-
SQLcl 参考:
・ 環境の準備
・ 格納された接続の管理
0) SQLclインストールと設定
MacBOok への SQLclインストールは次が参考になります。
1) sqlcl起動
shirok@macbook ~ % sql /nolog
SQLcl: Release 25.4 Production on Wed Jun 03 19:00:24 2026
Copyright (c) 1982, 2026, Oracle. All rights reserved.
2) SQLcl内で、保存済み接続を作成
ここでパスワード入力を求められたら、その場で手入力 します。
SQL> conn -save hr_local -savepwd HR@my_connection
Password? (**********?) ********************
Name: hr_local
Connect String: my_connection
User: HR
Password: ******
Connected.
3) 保存済み接続確認
SQL> connmgr list
.
├── cline_mcp
└── hr_local
SQL> connmgr show hr_local
Name: hr_local
Connect String: my_connection
User: HR
Password: ******
SQL> connmgr test hr_local
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Connection Test Successful
SQL>
4) 別のターミナルで接続
新しくターミナルを開き、conn -name <保存済み接続名> でパスワード無しで接続できることを確認
shirok@macbook ~ % sql /nolog
SQLcl: Release 25.4 Production on Wed Jun 03 19:16:29 2026
Copyright (c) 1982, 2026, Oracle. All rights reserved.
SQL> conn -name hr_local
Connected.
SQL> show con_name
CON_NAME
------------------------------
CDB_PDB1
■ 実際にOracle Databaseへ接続してみる
Oracle Database へ HRスキーマをインストールするには次が参考になります。
⚫︎ HRスキーマへ接続確認
1) SQLclで接続できることを確認
まず、SQLclの保存済み接続 hr_local を使って、HRスキーマへ接続できることを確認します。
Codexにはパスワードを渡さず、保存済み接続名だけを渡す形にしています。
shirok@macbook ~ % sql /nolog
SQLcl: Release 25.4 Production on Wed Jun 03 19:16:29 2026
Copyright (c) 1982, 2026, Oracle. All rights reserved.
SQL> conn -name hr_local
Connected.
SQL> show con_name
CON_NAME
------------------------------
CDB_PDB1
2) Codexへ投げたプロンプト
oracle-db-skills-ja を使って、SQLcl経由でOracle DatabaseのHRスキーマ接続確認をしてください。
前提:
- MacBookにSQLclはインストール済みです
- SQLclの sql コマンドでHRスキーマへ接続できる状態です
- 接続先はHRスキーマです
- パスワードはプロンプト内に書かないでください
- SQLclの保存済み接続 hr_local を使って接続してください。
- SQLclで接続する場合は conn -name hr_local または conn -n hr_local を使ってください。
- パスワードはプロンプト内に書かず、保存済み接続のウォレットに保存済みです。
制約:
- SELECTのみ実行してください
- DDL / DML / COMMIT / ROLLBACK / ALTER SESSION / パラメータ変更は実行しないでください
- AWR / ASH / DBA_HIST_* / DBMS_WORKLOAD_REPOSITORY は使わないでください
- まず実行予定のSQLを提示してください
- 私が承認してからSQLを実行してください
- HRユーザーで参照できるデータディクショナリビューだけを使ってください
- 権限が足りないビューがあれば、代替SQLを提示してください
確認してほしいこと:
1. 接続ユーザー、現在のスキーマ、DB名、PDB名、インスタンス名、バージョンを確認
2. HRスキーマのテーブル一覧を確認
3. 各テーブルの件数を確認
4. 主キー、外部キー、ユニーク制約を確認
5. 索引一覧を確認
6. EMPLOYEES / DEPARTMENTS / JOBS / LOCATIONS / COUNTRIES / REGIONS の関係を説明
7. このHRスキーマで次に試すと良さそうなSQLレビュー・実行計画レビューの題材を提案
出力形式:
1. 実行予定SQL
2. 接続確認結果
3. HRスキーマ概要
4. テーブル件数
5. 制約・索引の要約
6. ER図的な関係性の説明
7. 次に試すと良いOracle DB Skillsの使い方
3) Codexが提示したSQL
-- 1. 接続ユーザー、現在スキーマ、DB/PDB/インスタンス
SELECT
SYS_CONTEXT('USERENV', 'SESSION_USER') AS session_user,
SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') AS current_schema,
SYS_CONTEXT('USERENV', 'DB_NAME') AS db_name,
SYS_CONTEXT('USERENV', 'CON_NAME') AS pdb_name,
SYS_CONTEXT('USERENV', 'INSTANCE_NAME') AS instance_name
FROM dual;
-- 1b. バージョン
SELECT product, version, status
FROM product_component_version
WHERE product LIKE 'Oracle Database%'
ORDER BY product;
-- 2. HRスキーマのテーブル一覧
SELECT table_name, status, num_rows AS stats_num_rows, last_analyzed
FROM user_tables
ORDER BY table_name;
-- 2b. 主要テーブルの列概要
SELECT
table_name,
column_id,
column_name,
CASE
WHEN data_type IN ('CHAR','VARCHAR2','NCHAR','NVARCHAR2')
THEN data_type || '(' || char_length || ')'
WHEN data_type = 'NUMBER' AND data_precision IS NOT NULL
THEN data_type || '(' || data_precision ||
CASE WHEN data_scale IS NOT NULL THEN ',' || data_scale END || ')'
ELSE data_type
END AS data_type,
nullable
FROM user_tab_columns
WHERE table_name IN (
'EMPLOYEES','DEPARTMENTS','JOBS','LOCATIONS','COUNTRIES','REGIONS','JOB_HISTORY'
)
ORDER BY table_name, column_id;
-- 3. 標準HRテーブルの正確な件数
SELECT 'COUNTRIES' AS table_name, COUNT(*) AS row_count FROM countries
UNION ALL SELECT 'DEPARTMENTS', COUNT(*) FROM departments
UNION ALL SELECT 'EMPLOYEES', COUNT(*) FROM employees
UNION ALL SELECT 'JOBS', COUNT(*) FROM jobs
UNION ALL SELECT 'JOB_HISTORY', COUNT(*) FROM job_history
UNION ALL SELECT 'LOCATIONS', COUNT(*) FROM locations
UNION ALL SELECT 'REGIONS', COUNT(*) FROM regions
ORDER BY table_name;
-- 4a. 主キー・ユニーク制約
SELECT
CASE c.constraint_type WHEN 'P' THEN 'PRIMARY KEY' WHEN 'U' THEN 'UNIQUE' END AS constraint_kind,
c.table_name,
c.constraint_name,
LISTAGG(cc.column_name, ', ') WITHIN GROUP (ORDER BY cc.position) AS columns,
c.status,
c.validated,
c.index_name
FROM user_constraints c
JOIN user_cons_columns cc
ON cc.constraint_name = c.constraint_name
WHERE c.constraint_type IN ('P','U')
GROUP BY c.constraint_type, c.table_name, c.constraint_name, c.status, c.validated, c.index_name
ORDER BY c.table_name, constraint_kind, c.constraint_name;
-- 4b. 外部キー制約
SELECT
c.table_name AS child_table,
c.constraint_name AS fk_name,
LISTAGG(cc.column_name, ', ') WITHIN GROUP (ORDER BY cc.position) AS child_columns,
r.table_name AS parent_table,
r.constraint_name AS parent_key_name,
LISTAGG(rcc.column_name, ', ') WITHIN GROUP (ORDER BY rcc.position) AS parent_columns,
c.delete_rule,
c.status,
c.validated
FROM user_constraints c
JOIN user_cons_columns cc
ON cc.constraint_name = c.constraint_name
JOIN user_constraints r
ON r.constraint_name = c.r_constraint_name
JOIN user_cons_columns rcc
ON rcc.constraint_name = r.constraint_name
AND rcc.position = cc.position
WHERE c.constraint_type = 'R'
GROUP BY c.table_name, c.constraint_name, r.table_name, r.constraint_name,
c.delete_rule, c.status, c.validated
ORDER BY c.table_name, c.constraint_name;
-- 5. 索引一覧
SELECT
i.table_name,
i.index_name,
i.index_type,
i.uniqueness,
i.status,
i.visibility,
LISTAGG(ic.column_name, ', ') WITHIN GROUP (ORDER BY ic.column_position) AS columns
FROM user_indexes i
JOIN user_ind_columns ic
ON ic.index_name = i.index_name
WHERE i.table_name IN (SELECT table_name FROM user_tables)
GROUP BY i.table_name, i.index_name, i.index_type, i.uniqueness, i.status, i.visibility
ORDER BY i.table_name, i.index_name;
4) SQLclで実行した結果
SQL> @hr.sql
SESSION_USER CURRENT_SCHEMA DB_NAME PDB_NAME INSTANCE_NAME
_______________ _________________ ___________ ___________ ________________
HR HR CDB_PDB1 CDB_PDB1 CDB
PRODUCT VERSION STATUS
__________________________________________ _____________ _____________
Oracle Database 19c Enterprise Edition 19.0.0.0.0 Production
TABLE_NAME STATUS STATS_NUM_ROWS LAST_ANALYZED
______________ _________ _________________ ________________
COUNTRIES VALID
DEPARTMENTS VALID
EMPLOYEES VALID
JOBS VALID
JOB_HISTORY VALID
LOCATIONS VALID
REGIONS VALID
7 rows selected.
TABLE_NAME COLUMN_ID COLUMN_NAME DATA_TYPE_DETAIL NULLABLE
______________ ____________ __________________ ___________________ ___________
COUNTRIES 1 COUNTRY_ID CHAR(2) N
COUNTRIES 2 COUNTRY_NAME VARCHAR2(60) Y
COUNTRIES 3 REGION_ID NUMBER Y
DEPARTMENTS 1 DEPARTMENT_ID NUMBER(4,0) N
DEPARTMENTS 2 DEPARTMENT_NAME VARCHAR2(30) N
DEPARTMENTS 3 MANAGER_ID NUMBER(6,0) Y
DEPARTMENTS 4 LOCATION_ID NUMBER(4,0) Y
EMPLOYEES 1 EMPLOYEE_ID NUMBER(6,0) N
EMPLOYEES 2 FIRST_NAME VARCHAR2(20) Y
EMPLOYEES 3 LAST_NAME VARCHAR2(25) N
EMPLOYEES 4 EMAIL VARCHAR2(25) N
EMPLOYEES 5 PHONE_NUMBER VARCHAR2(20) Y
EMPLOYEES 6 HIRE_DATE DATE N
EMPLOYEES 7 JOB_ID VARCHAR2(10) N
EMPLOYEES 8 SALARY NUMBER(8,2) Y
EMPLOYEES 9 COMMISSION_PCT NUMBER(2,2) Y
EMPLOYEES 10 MANAGER_ID NUMBER(6,0) Y
EMPLOYEES 11 DEPARTMENT_ID NUMBER(4,0) Y
JOBS 1 JOB_ID VARCHAR2(10) N
JOBS 2 JOB_TITLE VARCHAR2(35) N
JOBS 3 MIN_SALARY NUMBER(6,0) Y
JOBS 4 MAX_SALARY NUMBER(6,0) Y
JOB_HISTORY 1 EMPLOYEE_ID NUMBER(6,0) N
JOB_HISTORY 2 START_DATE DATE N
JOB_HISTORY 3 END_DATE DATE N
TABLE_NAME COLUMN_ID COLUMN_NAME DATA_TYPE_DETAIL NULLABLE
______________ ____________ _________________ ___________________ ___________
JOB_HISTORY 4 JOB_ID VARCHAR2(10) N
JOB_HISTORY 5 DEPARTMENT_ID NUMBER(4,0) Y
LOCATIONS 1 LOCATION_ID NUMBER(4,0) N
LOCATIONS 2 STREET_ADDRESS VARCHAR2(40) Y
LOCATIONS 3 POSTAL_CODE VARCHAR2(12) Y
LOCATIONS 4 CITY VARCHAR2(30) N
LOCATIONS 5 STATE_PROVINCE VARCHAR2(25) Y
LOCATIONS 6 COUNTRY_ID CHAR(2) Y
REGIONS 1 REGION_ID NUMBER N
REGIONS 2 REGION_NAME VARCHAR2(25) Y
35 rows selected.
TABLE_NAME ROW_COUNT
______________ ____________
COUNTRIES 25
DEPARTMENTS 27
EMPLOYEES 107
JOBS 19
JOB_HISTORY 10
LOCATIONS 23
REGIONS 5
7 rows selected.
CONSTRAINT_KIND TABLE_NAME CONSTRAINT_NAME COLUMNS STATUS VALIDATED INDEX_NAME
__________________ ______________ __________________________ __________________________ __________ ____________ __________________________
PRIMARY KEY COUNTRIES COUNTRY_C_ID_PK COUNTRY_ID ENABLED VALIDATED COUNTRY_C_ID_PK
PRIMARY KEY DEPARTMENTS DEPT_ID_PK DEPARTMENT_ID ENABLED VALIDATED DEPT_ID_PK
PRIMARY KEY EMPLOYEES EMP_EMP_ID_PK EMPLOYEE_ID ENABLED VALIDATED EMP_EMP_ID_PK
UNIQUE EMPLOYEES EMP_EMAIL_UK EMAIL ENABLED VALIDATED EMP_EMAIL_UK
PRIMARY KEY JOBS JOB_ID_PK JOB_ID ENABLED VALIDATED JOB_ID_PK
PRIMARY KEY JOB_HISTORY JHIST_EMP_ID_ST_DATE_PK EMPLOYEE_ID, START_DATE ENABLED VALIDATED JHIST_EMP_ID_ST_DATE_PK
PRIMARY KEY LOCATIONS LOC_ID_PK LOCATION_ID ENABLED VALIDATED LOC_ID_PK
PRIMARY KEY REGIONS REG_ID_PK REGION_ID ENABLED VALIDATED REG_ID_PK
8 rows selected.
CHILD_TABLE FK_NAME CHILD_COLUMNS PARENT_TABLE PARENT_KEY_NAME PARENT_COLUMNS DELETE_RULE STATUS VALIDATED
______________ _________________ ________________ _______________ __________________ _________________ ______________ __________ ____________
COUNTRIES COUNTR_REG_FK REGION_ID REGIONS REG_ID_PK REGION_ID NO ACTION ENABLED VALIDATED
DEPARTMENTS DEPT_LOC_FK LOCATION_ID LOCATIONS LOC_ID_PK LOCATION_ID NO ACTION ENABLED VALIDATED
DEPARTMENTS DEPT_MGR_FK MANAGER_ID EMPLOYEES EMP_EMP_ID_PK EMPLOYEE_ID NO ACTION ENABLED VALIDATED
EMPLOYEES EMP_DEPT_FK DEPARTMENT_ID DEPARTMENTS DEPT_ID_PK DEPARTMENT_ID NO ACTION ENABLED VALIDATED
EMPLOYEES EMP_JOB_FK JOB_ID JOBS JOB_ID_PK JOB_ID NO ACTION ENABLED VALIDATED
EMPLOYEES EMP_MANAGER_FK MANAGER_ID EMPLOYEES EMP_EMP_ID_PK EMPLOYEE_ID NO ACTION ENABLED VALIDATED
JOB_HISTORY JHIST_DEPT_FK DEPARTMENT_ID DEPARTMENTS DEPT_ID_PK DEPARTMENT_ID NO ACTION ENABLED VALIDATED
JOB_HISTORY JHIST_EMP_FK EMPLOYEE_ID EMPLOYEES EMP_EMP_ID_PK EMPLOYEE_ID NO ACTION ENABLED VALIDATED
JOB_HISTORY JHIST_JOB_FK JOB_ID JOBS JOB_ID_PK JOB_ID NO ACTION ENABLED VALIDATED
LOCATIONS LOC_C_ID_FK COUNTRY_ID COUNTRIES COUNTRY_C_ID_PK COUNTRY_ID NO ACTION ENABLED VALIDATED
10 rows selected.
TABLE_NAME INDEX_NAME INDEX_TYPE UNIQUENESS STATUS VISIBILITY COLUMNS
______________ __________________________ _____________ _____________ _________ _____________ __________________________
COUNTRIES COUNTRY_C_ID_PK IOT - TOP UNIQUE VALID VISIBLE COUNTRY_ID
DEPARTMENTS DEPT_ID_PK NORMAL UNIQUE VALID VISIBLE DEPARTMENT_ID
DEPARTMENTS DEPT_LOCATION_IX NORMAL NONUNIQUE VALID VISIBLE LOCATION_ID
EMPLOYEES EMP_DEPARTMENT_IX NORMAL NONUNIQUE VALID VISIBLE DEPARTMENT_ID
EMPLOYEES EMP_EMAIL_UK NORMAL UNIQUE VALID VISIBLE EMAIL
EMPLOYEES EMP_EMP_ID_PK NORMAL UNIQUE VALID VISIBLE EMPLOYEE_ID
EMPLOYEES EMP_JOB_IX NORMAL NONUNIQUE VALID VISIBLE JOB_ID
EMPLOYEES EMP_MANAGER_IX NORMAL NONUNIQUE VALID VISIBLE MANAGER_ID
EMPLOYEES EMP_NAME_IX NORMAL NONUNIQUE VALID VISIBLE LAST_NAME, FIRST_NAME
JOBS JOB_ID_PK NORMAL UNIQUE VALID VISIBLE JOB_ID
JOB_HISTORY JHIST_DEPARTMENT_IX NORMAL NONUNIQUE VALID VISIBLE DEPARTMENT_ID
JOB_HISTORY JHIST_EMPLOYEE_IX NORMAL NONUNIQUE VALID VISIBLE EMPLOYEE_ID
JOB_HISTORY JHIST_EMP_ID_ST_DATE_PK NORMAL UNIQUE VALID VISIBLE EMPLOYEE_ID, START_DATE
JOB_HISTORY JHIST_JOB_IX NORMAL NONUNIQUE VALID VISIBLE JOB_ID
LOCATIONS LOC_CITY_IX NORMAL NONUNIQUE VALID VISIBLE CITY
LOCATIONS LOC_COUNTRY_IX NORMAL NONUNIQUE VALID VISIBLE COUNTRY_ID
LOCATIONS LOC_ID_PK NORMAL UNIQUE VALID VISIBLE LOCATION_ID
LOCATIONS LOC_STATE_PROVINCE_IX NORMAL NONUNIQUE VALID VISIBLE STATE_PROVINCE
REGIONS REG_ID_PK NORMAL UNIQUE VALID VISIBLE REGION_ID
19 rows selected.
■ HRスキーマで実行計画レビューをしてみる
⚫︎ チューニング要素あるSQLを用意
次のHRスキーマをクエリするSQLとその実行計画を取得し、その結果をCodexで分析します。
1) レビュー対象SQL
SELECT
e.employee_id,
e.first_name,
e.last_name,
e.email,
e.phone_number,
e.hire_date,
e.job_id,
j.job_title,
e.salary,
e.commission_pct,
d.department_id,
d.department_name,
l.city,
l.state_province,
c.country_name,
r.region_name
FROM
employees e
JOIN departments d
ON d.department_id = e.department_id
JOIN jobs j
ON j.job_id = e.job_id
JOIN locations l
ON l.location_id = d.location_id
JOIN countries c
ON c.country_id = l.country_id
JOIN regions r
ON r.region_id = c.region_id
WHERE
UPPER(e.last_name) LIKE 'S%'
AND TO_CHAR(e.hire_date, 'YYYY') >= '2005'
AND NVL(e.commission_pct, 0) = 0
ORDER BY
e.hire_date DESC,
e.salary DESC;
2) チューニング・ポイント
- UPPER(e.last_name) により、通常の last_name 索引があっても使いにくい
- TO_CHAR(e.hire_date, 'YYYY') により、hire_date の範囲検索にしにくい
- NVL(e.commission_pct, 0) により、commission_pct 条件の評価が分かりにくい
- 取得列が多い
- 複数JOINがあるため、Join順序やJoin方式の確認題材になる
- ORDER BYがあるため、SORT ORDER BYの有無を確認できる
⚫︎ 実行計画を取得
1) DBMS_XPLAN.DISPLAYで確認
次のSQLをHRスキーマへ実行し実行計画を取得
SQL> EXPLAIN PLAN FOR
SELECT
e.employee_id,
e.first_name,
e.last_name,
e.email,
e.phone_number,
e.hire_date,
e.job_id,
j.job_title,
e.salary,
e.commission_pct,
d.department_id,
d.department_name,
l.city,
l.state_province,
c.country_name,
r.region_name
FROM
employees e
JOIN departments d
ON d.department_id = e.department_id
JOIN jobs j
ON j.job_id = e.job_id
JOIN locations l
ON l.location_id = d.location_id
JOIN countries c
ON c.country_id = l.country_id
JOIN regions r
ON r.region_id = c.region_id
WHERE
UPPER(e.last_name) LIKE 'S%'
AND TO_CHAR(e.hire_date, 'YYYY') >= '2005'
AND NVL(e.commission_pct, 0) = 0
ORDER BY
e.hire_date DESC,
e.salary DESC;
Explained.
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format => 'TYPICAL +PREDICATE'));
PLAN_TABLE_OUTPUT
__________________________________________________________________________________________________________________
Plan hash value: 3429434307
---------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1565 | 3 (34)| 00:00:01 |
| 1 | SORT ORDER BY | | 5 | 1565 | 3 (34)| 00:00:01 |
| 2 | NESTED LOOPS | | 5 | 1565 | 2 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 5 | 1565 | 2 (0)| 00:00:01 |
| 4 | NESTED LOOPS | | 5 | 1435 | 2 (0)| 00:00:01 |
| 5 | NESTED LOOPS | | 27 | 4509 | 2 (0)| 00:00:01 |
| 6 | NESTED LOOPS | | 23 | 2852 | 2 (0)| 00:00:01 |
| 7 | NESTED LOOPS | | 25 | 1900 | 2 (0)| 00:00:01 |
| 8 | INDEX FAST FULL SCAN | COUNTRY_C_ID_PK | 25 | 1225 | 2 (0)| 00:00:01 |
| 9 | TABLE ACCESS BY INDEX ROWID | REGIONS | 1 | 27 | 0 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | REG_ID_PK | 1 | | 0 (0)| 00:00:01 |
| 11 | TABLE ACCESS BY INDEX ROWID BATCHED| LOCATIONS | 1 | 48 | 0 (0)| 00:00:01 |
|* 12 | INDEX RANGE SCAN | LOC_COUNTRY_IX | 2 | | 0 (0)| 00:00:01 |
| 13 | TABLE ACCESS BY INDEX ROWID BATCHED | DEPARTMENTS | 1 | 43 | 0 (0)| 00:00:01 |
|* 14 | INDEX RANGE SCAN | DEPT_LOCATION_IX | 4 | | 0 (0)| 00:00:01 |
|* 15 | TABLE ACCESS BY INDEX ROWID BATCHED | EMPLOYEES | 1 | 120 | 0 (0)| 00:00:01 |
|* 16 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 10 | | 0 (0)| 00:00:01 |
|* 17 | INDEX UNIQUE SCAN | JOB_ID_PK | 1 | | 0 (0)| 00:00:01 |
| 18 | TABLE ACCESS BY INDEX ROWID | JOBS | 1 | 26 | 0 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------
PLAN_TABLE_OUTPUT
________________________________________________________________________________________
Predicate Information (identified by operation id):
---------------------------------------------------
10 - access("R"."REGION_ID"="C"."REGION_ID")
12 - access("C"."COUNTRY_ID"="L"."COUNTRY_ID")
14 - access("L"."LOCATION_ID"="D"."LOCATION_ID")
15 - filter(UPPER("E"."LAST_NAME") LIKE 'S%' AND NVL("E"."COMMISSION_PCT",0)=0 AND
TO_CHAR(INTERNAL_FUNCTION("E"."HIRE_DATE"),'YYYY')>='2005')
16 - access("D"."DEPARTMENT_ID"="E"."DEPARTMENT_ID")
17 - access("J"."JOB_ID"="E"."JOB_ID")
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
40 rows selected.
⚫︎ Oracle DB Skillsでレビュー
1) Codexへ問い合わせプロンプト
oracle-db-skills-ja の SQLチューニング / 実行計画レビューの観点で、
以下のHRスキーマ向けSQLと実行計画をレビューしてください。
目的:
- 実行計画の読み方を確認したい
- このSQLの微妙な点を説明してほしい
- 改善SQLの例を出してほしい
- 索引を追加するならどのような候補があるか知りたい
- HRスキーマは小さいため、実際の性能問題というより学習用レビューとして扱ってください
制約:
- SELECTのみ
- DDLは実行せず、提案だけにしてください
- AWR / ASH / DBA_HIST_* / DBMS_WORKLOAD_REPOSITORY は使わないでください
- 実行計画の各行を、初心者にも分かるように説明してください
- Predicate Information の access / filter の違いも説明してください
- 改善案は「SQL書き換え」と「索引案」に分けてください
レビューしてほしい観点:
1. Full Scan / Index Scan の妥当性
2. Join方式とJoin順序
3. Predicate Information の読み方
4. UPPER / TO_CHAR / NVL が実行計画に与える影響
5. ORDER BY によるSORTの有無
6. E-Rowsの見方
7. HRのような小さい表では問題になりにくいが、本番データ量なら注意すべき点
添付:
- 対象SQL: sql01.sql
- DBMS_XPLAN.DISPLAY の実行結果: sql01-Explained.log
出力形式:
1. 結論
2. 実行計画の概要
3. 各ステップの読み解き
4. Predicate Information の解説
5. 微妙なポイント
6. 改善SQL例
7. 索引案
8. 本番適用前に確認すべきこと
9. Qiita記事に載せる要約
⚫︎ 改善SQL実行と実行計画確認
1) 改善後SQLの実行と実行計画取得
SQL> EXPLAIN PLAN FOR
2 SELECT
3 e.employee_id, e.first_name, e.last_name, e.email, e.phone_number,
4 e.hire_date, e.job_id, j.job_title, e.salary, e.commission_pct,
5 d.department_id, d.department_name,
6 l.city, l.state_province, c.country_name, r.region_name
7 FROM employees e
8 JOIN departments d ON d.department_id = e.department_id
9 JOIN jobs j ON j.job_id = e.job_id
10 JOIN locations l ON l.location_id = d.location_id
11 JOIN countries c ON c.country_id = l.country_id
12 JOIN regions r ON r.region_id = c.region_id
13 WHERE e.last_name LIKE 'S%'
14 AND e.hire_date >= DATE '2005-01-01'
15 AND (e.commission_pct = 0 OR e.commission_pct IS NULL)
16* ORDER BY e.hire_date DESC, e.salary DESC;
Explained.
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format => 'TYPICAL +PREDICATE'));
PLAN_TABLE_OUTPUT
_________________________________________________________________________________________________________________
Plan hash value: 1146796935
--------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1565 | 2 (50)| 00:00:01 |
| 1 | SORT ORDER BY | | 5 | 1565 | 2 (50)| 00:00:01 |
| 2 | NESTED LOOPS | | 5 | 1565 | 1 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 5 | 1565 | 1 (0)| 00:00:01 |
| 4 | NESTED LOOPS | | 5 | 1430 | 1 (0)| 00:00:01 |
| 5 | NESTED LOOPS | | 5 | 1300 | 1 (0)| 00:00:01 |
| 6 | NESTED LOOPS | | 5 | 1055 | 1 (0)| 00:00:01 |
| 7 | NESTED LOOPS | | 5 | 815 | 1 (0)| 00:00:01 |
|* 8 | TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES | 5 | 600 | 1 (0)| 00:00:01 |
|* 9 | INDEX RANGE SCAN | EMP_NAME_IX | 10 | | 1 (0)| 00:00:01 |
| 10 | TABLE ACCESS BY INDEX ROWID | DEPARTMENTS | 1 | 43 | 0 (0)| 00:00:01 |
|* 11 | INDEX UNIQUE SCAN | DEPT_ID_PK | 1 | | 0 (0)| 00:00:01 |
| 12 | TABLE ACCESS BY INDEX ROWID | LOCATIONS | 1 | 48 | 0 (0)| 00:00:01 |
|* 13 | INDEX UNIQUE SCAN | LOC_ID_PK | 1 | | 0 (0)| 00:00:01 |
|* 14 | INDEX UNIQUE SCAN | COUNTRY_C_ID_PK | 1 | 49 | 0 (0)| 00:00:01 |
| 15 | TABLE ACCESS BY INDEX ROWID | JOBS | 1 | 26 | 0 (0)| 00:00:01 |
|* 16 | INDEX UNIQUE SCAN | JOB_ID_PK | 1 | | 0 (0)| 00:00:01 |
|* 17 | INDEX UNIQUE SCAN | REG_ID_PK | 1 | | 0 (0)| 00:00:01 |
| 18 | TABLE ACCESS BY INDEX ROWID | REGIONS | 1 | 27 | 0 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------------
PLAN_TABLE_OUTPUT
________________________________________________________________________________________________
Predicate Information (identified by operation id):
---------------------------------------------------
8 - filter("E"."HIRE_DATE">=TO_DATE(' 2005-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
("E"."COMMISSION_PCT" IS NULL OR "E"."COMMISSION_PCT"=0))
9 - access("E"."LAST_NAME" LIKE 'S%')
filter("E"."LAST_NAME" LIKE 'S%')
11 - access("D"."DEPARTMENT_ID"="E"."DEPARTMENT_ID")
13 - access("L"."LOCATION_ID"="D"."LOCATION_ID")
14 - access("C"."COUNTRY_ID"="L"."COUNTRY_ID")
16 - access("J"."JOB_ID"="E"."JOB_ID")
17 - access("R"."REGION_ID"="C"."REGION_ID")
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
42 rows selected.
SQL>
3) 改善前後の違い
今回の例では、HRスキーマの小さなデータを使っているため、実行時間として大きな差は出ません。
ただし、実行計画を見ると改善前後の違いは確認できました。
改善前SQLでは、UPPER(e.last_name)、TO_CHAR(e.hire_date, 'YYYY')、NVL(e.commission_pct, 0) のように列側へ関数をかけていました。
そのため、Predicate Informationではこれらの条件が filter として評価されていました。
一方、改善後SQLでは、e.last_name LIKE 'S%'、e.hire_date >= DATE '2005-01-01' のように、列側に関数をかけない形へ書き換えました。
その結果、EMP_NAME_IX に対する INDEX RANGE SCAN が使われ、LAST_NAME LIKE 'S%' が access 条件として表示されました。
HRスキーマではデータ量が少ないため、コスト差は小さいですが、Oracle DB Skillsを使うことで、実行計画のどこを見るべきか、Predicate Information の access / filter をどう読むか、SQLをどのように書き換えるべきかを確認できました。
なお、UPPER(e.last_name) を外すと、大文字小文字を無視した検索ではなくなるため、実際の要件によっては関数ベース索引や照合順序の設計も検討が必要です。
4) Codexへ問い合わせプロンプト
SQL改善前後の分析をCodexにしてもらいます。
oracle-db-skills-ja の SQLチューニング / 実行計画レビュー観点で、
改善前SQLの実行計画 `sql01-Explained.log` と、
改善後SQLの実行計画 `sql02-Explained.log` を比較してください。
前提:
- HRスキーマを使った学習用のSQLレビューです
- データ量が少ないため、実行時間やCostの差は小さいです
- 目的は、Oracle DB Skillsで実行計画レビューの流れを確認することです
比較してほしいこと:
1. 改善前後のSQLの違い
2. Plan hash value の違い
3. JOIN順序の違い
4. EMPLOYEES表へのアクセス方法の違い
5. Predicate Information の access / filter の違い
6. `UPPER` / `TO_CHAR` / `NVL` を外した効果
7. 改善後も残る注意点
8. HRスキーマでは効果が小さい理由
9. 本番データ量で同じ考え方を使う場合の注意点
出力形式:
1. 結論
2. 改善前後の比較表
3. 実行計画の読み解き
4. Predicate Information の解説
5. Oracle DB Skillsの指摘まとめ
6. Qiita記事に載せる要約文
6) Oracle DB Skillsの指摘まとめ
Oracle DB Skillsのレビューでは、主に以下の点が指摘されました。
- WHERE句で列側に関数を使うと、通常の索引を使いにくくなる
- 日付列は
TO_CHARで文字列化せず、DATE型の範囲条件で比較する方がよい -
NVL(column, value)を条件に使う場合、索引利用やNULLの扱いに注意が必要 - 実行計画では、Predicate Information の
access/filterを確認する - HRスキーマは小さいため実行時間差は小さいが、SQLレビューの観点は確認できる
今回の改善後SQLでは、EMP_NAME_IX を使った INDEX RANGE SCAN に変わり、LAST_NAME LIKE 'S%' が access 条件として表示されました。
このため、Oracle DB Skillsを使うことで、単にSQLを書き換えるだけでなく、実行計画上どこに差分が出たかまで確認できました。
■ その他の問い合わせプロンプト例
⚫︎ SQLチューニング / 実行計画レビュー
skills/performance/explain-plan.md
skills/performance/index-strategy.md
skills/performance/optimizer-stats.md
skills/performance/awr-reports.md
skills/sql-dev/sql-tuning.md
oracle-db-skills-ja の 実行計画 / SQLチューニングの観点で、
必要に応じてAWR利用可否も確認しながら、
次のSQLと実行計画をレビューしてください。
目的:
- 遅い原因を特定したい
- 実行計画の読み方も説明してほしい
- 改善案を優先度順に出してほしい
確認してほしい観点:
- E-Rows と A-Rows の乖離
- Full Scan / Index Scan の妥当性
- Join方式とJoin順序
- Predicate Information の access/filter の違い
- 統計情報、ヒストグラム、バインド変数の問題
- Exadata環境の場合は Smart Scan / Storage Full / Offload の有無
SQL:添付SQLファイル名
実行計画:添付DBMS_XPLAN.DISPLAY_CURSOR の結果ファイル名
出力形式:
1. 結論
2. 遅い原因の候補
3. 実行計画の読み解き
4. 改善SQLまたは索引案
5. 追加で取得すべき情報
・実行計画をこれから取得する場合のプロンプト
oracle-db-skills-ja を使って、次のSQLの実行計画取得方法を教えてください。
可能なら DBMS_XPLAN.DISPLAY_CURSOR を優先し、ALLSTATS LAST / IOSTATS / PEEKED_BINDS を含めた取得SQLをください。
SQL: <ここにSQL文>
⚫︎ SQLcl MCP Server と組み合わせたDB接続プロンプト例
skills/sqlcl/sqlcl-mcp-server.md
※ここでは、別環境や別接続でSQLcl MCP Serverを使う場合の汎用プロンプト例として整理しています。
今回のHRスキーマ確認では、保存済み接続 hr_local を使って接続しています。
oracle-db-skills-ja の SQLcl MCP Server の手順に沿って、
保存済み接続を使ったOracle DB接続確認をしてください。
前提:
- SQLcl MCP Server は設定済みです
- 接続名は `my_connection` です
- read-only の確認だけ行ってください
- DDL / DML / パラメータ変更 / OSコマンド / 外部ファイル出力は実行しないでください
- 実行前に、これから実行するSQLを一覧で提示してください
- AWR / ASH / DBA_HIST_* は使わず、軽量なV$系ビューと基本情報の確認に限定してください
実施してほしいこと:
1. 利用可能な接続一覧を確認
2. `my_connection` に接続
3. DB名、インスタンス名、バージョン、CDB/PDB情報を確認
4. 現在接続しているスキーマを確認
5. 軽量な性能確認SQLを3つだけ提案し、承認後に実行
6. 結果を日本語で要約
出力形式:
- 接続確認結果
- 実行前に提示したSQL
- DB基本情報
- 結果の要約
- 次に実施できる分析案
・ AWR/性能診断デモ用の場合
SQLcl MCP Server 経由でOracleに接続し、oracle-db-skills-ja のAWR分析観点で
軽量な性能確認をしてください。
制約:
- SELECTのみ
- 長時間実行SQLは避ける
- DBA_HIST系を使う場合は対象期間を直近24時間に限定
- 実行前にSQLを提示してください
確認したい項目:
- Top SQL
- 待機イベント
- DB Time / CPU / I/O 傾向
- Exadataの場合は Offload / Smart Scan 関連統計
追加制約:
- Diagnostics Pack の利用可否が未確認の場合、AWR / ASH / DBA_HIST_* / DBMS_WORKLOAD_REPOSITORY は使わないでください
- まず `CONTROL_MANAGEMENT_PACK_ACCESS` の確認SQLを提示してください
- ライセンス確認が必要なSQLと、不要な軽量確認SQLを分けて提示してください
注意:
AWR / ASH / DBA_HIST_* / DBMS_WORKLOAD_REPOSITORY などを利用する場合は、Oracle Diagnostics Pack の利用可否を事前に確認してください。
検証環境であっても、利用可能なライセンスや CONTROL_MANAGEMENT_PACK_ACCESS の設定を確認したうえで実行するのが安全です。
SQLcl MCP Serverを使って実DBに接続する場合は、まず検証用DB・read-onlyユーザー・最小権限で試すのがおすすめです。
本番DBへ直接つなぐのではなく、必要に応じてサニタイズ済みの検証データや読み取り専用レプリカを使い、実行されたSQLを監査できる状態にしておきます。
SQLcl MCP Serverは -mcp で起動した場合、デフォルトでRestrict Level 4になります。
ただし、Restrict Levelだけに頼らず、DB側でもread-only相当の最小権限ユーザーを使うのが安全です。
⚫︎ PostgreSQL/MySQLからOracleへの移行チェック
skills/migrations/
oracle-db-skills-ja の migration-assessment の観点で、
PostgreSQL から Oracle への移行アセスメントをしてください。
対象:
- PostgreSQL バージョン: xx
- Oracle 移行先: Oracle Database 19c / 23ai
- 対象スキーマ: public
- アプリケーション: Java / Python / BI など
確認してほしい観点:
- SERIAL / IDENTITY
- BOOLEAN
- TEXT / BYTEA
- JSON / JSONB
- ARRAY
- ENUM
- ILIKE
- generate_series
- ON CONFLICT
- LIMIT / OFFSET
- 関数、トリガー、ビュー
- 権限、ロール、スキーマ構成
- 移行難易度とリスク
添付ファイル:
sql:ここにDDL、pg_dumpの一部、または調査SQLの結果を貼る
おわりに
今回は、Oracle DB Skills を Codex に導入し、SQLcl 経由で HR スキーマへ接続して、スキーマ確認と SQL 実行計画レビューを試してみました。
これまで Oracle Database の SQL チューニングや実行計画レビューは、経験者が DBMS_XPLAN の結果を見ながら、索引、Join順序、Predicate Information、統計情報などを一つずつ読み解いていく作業でした。もちろん最終判断には今でも人間の理解が必要ですが、Codex に Oracle DB Skills を入れることで、その読み解きの入口を AI と一緒に進められるようになります。
今回の検証では、UPPER / TO_CHAR / NVL を含む SQL を題材に、なぜ列側に関数を使うと索引を使いにくくなるのか、DATE型はどのように範囲条件で書くべきか、Predicate Information の access / filter にどのような違いが出るのかを確認しました。
HR スキーマは小さいため、性能差そのものは大きくありません。しかし、SQLの書き方を変えることで実行計画がどう変わるのか、Oracle DB Skills がどのような観点でレビューしてくれるのかを確認するには、とても良い題材でした。
また、SQLcl の保存済み接続を利用することで、Codex のプロンプトへパスワードを直接書かずに接続できることも確認できました。AIエージェントに実DBを扱わせる場合、便利さだけでなく、最小権限、読み取り専用、実行前承認、ライセンス確認といった安全な使い方がますます重要になると感じます。
AI時代になると、データベースエンジニアの仕事はなくなるのではなく、むしろ AIに何を確認させるか、どの結果を信じるか、どこを深掘りするかを判断する力 がより重要になると思います。
Oracle Database の知識と、Codex のような AI エージェントを組み合わせることで、SQLレビュー、性能分析、構成確認、運用手順の整理をこれまでより速く、そして深く進められる可能性があります。
今回はその第一歩として、Codex + Oracle DB Skills + SQLcl で HR スキーマの実行計画レビューをしてみました。
次は、より実践的なSQLや、統計情報、バインド変数、AWR / ASH、Exadata の Smart Scan / Offload なども含めて、AI と一緒に Oracle Database の性能分析をどこまで進められるか試してみたいと思います。
■ 参考
-
Oracle Skills
・ Oracle Skills 公式リポジトリ
・ Oracle DB Skills 日本語版 -
Oracle Database Skills series
1. AI agents know SQL. Oracle Database Skills teach them Oracle
2. Route, Don’t Flood: Using db/SKILL.md to Steer Oracle‑Aware Assistants
3. Controlled Oracle Actions with SQLcl MCP
4. Managed MCP in Autonomous AI Database: remote, governed tools per database
5. Safer NL2SQL with Select AI and AI Profiles
6. Vector‑native RAG on Oracle: embeddings, HNSW/IVF, and hybrid search under database governance
7. Agent‑safe change delivery on Oracle: discovery, online mechanics, idempotent migrations, and provable rollbacks -
Oracle SQLcl
Oracle SQLclドキュメント・ライブラリ
環境の準備
格納された接続の管理
■ 解説
■ おまけ
※ 本漫画は筆者による非公式の二次創作です。
※ 使用キャラクター:ずんだもん / 四国めたん / 春日部つむぎ
※ キャラクターの権利は各権利元に帰属します。
※ クレジット
- ずんだもん / 四国めたん:東北ずん子・ずんだもんプロジェクト関連ガイドラインに基づいて利用
- 春日部つむぎ:公式利用規約に基づいて利用




