@gordon.gan/specflow 1.4.0-beta → 1.4.2-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/prompts/approval/database-guidance.md +79 -0
  4. package/prompts/approval/generate.md +600 -290
  5. package/skills/database/LICENSE +405 -0
  6. package/skills/database/ORIGIN.md +6 -0
  7. package/skills/database/README.md +30 -0
  8. package/skills/database/elasticsearch/LICENSE.txt +202 -0
  9. package/skills/database/elasticsearch/SKILL.md +199 -0
  10. package/skills/database/elasticsearch/examples/01-fulltext-search.md +215 -0
  11. package/skills/database/elasticsearch/examples/02-aggregation-report.md +206 -0
  12. package/skills/database/elasticsearch/examples/03-reindex-zero-downtime.md +200 -0
  13. package/skills/database/elasticsearch/examples/04-cluster-monitoring.md +204 -0
  14. package/skills/database/elasticsearch/references/01-query-dsl-fulltext.md +162 -0
  15. package/skills/database/elasticsearch/references/02-query-dsl-term.md +210 -0
  16. package/skills/database/elasticsearch/references/03-aggregations-metric.md +161 -0
  17. package/skills/database/elasticsearch/references/04-aggregations-bucket.md +236 -0
  18. package/skills/database/elasticsearch/references/05-mapping-types.md +134 -0
  19. package/skills/database/elasticsearch/references/06-analyzers.md +187 -0
  20. package/skills/database/elasticsearch/references/07-cluster-ops.md +225 -0
  21. package/skills/database/elasticsearch/references/08-elk-integration.md +170 -0
  22. package/skills/database/mysql/SKILL.md +195 -0
  23. package/skills/database/mysql/examples/01-connection-pool.md +75 -0
  24. package/skills/database/mysql/examples/02-slow-query-optimization.md +98 -0
  25. package/skills/database/mysql/examples/03-master-slave-setup.md +144 -0
  26. package/skills/database/mysql/examples/04-backup-strategy.md +212 -0
  27. package/skills/database/mysql/references/01-functions-string.md +103 -0
  28. package/skills/database/mysql/references/02-functions-date.md +152 -0
  29. package/skills/database/mysql/references/03-functions-aggregate-window.md +167 -0
  30. package/skills/database/mysql/references/04-functions-json.md +129 -0
  31. package/skills/database/mysql/references/05-sql-ddl-types.md +235 -0
  32. package/skills/database/mysql/references/06-index-optimization.md +232 -0
  33. package/skills/database/mysql/references/07-replication-ha.md +213 -0
  34. package/skills/database/mysql/references/08-backup-restore.md +207 -0
  35. package/skills/database/mysql/references/09-advanced-features.md +345 -0
  36. package/skills/database/oracle/LICENSE.txt +202 -0
  37. package/skills/database/oracle/SKILL.md +238 -0
  38. package/skills/database/oracle/examples/01-plsql-procedure.md +90 -0
  39. package/skills/database/oracle/examples/02-awr-analysis.md +99 -0
  40. package/skills/database/oracle/examples/03-rman-backup.md +108 -0
  41. package/skills/database/oracle/examples/04-dataguard-setup.md +146 -0
  42. package/skills/database/oracle/references/01-functions-string.md +91 -0
  43. package/skills/database/oracle/references/02-functions-date.md +71 -0
  44. package/skills/database/oracle/references/03-analytic-functions.md +103 -0
  45. package/skills/database/oracle/references/04-plsql-guide.md +303 -0
  46. package/skills/database/oracle/references/05-performance-tuning.md +164 -0
  47. package/skills/database/oracle/references/06-backup-recovery.md +115 -0
  48. package/skills/database/oracle/references/07-dataguard-rac.md +76 -0
  49. package/skills/database/oracle/references/08-security.md +170 -0
  50. package/skills/database/oracle/references/09-sql-syntax.md +152 -0
  51. package/skills/database/oracle/references/10-features.md +174 -0
  52. package/skills/database/postgresql/LICENSE.txt +202 -0
  53. package/skills/database/postgresql/SKILL.md +182 -0
  54. package/skills/database/postgresql/examples/.gitkeep +0 -0
  55. package/skills/database/postgresql/examples/01-jsonb-query.md +72 -0
  56. package/skills/database/postgresql/examples/02-cte-recursive.md +110 -0
  57. package/skills/database/postgresql/examples/03-performance-tuning.md +114 -0
  58. package/skills/database/postgresql/examples/04-streaming-replication.md +113 -0
  59. package/skills/database/postgresql/references/.gitkeep +0 -0
  60. package/skills/database/postgresql/references/01-functions-string.md +174 -0
  61. package/skills/database/postgresql/references/02-functions-datetime.md +54 -0
  62. package/skills/database/postgresql/references/03-functions-aggregate-window.md +142 -0
  63. package/skills/database/postgresql/references/04-functions-jsonb.md +117 -0
  64. package/skills/database/postgresql/references/05-fulltext-search.md +109 -0
  65. package/skills/database/postgresql/references/06-index-types.md +95 -0
  66. package/skills/database/postgresql/references/07-partition-fdw.md +133 -0
  67. package/skills/database/postgresql/references/08-replication-backup.md +215 -0
  68. package/skills/database/redis/LICENSE.txt +202 -0
  69. package/skills/database/redis/SKILL.md +922 -0
  70. package/skills/database/redis/examples/01-cache-usage.md +104 -0
  71. package/skills/database/redis/examples/02-session-storage.md +72 -0
  72. package/skills/database/redis/examples/03-leaderboard.md +63 -0
  73. package/skills/database/redis/examples/04-redis-cluster-setup.md +70 -0
  74. package/skills/database/redis/examples/05-stream-queue.md +65 -0
  75. package/skills/database/redis/references/command-quick-ref.md +180 -0
  76. package/skills/database/redis/references/commands-admin-key.md +413 -0
  77. package/skills/database/redis/references/commands-set-sorted-advanced.md +539 -0
  78. package/skills/database/redis/references/commands-string-hash-list.md +458 -0
  79. package/skills/database/redis/references/memory-optimization.md +150 -0
  80. package/skills/database/redis/references/redis-conf-production.md +139 -0
  81. package/skills/specflow-approval/SKILL.md +102 -181
  82. package/templates/approval.md +321 -221
@@ -0,0 +1,91 @@
1
+ # 字符串 / 数字 / NULL 处理函数
2
+
3
+ ## 字符串函数
4
+
5
+ ```sql
6
+ -- CONCAT / || — 字符串连接
7
+ SELECT 'Hello' || ' ' || 'World' FROM DUAL; -- Hello World (推荐 ||)
8
+
9
+ -- SUBSTR — 截取子串
10
+ SELECT SUBSTR('Oracle Database', 1, 6) FROM DUAL; -- Oracle
11
+ SELECT SUBSTR('Oracle Database', 8) FROM DUAL; -- Database
12
+
13
+ -- INSTR — 查找子串位置
14
+ SELECT INSTR('oracleoracle', 'oracle') FROM DUAL; -- 1
15
+ SELECT INSTR('oracleoracle', 'oracle', 1, 2) FROM DUAL; -- 7(第 2 次出现)
16
+
17
+ -- LPAD / RPAD — 左/右填充
18
+ SELECT LPAD('123', 10, '*') FROM DUAL; -- *******123
19
+ SELECT RPAD('Oracle', 10, '-.-') FROM DUAL; -- Oracle-.-.-
20
+
21
+ -- TRIM / LTRIM / RTRIM — 去除空格/字符
22
+ SELECT TRIM(' Hello ') FROM DUAL; -- Hello
23
+ SELECT LTRIM('xxxHello', 'x') FROM DUAL; -- Hello
24
+ SELECT RTRIM('Hello...', '.') FROM DUAL; -- Hello
25
+
26
+ -- REPLACE — 替换子串
27
+ SELECT REPLACE('Oracle Database 19c', '19c', '23c') FROM DUAL; -- Oracle Database 23c
28
+
29
+ -- TRANSLATE — 字符级替换
30
+ SELECT TRANSLATE('12345', '123', 'abc') FROM DUAL; -- abc45
31
+
32
+ -- 正则表达式系列
33
+ -- REGEXP_LIKE — 正则匹配
34
+ SELECT * FROM employees WHERE REGEXP_LIKE(email, '^[A-Z]');
35
+ -- REGEXP_SUBSTR — 正则提取子串
36
+ SELECT REGEXP_SUBSTR('contact@oracle.com', '@[^.]+\\.com') FROM DUAL;
37
+ -- REGEXP_REPLACE — 正则替换(手机号脱敏)
38
+ SELECT REGEXP_REPLACE('13812345678', '(\d{3})\d{4}(\d{4})', '\1****\2') FROM DUAL;
39
+ -- REGEXP_INSTR — 正则查找位置
40
+ SELECT REGEXP_INSTR('Hello World', '[aeiou]') FROM DUAL; -- 2
41
+ ```
42
+
43
+ ## 数字函数
44
+
45
+ ```sql
46
+ -- ROUND — 四舍五入
47
+ SELECT ROUND(123.4567) FROM DUAL; -- 123
48
+ SELECT ROUND(123.4567, 2) FROM DUAL; -- 123.46
49
+ SELECT ROUND(123.4567, -2) FROM DUAL; -- 100
50
+
51
+ -- TRUNC — 截断(不四舍五入)
52
+ SELECT TRUNC(123.4567) FROM DUAL; -- 123
53
+ SELECT TRUNC(123.4567, 2) FROM DUAL; -- 123.45
54
+
55
+ -- MOD — 取模
56
+ SELECT MOD(10, 3) FROM DUAL; -- 1
57
+
58
+ -- ABS / CEIL / FLOOR
59
+ SELECT ABS(-10) FROM DUAL; -- 10
60
+ SELECT CEIL(3.14) FROM DUAL; -- 4
61
+ SELECT FLOOR(3.14) FROM DUAL; -- 3
62
+
63
+ -- POWER / SQRT
64
+ SELECT POWER(2, 10) FROM DUAL; -- 1024
65
+ SELECT SQRT(144) FROM DUAL; -- 12
66
+
67
+ -- GREATEST / LEAST
68
+ SELECT GREATEST(10, 20, 5, 30) FROM DUAL; -- 30
69
+ SELECT LEAST(10, 20, 5, 30) FROM DUAL; -- 5
70
+ ```
71
+
72
+ ## NULL 处理函数
73
+
74
+ ```sql
75
+ -- NVL — 空值替换(2 个参数)
76
+ SELECT last_name, NVL(commission_pct, 0) AS commission FROM employees;
77
+
78
+ -- NVL2 — 空值条件(3 个参数)
79
+ SELECT last_name, NVL2(commission_pct, '有佣金', '无佣金') AS status FROM employees;
80
+
81
+ -- COALESCE — 返回第一个非空值(可变参数)
82
+ SELECT COALESCE(phone_number, email, '无联系方式') AS contact FROM employees;
83
+
84
+ -- NULLIF — 两值相等返回 NULL
85
+ SELECT NULLIF('A', 'B') FROM DUAL; -- 'A'
86
+ SELECT NULLIF('A', 'A') FROM DUAL; -- NULL
87
+
88
+ -- LNNVL — 反转条件结果(对 NULL 敏感)
89
+ SELECT * FROM employees WHERE LNNVL(commission_pct > 0.2);
90
+ -- 等价于: WHERE commission_pct IS NULL OR commission_pct <= 0.2
91
+ ```
@@ -0,0 +1,71 @@
1
+ # 日期 / 转换函数
2
+
3
+ ## 日期函数
4
+
5
+ ```sql
6
+ -- 当前日期时间
7
+ SELECT SYSDATE FROM DUAL; -- 当前系统日期时间
8
+ SELECT CURRENT_DATE FROM DUAL; -- 当前会话时区日期时间
9
+ SELECT SYSTIMESTAMP FROM DUAL; -- 当前系统时间戳(带时区)
10
+
11
+ -- EXTRACT — 提取日期部分
12
+ SELECT EXTRACT(YEAR FROM SYSDATE) FROM DUAL;
13
+ SELECT EXTRACT(MONTH FROM SYSDATE) FROM DUAL;
14
+ SELECT EXTRACT(DAY FROM SYSDATE) FROM DUAL;
15
+ SELECT EXTRACT(HOUR FROM SYSTIMESTAMP) FROM DUAL;
16
+
17
+ -- TO_DATE — 字符串转日期
18
+ SELECT TO_DATE('2024-01-15', 'YYYY-MM-DD') FROM DUAL;
19
+ SELECT TO_DATE('2024/01/15 14:30:00', 'YYYY/MM/DD HH24:MI:SS') FROM DUAL;
20
+ SELECT TO_DATE('15-JAN-24', 'DD-MON-YY') FROM DUAL;
21
+
22
+ -- TO_CHAR — 日期格式化
23
+ SELECT TO_CHAR(SYSDATE, 'YYYY-MM-DD') FROM DUAL;
24
+ SELECT TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') FROM DUAL;
25
+ SELECT TO_CHAR(SYSDATE, 'MONTH DD, YYYY') FROM DUAL;
26
+ SELECT TO_CHAR(SYSDATE, 'Dy') FROM DUAL; -- 星期缩写
27
+
28
+ -- ADD_MONTHS — 加减月份
29
+ SELECT ADD_MONTHS(SYSDATE, 3) FROM DUAL;
30
+ SELECT ADD_MONTHS(SYSDATE, -6) FROM DUAL;
31
+
32
+ -- MONTHS_BETWEEN — 月份间隔
33
+ SELECT MONTHS_BETWEEN(DATE '2024-12-31', DATE '2024-01-01') FROM DUAL;
34
+
35
+ -- LAST_DAY — 月末日期
36
+ SELECT LAST_DAY(SYSDATE) FROM DUAL;
37
+
38
+ -- NEXT_DAY — 下一个星期几
39
+ SELECT NEXT_DAY(SYSDATE, 'FRIDAY') FROM DUAL;
40
+
41
+ -- TRUNC 日期版 — 截断到指定精度
42
+ SELECT TRUNC(SYSDATE) FROM DUAL; -- 当天 00:00:00
43
+ SELECT TRUNC(SYSDATE, 'MONTH') FROM DUAL; -- 当月第一天
44
+ SELECT TRUNC(SYSDATE, 'YEAR') FROM DUAL; -- 当年第一天
45
+ SELECT TRUNC(SYSDATE, 'IW') FROM DUAL; -- 当周周一(ISO 周)
46
+
47
+ -- 时间间隔
48
+ SELECT SYSDATE + TO_YMINTERVAL('01-06') FROM DUAL; -- 加 1年6个月
49
+ SELECT SYSDATE + TO_DSINTERVAL('3 12:00:00') FROM DUAL; -- 加 3天12小时
50
+ ```
51
+
52
+ ## 转换函数
53
+
54
+ ```sql
55
+ -- TO_CHAR — 数字格式化
56
+ SELECT TO_CHAR(1234567.89, 'FM999,999,999.00') FROM DUAL; -- 1,234,567.89
57
+ SELECT TO_CHAR(1234567.89, 'FML999,999,999.00') FROM DUAL; -- $1,234,567.89
58
+
59
+ -- TO_NUMBER — 字符串转数字
60
+ SELECT TO_NUMBER('1,234.56', '999,999.99') FROM DUAL; -- 1234.56
61
+
62
+ -- CAST — ANSI SQL 标准类型转换
63
+ SELECT CAST('12345' AS NUMBER) FROM DUAL;
64
+ SELECT CAST('2024-01-15' AS DATE) FROM DUAL;
65
+
66
+ -- CONVERT — 字符集转换
67
+ SELECT CONVERT('Oracle', 'AL32UTF8', 'ZHS16GBK') FROM DUAL;
68
+
69
+ -- SCN_TO_TIMESTAMP / TIMESTAMP_TO_SCN — SCN 与时间互转
70
+ SELECT SCN_TO_TIMESTAMP(ORA_ROWSCN) FROM employees WHERE employee_id = 100;
71
+ ```
@@ -0,0 +1,103 @@
1
+ # 分析函数(窗口函数)+ 聚合
2
+
3
+ 分析函数是 Oracle 最强大的功能之一,在不改变行数的情况下进行聚合计算。
4
+
5
+ ## 聚合函数
6
+
7
+ ```sql
8
+ -- 基础聚合
9
+ SELECT COUNT(*) FROM employees; -- 总行数
10
+ SELECT COUNT(commission_pct) FROM employees; -- 非 NULL 行数
11
+ SELECT SUM(salary) FROM employees;
12
+ SELECT AVG(salary) FROM employees;
13
+ SELECT MAX(hire_date) FROM employees;
14
+ SELECT MIN(hire_date) FROM employees;
15
+
16
+ -- MEDIAN — 中位数
17
+ SELECT MEDIAN(salary) FROM employees;
18
+
19
+ -- STATS_MODE — 众数
20
+ SELECT STATS_MODE(department_id) FROM employees;
21
+
22
+ -- GROUP BY ROLLUP / CUBE(小计+总计)
23
+ SELECT department_id, job_id, SUM(salary)
24
+ FROM employees
25
+ WHERE department_id IN (50, 80)
26
+ GROUP BY ROLLUP(department_id, job_id); -- 小计 + 总计
27
+
28
+ SELECT department_id, job_id, SUM(salary)
29
+ FROM employees
30
+ WHERE department_id IN (50, 80)
31
+ GROUP BY CUBE(department_id, job_id); -- 所有维度小计
32
+
33
+ -- GROUPING — 区分 NULL 是数据值还是小计行
34
+ SELECT department_id, job_id, SUM(salary),
35
+ CASE WHEN GROUPING(department_id)=1 THEN '总计'
36
+ WHEN GROUPING(job_id)=1 THEN '小计'
37
+ ELSE '明细'
38
+ END AS rollup_level
39
+ FROM employees GROUP BY ROLLUP(department_id, job_id);
40
+ ```
41
+
42
+ ## 分析函数(窗口函数)
43
+
44
+ ### 排序函数
45
+
46
+ ```sql
47
+ -- ROW_NUMBER — 唯一序号
48
+ SELECT department_id, last_name, salary,
49
+ ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS seq
50
+ FROM employees;
51
+
52
+ -- RANK / DENSE_RANK — 排名(允许并列)
53
+ SELECT department_id, last_name, salary,
54
+ RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rank,
55
+ DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS dense_rank
56
+ FROM employees;
57
+ -- RANK: 1,2,2,4 DENSE_RANK: 1,2,2,3
58
+
59
+ -- NTILE — 分桶
60
+ SELECT customer_id, amount,
61
+ NTILE(5) OVER (ORDER BY amount DESC) AS bucket
62
+ FROM orders;
63
+ ```
64
+
65
+ ### 前后行访问
66
+
67
+ ```sql
68
+ -- LAG — 访问前一行(环比计算)
69
+ SELECT hire_date, salary,
70
+ LAG(salary, 1, 0) OVER (ORDER BY hire_date) AS prev_salary,
71
+ salary - LAG(salary, 1, 0) OVER (ORDER BY hire_date) AS diff
72
+ FROM employees;
73
+
74
+ -- LEAD — 访问后一行
75
+ SELECT hire_date, salary,
76
+ LEAD(salary, 1) OVER (ORDER BY hire_date) AS next_salary
77
+ FROM employees;
78
+ ```
79
+
80
+ ### 窗口聚合
81
+
82
+ ```sql
83
+ -- FIRST_VALUE / LAST_VALUE — 窗口首尾行
84
+ SELECT department_id, last_name, salary,
85
+ FIRST_VALUE(salary) OVER (PARTITION BY department_id
86
+ ORDER BY salary DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS max_in_dept
87
+ FROM employees;
88
+
89
+ -- 累计求和
90
+ SELECT sale_date, amount,
91
+ SUM(amount) OVER (ORDER BY sale_date) AS running_total
92
+ FROM daily_sales;
93
+
94
+ -- 移动平均(7 日)
95
+ SELECT sale_date, amount,
96
+ AVG(amount) OVER (ORDER BY sale_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS moving_avg_7d
97
+ FROM daily_sales;
98
+
99
+ -- RATIO_TO_REPORT — 占比
100
+ SELECT department_id, last_name, salary,
101
+ RATIO_TO_REPORT(salary) OVER (PARTITION BY department_id) AS pct_of_dept
102
+ FROM employees;
103
+ ```
@@ -0,0 +1,303 @@
1
+ # PL/SQL 详解
2
+
3
+ PL/SQL(Procedural Language/SQL)是 Oracle 的扩展 SQL,支持变量、条件、循环、异常处理等过程式编程。
4
+
5
+ ## 块结构
6
+
7
+ ```sql
8
+ DECLARE
9
+ v_employee_id employees.employee_id%TYPE;
10
+ v_salary employees.salary%TYPE := 5000;
11
+ BEGIN
12
+ SELECT employee_id, salary INTO v_employee_id, v_salary
13
+ FROM employees WHERE employee_id = 100;
14
+ DBMS_OUTPUT.PUT_LINE('员工 ' || v_employee_id || ' 工资: ' || v_salary);
15
+ EXCEPTION
16
+ WHEN NO_DATA_FOUND THEN
17
+ DBMS_OUTPUT.PUT_LINE('未找到员工');
18
+ WHEN OTHERS THEN
19
+ DBMS_OUTPUT.PUT_LINE('错误: ' || SQLERRM);
20
+ END;
21
+ /
22
+ ```
23
+
24
+ ## 游标 (Cursor)
25
+
26
+ ```sql
27
+ -- 隐式游标(SELECT INTO)
28
+ DECLARE v_name employees.last_name%TYPE;
29
+ BEGIN
30
+ SELECT last_name INTO v_name FROM employees WHERE employee_id = 100;
31
+ EXCEPTION WHEN NO_DATA_FOUND THEN NULL;
32
+ END;
33
+ /
34
+
35
+ -- 显式游标
36
+ DECLARE
37
+ CURSOR emp_cursor IS SELECT employee_id, last_name FROM employees WHERE department_id = 50;
38
+ v_emp emp_cursor%ROWTYPE;
39
+ BEGIN
40
+ OPEN emp_cursor;
41
+ LOOP
42
+ FETCH emp_cursor INTO v_emp;
43
+ EXIT WHEN emp_cursor%NOTFOUND;
44
+ DBMS_OUTPUT.PUT_LINE(v_emp.last_name);
45
+ END LOOP;
46
+ CLOSE emp_cursor;
47
+ END;
48
+ /
49
+
50
+ -- CURSOR FOR LOOP(最简洁)
51
+ BEGIN
52
+ FOR rec IN (SELECT last_name, salary FROM employees WHERE department_id = 50)
53
+ LOOP
54
+ DBMS_OUTPUT.PUT_LINE(rec.last_name || ' 工资: ' || rec.salary);
55
+ END LOOP;
56
+ END;
57
+ /
58
+
59
+ -- REF CURSOR(动态游标)
60
+ DECLARE
61
+ TYPE refcur IS REF CURSOR;
62
+ c_ref refcur;
63
+ v_id employees.employee_id%TYPE;
64
+ v_name employees.last_name%TYPE;
65
+ BEGIN
66
+ OPEN c_ref FOR 'SELECT employee_id, last_name FROM employees WHERE department_id = :d' USING 50;
67
+ LOOP
68
+ FETCH c_ref INTO v_id, v_name;
69
+ EXIT WHEN c_ref%NOTFOUND;
70
+ END LOOP;
71
+ CLOSE c_ref;
72
+ END;
73
+ /
74
+
75
+ -- SYS_REFCURSOR 函数返回
76
+ CREATE OR REPLACE FUNCTION get_employees(p_dept_id NUMBER) RETURN SYS_REFCURSOR AS
77
+ c SYS_REFCURSOR;
78
+ BEGIN
79
+ OPEN c FOR SELECT * FROM employees WHERE department_id = p_dept_id;
80
+ RETURN c;
81
+ END;
82
+ /
83
+ ```
84
+
85
+ ## 存储过程与函数
86
+
87
+ ```sql
88
+ -- 存储过程
89
+ CREATE OR REPLACE PROCEDURE update_salary(
90
+ p_employee_id IN employees.employee_id%TYPE,
91
+ p_percent IN NUMBER
92
+ ) AS
93
+ v_old_salary employees.salary%TYPE;
94
+ BEGIN
95
+ SELECT salary INTO v_old_salary FROM employees WHERE employee_id = p_employee_id FOR UPDATE;
96
+ UPDATE employees SET salary = salary * (1 + p_percent / 100) WHERE employee_id = p_employee_id;
97
+ COMMIT;
98
+ EXCEPTION
99
+ WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20001, '员工不存在');
100
+ WHEN OTHERS THEN ROLLBACK; RAISE;
101
+ END update_salary;
102
+ /
103
+
104
+ -- 函数(必须有返回值)
105
+ CREATE OR REPLACE FUNCTION get_annual_salary(p_employee_id NUMBER) RETURN NUMBER AS
106
+ v_salary employees.salary%TYPE;
107
+ v_commission employees.commission_pct%TYPE;
108
+ BEGIN
109
+ SELECT salary, NVL(commission_pct, 0) INTO v_salary, v_commission
110
+ FROM employees WHERE employee_id = p_employee_id;
111
+ RETURN v_salary * 12 + v_salary * v_commission;
112
+ END get_annual_salary;
113
+ /
114
+
115
+ -- DETERMINISTIC 函数(可用于函数索引)
116
+ CREATE OR REPLACE FUNCTION calculate_tax(p_amount NUMBER) RETURN NUMBER DETERMINISTIC AS
117
+ BEGIN
118
+ RETURN p_amount * 0.13;
119
+ END;
120
+ /
121
+ ```
122
+
123
+ ## 包 (Package)
124
+
125
+ ```sql
126
+ -- 包规范(公开接口)
127
+ CREATE OR REPLACE PACKAGE emp_mgmt AS
128
+ c_max_salary CONSTANT NUMBER := 50000;
129
+ FUNCTION get_salary(p_emp_id NUMBER) RETURN NUMBER;
130
+ PROCEDURE raise_salary(p_emp_id NUMBER, p_pct NUMBER);
131
+ PROCEDURE hire_employee(p_last_name VARCHAR2, p_email VARCHAR2, p_job_id VARCHAR2, p_salary NUMBER);
132
+ PROCEDURE set_debug(p_mode BOOLEAN);
133
+ PROCEDURE set_debug(p_mode VARCHAR2); -- 重载
134
+ END emp_mgmt;
135
+ /
136
+
137
+ -- 包体(实现)
138
+ CREATE OR REPLACE PACKAGE BODY emp_mgmt AS
139
+ v_last_action VARCHAR2(100); -- 私有变量
140
+
141
+ FUNCTION get_salary(p_emp_id NUMBER) RETURN NUMBER IS
142
+ v_sal employees.salary%TYPE;
143
+ BEGIN
144
+ SELECT salary INTO v_sal FROM employees WHERE employee_id = p_emp_id;
145
+ RETURN v_sal;
146
+ EXCEPTION WHEN NO_DATA_FOUND THEN RETURN NULL;
147
+ END;
148
+
149
+ PROCEDURE raise_salary(p_emp_id NUMBER, p_pct NUMBER) IS
150
+ BEGIN
151
+ UPDATE employees SET salary = salary * (1 + p_pct/100) WHERE employee_id = p_emp_id;
152
+ v_last_action := 'Raised salary for ' || p_emp_id;
153
+ END;
154
+
155
+ PROCEDURE hire_employee(p_last_name VARCHAR2, p_email VARCHAR2, p_job_id VARCHAR2, p_salary NUMBER) IS
156
+ BEGIN
157
+ INSERT INTO employees(employee_id, last_name, email, job_id, salary, hire_date)
158
+ VALUES (employees_seq.NEXTVAL, p_last_name, p_email, p_job_id, p_salary, SYSDATE);
159
+ v_last_action := 'Hired ' || p_last_name;
160
+ END;
161
+
162
+ PROCEDURE set_debug(p_mode BOOLEAN) IS BEGIN g_debug_mode := p_mode; END;
163
+ PROCEDURE set_debug(p_mode VARCHAR2) IS BEGIN g_debug_mode := UPPER(p_mode) = 'ON'; END;
164
+
165
+ -- 包初始化(首次引用时执行一次)
166
+ BEGIN
167
+ v_last_action := 'Package initialized';
168
+ END;
169
+ END emp_mgmt;
170
+ /
171
+ ```
172
+
173
+ ## 触发器 (Trigger)
174
+
175
+ ```sql
176
+ -- DML 行级触发器(工资变更审计)
177
+ CREATE OR REPLACE TRIGGER trg_emp_salary_audit
178
+ BEFORE UPDATE OF salary ON employees FOR EACH ROW
179
+ WHEN (OLD.salary != NEW.salary)
180
+ BEGIN
181
+ INSERT INTO salary_audit_log(employee_id, old_salary, new_salary, changed_by, changed_at)
182
+ VALUES (:OLD.employee_id, :OLD.salary, :NEW.salary, USER, SYSDATE);
183
+ END;
184
+ /
185
+
186
+ -- 语句级触发器(非工作时间禁止修改)
187
+ CREATE OR REPLACE TRIGGER trg_no_dml_nonbusiness
188
+ BEFORE INSERT OR UPDATE OR DELETE ON employees
189
+ BEGIN
190
+ IF TO_CHAR(SYSDATE, 'DY') IN ('SAT', 'SUN') OR
191
+ TO_NUMBER(TO_CHAR(SYSDATE, 'HH24')) NOT BETWEEN 9 AND 18 THEN
192
+ RAISE_APPLICATION_ERROR(-20001, '非工作时间禁止修改');
193
+ END IF;
194
+ END;
195
+ /
196
+
197
+ -- INSTEAD OF 触发器(视图 DML)
198
+ CREATE OR REPLACE TRIGGER trg_v_emp_dept_ioi
199
+ INSTEAD OF INSERT ON v_emp_dept FOR EACH ROW
200
+ BEGIN
201
+ INSERT INTO employees(employee_id, last_name, salary, department_id)
202
+ VALUES (:NEW.employee_id, :NEW.last_name, :NEW.salary,
203
+ (SELECT department_id FROM departments WHERE department_name = :NEW.department_name));
204
+ END;
205
+ /
206
+
207
+ -- 登录审计触发器
208
+ CREATE OR REPLACE TRIGGER trg_logon_audit
209
+ AFTER LOGON ON DATABASE
210
+ BEGIN
211
+ INSERT INTO logon_audit_log(session_id, user_name, logon_time)
212
+ VALUES (SYS_CONTEXT('USERENV', 'SESSIONID'), USER, SYSDATE);
213
+ END;
214
+ /
215
+ ```
216
+
217
+ ## 异常处理
218
+
219
+ ```sql
220
+ -- 预定义异常
221
+ EXCEPTION
222
+ WHEN NO_DATA_FOUND THEN ...
223
+ WHEN TOO_MANY_ROWS THEN ...
224
+ WHEN DUP_VAL_ON_INDEX THEN ...
225
+ WHEN VALUE_ERROR THEN ...
226
+ WHEN ZERO_DIVIDE THEN ...
227
+ WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(SQLERRM); RAISE;
228
+
229
+ -- 自定义异常
230
+ DECLARE
231
+ e_salary_too_high EXCEPTION;
232
+ PRAGMA EXCEPTION_INIT(e_salary_too_high, -20001);
233
+ BEGIN
234
+ IF v_salary > 50000 THEN RAISE e_salary_too_high; END IF;
235
+ EXCEPTION WHEN e_salary_too_high THEN ... END;
236
+
237
+ -- RAISE_APPLICATION_ERROR
238
+ RAISE_APPLICATION_ERROR(-20001, '订单已取消', TRUE);
239
+ ```
240
+
241
+ ## 集合类型
242
+
243
+ ```sql
244
+ -- 关联数组(Index-By Table)
245
+ DECLARE
246
+ TYPE dept_name_tab IS TABLE OF departments.department_name%TYPE INDEX BY PLS_INTEGER;
247
+ t_dept_names dept_name_tab;
248
+ BEGIN
249
+ FOR rec IN (SELECT department_id, department_name FROM departments)
250
+ LOOP t_dept_names(rec.department_id) := rec.department_name; END LOOP;
251
+ END;
252
+ /
253
+
254
+ -- 嵌套表
255
+ CREATE OR REPLACE TYPE phone_list AS TABLE OF VARCHAR2(20);
256
+ /
257
+ DECLARE t_phones phone_list := phone_list('13800138000', '13900139000');
258
+ BEGIN t_phones.EXTEND(1); t_phones(3) := '13700137000'; END;
259
+ /
260
+
261
+ -- VARRAY(定长数组)
262
+ CREATE OR REPLACE TYPE score_list IS VARRAY(10) OF NUMBER;
263
+ /
264
+
265
+ -- 集合方法: EXISTS, COUNT, LIMIT, FIRST/LAST, PRIOR/NEXT, EXTEND, TRIM, DELETE
266
+ ```
267
+
268
+ ## 动态 SQL
269
+
270
+ ```sql
271
+ -- EXECUTE IMMEDIATE(简单动态 SQL)
272
+ CREATE OR REPLACE FUNCTION count_rows(p_table_name VARCHAR2) RETURN NUMBER AS
273
+ v_sql VARCHAR2(200); v_cnt NUMBER;
274
+ BEGIN
275
+ v_sql := 'SELECT COUNT(*) FROM ' || p_table_name;
276
+ EXECUTE IMMEDIATE v_sql INTO v_cnt;
277
+ RETURN v_cnt;
278
+ END;
279
+ /
280
+
281
+ -- 带绑定变量(防 SQL 注入)
282
+ EXECUTE IMMEDIATE 'SELECT last_name FROM employees WHERE employee_id = :id' INTO v_name USING 100;
283
+
284
+ -- FORALL(批量 DML,提升性能)
285
+ DECLARE
286
+ TYPE id_list IS TABLE OF employees.employee_id%TYPE;
287
+ t_ids id_list := id_list(100, 101, 102);
288
+ BEGIN
289
+ FORALL i IN t_ids.FIRST..t_ids.LAST
290
+ UPDATE employees SET salary = salary * 1.1 WHERE employee_id = t_ids(i);
291
+ COMMIT;
292
+ END;
293
+ /
294
+
295
+ -- BULK COLLECT(批量读取)
296
+ DECLARE
297
+ TYPE emp_tab IS TABLE OF employees%ROWTYPE;
298
+ t_emps emp_tab;
299
+ BEGIN
300
+ SELECT * BULK COLLECT INTO t_emps FROM employees WHERE department_id = 50;
301
+ END;
302
+ /
303
+ ```