-- 投影 + 过滤 + 排序 SELECT emp_id, first_name ||' '|| last_name AS full_name, salary, hire_date FROM employees WHERE dept_id =1 AND salary >5000 ORDERBY salary DESC, hire_date ASC;
-- 去重 SELECTDISTINCT job_id FROM employees;
-- 模糊匹配(% 任意多字符,_ 单个字符) SELECT*FROM employees WHERE last_name LIKE'S%'; SELECT*FROM employees WHERE last_name LIKE'_A%';
-- 范围查询 SELECT*FROM employees WHERE salary BETWEEN5000AND15000; SELECT*FROM employees WHERE dept_id IN (1, 2, 3);
-- 判空(注意:NULL 不能用 = / <>,必须 IS NULL / IS NOT NULL) SELECT*FROM employees WHERE dept_id ISNULL;
2.3 多表连接(JOIN)
Oracle 支持标准 SQL 的 JOIN ... ON(推荐),也支持老旧的逗号连接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
-- 内连接:只保留两边都匹配的行 SELECT e.emp_id, e.last_name, e.salary, d.dept_name FROM employees e JOIN departments d ON e.dept_id = d.dept_id;
-- 左外连接:左表全留,右表没匹配补 NULL SELECT e.emp_id, e.last_name, d.dept_name FROM employees e LEFTJOIN departments d ON e.dept_id = d.dept_id;
-- 右外 / 全外(RIGHT / FULL OUTER JOIN)同理
-- 自连接:员工 + 上级 SELECT e.last_name AS emp, m.last_name AS mgr FROM employees e LEFTJOIN employees m ON e.emp_id = m.emp_id; -- 实际按你的 manager_id 列关联
2.4 分组与聚合(GROUP BY)
1 2 3 4 5 6 7 8 9 10 11
-- 每个部门的人数、平均薪、最高/最低薪 SELECT dept_id, COUNT(*) AS headcount, ROUND(AVG(salary), 2) AS avg_sal, MAX(salary) AS max_sal, MIN(salary) AS min_sal, SUM(salary) AS total_sal FROM employees GROUPBY dept_id HAVINGCOUNT(*) >=5-- HAVING 过滤聚合结果(WHERE 过滤行) ORDERBY headcount DESC;
黄金法则:SELECT 里除聚合函数外的列,必须全部出现在 GROUP BY 中。
2.5 分页(12c+ 标准写法)
1 2 3 4
SELECT emp_id, last_name, salary, hire_date FROM employees ORDERBY salary DESC OFFSET10ROWSFETCH NEXT 10ROWSONLY; -- 第 2 页,每页 10 条
兼容 11g 及更老版本的 ROWNUM 写法:
1 2 3 4 5
SELECT* FROM (SELECT t.*, ROWNUM rn FROM (SELECT emp_id, last_name, salary FROM employees ORDERBY salary DESC) t WHERE ROWNUM <=20) WHERE rn >10;
2.6 更新(UPDATE)
1 2 3 4 5 6 7 8 9 10 11 12
-- 单列更新 UPDATE employees SET salary = salary *1.10, updated_at = SYSTIMESTAMP WHERE dept_id =1;
-- 基于子查询 / 其它表更新(用 MERGE 更直观,见下节) UPDATE employees e SET (salary, updated_at) = ( SELECT e.salary *1.05, SYSTIMESTAMP FROM dual ) WHERE e.dept_id IN (SELECT dept_id FROM departments WHERE location LIKE'Shanghai%');
DELETE vs TRUNCATE:前者是 DML,一行行删,可回滚,会触发行级触发器;后者是 DDL,直接释放段,不可回滚,不触发器。生产删全表 优先 TRUNCATE(但要确认备份)。
2.8 MERGE:一把梭做 UPSERT(存在就更新,不存在就插入)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
MERGEINTO employees t USING ( SELECT100AS emp_id, 'New'AS first_name, 'Name'AS last_name, 'new.name@corp.com'AS email, 'ENGINEER'AS job_id, 12000AS salary, 1AS dept_id FROM dual ) s ON (t.emp_id = s.emp_id) WHEN MATCHED THEN UPDATESET t.first_name = s.first_name, t.last_name = s.last_name, t.email = s.email, t.salary = s.salary, t.dept_id = s.dept_id, t.updated_at = SYSTIMESTAMP WHENNOT MATCHED THEN INSERT (first_name, last_name, email, job_id, salary, dept_id) VALUES (s.first_name, s.last_name, s.email, s.job_id, s.salary, s.dept_id);
3) 视图(View):把复杂查询「虚表化」
3.1 为什么用视图
简化调用:把 JOIN / 聚合 / CASE 复杂 SQL 封成视图,业务代码只 SELECT * FROM v_xxx。
安全:屏蔽敏感列(如 salary 只给经理看);配合权限,做到「列级」授权。
逻辑独立性:基础表重构时,视图对外接口不变,应用层不用改。
3.2 创建视图
1 2 3 4 5 6 7 8 9 10 11 12 13
CREATEOR REPLACE VIEW v_emp_dept AS SELECT e.emp_id, e.first_name ||' '|| e.last_name AS full_name, e.email, e.hire_date, e.salary, d.dept_id, d.dept_name, d.location FROM employees e LEFTJOIN departments d ON e.dept_id = d.dept_id;
COMMENT ONTABLE v_emp_dept IS'员工-部门明细视图';
3.3 只读视图(防误改)
1 2 3 4 5 6
CREATEOR REPLACE VIEW v_emp_public (emp_id, full_name, dept_name, location) AS SELECT e.emp_id, e.first_name||' '||e.last_name, d.dept_name, d.location FROM employees e LEFTJOIN departments d ON e.dept_id = d.dept_id WITH READ ONLY;
加了 WITH READ ONLY,任何 INSERT/UPDATE/DELETE 都会被 Oracle 拒绝。对给报表 / 只读用户用的视图强烈推荐。
CREATE MATERIALIZED VIEW mv_dept_sal_summary BUILD IMMEDIATE REFRESH COMPLETE STARTWITH SYSDATE NEXT SYSDATE +1/24-- 每小时刷新一次 AS SELECT dept_id, COUNT(*) AS headcount, SUM(salary) AS total_sal, AVG(salary) AS avg_sal FROM employees GROUPBY dept_id;
-- 开发账号常用(谨慎!ANY 权限范围很大,生产不要随便给) GRANT CREATE TABLE, CREATEVIEW, CREATE SEQUENCE, CREATEPROCEDURE, CREATETRIGGER, CREATE SYNONYM, CREATE MATERIALIZED VIEW TO app_user;
查看自己的系统权限:
1
SELECT*FROM user_sys_privs;
5.3 对象权限:精确到「谁对什么能干什么」
最常见的是把 HR schema 下的表给业务账号开权限:
1 2 3 4 5 6 7 8 9
-- 以 HR / 表拥有者身份执行(或有 GRANT ANY OBJECT PRIVILEGE) GRANTSELECT, INSERT, UPDATE, DELETEON hr.departments TO app_user; GRANTSELECTON hr.employees TO app_user;
-- 列级授权:只允许改某个字段(UPDATE / INSERT / REFERENCES 支持) GRANTUPDATE(salary, dept_id) ON hr.employees TO app_mgr;
-- 允许被授权者再转授他人(慎用):WITH GRANT OPTION GRANTSELECTON hr.employees TO app_user WITHGRANT OPTION;
查看对象权限:
1 2 3 4 5 6 7 8
-- 我授予别人的 SELECT table_name, privilege, grantee FROM user_tab_privs_made;
-- 别人授予我的 SELECT table_name, owner, privilege FROM user_tab_privs_recd;
-- 当前用户对某列的列级权限 SELECT table_name, column_name, privilege FROM user_col_privs_recd;
回收权限:
1 2
REVOKEDELETEON hr.departments FROM app_user; REVOKEALLON hr.employees FROM app_user;
5.4 角色(Role):把权限打包、治理更干净
当权限一多,「给每个用户重复 GRANT 一遍」是噩梦。角色登场:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
-- 1) 建角色 CREATE ROLE r_app_read; CREATE ROLE r_app_write;
-- 2) 把权限塞给角色 GRANTCREATE SESSION TO r_app_read; GRANTSELECTON hr.departments TO r_app_read; GRANTSELECTON hr.employees TO r_app_read; GRANTSELECTON hr.v_emp_public TO r_app_read;
GRANTSELECT, INSERT, UPDATE, DELETEON hr.departments TO r_app_write;
-- 3) 把角色赋给用户(一条搞定,不用写一堆) GRANT r_app_read TO app_user; GRANT r_app_write TO app_mgr;
-- 4) 设为用户默认登录就启用的角色(可选) ALTERUSER app_user DEFAULT ROLE r_app_read;
-- 我有哪些表 SELECT table_name FROM user_tables ORDERBY table_name;
-- 某表的列(字段名/类型/长度/是否可空) SELECT column_id, column_name, data_type, CASEWHEN data_type IN ('VARCHAR2','CHAR') THEN data_length WHEN data_type ='NUMBER'THEN data_precision||','||data_scale ELSENULLENDAS type_detail, nullable FROM user_tab_columns WHERE table_name ='EMPLOYEES' ORDERBY column_id;
-- 某表的约束 SELECT constraint_name, constraint_type, status, search_condition FROM user_constraints WHERE table_name ='EMPLOYEES'; -- c 类型:P=主键 U=唯一 C=检查/非空 R=外键
-- 外键列对应关系 SELECT c.constraint_name, col.column_name, r_constraint_name AS fk_to, r_col.owner||'.'||r_col.table_name||'('||r_col.column_name||')'AS ref_col FROM user_cons_columns col JOIN user_constraints c ON c.constraint_name = col.constraint_name LEFTJOIN user_cons_columns r_col ON r_col.constraint_name = c.r_constraint_name AND r_col.position = col.position WHERE c.table_name ='EMPLOYEES' AND c.constraint_type ='R';
-- 某对象的定义(视图/过程/函数/触发器源码) SELECT text FROM user_source WHERE name ='V_EMP_DEPT'ORDERBY line;