掌握進階 SQL 查詢技巧,能將複雜業務邏輯推至資料庫層執行,大幅降低應用層資料傳輸與處理負擔。本文涵蓋偽欄位、日期時間函數、集合運算、字串/轉換函數、空值處理、ROLLUP/CUBE 多維彙總、分析函數與層次化查詢,附完整範例。
ROWID:資料列物理位址,最快單列存取路徑。ROWNUM:查詢結果集順序號(自 1 起),常用於分頁 WHERE ROWNUM <= 10。-- 修改欄位長度
ALTER TABLE mytable MODIFY (address VARCHAR2(50));
-- 新增欄位
ALTER TABLE mytable ADD (phone VARCHAR2(20));
-- 刪除欄位
ALTER TABLE mytable DROP COLUMN phone;
-- 截斷表(保留結構,高速清空,不可回滾)
TRUNCATE TABLE mytable;
-- 查看表結構
DESC mytable;
-- 以現有表建立新表(含資料)
CREATE TABLE new_mytable AS SELECT * FROM mytable;
-- 僅複製結構無資料
CREATE TABLE new_mytable AS SELECT * FROM mytable WHERE 1=2;
| 需求 | 函數/語法 | 範例 |
|---|---|---|
| 插入標準日期 | DATE 'YYYY-MM-DD' | INSERT ... VALUES (..., DATE '2007-10-12') |
| 字串轉日期 | TO_DATE(str, fmt) | TO_DATE('2007-10-23 12:36:58','YYYY-MM-DD HH24:MI:SS') |
| 日期轉字串 | TO_CHAR(date, fmt) | TO_CHAR(regdate,'YYYY-MM-DD HH24:MI:SS') |
| 月份最後一天 | LAST_DAY(date) | LAST_DAY(regdate) |
| 加減月份 | ADD_MONTHS(date, n) | ADD_MONTHS(SYSDATE, 3) |
| 四捨五入日期 | ROUND(date, fmt) | ROUND(TO_DATE('2007-06-30'),'YEAR') → 2008-01-01 |
| 截斷日期 | TRUNC(date, fmt) | TRUNC(SYSDATE,'YEAR') → 2024-01-01 |
| 下一個指定週幾 | NEXT_DAY(date, '星期二') | NEXT_DAY(SYSDATE,'星期二') |
| 擷取日期元件 | EXTRACT(part FROM date) | EXTRACT(YEAR FROM SYSDATE) |
INSERT INTO mytable VALUES (7,'dick','tianjin','dick@sdi.com',DATE '2007-10-10');
SAVEPOINT p1;
UPDATE mytable SET name='abck' WHERE id=1;
SAVEPOINT p2;
INSERT INTO mytable VALUES (8,'qing','tianjin','dick@sdi.com',DATE '2008-10-10');
SAVEPOINT p3;
UPDATE mytable SET name='ddddd' WHERE id=3;
SAVEPOINT p4;
-- 回滾至 p2(撤銷 p3、p4 操作)
ROLLBACK TO SAVEPOINT p2;
-- 繼續或 COMMIT
| 運算子 | 行為 | 重複列 |
|---|---|---|
| UNION ALL | 合併兩結果集 | 保留 |
| UNION | 合併並去重 | 移除 |
| INTERSECT | 交集 | 移除 |
| MINUS | 差集(第一組減第二組) | 移除 |
-- 範例:欄位名稱/型別需相容
SELECT product_id, product_type_id, name FROM products
UNION ALL
SELECT prd_id, prd_type_id, name FROM more_products;
SELECT product_id, product_type_id, name FROM products
UNION
SELECT prd_id, prd_type_id, name FROM more_products;
SELECT product_id, product_type_id, name FROM products
INTERSECT
SELECT prd_id, prd_type_id, name FROM more_products;
SELECT product_id, product_type_id, name FROM products
MINUS
SELECT prd_id, prd_type_id, name FROM more_products;
-- 連接運算子
SELECT '商品名稱為:' || name AS info FROM products;
-- TRANSLATE 字元級替換
SELECT name, TRANSLATE(name, 'spring', 'SPRING') FROM products;
-- LPAD 左填充 (字串, 總長度, 填充字元)
SELECT LPAD('function', 10, '=') FROM dual; -- ==function
-- RPAD 右填充
SELECT RPAD('function', 10, '=') FROM dual; -- function==
DECODE(expr, search1, result1, ..., default) 類似 CASE WHEN 的簡潔寫法。
-- 基本用法
SELECT DECODE(1, 1, 2, 3) FROM dual; -- 回傳 2
-- 多條件
SELECT prd_id, available,
DECODE(available, 'Y', '產品可用', 'N', '產品不可用', '未知狀態')
FROM more_products;
-- 多搜尋值
SELECT product_id, product_type_id,
DECODE(product_type_id,
1, '書本',
2, '視頻',
3, 'DVD',
4, 'CD',
'雜志')
FROM products;
| 函數 | 語法 | 邏輯 | 範例結果 |
|---|---|---|---|
| NVL | NVL(expr1, expr2) | expr1 不為 NULL 回傳 expr1,否則 expr2 | NVL(regdate, SYSDATE) |
| NVL2 | NVL2(expr1, expr2, expr3) | expr1 不為 NULL 回傳 expr2,否則 expr3 | NVL2(id, name, email) |
| NULLIF | NULLIF(expr1, expr2) | expr1=expr2 回傳 NULL,否則 expr1 | NULLIF(name, email) |
-- 基本群組
SELECT division_id, SUM(salary) FROM employees2 GROUP BY division_id;
-- ROLLUP:單欄位產生小計 + 總計
SELECT division_id, SUM(salary)
FROM employees2
GROUP BY ROLLUP(division_id);
-- ROLLUP:多欄位階層小計
SELECT division_id, job_id, SUM(salary)
FROM employees2
GROUP BY ROLLUP(division_id, job_id);
-- CUBE:所有維度組合小計 + 總計
SELECT division_id, job_id, SUM(salary)
FROM employees2
GROUP BY CUBE(division_id, job_id);
差異:ROLLUP 產生 n+1 層級(階層),CUBE 產生 2^n 組合(全交叉)。
語法:函數() OVER (PARTITION BY 分組欄位 ORDER BY 排序欄位)
-- 全表薪資排名(相同薪資仍給不同序號)
SELECT employee_id, division_id, job_id, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn
FROM employees2;
-- 依職務分組排名
SELECT employee_id, division_id, job_id, salary,
ROW_NUMBER() OVER (PARTITION BY job_id ORDER BY salary DESC) AS rn
FROM employees2;
SELECT employee_id, division_id, job_id, salary,
RANK() OVER (PARTITION BY division_id ORDER BY salary DESC) AS rk
FROM employees2;
SELECT d.dname, e.ename, e.sal,
DENSE_RANK() OVER (PARTITION BY e.deptno ORDER BY e.sal DESC) AS dense_rk
FROM emp e, dept d
WHERE e.deptno = d.deptno;
Oracle 專有 START WITH ... CONNECT BY PRIOR 語法支援遞迴查詢。
SELECT employee_id, manager_id, empname, title, salary
FROM more_employees
START WITH employee_id = 1
CONNECT BY PRIOR employee_id = manager_id;
START WITH:指定根節點條件。CONNECT BY PRIOR:定義父子關聯,PRIOR 修飾父欄位。SELECT LEVEL, employee_id, manager_id, empname, title, salary
FROM more_employees
START WITH employee_id = 1
CONNECT BY PRIOR employee_id = manager_id
ORDER BY LEVEL;
LEVEL:根節點=1,子節點=2,以此類推。
SELECT LEVEL,
LPAD(' ', 2*(LEVEL-1)) || empname AS employee_name
FROM more_employees
START WITH employee_id = 1
CONNECT BY PRIOR employee_id = manager_id;
TO_CHAR/TO_DATE/ADD_MONTHS 處理日期,避免隱性轉換導致索引失效。NVL/NVL2/NULLIF 精準控制空值語意,減少應用層判斷。ROLLUP/CUBE,避免多次掃描表。LEVEL 與 SYS_CONNECT_BY_PATH 可輸出完整路徑。