@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,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
|
+
```
|