【Oracle】DUAL 表の使い方完全ガイド|仕組み・FAST DUAL 最適化・PL/SQL での代替・他 RDBMS 比較・実務パターンまで解説

【Oracle】DUAL 表の使い方完全ガイド|仕組み・FAST DUAL 最適化・PL/SQL での代替・他 RDBMS 比較・実務パターンまで解説 Oracle

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 表とは

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 では 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 行を返すだけの最適化を行います。そのため 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 内で 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 は絶対に実行しない
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 := SYSDATEv := USERv := 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 ユーザーで以下を実行して復旧できます。
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 (…)