WITH 子句 (Common Table Expression, CTE) 讓複雜查詢模組化、可讀性大增。Oracle 11g+ 支援 **Materialize Hint** 將 CTE 具體化為臨時表,避免重複解析與運算,提升效能。本文涵蓋語法、範例、具體化機制與避坑指南。
WITH
alias_name1 AS (subquery1),
alias_name2 AS (subquery2),
...
alias_nameN AS (subqueryN)
SELECT col1, col2, ...
FROM alias_name1, alias_name2, ..., alias_nameN
WHERE ...;
WITH
q1 AS (SELECT 3 + 5 AS s FROM DUAL),
q2 AS (SELECT 3 * 5 AS m FROM DUAL),
q3 AS (SELECT s, m, s + m AS sum_sm, s * m AS prod_sm FROM q1, q2)
SELECT * FROM q3;
-- 結果: S=8, M=15, SUM_SM=23, PROD_SM=120
| 模式 | 觸發條件 | 行為 | 優點 | 缺點 |
|---|---|---|---|---|
| Inline (預設) | CTE 僅被引用 1 次、或簡單投影 | 優化器將 CTE 文本直接展開到主查詢 | 無額外臨時段開銷 | 多次引用會重複運算 |
| Materialize | 加 Hint /*+ MATERIALIZE */、或引用 ≥ 2 次、或包含非確定性函數 | 建立 **Global Temporary Table** (SYS_TEMP_...) 儲存結果,後續引用直接讀取 | 避免重複運算、支援複雜中間結果重用 | 產生臨時段 I/O、佔用 Temporary Tablespace、可能產生統計資訊缺失導致劣質計畫 |
WITH
sales_agg AS (
SELECT /*+ MATERIALIZE */
product_id, SUM(amount) AS total_amt
FROM sales
WHERE sale_date >= TRUNC(SYSDATE) - 30
GROUP BY product_id
)
SELECT s.product_id, p.name, s.total_amt
FROM sales_agg s
JOIN products p ON p.id = s.product_id
WHERE s.total_amt > 10000
UNION ALL
SELECT s.product_id, p.name, s.total_amt
FROM sales_agg s
JOIN products p ON p.id = s.product_id
WHERE s.total_amt BETWEEN 5000 AND 10000;
sales_agg 只掃描/彙總一次,兩個 UNION ALL 分支直接讀取臨時表。
WITH org_tree (emp_id, mgr_id, lvl, path) AS (
SELECT emp_id, mgr_id, 1, TO_CHAR(emp_id)
FROM employees
WHERE mgr_id IS NULL -- Anchor: 根節點
UNION ALL
SELECT e.emp_id, e.mgr_id, ot.lvl + 1, ot.path || '>' || e.emp_id
FROM employees e
JOIN org_tree ot ON e.mgr_id = ot.emp_id -- Recursive: 子節點
WHERE ot.lvl < 10 -- 防無限遞迴
)
SELECT * FROM org_tree ORDER BY path;
filtered_orders, monthly_sales) 提升可讀性。SYSDATE, DBMS_RANDOM) 用 MATERIALIZE。/*+ OPT_ESTIMATE(TABLE sales_agg ROWS=50000) */ 輔助估算。WHERE lvl < N 或 CYCLE emp_id SET is_cycle TO 1 DEFAULT 0 (12c+) 防循環。MERGE 或 PL/SQL。