@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,110 @@
1
+ # 递归 CTE 示例
2
+
3
+ ## 场景 1:组织架构树
4
+
5
+ 查询从根节点到所有子节点的完整部门树。
6
+
7
+ ```sql
8
+ -- 建表
9
+ CREATE TABLE departments (
10
+ id SERIAL PRIMARY KEY,
11
+ name TEXT NOT NULL,
12
+ parent_id INTEGER REFERENCES departments(id)
13
+ );
14
+
15
+ -- 插入层级数据
16
+ INSERT INTO departments (id, name, parent_id) VALUES
17
+ (1, '总公司', NULL),
18
+ (2, '技术部', 1),
19
+ (3, '市场部', 1),
20
+ (4, '后端组', 2),
21
+ (5, '前端组', 2),
22
+ (6, '数据组', 2),
23
+ (7, '广告组', 3),
24
+ (8, 'PR 组', 3);
25
+
26
+ -- 递归 CTE: 展开整个树
27
+ WITH RECURSIVE org_tree AS (
28
+ -- 基础: 根节点
29
+ SELECT id, name, parent_id, 1 AS level, ARRAY[id] AS path
30
+ FROM departments
31
+ WHERE parent_id IS NULL
32
+
33
+ UNION ALL
34
+
35
+ -- 递归: 子节点
36
+ SELECT d.id, d.name, d.parent_id, t.level + 1, t.path || d.id
37
+ FROM departments d
38
+ JOIN org_tree t ON d.parent_id = t.id
39
+ )
40
+ SELECT id, name, level, repeat(' ', level - 1) || name AS tree_display
41
+ FROM org_tree ORDER BY path;
42
+ ```
43
+
44
+ ## 场景 2:商品分类全路径
45
+
46
+ 将树形分类扁平化并显示完整路径。
47
+
48
+ ```sql
49
+ -- 建表
50
+ CREATE TABLE categories (
51
+ id SERIAL PRIMARY KEY,
52
+ name TEXT NOT NULL,
53
+ parent_id INTEGER REFERENCES categories(id)
54
+ );
55
+
56
+ INSERT INTO categories (id, name, parent_id) VALUES
57
+ (1, '电子产品', NULL),
58
+ (2, '手机', 1),
59
+ (3, '电脑', 1),
60
+ (4, '智能手机', 2),
61
+ (5, '功能机', 2),
62
+ (6, '笔记本', 3),
63
+ (7, '台式机', 3);
64
+
65
+ -- 从指定节点开始,查询所有子分类及其全路径
66
+ WITH RECURSIVE category_tree AS (
67
+ SELECT id, name, parent_id, name AS full_path
68
+ FROM categories WHERE id = 1 -- 从 "电子产品" 开始
69
+
70
+ UNION ALL
71
+
72
+ SELECT c.id, c.name, c.parent_id,
73
+ ct.full_path || ' > ' || c.name
74
+ FROM categories c
75
+ JOIN category_tree ct ON c.parent_id = ct.id
76
+ )
77
+ SELECT * FROM category_tree ORDER BY full_path;
78
+ ```
79
+
80
+ ## 场景 3:斐波那契数列
81
+
82
+ ```sql
83
+ WITH RECURSIVE fib(a, b) AS (
84
+ SELECT 0::BIGINT, 1::BIGINT
85
+ UNION ALL
86
+ SELECT b, a + b FROM fib WHERE b < 1000
87
+ )
88
+ SELECT a FROM fib;
89
+ ```
90
+
91
+ ## 场景 4:销售统计占比
92
+
93
+ 使用非递归 CTE 计算每个分类的销售额占比。
94
+
95
+ ```sql
96
+ WITH category_sales AS (
97
+ SELECT c.name AS category, SUM(oi.quantity * oi.price) AS total
98
+ FROM order_items oi
99
+ JOIN products p ON p.id = oi.product_id
100
+ JOIN categories c ON c.id = p.category_id
101
+ GROUP BY c.name
102
+ ),
103
+ grand_total AS (
104
+ SELECT SUM(total) AS total FROM category_sales
105
+ )
106
+ SELECT cs.category, cs.total,
107
+ ROUND(cs.total / gt.total * 100, 2) AS pct
108
+ FROM category_sales cs, grand_total gt
109
+ ORDER BY cs.total DESC;
110
+ ```
@@ -0,0 +1,114 @@
1
+ # 性能调优示例
2
+
3
+ ## 场景 1:定位慢查询
4
+
5
+ 使用 EXPLAIN ANALYZE 诊断查询性能问题。
6
+
7
+ ```sql
8
+ -- 创建测试表
9
+ CREATE TABLE orders (
10
+ id BIGSERIAL, user_id INTEGER NOT NULL, status TEXT,
11
+ total_amount NUMERIC(12,2), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
12
+ );
13
+
14
+ -- 插入测试数据(假设已有数百万行)
15
+
16
+ -- 诊断慢查询
17
+ EXPLAIN (ANALYZE, BUFFERS)
18
+ SELECT * FROM orders WHERE status = 'pending' AND created_at > '2024-01-01';
19
+ -- 如果看到 Seq Scan → 需要加索引
20
+
21
+ -- 创建复合索引
22
+ CREATE INDEX idx_orders_status_created ON orders (status, created_at DESC);
23
+
24
+ -- 再次验证
25
+ EXPLAIN (ANALYZE, BUFFERS)
26
+ SELECT * FROM orders WHERE status = 'pending' AND created_at > '2024-01-01';
27
+ -- 现在应该看到 Index Scan
28
+ ```
29
+
30
+ ## 场景 2:JOIN 性能优化
31
+
32
+ ```sql
33
+ -- 慢查询:大表 JOIN + GROUP BY
34
+ EXPLAIN (ANALYZE, BUFFERS)
35
+ SELECT u.name, COUNT(o.id) AS order_count
36
+ FROM users u LEFT JOIN orders o ON o.user_id = u.id
37
+ WHERE u.created_at > '2024-01-01'
38
+ GROUP BY u.id, u.name;
39
+
40
+ -- 检查输出中的 Sort Method
41
+ -- 如果看到 "external merge Disk: 1536kB" → work_mem 不足
42
+
43
+ -- 临时增加 work_mem(当前会话)
44
+ SET work_mem = '256MB';
45
+
46
+ -- 或者创建覆盖索引
47
+ CREATE INDEX idx_orders_user_id_covering ON orders (user_id) INCLUDE (id);
48
+ ```
49
+
50
+ ## 场景 3:VACUUM 与膨胀监控
51
+
52
+ ```sql
53
+ -- 查看表膨胀情况
54
+ SELECT relname, n_live_tup, n_dead_tup,
55
+ ROUND(n_dead_tup::NUMERIC / NULLIF(n_live_tup, 0) * 100, 2) AS dead_pct,
56
+ last_autovacuum, last_autoanalyze
57
+ FROM pg_stat_user_tables
58
+ WHERE n_dead_tup > 0
59
+ ORDER BY n_dead_tup DESC LIMIT 20;
60
+
61
+ -- 为高频更新表调优 autovacuum
62
+ ALTER TABLE orders SET (
63
+ autovacuum_vacuum_scale_factor = 0.05,
64
+ autovacuum_vacuum_threshold = 1000
65
+ );
66
+
67
+ -- 检查事务 ID 回卷风险
68
+ SELECT datname, age(datfrozenxid) AS age,
69
+ ROUND(100 * age(datfrozenxid)::NUMERIC / 2000000000, 2) AS pct_wraparound
70
+ FROM pg_database ORDER BY age DESC;
71
+ ```
72
+
73
+ ## 场景 4:pg_stat_statements 分析
74
+
75
+ ```sql
76
+ CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
77
+
78
+ -- TOP 10 最耗时查询
79
+ SELECT queryid, LEFT(query, 80) AS query_preview, calls,
80
+ ROUND(total_exec_time::NUMERIC, 2) AS total_ms,
81
+ ROUND(mean_exec_time::NUMERIC, 2) AS avg_ms,
82
+ ROUND(shared_blks_hit::NUMERIC / NULLIF(shared_blks_hit + shared_blks_read, 0) * 100, 2) AS hit_ratio
83
+ FROM pg_stat_statements
84
+ ORDER BY total_exec_time DESC LIMIT 10;
85
+
86
+ -- TOP 10 I/O 密集查询
87
+ SELECT queryid, LEFT(query, 80) AS query_preview,
88
+ shared_blks_read, temp_blks_read
89
+ FROM pg_stat_statements
90
+ WHERE shared_blks_read > 1000
91
+ ORDER BY shared_blks_read DESC LIMIT 10;
92
+
93
+ -- TOP 10 临时文件使用(work_mem 不足)
94
+ SELECT queryid, LEFT(query, 80) AS query_preview,
95
+ temp_blk_read_time, temp_blk_write_time
96
+ FROM pg_stat_statements
97
+ WHERE temp_blk_read_time > 0
98
+ ORDER BY temp_blk_read_time DESC LIMIT 10;
99
+ ```
100
+
101
+ ## 场景 5:配置调优参考
102
+
103
+ ```conf
104
+ # 64GB 内存服务器参考配置
105
+ shared_buffers = 12GB # 物理内存 20-25%
106
+ work_mem = 64MB # 每个排序操作
107
+ maintenance_work_mem = 1GB # VACUUM/CREATE INDEX
108
+ effective_cache_size = 12GB # 规划器缓存估计
109
+ wal_buffers = 16MB
110
+ max_connections = 200 # 超过则用 PgBouncer
111
+ checkpoint_timeout = 15min
112
+ max_wal_size = 16GB
113
+ default_statistics_target = 100 # 大表可调至 500-1000
114
+ ```
@@ -0,0 +1,113 @@
1
+ # 流复制搭建示例
2
+
3
+ ## 场景:搭建一主一从的高可用架构
4
+
5
+ ### 主库配置
6
+
7
+ ```conf
8
+ # postgresql.conf 配置
9
+ listen_addresses = 'localhost,192.168.1.100'
10
+ wal_level = replica
11
+ max_wal_senders = 10
12
+ wal_keep_size = 1GB
13
+ hot_standby = on
14
+ ```
15
+
16
+ ```bash
17
+ # 重启主库
18
+ systemctl restart postgresql
19
+ ```
20
+
21
+ ### 创建复制用户
22
+
23
+ ```sql
24
+ -- 在主库执行
25
+ CREATE USER replicator WITH REPLICATION LOGIN PASSWORD 'Str0ng!Pass';
26
+ ```
27
+
28
+ ```bash
29
+ # 在 pg_hba.conf 添加
30
+ echo 'host replication replicator 192.168.1.0/24 md5' >> /var/lib/pgsql/data/pg_hba.conf
31
+
32
+ # 重新加载配置
33
+ psql -c "SELECT pg_reload_conf();"
34
+ ```
35
+
36
+ ### 从库搭建
37
+
38
+ ```bash
39
+ # 安装相同版本 PostgreSQL
40
+
41
+ # 停从库
42
+ systemctl stop postgresql
43
+
44
+ # 清空从库数据目录
45
+ rm -rf /var/lib/pgsql/data/*
46
+
47
+ # 从主库拉取基础备份
48
+ pg_basebackup -h 192.168.1.100 -U replicator \
49
+ -D /var/lib/pgsql/data -P -v --wal-method=stream
50
+
51
+ # PG 12+: 创建 standby 信号文件
52
+ touch /var/lib/pgsql/data/standby.signal
53
+
54
+ # 配置主库连接信息
55
+ cat > /var/lib/pgsql/data/postgresql.auto.conf << EOF
56
+ primary_conninfo = 'host=192.168.1.100 port=5432 user=replicator password=Str0ng!Pass'
57
+ EOF
58
+
59
+ # 启动从库
60
+ systemctl start postgresql
61
+ ```
62
+
63
+ ### 验证复制
64
+
65
+ ```sql
66
+ -- 在主库检查复制状态
67
+ SELECT pid, application_name, state, sync_state,
68
+ write_lag, flush_lag, replay_lag
69
+ FROM pg_stat_replication;
70
+
71
+ -- 在从库检查接收状态
72
+ SELECT pid, status, receive_start_lsn, received_lsn,
73
+ latest_end_lsn, latest_end_time
74
+ FROM pg_stat_wal_receiver;
75
+
76
+ -- 测试: 在主库创建表并插入数据
77
+ CREATE TABLE test_replication (id SERIAL PRIMARY KEY, data TEXT, ts TIMESTAMPTZ DEFAULT NOW());
78
+ INSERT INTO test_replication (data) VALUES ('hello from primary');
79
+
80
+ -- 在从库验证(从库为只读模式)
81
+ SELECT * FROM test_replication;
82
+ ```
83
+
84
+ ### 故障转移
85
+
86
+ ```bash
87
+ # 手动提升从库为主库
88
+ # 在从库执行
89
+ pg_ctl promote -D /var/lib/pgsql/data
90
+ # 或
91
+ systemctl stop postgresql
92
+ # 删除 standby.signal 后启动
93
+ rm /var/lib/pgsql/data/standby.signal
94
+ systemctl start postgresql
95
+
96
+ # 此时原从库变为可读写
97
+ ```
98
+
99
+ ### 常见问题排查
100
+
101
+ ```bash
102
+ # 查看复制日志
103
+ tail -f /var/lib/pgsql/data/log/postgresql-*.log
104
+
105
+ # 检查网络连通性
106
+ psql -h 192.168.1.100 -U replicator -d postgres -c "SELECT 1"
107
+
108
+ # 检查 WAL 发送进程
109
+ ps aux | grep wal_sender
110
+
111
+ # 检查磁盘空间(WAL 堆积会导致磁盘满)
112
+ df -h /var/lib/pgsql/data/
113
+ ```
File without changes
@@ -0,0 +1,174 @@
1
+ # 字符串/正则函数详解
2
+
3
+ ## 字符串函数
4
+
5
+ ```sql
6
+ -- FORMAT — 格式化字符串
7
+ SELECT FORMAT('ORD-%s-%04d', TO_CHAR(NOW(), 'YYYYMMDD'), 123);
8
+ -- 结果: ORD-20240529-0123
9
+
10
+ -- SPLIT_PART — 分割字符串
11
+ SELECT SPLIT_PART('北京市海淀区中关村', '区', 1);
12
+ -- 结果: 北京市海淀
13
+
14
+ -- STRING_AGG — 字符串聚合(将分类名称合并为逗号分隔字符串)
15
+ SELECT STRING_AGG(DISTINCT c.name, ', ' ORDER BY c.name) AS categories
16
+ FROM products p
17
+ JOIN product_categories pc ON pc.product_id = p.id
18
+ JOIN categories c ON c.id = pc.category_id
19
+ WHERE p.id = 1001;
20
+
21
+ -- CONCAT / CONCAT_WS
22
+ SELECT CONCAT_WS(', ', province, city, district, detail) AS full_address FROM addresses;
23
+
24
+ -- LEFT / RIGHT
25
+ SELECT LEFT('Hello World', 5); -- Hello
26
+ SELECT RIGHT('Hello World', 5); -- World
27
+
28
+ -- REPEAT / REVERSE
29
+ SELECT REPEAT('*', 5); -- *****
30
+ SELECT REVERSE('PostgreSQL'); -- LQSregtsoP
31
+
32
+ -- POSITION / STRPOS
33
+ SELECT POSITION('SQL' IN 'PostgreSQL'); -- 7
34
+ SELECT STRPOS('PostgreSQL', 'SQL'); -- 7
35
+
36
+ -- SUBSTRING (支持正则)
37
+ SELECT SUBSTRING('abc123def' FROM '[0-9]+'); -- 123
38
+
39
+ -- TRANSLATE
40
+ SELECT TRANSLATE('hello', 'aeiou', '12345'); -- h2ll4
41
+ ```
42
+
43
+ ## 正则函数
44
+
45
+ ```sql
46
+ -- REGEXP_MATCH — 正则匹配(提取邮箱)
47
+ SELECT REGEXP_MATCH(
48
+ '联系邮箱: alice@example.com, 备用: bob@test.com',
49
+ '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
50
+ 'g'
51
+ );
52
+ -- 结果: {alice@example.com,bob@test.com}
53
+
54
+ -- REGEXP_REPLACE — 正则替换(脱敏手机号)
55
+ SELECT REGEXP_REPLACE('13812345678', '(\d{3})\d{4}(\d{4})', '\1****\2');
56
+ -- 结果: 138****5678
57
+ ```
58
+
59
+ ## PL/pgSQL 函数
60
+
61
+ ```sql
62
+ -- 标量函数
63
+ CREATE OR REPLACE FUNCTION calculate_discount(
64
+ price NUMERIC, discount_pct NUMERIC, max_discount NUMERIC DEFAULT 100
65
+ ) RETURNS NUMERIC
66
+ LANGUAGE plpgsql IMMUTABLE
67
+ AS $$
68
+ BEGIN
69
+ RETURN GREATEST(price * (1 - discount_pct / 100), price - max_discount);
70
+ END;
71
+ $$;
72
+
73
+ -- 表函数 (RETURNS TABLE)
74
+ CREATE OR REPLACE FUNCTION get_user_orders(
75
+ p_user_id INTEGER, p_status TEXT DEFAULT NULL, p_limit INTEGER DEFAULT 100
76
+ ) RETURNS TABLE (order_id BIGINT, total_amount NUMERIC(12,2), status TEXT, created_at TIMESTAMPTZ)
77
+ LANGUAGE plpgsql STABLE
78
+ AS $$
79
+ BEGIN
80
+ RETURN QUERY
81
+ SELECT o.id, o.total_amount, o.status, o.created_at
82
+ FROM orders o
83
+ WHERE o.user_id = p_user_id AND (p_status IS NULL OR o.status = p_status)
84
+ ORDER BY o.created_at DESC LIMIT p_limit;
85
+ END;
86
+ $$;
87
+
88
+ -- 函数 (FUNCTION) vs 过程 (PROCEDURE)
89
+ -- FUNCTION: 必须返回值, SELECT 中调用
90
+ -- PROCEDURE (PG 11+): 无返回值, CALL 调用, 支持事务控制
91
+
92
+ -- 函数重载
93
+ CREATE OR REPLACE FUNCTION format_price(price NUMERIC) RETURNS TEXT
94
+ LANGUAGE SQL IMMUTABLE AS $$ SELECT '¥' || ROUND(price, 2)::TEXT; $$;
95
+
96
+ CREATE OR REPLACE FUNCTION format_price(price NUMERIC, currency TEXT) RETURNS TEXT
97
+ LANGUAGE SQL IMMUTABLE AS $$ SELECT currency || ROUND(price, 2)::TEXT; $$;
98
+
99
+ -- PL/pgSQL 控制结构
100
+ CREATE OR REPLACE FUNCTION process_order(p_order_id BIGINT) RETURNS TEXT
101
+ LANGUAGE plpgsql
102
+ AS $$
103
+ DECLARE
104
+ v_order orders%ROWTYPE;
105
+ v_log TEXT := '';
106
+ BEGIN
107
+ SELECT * INTO STRICT v_order FROM orders WHERE id = p_order_id;
108
+
109
+ IF v_order.status = 'pending' THEN
110
+ v_log := '待处理';
111
+ ELSIF v_order.status = 'paid' THEN
112
+ v_log := '已支付';
113
+ END IF;
114
+
115
+ RETURN v_log;
116
+ EXCEPTION
117
+ WHEN NO_DATA_FOUND THEN RETURN '订单不存在';
118
+ WHEN OTHERS THEN RETURN '错误: ' || SQLERRM;
119
+ END;
120
+ $$;
121
+ ```
122
+
123
+ ## 触发器
124
+
125
+ ```sql
126
+ -- 自动更新 updated_at
127
+ CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER
128
+ LANGUAGE plpgsql AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$;
129
+
130
+ CREATE TRIGGER trg_users_updated_at
131
+ BEFORE UPDATE ON users
132
+ FOR EACH ROW WHEN (OLD.* IS DISTINCT FROM NEW.*)
133
+ EXECUTE FUNCTION update_updated_at_column();
134
+
135
+ -- 审计日志触发器
136
+ CREATE OR REPLACE FUNCTION audit_order_changes() RETURNS TRIGGER
137
+ LANGUAGE plpgsql
138
+ AS $$
139
+ BEGIN
140
+ IF TG_OP = 'INSERT' THEN
141
+ INSERT INTO order_audit_log (order_id, new_data, action) VALUES (NEW.id, row_to_json(NEW)::JSONB, 'INSERT');
142
+ ELSIF TG_OP = 'UPDATE' THEN
143
+ INSERT INTO order_audit_log (order_id, old_data, new_data, action) VALUES (NEW.id, row_to_json(OLD)::JSONB, row_to_json(NEW)::JSONB, 'UPDATE');
144
+ ELSIF TG_OP = 'DELETE' THEN
145
+ INSERT INTO order_audit_log (order_id, old_data, action) VALUES (OLD.id, row_to_json(OLD)::JSONB, 'DELETE');
146
+ END IF;
147
+ RETURN NEW;
148
+ END;
149
+ $$;
150
+
151
+ -- 事件触发器 (DDL)
152
+ CREATE OR REPLACE FUNCTION prevent_table_drop() RETURNS EVENT_TRIGGER
153
+ LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION '禁止删除表'; END; $$;
154
+ CREATE EVENT TRIGGER prevent_drop_trigger ON sql_drop EXECUTE FUNCTION prevent_table_drop();
155
+ ```
156
+
157
+ ## 数字函数
158
+
159
+ ```sql
160
+ -- RANDOM — 随机抽样
161
+ SELECT * FROM users ORDER BY RANDOM() LIMIT 5;
162
+
163
+ -- GENERATE_SERIES — 生成序列
164
+ SELECT GENERATE_SERIES('2024-01-01'::DATE, '2024-01-10'::DATE, '1 day');
165
+ SELECT GENERATE_SERIES(1, 10, 2); -- 1, 3, 5, 7, 9
166
+
167
+ -- WIDTH_BUCKET — 等宽分桶
168
+ SELECT WIDTH_BUCKET(age, 0, 100, 10) AS bucket, MIN(age), MAX(age), COUNT(*)
169
+ FROM users GROUP BY bucket ORDER BY bucket;
170
+
171
+ -- ROUND / TRUNC / CEIL / FLOOR / POWER / SQRT / ABS / DIV / MOD / GCD / LCM
172
+ SELECT ROUND(123.456, 2), TRUNC(123.456, 2), CEIL(123.001), FLOOR(123.999);
173
+ SELECT POWER(2,10), SQRT(144), ABS(-42), DIV(10,3), MOD(10,3), GCD(12,18), LCM(12,18);
174
+ ```
@@ -0,0 +1,54 @@
1
+ # 日期/时间函数详解
2
+
3
+ ```sql
4
+ -- AGE — 计算时间差
5
+ SELECT AGE('2024-05-29', '2023-01-15');
6
+ -- 结果: 1 year 4 mons 14 days
7
+
8
+ -- 计算年龄
9
+ SELECT id, EXTRACT(YEAR FROM AGE(birth_date)) AS age FROM users;
10
+
11
+ -- DATE_TRUNC — 时间截断(支持: microsecond, millisecond, second, minute, hour, day, week, month, quarter, year, decade, century, millennium)
12
+ SELECT DATE_TRUNC('month', created_at) AS month, COUNT(*) AS order_count
13
+ FROM orders GROUP BY month;
14
+
15
+ -- EXTRACT — 提取日期部分
16
+ SELECT
17
+ EXTRACT(YEAR FROM created_at) AS year,
18
+ EXTRACT(MONTH FROM created_at) AS month,
19
+ EXTRACT(DOW FROM created_at) AS day_of_week, -- 0=Sunday
20
+ EXTRACT(HOUR FROM created_at) AS hour,
21
+ EXTRACT(QUARTER FROM created_at) AS quarter
22
+ FROM orders;
23
+
24
+ -- TO_CHAR — 日期格式化
25
+ SELECT
26
+ TO_CHAR(created_at, 'YYYY-MM-DD HH24:MI:SS') AS formatted_time,
27
+ TO_CHAR(created_at, 'YYYY年MM月DD日') AS chinese_date,
28
+ TO_CHAR(created_at, 'Day, DD Month YYYY') AS english_date,
29
+ TO_CHAR(created_at, 'IW') AS iso_week_number
30
+ FROM orders;
31
+
32
+ -- JUSTIFY_DAYS / JUSTIFY_HOURS / JUSTIFY_INTERVAL
33
+ SELECT JUSTIFY_DAYS(30::INTERVAL); -- 30 days → 1 mon
34
+ SELECT JUSTIFY_HOURS(100::INTERVAL); -- 100:00:00 → 4 days 04:00:00
35
+
36
+ -- MAKE_DATE / MAKE_TIMESTAMPTZ / MAKE_INTERVAL (PG 10+)
37
+ SELECT MAKE_DATE(2024, 6, 1);
38
+ SELECT MAKE_TIMESTAMPTZ(2024, 6, 1, 10, 30, 0, 'Asia/Shanghai');
39
+ SELECT MAKE_INTERVAL(days => 10, hours => 5);
40
+
41
+ -- DATE 运算
42
+ SELECT NOW(), NOW() + INTERVAL '1 day', NOW() - INTERVAL '3 hours';
43
+ SELECT CURRENT_DATE, CURRENT_TIME;
44
+
45
+ -- 时区转换
46
+ SELECT
47
+ NOW() AT TIME ZONE 'Asia/Shanghai',
48
+ NOW() AT TIME ZONE 'UTC',
49
+ '2024-06-01 10:00:00+08'::TIMESTAMPTZ AT TIME ZONE 'America/New_York';
50
+
51
+ -- 日期范围查询最佳实践
52
+ -- ❌ 避免: WHERE created_at BETWEEN '2024-01-01' AND '2024-01-31'
53
+ -- ✅ 推荐: WHERE created_at >= '2024-01-01' AND created_at < '2024-02-01'
54
+ ```
@@ -0,0 +1,142 @@
1
+ # 聚合/窗口函数详解
2
+
3
+ ## 聚合函数
4
+
5
+ ```sql
6
+ -- ARRAY_AGG — 聚合为数组
7
+ SELECT o.id, ARRAY_AGG(p.name ORDER BY p.name) AS products
8
+ FROM orders o JOIN order_items oi ON oi.order_id = o.id
9
+ JOIN products p ON p.id = oi.product_id GROUP BY o.id;
10
+
11
+ -- STRING_AGG — 聚合为字符串
12
+ SELECT article_id, STRING_AGG(DISTINCT tag, ', ' ORDER BY tag) AS tags
13
+ FROM article_tags GROUP BY article_id;
14
+
15
+ -- JSON_AGG / JSONB_AGG — 聚合为 JSON
16
+ SELECT o.id, JSONB_AGG(JSONB_BUILD_OBJECT('product_id', oi.product_id, 'qty', oi.quantity)) AS items
17
+ FROM orders o JOIN order_items oi ON oi.order_id = o.id GROUP BY o.id;
18
+
19
+ -- MODE — 众数
20
+ SELECT MODE() WITHIN GROUP (ORDER BY category_id) FROM products;
21
+
22
+ -- PERCENTILE_CONT / PERCENTILE_DISC — 百分位数
23
+ SELECT
24
+ PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY total_amount) AS median,
25
+ PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY total_amount) AS q1,
26
+ PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY total_amount) AS p90
27
+ FROM orders;
28
+
29
+ -- GROUPING SETS / CUBE / ROLLUP
30
+ SELECT COALESCE(department, 'ALL') AS dept, COALESCE(role, 'ALL') AS role,
31
+ COUNT(*) AS cnt, AVG(salary)::NUMERIC(10,2) AS avg_sal
32
+ FROM employees
33
+ GROUP BY GROUPING SETS ((department, role), (department), (role), ());
34
+
35
+ SELECT category, brand, COUNT(*) FROM products GROUP BY CUBE (category, brand);
36
+
37
+ SELECT EXTRACT(YEAR FROM created_at) AS year, EXTRACT(MONTH FROM created_at) AS month,
38
+ COUNT(*) FROM orders GROUP BY ROLLUP (year, month) ORDER BY year, month;
39
+ ```
40
+
41
+ ## 窗口函数
42
+
43
+ ```sql
44
+ -- ROW_NUMBER — 行号
45
+ SELECT id, name, category_id, price,
46
+ ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY price DESC) AS rn
47
+ FROM products;
48
+
49
+ -- RANK / DENSE_RANK — 排名
50
+ SELECT salesperson, amount,
51
+ RANK() OVER (ORDER BY amount DESC) AS rank,
52
+ DENSE_RANK() OVER (ORDER BY amount DESC) AS dense_rank
53
+ FROM monthly_sales;
54
+
55
+ -- NTILE — 分桶
56
+ SELECT id, total_spent, NTILE(4) OVER (ORDER BY total_spent DESC) AS quartile
57
+ FROM customers;
58
+
59
+ -- LAG / LEAD — 前后行访问
60
+ SELECT dt, revenue,
61
+ LAG(revenue, 1) OVER (ORDER BY dt) AS prev_day,
62
+ LAG(revenue, 7) OVER (ORDER BY dt) AS prev_week,
63
+ ROUND((revenue - LAG(revenue, 1) OVER (ORDER BY dt))
64
+ / NULLIF(LAG(revenue, 1) OVER (ORDER BY dt), 0) * 100, 2) AS dod_pct,
65
+ LEAD(revenue, 1) OVER (ORDER BY dt) AS next_day
66
+ FROM daily_revenue;
67
+
68
+ -- FIRST_VALUE / LAST_VALUE
69
+ SELECT category_id, name, price,
70
+ FIRST_VALUE(price) OVER (PARTITION BY category_id ORDER BY price) AS min_price,
71
+ LAST_VALUE(price) OVER (PARTITION BY category_id ORDER BY price
72
+ RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS max_price
73
+ FROM products;
74
+
75
+ -- NTH_VALUE
76
+ SELECT DISTINCT category_id,
77
+ NTH_VALUE(name, 3) OVER (PARTITION BY category_id ORDER BY price DESC
78
+ RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS third_expensive
79
+ FROM products;
80
+
81
+ -- 窗口帧控制
82
+ SELECT dt, revenue,
83
+ AVG(revenue) OVER (ORDER BY dt ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma_7d,
84
+ AVG(revenue) OVER (ORDER BY dt ROWS BETWEEN 29 PRECEDING AND CURRENT ROW) AS ma_30d
85
+ FROM daily_revenue;
86
+
87
+ -- 累积求和
88
+ SELECT dt, revenue, SUM(revenue) OVER (ORDER BY dt) AS cumulative_revenue
89
+ FROM daily_revenue;
90
+
91
+ -- 分组累计
92
+ SELECT category_id, dt, revenue,
93
+ SUM(revenue) OVER (PARTITION BY category_id ORDER BY dt ROWS UNBOUNDED PRECEDING) AS cum_by_cat
94
+ FROM daily_revenue_by_category;
95
+
96
+ -- 窗口函数 + FILTER 条件聚合
97
+ SELECT dt,
98
+ COUNT(*) AS total,
99
+ COUNT(*) FILTER (WHERE status = 'paid') AS paid,
100
+ COUNT(*) FILTER (WHERE status = 'cancelled') AS cancelled
101
+ FROM orders GROUP BY dt;
102
+ ```
103
+
104
+ ## 数组函数
105
+
106
+ ```sql
107
+ -- ARRAY — 构建数组
108
+ SELECT ARRAY[1, 2, 3], ARRAY['a', 'b', 'c'];
109
+
110
+ -- ANY / ALL — 元素检查
111
+ SELECT * FROM articles WHERE '数据库' = ANY(tags);
112
+
113
+ -- UNNEST — 展开数组
114
+ SELECT UNNEST(tags) AS tag, COUNT(*) AS freq
115
+ FROM articles GROUP BY tag ORDER BY freq DESC;
116
+
117
+ -- ARRAY_APPEND / ARRAY_PREPEND / ARRAY_REMOVE / ARRAY_CAT
118
+ SELECT ARRAY_APPEND(ARRAY[1,2], 3); -- {1,2,3}
119
+ SELECT ARRAY_PREPEND(0, ARRAY[1,2]); -- {0,1,2}
120
+ SELECT ARRAY_REMOVE(ARRAY[1,2,3], 2); -- {1,3}
121
+ SELECT ARRAY_CAT(ARRAY[1,2], ARRAY[3,4]); -- {1,2,3,4}
122
+
123
+ -- 数组信息
124
+ SELECT ARRAY_NDIMS(ARRAY[[1,2],[3,4]]), ARRAY_LENGTH(ARRAY[1,2,3], 1);
125
+
126
+ -- ARRAY_POSITION / ARRAY_POSITIONS
127
+ SELECT ARRAY_POSITION(ARRAY['a','b','c','b'], 'b'); -- 2
128
+ SELECT ARRAY_POSITIONS(ARRAY['a','b','c','b'], 'b'); -- {2,4}
129
+
130
+ -- STRING_TO_ARRAY / ARRAY_TO_STRING
131
+ SELECT STRING_TO_ARRAY('a,b,c', ','), ARRAY_TO_STRING(ARRAY['a','b','c'], '|');
132
+
133
+ -- 数组切片
134
+ SELECT tags[1:3] FROM articles;
135
+
136
+ -- @> / <@ — 包含, && — 重叠
137
+ SELECT * FROM articles WHERE tags @> ARRAY['SQL', '高级'];
138
+ SELECT * FROM articles WHERE tags && ARRAY['数据库', 'JSON'];
139
+
140
+ -- 数组 GIN 索引
141
+ CREATE INDEX idx_articles_tags_gin ON articles USING GIN (tags);
142
+ ```