@gordon.gan/specflow 1.3.3-beta → 1.4.1
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.
- package/README.md +6 -2
- package/dist/integrations/shared/capability-evidence.js +37 -1
- package/dist/integrations/shared/command-catalog.js +1 -0
- package/dist/integrations/shared/parity-manifest.js +6 -0
- package/package.json +1 -1
- package/prompts/approval/database-guidance.md +79 -0
- package/prompts/approval/generate.md +1191 -0
- package/skills/database/LICENSE +405 -0
- package/skills/database/ORIGIN.md +6 -0
- package/skills/database/README.md +30 -0
- package/skills/database/elasticsearch/LICENSE.txt +202 -0
- package/skills/database/elasticsearch/SKILL.md +199 -0
- package/skills/database/elasticsearch/examples/01-fulltext-search.md +215 -0
- package/skills/database/elasticsearch/examples/02-aggregation-report.md +206 -0
- package/skills/database/elasticsearch/examples/03-reindex-zero-downtime.md +200 -0
- package/skills/database/elasticsearch/examples/04-cluster-monitoring.md +204 -0
- package/skills/database/elasticsearch/references/01-query-dsl-fulltext.md +162 -0
- package/skills/database/elasticsearch/references/02-query-dsl-term.md +210 -0
- package/skills/database/elasticsearch/references/03-aggregations-metric.md +161 -0
- package/skills/database/elasticsearch/references/04-aggregations-bucket.md +236 -0
- package/skills/database/elasticsearch/references/05-mapping-types.md +134 -0
- package/skills/database/elasticsearch/references/06-analyzers.md +187 -0
- package/skills/database/elasticsearch/references/07-cluster-ops.md +225 -0
- package/skills/database/elasticsearch/references/08-elk-integration.md +170 -0
- package/skills/database/mysql/SKILL.md +195 -0
- package/skills/database/mysql/examples/01-connection-pool.md +75 -0
- package/skills/database/mysql/examples/02-slow-query-optimization.md +98 -0
- package/skills/database/mysql/examples/03-master-slave-setup.md +144 -0
- package/skills/database/mysql/examples/04-backup-strategy.md +212 -0
- package/skills/database/mysql/references/01-functions-string.md +103 -0
- package/skills/database/mysql/references/02-functions-date.md +152 -0
- package/skills/database/mysql/references/03-functions-aggregate-window.md +167 -0
- package/skills/database/mysql/references/04-functions-json.md +129 -0
- package/skills/database/mysql/references/05-sql-ddl-types.md +235 -0
- package/skills/database/mysql/references/06-index-optimization.md +232 -0
- package/skills/database/mysql/references/07-replication-ha.md +213 -0
- package/skills/database/mysql/references/08-backup-restore.md +207 -0
- package/skills/database/mysql/references/09-advanced-features.md +345 -0
- package/skills/database/oracle/LICENSE.txt +202 -0
- package/skills/database/oracle/SKILL.md +238 -0
- package/skills/database/oracle/examples/01-plsql-procedure.md +90 -0
- package/skills/database/oracle/examples/02-awr-analysis.md +99 -0
- package/skills/database/oracle/examples/03-rman-backup.md +108 -0
- package/skills/database/oracle/examples/04-dataguard-setup.md +146 -0
- package/skills/database/oracle/references/01-functions-string.md +91 -0
- package/skills/database/oracle/references/02-functions-date.md +71 -0
- package/skills/database/oracle/references/03-analytic-functions.md +103 -0
- package/skills/database/oracle/references/04-plsql-guide.md +303 -0
- package/skills/database/oracle/references/05-performance-tuning.md +164 -0
- package/skills/database/oracle/references/06-backup-recovery.md +115 -0
- package/skills/database/oracle/references/07-dataguard-rac.md +76 -0
- package/skills/database/oracle/references/08-security.md +170 -0
- package/skills/database/oracle/references/09-sql-syntax.md +152 -0
- package/skills/database/oracle/references/10-features.md +174 -0
- package/skills/database/postgresql/LICENSE.txt +202 -0
- package/skills/database/postgresql/SKILL.md +182 -0
- package/skills/database/postgresql/examples/.gitkeep +0 -0
- package/skills/database/postgresql/examples/01-jsonb-query.md +72 -0
- package/skills/database/postgresql/examples/02-cte-recursive.md +110 -0
- package/skills/database/postgresql/examples/03-performance-tuning.md +114 -0
- package/skills/database/postgresql/examples/04-streaming-replication.md +113 -0
- package/skills/database/postgresql/references/.gitkeep +0 -0
- package/skills/database/postgresql/references/01-functions-string.md +174 -0
- package/skills/database/postgresql/references/02-functions-datetime.md +54 -0
- package/skills/database/postgresql/references/03-functions-aggregate-window.md +142 -0
- package/skills/database/postgresql/references/04-functions-jsonb.md +117 -0
- package/skills/database/postgresql/references/05-fulltext-search.md +109 -0
- package/skills/database/postgresql/references/06-index-types.md +95 -0
- package/skills/database/postgresql/references/07-partition-fdw.md +133 -0
- package/skills/database/postgresql/references/08-replication-backup.md +215 -0
- package/skills/database/redis/LICENSE.txt +202 -0
- package/skills/database/redis/SKILL.md +922 -0
- package/skills/database/redis/examples/01-cache-usage.md +104 -0
- package/skills/database/redis/examples/02-session-storage.md +72 -0
- package/skills/database/redis/examples/03-leaderboard.md +63 -0
- package/skills/database/redis/examples/04-redis-cluster-setup.md +70 -0
- package/skills/database/redis/examples/05-stream-queue.md +65 -0
- package/skills/database/redis/references/command-quick-ref.md +180 -0
- package/skills/database/redis/references/commands-admin-key.md +413 -0
- package/skills/database/redis/references/commands-set-sorted-advanced.md +539 -0
- package/skills/database/redis/references/commands-string-hash-list.md +458 -0
- package/skills/database/redis/references/memory-optimization.md +150 -0
- package/skills/database/redis/references/redis-conf-production.md +139 -0
- package/skills/specflow-approval/SKILL.md +427 -0
- package/templates/approval.md +464 -0
|
@@ -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
|
+
```
|