Oracle で「SELECT SYSDATE FROM DUAL」のように使われる DUAL 表は、Oracle 固有の特別なテーブルです。計算結果の確認、システム関数の実行、シーケンスの取得など、テーブルを参照する必要がないのに FROM 句が必須な場面で使います。
しかし「DUAL の中身はどうなっているのか」「PL/SQL でも DUAL は必要なのか」「MySQL のように FROM を省略できないのか」といった疑問もあります。
本記事では、DUAL 表の内部構造と FAST DUAL 最適化の仕組み、基本的な使い方、PL/SQL での代替、他 RDBMS との比較、そして連番生成・日付計算・定数行生成の実務パターンまで解説します。
この記事でわかること
・DUAL 表とは何か(1 行 1 列の構造・DUMMY 列)
・FAST DUAL 最適化(Oracle が実際にはテーブルにアクセスしない仕組み)
・DUAL を使った計算・日付取得・シーケンス取得・文字列操作
・PL/SQL では DUAL が不要なケース
・他 RDBMS との比較(FROM 句省略の可否)
・Oracle 23c の FROM 句省略機能
・連番生成・日付一覧・定数行生成の実務パターン
・DUAL 表とは何か(1 行 1 列の構造・DUMMY 列)
・FAST DUAL 最適化(Oracle が実際にはテーブルにアクセスしない仕組み)
・DUAL を使った計算・日付取得・シーケンス取得・文字列操作
・PL/SQL では DUAL が不要なケース
・他 RDBMS との比較(FROM 句省略の可否)
・Oracle 23c の FROM 句省略機能
・連番生成・日付一覧・定数行生成の実務パターン
DUAL 表とは
SQL(DUAL の中身を確認)
-- DUAL は 1 行 1 列のテーブル SELECT * FROM DUAL; -- 結果: -- DUMMY -- ----- -- X -- 列の情報 DESC DUAL; -- Name Null? Type -- DUMMY VARCHAR2(1)
| 項目 | 内容 |
|---|---|
| 所有者 | SYS スキーマ |
| アクセス | PUBLIC シノニム経由で全ユーザーがアクセス可能 |
| 列 | DUMMY(VARCHAR2(1))1 列のみ |
| 行 | 1 行のみ(値は ‘X’) |
| 用途 | 計算結果やシステム関数の値を取得するための「台座」 |
なぜ DUAL が必要なのか
Oracle の SQL では
Oracle の SQL では
SELECT 文に FROM 句が必須です。SELECT 1 + 1 だけでは構文エラーになります(Oracle 23c 未満)。テーブルを参照する必要がないのに FROM が必要な場面で、「確実に 1 行だけ返すテーブル」として DUAL が用意されています。FAST DUAL 最適化
「DUAL はテーブルだから毎回ディスクにアクセスするのでは?」と心配する必要はありません。Oracle のオプティマイザには FAST DUAL という専用の最適化が組み込まれており、SELECT ... FROM DUAL は実際にはテーブルにアクセスしません。
SQL(実行計画で FAST DUAL を確認)
EXPLAIN PLAN FOR SELECT SYSDATE FROM DUAL; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY); -- 実行計画: -- | Id | Operation | Name | -- |----|------------------|------| -- | 0 | SELECT STATEMENT | | -- | 1 | FAST DUAL | | <-- テーブルスキャンではない
FAST DUAL = 物理的な I/O なし
実行計画に「TABLE ACCESS FULL」ではなく「FAST DUAL」と表示されます。Oracle はテーブルの実体にアクセスせず、内部的に 1 行を返すだけの最適化を行います。そのため
実行計画に「TABLE ACCESS FULL」ではなく「FAST DUAL」と表示されます。Oracle はテーブルの実体にアクセスせず、内部的に 1 行を返すだけの最適化を行います。そのため
SELECT ... FROM DUAL のパフォーマンスは事実上ゼロコストです。DUAL 表の基本的な使い方
算術計算
SQL(計算)
SELECT 100 + 200 AS result FROM DUAL; -- 300 SELECT 1000 * 1.08 AS tax_incl FROM DUAL; -- 1080 SELECT POWER(2, 10) AS result FROM DUAL; -- 1024 SELECT MOD(17, 5) AS remainder FROM DUAL; -- 2 SELECT ROUND(3.14159, 2) AS pi FROM DUAL; -- 3.14
日付・時刻の取得
SQL(日付・時刻)
SELECT SYSDATE FROM DUAL; -- 現在日時 SELECT SYSTIMESTAMP FROM DUAL; -- ミリ秒+タイムゾーン付き SELECT CURRENT_DATE FROM DUAL; -- セッションタイムゾーンの現在日時 -- 日付計算 SELECT SYSDATE + 7 AS one_week_later FROM DUAL; -- 1 週間後 SELECT SYSDATE - 30 AS thirty_days_ago FROM DUAL; -- 30 日前 SELECT ADD_MONTHS(SYSDATE, 3) AS three_months_later FROM DUAL; SELECT LAST_DAY(SYSDATE) AS month_end FROM DUAL; -- 今月末
文字列操作
SQL(文字列操作)
SELECT UPPER('hello') AS result FROM DUAL; -- HELLO
SELECT SUBSTR('Oracle Database', 1, 6) FROM DUAL; -- Oracle
SELECT LENGTH('ABCDE') AS len FROM DUAL; -- 5
SELECT REPLACE('2026-03-28', '-', '/') FROM DUAL; -- 2026/03/28
SELECT LPAD(42, 5, '0') AS padded FROM DUAL; -- 00042
ユーザー・セッション情報
SQL(ユーザー・セッション情報)
SELECT USER FROM DUAL; -- 現在のユーザー名
SELECT SYS_CONTEXT('USERENV', 'SESSION_USER') FROM DUAL;
SELECT SYS_CONTEXT('USERENV', 'IP_ADDRESS') FROM DUAL;
SELECT SYS_CONTEXT('USERENV', 'HOST') FROM DUAL;
SELECT SYS_CONTEXT('USERENV', 'DB_NAME') FROM DUAL;
シーケンスの取得
SQL(シーケンス)
-- 次の値を取得(NEXTVAL) SELECT seq_order_id.NEXTVAL FROM DUAL; -- 現在の値を確認(CURRVAL: 同セッションで NEXTVAL を実行済みの場合のみ) SELECT seq_order_id.CURRVAL FROM DUAL;
型変換の確認
SQL(型変換のテスト)
-- TO_CHAR: 日付を文字列に変換
SELECT TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') FROM DUAL;
-- TO_DATE: 文字列を日付に変換
SELECT TO_DATE('2026-03-28', 'YYYY-MM-DD') FROM DUAL;
-- TO_NUMBER: 文字列を数値に変換
SELECT TO_NUMBER('12345') FROM DUAL;
PL/SQL では DUAL が不要なケース
PL/SQL では多くの場面で DUAL を使わずに値を取得できます。SELECT ... INTO ... FROM DUAL と書くよりも直接代入する方がシンプルで高速です。
SQL(PL/SQL での DUAL 不要パターン)
DECLARE
v_now DATE;
v_user VARCHAR2(30);
v_calc NUMBER;
BEGIN
-- NG: DUAL 経由(不必要なSQL実行)
-- SELECT SYSDATE INTO v_now FROM DUAL;
-- SELECT USER INTO v_user FROM DUAL;
-- OK: 直接代入(DUAL 不要、高速)
v_now := SYSDATE;
v_user := USER;
v_calc := 100 * 1.08;
DBMS_OUTPUT.PUT_LINE(v_now || ' / ' || v_user || ' / ' || v_calc);
END;
/
| 操作 | DUAL が必要 | DUAL 不要(直接代入) |
|---|---|---|
| SYSDATE の取得 | SELECT SYSDATE INTO v FROM DUAL | v := SYSDATE |
| USER の取得 | SELECT USER INTO v FROM DUAL | v := USER |
| 算術計算 | SELECT 100*1.08 INTO v FROM DUAL | v := 100 * 1.08 |
| シーケンス NEXTVAL | SELECT seq.NEXTVAL INTO v FROM DUAL(必要) | 12c+: v := seq.NEXTVAL |
PL/SQL ではシーケンスも DUAL 不要(12c 以降)
Oracle 12c 以降では、PL/SQL 内で
Oracle 12c 以降では、PL/SQL 内で
v_id := seq_name.NEXTVAL; と直接代入できます。11g 以前では SELECT seq_name.NEXTVAL INTO v_id FROM DUAL が必要です。他 RDBMS との比較
| RDBMS | FROM DUAL の必要性 | 書き方 |
|---|---|---|
| Oracle (23c 未満) | 必須 | SELECT 1+1 FROM DUAL |
| Oracle 23c | 省略可能(新機能) | SELECT 1+1 |
| MySQL | 省略可能 | SELECT 1+1 / SELECT 1+1 FROM DUAL(どちらも可) |
| PostgreSQL | 省略可能 | SELECT 1+1 |
| SQL Server | 省略可能 | SELECT 1+1 |
| SQLite | 省略可能 | SELECT 1+1 |
Oracle 23c: FROM 句の省略
SQL(Oracle 23c の FROM 省略)
-- Oracle 23c 以降: FROM DUAL を省略可能 SELECT 1 + 1; -- OK(23c 以降) SELECT SYSDATE; -- OK(23c 以降) SELECT USER; -- OK(23c 以降) -- 23c 未満では上記は ORA-00923 エラー -- 23c 未満: SELECT 1 + 1 FROM DUAL; と書く必要がある
DUAL に対する DML は禁止
DUAL はシステムテーブルであり、INSERT / UPDATE / DELETE を実行してはいけません。万が一 DUAL の行を削除すると、データベース全体で SELECT ... FROM DUAL が失敗し、深刻な障害につながります。
DUAL への DML は絶対に実行しない
・
・
・万が一 DUAL を壊してしまった場合:
・
DELETE FROM DUAL → DUAL が空になり、SELECT ... FROM DUAL が「no rows」になる・
INSERT INTO DUAL VALUES ('Y') → DUAL が 2 行になり、SELECT ... FROM DUAL が 2 行返って予期しない動作を引き起こす・万が一 DUAL を壊してしまった場合:
DELETE FROM SYS.DUAL; INSERT INTO SYS.DUAL VALUES ('X'); COMMIT; で復旧可能(SYS ユーザーで実行)実務パターン集
パターン(1): 連番(数値リスト)を生成する
SQL(CONNECT BY LEVEL で連番生成)
-- 1 から 10 までの連番を生成 SELECT LEVEL AS num FROM DUAL CONNECT BY LEVEL <= 10; -- 指定範囲(2025年1月〜12月)の月初日一覧 SELECT ADD_MONTHS(DATE '2025-01-01', LEVEL - 1) AS month_start FROM DUAL CONNECT BY LEVEL <= 12; -- アルファベット A〜Z を生成 SELECT CHR(64 + LEVEL) AS letter FROM DUAL CONNECT BY LEVEL <= 26;
パターン(2): 日付の範囲一覧を生成
SQL(日付一覧の生成)
-- 2026-03-01 から 2026-03-31 までの日付一覧 SELECT DATE '2026-03-01' + LEVEL - 1 AS cal_date FROM DUAL CONNECT BY LEVEL <= DATE '2026-03-31' - DATE '2026-03-01' + 1; -- 今月の全日付 SELECT TRUNC(SYSDATE, 'MM') + LEVEL - 1 AS cal_date FROM DUAL CONNECT BY LEVEL <= LAST_DAY(SYSDATE) - TRUNC(SYSDATE, 'MM') + 1;
パターン(3): 複数の定数行を生成(UNION ALL の代替)
SQL(定数行の生成)
-- UNION ALL で定数行を作る従来の方法 SELECT 'Active' AS status FROM DUAL UNION ALL SELECT 'Inactive' FROM DUAL UNION ALL SELECT 'Pending' FROM DUAL; -- サンプルデータの生成 SELECT 1 AS id, 'Alice' AS name, 8000 AS salary FROM DUAL UNION ALL SELECT 2, 'Bob', 6000 FROM DUAL UNION ALL SELECT 3, 'Carol', 7500 FROM DUAL;
パターン(4): 関数の動作確認・デバッグ
SQL(関数のテスト)
-- TO_CHAR の書式テスト
SELECT TO_CHAR(SYSDATE, 'YYYY/MM/DD HH24:MI:SS') FROM DUAL;
-- 正規表現のテスト
SELECT REGEXP_SUBSTR('abc-123-def', '[0-9]+') AS result FROM DUAL; -- 123
-- CASE 式のテスト
SELECT CASE WHEN 1 = 1 THEN 'TRUE' ELSE 'FALSE' END AS result FROM DUAL;
-- NVL / COALESCE のテスト
SELECT NVL(NULL, 'default') AS result FROM DUAL; -- default
パターン(5): 存在チェック(高速な行数確認)
SQL(DUAL + EXISTS で存在チェック)
-- テーブルに条件に合う行が 1 件でもあるか高速に確認
SELECT 1 FROM DUAL
WHERE EXISTS (
SELECT 1 FROM orders WHERE status = 'PENDING'
);
-- 1 行返れば存在、0 行なら不在
-- PL/SQL での活用
DECLARE
v_exists NUMBER;
BEGIN
SELECT COUNT(*) INTO v_exists FROM DUAL
WHERE EXISTS (SELECT 1 FROM orders WHERE status = 'PENDING');
IF v_exists = 1 THEN
DBMS_OUTPUT.PUT_LINE('Pending orders exist');
END IF;
END;
/
よくある質問
QDUAL の中身は何ですか?
ADUAL は 1 行 1 列のテーブルで、列名は
DUMMY(VARCHAR2(1))、値は 'X' です。SYS スキーマが所有し、PUBLIC シノニム経由で全ユーザーがアクセスできます。ただし、Oracle は FAST DUAL 最適化により実際にはテーブルにアクセスしないため、中身を意識する必要はほとんどありません。QSELECT … FROM DUAL は遅くないですか?
A遅くありません。Oracle は FAST DUAL 最適化により、DUAL を使った SELECT では物理的なテーブルアクセスが発生しません。実行計画を確認すると「TABLE ACCESS FULL」ではなく「FAST DUAL」と表示されます。パフォーマンス上のコストは事実上ゼロです。
QPL/SQL でも DUAL は必要ですか?
A多くの場合不要です。
v := SYSDATE、v := USER、v := 100 * 1.08 のように直接代入できます。ただしシーケンス NEXTVAL は 11g 以前では SELECT seq.NEXTVAL INTO v FROM DUAL が必要でした。12c 以降では v := seq.NEXTVAL と直接代入可能です。QMySQL のように FROM を省略できますか?
AOracle 23c(2023年リリース)から
SELECT 1+1 のようにFROM 句を省略できるようになりました。23c 未満では FROM DUAL が必須です。他の RDBMS(MySQL, PostgreSQL, SQL Server, SQLite)では以前から FROM 省略が可能です。QDUAL を DELETE してしまいました。どう復旧しますか?
ASYS ユーザーで以下を実行して復旧できます。
2 行以上になっている場合は、まず
INSERT INTO SYS.DUAL VALUES ('X');COMMIT;2 行以上になっている場合は、まず
DELETE FROM SYS.DUAL で全行削除してから上記の INSERT を実行してください。DUAL の破損はデータベース全体に影響するため、本番環境では細心の注意が必要です。QCONNECT BY LEVEL で大量の連番を生成すると遅いですか?
A数万行程度なら問題ありませんが、数十万行以上になるとメモリ使用量が増加します。大量連番が必要な場合は XMLTable や再帰 CTE(WITH RECURSIVE、Oracle 11gR2 以降)も検討してください。通常の実務(月次日付一覧、連番 1-100 程度)では CONNECT BY LEVEL で十分高速です。
まとめ
DUAL 表の要点をまとめます。
| やりたいこと | 書き方 |
|---|---|
| 算術計算の結果を確認 | SELECT 100 * 1.08 FROM DUAL |
| 現在日時を取得 | SELECT SYSDATE FROM DUAL |
| ユーザー名を取得 | SELECT USER FROM DUAL |
| シーケンスの次の値を取得 | SELECT seq.NEXTVAL FROM DUAL |
| 関数の動作確認・テスト | SELECT function(…) FROM DUAL |
| 1〜N の連番を生成 | SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= N |
| 月の日付一覧を生成 | SELECT TRUNC(SYSDATE,’MM’) + LEVEL – 1 FROM DUAL CONNECT BY … |
| 定数行(サンプルデータ)を生成 | SELECT 1, ‘A’ FROM DUAL UNION ALL SELECT 2, ‘B’ FROM DUAL |
| 存在チェック | SELECT 1 FROM DUAL WHERE EXISTS (…) |
