aukeys-opscli 0.0.6__py3-none-any.whl

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 (92) hide show
  1. aukeys_opscli-0.0.6.dist-info/METADATA +15 -0
  2. aukeys_opscli-0.0.6.dist-info/RECORD +92 -0
  3. aukeys_opscli-0.0.6.dist-info/WHEEL +4 -0
  4. aukeys_opscli-0.0.6.dist-info/entry_points.txt +2 -0
  5. opscli/__init__.py +9 -0
  6. opscli/amazon/__init__.py +13 -0
  7. opscli/amazon/cli.py +5 -0
  8. opscli/amazon/client.py +5 -0
  9. opscli/amazon/commands/__init__.py +1 -0
  10. opscli/amazon/commands/cli.py +200 -0
  11. opscli/amazon/domain/__init__.py +25 -0
  12. opscli/amazon/domain/exceptions.py +73 -0
  13. opscli/amazon/domain/models.py +74 -0
  14. opscli/amazon/exceptions.py +21 -0
  15. opscli/amazon/manager.py +5 -0
  16. opscli/amazon/models.py +5 -0
  17. opscli/amazon/parser.py +5 -0
  18. opscli/amazon/scraper.py +5 -0
  19. opscli/amazon/scraping/__init__.py +12 -0
  20. opscli/amazon/scraping/parser.py +66 -0
  21. opscli/amazon/scraping/scraper.py +315 -0
  22. opscli/amazon/services/__init__.py +5 -0
  23. opscli/amazon/services/manager.py +105 -0
  24. opscli/amazon/transport/__init__.py +5 -0
  25. opscli/amazon/transport/client.py +77 -0
  26. opscli/auth/__init__.py +79 -0
  27. opscli/auth/cli.py +255 -0
  28. opscli/auth/commands/__init__.py +1 -0
  29. opscli/auth/commands/cli.py +5 -0
  30. opscli/auth/config.py +55 -0
  31. opscli/auth/core/__init__.py +1 -0
  32. opscli/auth/core/device_flow.py +80 -0
  33. opscli/auth/core/system_registry.py +89 -0
  34. opscli/auth/core/token_manager.py +167 -0
  35. opscli/auth/domain/__init__.py +23 -0
  36. opscli/auth/domain/exceptions.py +33 -0
  37. opscli/auth/exceptions.py +23 -0
  38. opscli/auth/storage/__init__.py +1 -0
  39. opscli/auth/storage/credential_store.py +163 -0
  40. opscli/auth/storage/crypto.py +67 -0
  41. opscli/cli.py +36 -0
  42. opscli/config.py +3 -0
  43. opscli/query/__init__.py +6 -0
  44. opscli/query/cli.py +5 -0
  45. opscli/query/client.py +5 -0
  46. opscli/query/commands/__init__.py +1 -0
  47. opscli/query/commands/cli.py +140 -0
  48. opscli/query/domain/__init__.py +23 -0
  49. opscli/query/domain/exceptions.py +73 -0
  50. opscli/query/domain/models.py +21 -0
  51. opscli/query/exceptions.py +21 -0
  52. opscli/query/manager.py +5 -0
  53. opscli/query/models.py +5 -0
  54. opscli/query/services/__init__.py +5 -0
  55. opscli/query/services/manager.py +373 -0
  56. opscli/query/transport/__init__.py +5 -0
  57. opscli/query/transport/client.py +57 -0
  58. opscli/skills/__init__.py +37 -0
  59. opscli/skills/cli.py +5 -0
  60. opscli/skills/commands/__init__.py +1 -0
  61. opscli/skills/commands/cli.py +389 -0
  62. opscli/skills/detector.py +5 -0
  63. opscli/skills/discovery/__init__.py +5 -0
  64. opscli/skills/discovery/detector.py +225 -0
  65. opscli/skills/domain/__init__.py +23 -0
  66. opscli/skills/domain/exceptions.py +51 -0
  67. opscli/skills/domain/models.py +144 -0
  68. opscli/skills/exceptions.py +5 -0
  69. opscli/skills/manager.py +5 -0
  70. opscli/skills/models.py +19 -0
  71. opscli/skills/services/__init__.py +5 -0
  72. opscli/skills/services/manager.py +276 -0
  73. opscli/skills/sync/__init__.py +5 -0
  74. opscli/skills/sync/updater.py +274 -0
  75. opscli/skills/templates/ops-amazon/SKILL.md +181 -0
  76. opscli/skills/templates/ops-amazon/data/VERSION.json +4 -0
  77. opscli/skills/templates/ops-auth/SKILL.md +466 -0
  78. opscli/skills/templates/ops-auth/data/VERSION.json +4 -0
  79. opscli/skills/templates/ops-dataset-query/SKILL.md +691 -0
  80. opscli/skills/templates/ops-dataset-query/data/VERSION.json +4 -0
  81. opscli/skills/templates/ops-dataset-query/data/dataset_fields.csv +1 -0
  82. opscli/skills/templates/ops-dataset-query/data/datasets.csv +1 -0
  83. opscli/skills/templates/ops-dataset-query/data/query_metadata.json +4 -0
  84. opscli/skills/templates/ops-dataset-query/references//346/225/260/346/215/256/346/237/245/350/257/242/346/234/215/345/212/241/345/274/200/345/217/221/350/257/264/346/230/216/346/226/207/346/241/243.md +1126 -0
  85. opscli/skills/templates/ops-dataset-query/scripts/core.py +140 -0
  86. opscli/skills/templates/ops-dataset-query/scripts/query.py +145 -0
  87. opscli/skills/templates/ops-dataset-query/scripts/search.py +36 -0
  88. opscli/skills/templates/ops-dataset-query/scripts/updater.py +106 -0
  89. opscli/skills/templates/ops-skills/SKILL.md +494 -0
  90. opscli/skills/templates/ops-skills/data/VERSION.json +4 -0
  91. opscli/skills/updater.py +5 -0
  92. opscli/version.py +19 -0
@@ -0,0 +1,1126 @@
1
+ # 数据查询服务开发说明文档
2
+
3
+ > 版本:1.1 | 更新日期:2026-04-11 | 适用人员:后端开发、前端开发
4
+ >
5
+ > **v1.1 变更**:新增高级计算算法(同环比/累加/占比)、WHERE 条件 translate 翻译枚举、完整权限字段枚举表
6
+
7
+ ---
8
+
9
+ ## 目录
10
+
11
+ 1. [概述](#一概述)
12
+ 2. [请求与响应结构完整参考](#二请求与响应结构完整参考)
13
+ 3. [数据集类型详解](#三数据集类型详解)
14
+ 4. [权限控制机制](#四权限控制机制)
15
+ 5. [WHERE 条件构建指南](#五where-条件构建指南)
16
+ 6. [dataComparison 数据对比](#六datacomparison-数据对比)
17
+ 7. [多次查询场景说明](#七多次查询场景说明)
18
+ 8. [SELECT 字段开发规范](#八select-字段开发规范)
19
+ 9. [分页与排序规范](#九分页与排序规范)
20
+ 10. [开发注意事项](#十开发注意事项)
21
+
22
+ > **快速导航**:[同环比 MOY](#841-同环比moy) | [累加 ACC](#842-累加acc) | [占比 PPT](#843-占比ppt) | [translate 翻译枚举](#54-translate-条件字段翻译枚举) | [权限字段枚举](#44-权限字段完整枚举)
23
+
24
+ ---
25
+
26
+ ## 一、概述
27
+
28
+ ### 1.1 服务定位
29
+
30
+ 数据查询服务(Data Query Service)是北极星运营系统的核心数据检索层,负责将前端图表配置(如柱状图、折线图、交叉表、指标卡等)转换为结构化的 Python API 请求,并将查询结果返回给前端渲染。
31
+
32
+ PHP 层(`QueryBuilder`)承担以下职责:
33
+
34
+ - 解析图表配置,拼装 Python API 所需的请求体
35
+ - 根据图表类型决定是否发起多次查询(如交叉表的小计/总计、堆叠图的两阶段查询等)
36
+ - 合并多次查询结果,组装最终数据结构
37
+
38
+ Python 服务承担以下职责:
39
+
40
+ - 接收结构化查询请求,生成并执行 SQL
41
+ - 处理权限占位符替换
42
+ - 处理 dataComparison(数据对比)的 SQL 改写
43
+ - 返回查询结果和执行元信息
44
+
45
+ ### 1.2 支持的数据源
46
+
47
+ | 数据源标识 | 说明 |
48
+ |------------|------|
49
+ | `doris_analytics` | Apache Doris 分析型数据库(主力数据源) |
50
+
51
+ 数据源标识通过请求体的 `dataSource` 字段传入,Python 服务根据该值选择对应的数据库连接。
52
+
53
+ ### 1.3 认证方式
54
+
55
+ Python API 支持两种认证方式(二选一):
56
+
57
+ | 认证方式 | 请求头 | 示例 |
58
+ |----------|--------|------|
59
+ | Bearer Token | `Authorization: Bearer <token>` | `Authorization: Bearer eyJhbGci...` |
60
+ | API Key | `X-API-Key: <key>` | `X-API-Key: sk-prod-xxxxx` |
61
+
62
+ ### 1.4 基础信息
63
+
64
+ | 项目 | 说明 |
65
+ |------|------|
66
+ | 基础 URL | `http://localhost:8000` |
67
+ | 核心查询端点 | `POST /api/v1/query` |
68
+ | Content-Type | `application/json` |
69
+
70
+ ---
71
+
72
+ ## 二、请求与响应结构完整参考
73
+
74
+ ### 2.1 请求体完整结构
75
+
76
+ ```json
77
+ {
78
+ // ============ 顶层字段 ============
79
+ "userEmail": "zhangsan@example.com", // 用户邮箱,用于权限控制和审计追踪(必填)
80
+ "dataSource": "doris_analytics", // 数据源标识符(必填)
81
+ "dryRun": false, // 干运行模式:true 时仅生成 SQL 不执行,用于调试
82
+
83
+ // ============ 核心查询配置 ============
84
+ "query": {
85
+
86
+ // --- FROM 子句配置 ---
87
+ "from": {
88
+ "table": "string", // 表名或子查询 SQL 字符串(必填,详见第三章)
89
+ "alias": "string", // 数据集别名,格式通常为 ds_[随机哈希](必填)
90
+ "database": "string", // 数据库名,子查询时传空字符串 ""(普通表时传库名)
91
+ "permission": ["string"] // 权限控制维度数组,如 ["channel_uuid","listing_uuid"]
92
+ },
93
+
94
+ // --- SELECT 字段配置 ---
95
+ "select": [
96
+ {
97
+ "expr": "string", // 字段表达式或完整计算表达式(必填)
98
+ "alias": "string", // 字段别名,格式为 f_[随机哈希](必填)
99
+ "aggregation": "string" // 聚合函数(可选),如 SUM/COUNT/AVG/MAX/MIN/DISTINCT_COUNT
100
+ }
101
+ ],
102
+
103
+ // --- JOIN 配置(可选)---
104
+ "joins": [
105
+ {
106
+ "type": "LEFT", // JOIN 类型:LEFT / RIGHT / INNER / FULL
107
+ "table": "string", // 被 JOIN 的表名或子查询
108
+ "alias": "string", // JOIN 表别名
109
+ "on": "string" // ON 条件表达式
110
+ }
111
+ ],
112
+
113
+ // --- WHERE 外层条件(非子查询类型用此字段,子查询类型的外层日期条件也在此)---
114
+ "where": {
115
+ "operator": "AND", // 逻辑操作符:AND | OR
116
+ "conditions": [
117
+ // 叶子节点(过滤条件)
118
+ {
119
+ "field": "ds_xxx.date_id",
120
+ "operator": "between",
121
+ "value": ["2026-03-13", "2026-04-11"]
122
+ },
123
+ // 逻辑节点(嵌套分组)
124
+ {
125
+ "operator": "OR",
126
+ "conditions": [
127
+ { "field": "ds_xxx.platform_name", "operator": "eq", "value": "Amazon" },
128
+ { "field": "ds_xxx.platform_name", "operator": "eq", "value": "eBay" }
129
+ ]
130
+ }
131
+ ]
132
+ },
133
+
134
+ // --- innerWhere:子查询内层条件(仅子查询类型使用)---
135
+ // 数组顺序对应子查询嵌套层级(从外到内)
136
+ "innerWhere": [
137
+ {}, // 第一层(最外层子查询),通常为空对象
138
+ { // 第二层(最内层原始表),填写业务维度过滤条件
139
+ "operator": "AND",
140
+ "conditions": [
141
+ { "field": "bc.platform_name", "operator": "in", "value": ["Amazon"] }
142
+ ]
143
+ }
144
+ ],
145
+
146
+ // --- GROUP BY(用 alias 引用)---
147
+ "groupBy": ["f_xxx_alias_1", "f_xxx_alias_2"],
148
+
149
+ // --- HAVING 条件(可选)---
150
+ "having": [
151
+ { "field": "f_xxx_alias", "operator": "gt", "value": 0 }
152
+ ],
153
+
154
+ // --- ORDER BY ---
155
+ "orderBy": [
156
+ { "expr": "f_xxx_alias", "desc": false } // expr 为 select 中的 alias
157
+ ],
158
+
159
+ // --- 分页 ---
160
+ "limit": 20,
161
+ "offset": 0,
162
+
163
+ // --- 缓存控制 ---
164
+ "cacheControl": {
165
+ "enabled": true, // 是否启用缓存
166
+ "forceRefresh": false, // 是否强制刷新缓存
167
+ "ttl": 300 // 缓存时间(秒)
168
+ }
169
+ },
170
+
171
+ // ============ 数据对比配置(同/环比,可选)============
172
+ "dataComparison": {
173
+ "switch": true, // true 开启对比
174
+ "field": "ds_xxx.date_id", // 日期字段(数据集别名.date_id)
175
+ "startDate": "2026-02-11", // 对比期开始日期
176
+ "endDate": "2026-03-12" // 对比期结束日期
177
+ }
178
+ }
179
+ ```
180
+
181
+ ### 2.2 响应体结构
182
+
183
+ ```json
184
+ {
185
+ "success": true, // 查询是否成功
186
+
187
+ // 查询结果数组,每个元素为一行数据,key 为 select 中指定的 alias
188
+ "data": [
189
+ {
190
+ "f_f3969fbc48264125": "OR-C",
191
+ "f_754ed2fb474f09f9": "115654.6169",
192
+ "last_f_754ed2fb474f09f9": "108601.5413", // dataComparison 开启时裂变的上期字段
193
+ "diff_f_754ed2fb474f09f9": "7053.0756", // 绝对差值字段
194
+ "pct_f_754ed2fb474f09f9": "0.0649" // 环比变化率字段
195
+ }
196
+ ],
197
+
198
+ // 查询元信息
199
+ "meta": {
200
+ "dataSource": "doris_analytics", // 数据源
201
+ "queryId": "q_abc123", // 查询唯一 ID(用于追踪)
202
+ "timestamp": "2026-04-11T08:00:00Z",// 查询时间
203
+ "executionTimeMs": 1045, // SQL 执行耗时(毫秒)
204
+ "rowCount": 20, // 本次返回的行数
205
+ "totalCount": 652, // 满足条件的总行数(分页时使用)
206
+ "generatedSql": "SELECT ...", // 生成的 SQL(调试用)
207
+ "dialect": "doris", // SQL 方言
208
+ "cached": false // 是否命中缓存
209
+ },
210
+
211
+ // 错误信息(success 为 false 时有值)
212
+ "error": {
213
+ "code": "VALIDATION_ERROR",
214
+ "message": "字段 from.table 不能为空"
215
+ }
216
+ }
217
+ ```
218
+
219
+ ---
220
+
221
+ ## 三、数据集类型详解
222
+
223
+ 数据集类型决定了 `from.table` 的结构,以及过滤条件应放在 `where` 还是 `innerWhere` 中。判断依据是 `from.table` 的内容中是否包含内层 WHERE 占位符。
224
+
225
+ ### 3.1 判断方法
226
+
227
+ ```
228
+ from.table 中含有 {where_sub_placeholder_N} 或 {and_sub_placeholder_N}
229
+ → 子查询类型(inner_where_enabled = true)→ 使用 innerWhere
230
+
231
+ from.table 中只含有 {permission_placeholder_N},没有上述占位符
232
+ → 非子查询类型(标准模式)→ 只使用 where
233
+ ```
234
+
235
+ ### 3.2 非子查询类型(标准模式)
236
+
237
+ **特征**:`from.table` 是一个封装好的视图或子查询 SQL,内部仅含权限占位符,**不含** `{where_sub_placeholder_N}` 或 `{and_sub_placeholder_N}`。
238
+
239
+ **过滤条件**:全部放在外层 `where` 中,包括维度过滤和日期范围过滤。
240
+
241
+ **完整示例**:
242
+
243
+ ```json
244
+ {
245
+ "query": {
246
+ "from": {
247
+ "table": "(SELECT bc.channel_uuid AS 'channel_uuid', bc.platform_name AS 'platform_name', npds.date_id AS 'date_id', npds.price AS 'price' FROM core_data.smarty_sale_stat AS npds LEFT JOIN core_data.bi_channel AS bc ON npds.channel_id = bc.channel_id WHERE bc.aukey_account_id > 0 AND (bc.channel_uuid IN({permission_placeholder_1}) OR bc.listing_uuid IN({permission_placeholder_2})))",
248
+ "database": "",
249
+ "alias": "ds_d35ac6f3910c",
250
+ "permission": ["channel_uuid", "listing_uuid"]
251
+ },
252
+ "select": [
253
+ { "expr": "ds_d35ac6f3910c.platform_name", "alias": "f_dim001" },
254
+ { "expr": "ds_d35ac6f3910c.price", "alias": "f_metric001", "aggregation": "SUM" }
255
+ ],
256
+ "where": {
257
+ "operator": "AND",
258
+ "conditions": [
259
+ { "field": "ds_d35ac6f3910c.platform_name", "operator": "in", "value": ["Amazon"] },
260
+ { "field": "ds_d35ac6f3910c.country_name", "operator": "in", "value": ["美国"] },
261
+ { "field": "ds_d35ac6f3910c.date_id", "operator": "between", "value": ["2026-03-13", "2026-04-11"] }
262
+ ]
263
+ },
264
+ "groupBy": ["f_dim001"],
265
+ "limit": 20,
266
+ "offset": 0
267
+ },
268
+ "dataSource": "doris_analytics",
269
+ "userEmail": "zhangsan@example.com"
270
+ }
271
+ ```
272
+
273
+ ### 3.3 子查询类型(inner_where_enabled = true)
274
+
275
+ **特征**:`from.table` 是多层嵌套的 SQL 字符串,内部含有以下占位符:
276
+
277
+ | 占位符 | 说明 | 对应 innerWhere 位置 |
278
+ |--------|------|---------------------|
279
+ | `{where_sub_placeholder_1}` | 第一层子查询的 WHERE 位置(注意:此处 PHP 实际插入的是 `WHERE 条件` 语句) | `innerWhere[0]` |
280
+ | `{and_sub_placeholder_2}` | 第二层 AND 条件位置 | `innerWhere[1]` |
281
+ | `{permission_placeholder_1}` | 权限条件 1(由 `from.permission[0]` 驱动) | 由 Python 服务内部替换 |
282
+ | `{permission_placeholder_2}` | 权限条件 2(由 `from.permission[1]` 驱动) | 由 Python 服务内部替换 |
283
+
284
+ **innerWhere 层级对应关系**:
285
+
286
+ ```
287
+ innerWhere[0] → 对应 {where_sub_placeholder_1}(外层子查询,通常传空 [])
288
+ innerWhere[1] → 对应 {and_sub_placeholder_2}(内层原始表,填写业务过滤条件)
289
+ ```
290
+
291
+ > 注意:`innerWhere[0]` 即使是外层子查询条件,也通常传空对象 `{}` 或空数组 `[]`,实际业务过滤放在 `innerWhere[1]` 中。
292
+
293
+ **where 与 innerWhere 同时使用**:
294
+
295
+ - `where` 中放日期范围条件(translate 字段,如 `date_id between ...`)
296
+ - `innerWhere` 中放各层的业务维度过滤条件
297
+
298
+ **完整示例**:
299
+
300
+ ```json
301
+ {
302
+ "query": {
303
+ "from": {
304
+ "table": "(SELECT bc.channel_uuid AS 'channel_uuid', bc.platform_name AS 'platform_name', SUM(ads.spend) AS 'ads_spend' FROM business_data.advertising_list {where_sub_placeholder_1} GROUP BY bc.channel_uuid, bc.platform_name) AS bdal LEFT JOIN core_data.bi_channel AS bc ON bdal.channel_uuid = bc.channel_uuid WHERE bdal.dept_id NOT IN('ac','sup','fh') AND bc.aukey_account_id > 0 {and_sub_placeholder_2} AND (bc.channel_uuid IN({permission_placeholder_1}) OR bc.listing_uuid IN({permission_placeholder_2}))",
305
+ "database": "",
306
+ "alias": "ds_0759e20F0DrG",
307
+ "permission": ["channel_uuid", "listing_uuid"]
308
+ },
309
+ "select": [
310
+ { "expr": "ds_0759e20F0DrG.platform_name", "alias": "f_dim001" },
311
+ { "expr": "ds_0759e20F0DrG.ads_spend", "alias": "f_metric001", "aggregation": "SUM" }
312
+ ],
313
+ "innerWhere": [
314
+ [], // innerWhere[0]:对应 where_sub_placeholder_1,此处为空
315
+ { // innerWhere[1]:对应 and_sub_placeholder_2,填写业务条件
316
+ "operator": "AND",
317
+ "conditions": [
318
+ { "field": "bc.platform_name", "operator": "in", "value": ["Amazon"] },
319
+ { "field": "bc.country_name", "operator": "in", "value": ["美国"] }
320
+ ]
321
+ }
322
+ ],
323
+ "where": { // 外层 where:放日期条件(translate 逻辑)
324
+ "conditions": [
325
+ { "field": "ds_0759e20F0DrG.date_id", "operator": "between", "value": ["2026-03-13", "2026-04-11"] }
326
+ ],
327
+ "operator": "AND"
328
+ },
329
+ "groupBy": ["f_dim001"],
330
+ "limit": 20,
331
+ "offset": 0
332
+ },
333
+ "dataSource": "doris_analytics",
334
+ "userEmail": "zhangsan@example.com"
335
+ }
336
+ ```
337
+
338
+ ### 3.4 两种类型对比总结
339
+
340
+ | 对比维度 | 非子查询类型 | 子查询类型 |
341
+ |----------|------------|-----------|
342
+ | from.table 内容 | 普通视图/封装子查询 | 多层嵌套 SQL 含内层占位符 |
343
+ | 内层占位符 | 无 | 含 `{where_sub_placeholder_N}` / `{and_sub_placeholder_N}` |
344
+ | 过滤条件位置 | 全部放 `where` | 维度过滤放 `innerWhere`,日期放 `where` |
345
+ | innerWhere 字段 | 不传 | 必传,数组顺序对应嵌套层级 |
346
+ | database 字段 | 普通表时传库名 | 子查询时传空字符串 `""` |
347
+
348
+ ---
349
+
350
+ ## 四、权限控制机制
351
+
352
+ ### 4.1 permission 字段说明
353
+
354
+ `from.permission` 是一个字符串数组,声明该数据集的权限控制维度:
355
+
356
+ | 维度值 | 含义 |
357
+ |--------|------|
358
+ | `channel_uuid` | 店铺级权限(按渠道 UUID 控制) |
359
+ | `listing_uuid` | 商品级权限(按 listing 复合主键 UUID 控制) |
360
+
361
+ ```json
362
+ "permission": ["channel_uuid", "listing_uuid"]
363
+ ```
364
+
365
+ ### 4.2 占位符替换原理
366
+
367
+ Python 服务在执行 SQL 前,会将 `from.table` 中的权限占位符替换为子查询,从权限表中动态获取当前用户被授权的数据范围:
368
+
369
+ | 占位符 | 替换为 |
370
+ |--------|--------|
371
+ | `{permission_placeholder_1}` | `SELECT DISTINCT auth_value FROM base.v_user_permission_flat WHERE user_email = '<userEmail>' AND field_type = 'channel_uuid'` |
372
+ | `{permission_placeholder_2}` | `SELECT DISTINCT auth_value FROM base.v_user_permission_flat WHERE user_email = '<userEmail>' AND field_type = 'listing_uuid'` |
373
+
374
+ > `{permission_placeholder_N}` 中的 N 与 `from.permission` 数组的下标(从 1 开始)一一对应。
375
+
376
+ ### 4.3 多权限维度 OR 逻辑
377
+
378
+ 当 `from.permission` 包含两个维度时,两个权限条件使用 `OR` 连接,满足任一权限即可查看数据:
379
+
380
+ ```sql
381
+ -- 最终生成的权限 SQL 片段(示意)
382
+ AND (
383
+ bc.channel_uuid IN (SELECT DISTINCT auth_value FROM base.v_user_permission_flat WHERE user_email = 'zhangsan@example.com' AND field_type = 'channel_uuid')
384
+ OR
385
+ bc.listing_uuid IN (SELECT DISTINCT auth_value FROM base.v_user_permission_flat WHERE user_email = 'zhangsan@example.com' AND field_type = 'listing_uuid')
386
+ )
387
+ ```
388
+
389
+ ### 4.4 权限字段完整枚举
390
+
391
+ `from.permission` 数组中可传入的权限字段枚举值如下(对应 `base.v_user_permission_flat` 表中的 `field_type`):
392
+
393
+ | 分组 | 字段值 | 字段名称 | 备注 |
394
+ |------|--------|---------|------|
395
+ | 全渠道 | `dept_id` | 部门 ID | — |
396
+ | 全渠道 | `channel_uuid` | 渠道 UUID | 生成规则:部门 + 平台 + 佰易账号 + 国家 |
397
+ | 全渠道 | `channel_code` | 渠道 CODE | 生成规则:平台 + 佰易账号 + 国家 |
398
+ | 部分渠道 | `listing_uuid` | LISTING UUID | 生成规则:平台 + 佰易账号 + 国家 + 渠道SKU |
399
+ | 部分渠道 | `listing_uuid_share` | 共享 LISTING UUID | 佰易数据集使用;生成规则:平台 + 佰易账号 + 国家 + 渠道SKU |
400
+ | 部分渠道 | `asin_uuid` | ASIN UUID | 生成规则:平台 + 佰易账号 + 国家 + ASIN |
401
+ | 部分渠道 | `asin_uuid_share` | 共享 ASIN UUID | 佰易数据集使用;生成规则:平台 + 佰易账号 + 国家 + ASIN |
402
+ | 部分渠道 | `ed_sku` | 公司 SKU | — |
403
+ | 部分渠道 | `team_uuid` | 销售小组 UUID | 生成规则:部门 + 大组 + 销售小组 |
404
+ | 部分渠道 | `dev_team_uuid` | 开发小组 UUID | 生成规则:部门 + 大组 + 开发小组 |
405
+ | 部分渠道 | `asin_ps_uuid` | ASIN PS UUID | 运营监控数据集(爬虫数据集)使用;生成规则:平台 + 国家 + ASIN |
406
+
407
+ > **说明**:"全渠道"字段控制整个渠道的数据访问;"部分渠道"字段控制渠道内更细粒度(SKU/ASIN/团队)的数据访问。多个字段同时使用时,Python 服务将生成 OR 逻辑的子查询。
408
+
409
+ ---
410
+
411
+ ## 五、WHERE 条件构建指南
412
+
413
+ ### 5.1 操作符完整列表
414
+
415
+ | 操作符 | SQL 语义 | value 类型 | 示例 |
416
+ |--------|----------|-----------|------|
417
+ | `eq` | `=` | string / number | `{ "field": "platform", "operator": "eq", "value": "Amazon" }` |
418
+ | `ne` | `!=` | string / number | `{ "field": "status", "operator": "ne", "value": 0 }` |
419
+ | `gt` | `>` | number | `{ "field": "price", "operator": "gt", "value": 100 }` |
420
+ | `gte` | `>=` | number | `{ "field": "price", "operator": "gte", "value": 100 }` |
421
+ | `lt` | `<` | number | `{ "field": "price", "operator": "lt", "value": 1000 }` |
422
+ | `lte` | `<=` | number | `{ "field": "price", "operator": "lte", "value": 1000 }` |
423
+ | `like` | `LIKE` | string(含 `%` 通配符) | `{ "field": "name", "operator": "like", "value": "%iphone%" }` |
424
+ | `not_like` | `NOT LIKE` | string | - |
425
+ | `in` | `IN` | array | `{ "field": "country", "operator": "in", "value": ["美国","英国"] }` |
426
+ | `not_in` | `NOT IN` | array | `{ "field": "status", "operator": "not_in", "value": [0, -1] }` |
427
+ | `between` | `BETWEEN` | `[min, max]` 二元数组 | `{ "field": "date_id", "operator": "between", "value": ["2026-01-01","2026-03-31"] }` |
428
+ | `not_between` | `NOT BETWEEN` | `[min, max]` 二元数组 | - |
429
+ | `is_null` | `IS NULL` | 无需传 value | `{ "field": "remark", "operator": "is_null" }` |
430
+ | `is_not_null` | `IS NOT NULL` | 无需传 value | `{ "field": "remark", "operator": "is_not_null" }` |
431
+ | `regexp` | `REGEXP` | string(正则表达式) | `{ "field": "sku", "operator": "regexp", "value": "^A[0-9]{4}" }` |
432
+ | `not_regexp` | `NOT REGEXP` | string | - |
433
+
434
+ ### 5.2 树形嵌套结构规范
435
+
436
+ WHERE 条件支持任意层级的树形嵌套,节点分为两类:
437
+
438
+ **逻辑节点**(分组节点):
439
+
440
+ ```json
441
+ {
442
+ "operator": "AND", // 必填:AND | OR
443
+ "conditions": [...], // 必填:子节点数组(可以是逻辑节点或叶子节点)
444
+ "negate": false, // 可选:true 时对整个节点取反(NOT)
445
+ "case_sensitive": true // 可选:false 时字符串比较大小写不敏感
446
+ }
447
+ ```
448
+
449
+ > 逻辑节点不能包含 `field` 属性。
450
+
451
+ **叶子节点**(条件节点):
452
+
453
+ ```json
454
+ {
455
+ "field": "ds_xxx.platform_name", // 必填:字段名(含数据集别名)
456
+ "operator": "in", // 必填:操作符(见上表)
457
+ "value": ["Amazon", "eBay"], // 按操作符要求传值
458
+ "negate": false, // 可选:取反
459
+ "case_sensitive": true // 可选:大小写敏感
460
+ }
461
+ ```
462
+
463
+ > 叶子节点不能包含 `conditions` 属性。
464
+
465
+ **嵌套示例(AND 中含 OR 子组)**:
466
+
467
+ ```json
468
+ {
469
+ "operator": "AND",
470
+ "conditions": [
471
+ { "field": "ds_xxx.date_id", "operator": "between", "value": ["2026-03-13", "2026-04-11"] },
472
+ {
473
+ "operator": "OR",
474
+ "conditions": [
475
+ { "field": "ds_xxx.platform_name", "operator": "eq", "value": "Amazon" },
476
+ { "field": "ds_xxx.platform_name", "operator": "eq", "value": "eBay" }
477
+ ]
478
+ },
479
+ { "field": "ds_xxx.order_status", "operator": "not_in", "value": [-1, 0] }
480
+ ]
481
+ }
482
+ ```
483
+
484
+ ### 5.3 translate 条件字段翻译枚举
485
+
486
+ 在 WHERE 条件的叶子节点中,可以额外传入 `translate` 字段,指示 Python 服务在执行过滤前将用户输入的值转换为另一种 ID 类型。例如,用户选择"渠道名称"过滤时,后端需将渠道名称翻译为对应的 ASIN 或 SKU 后再过滤底层数据。
487
+
488
+ **带 translate 的叶子节点示例**:
489
+
490
+ ```json
491
+ {
492
+ "field": "ds_d35ac6f3910c.channel_name",
493
+ "operator": "in",
494
+ "translate": "SKU_TO_ASIN",
495
+ "value": ["Ohwill-SF-美国", "Onbrill-SF-美国", "CP-美国"]
496
+ }
497
+ ```
498
+
499
+ **translate 枚举值完整列表**:
500
+
501
+ | 过滤字段(field_name)| translate 枚举值 | 含义 |
502
+ |----------------------|-----------------|------|
503
+ | `date_id` | — | 日期字段,无需翻译 |
504
+ | `platform_name` | `PLATFORM_TO_SKU` | 平台 → 公司 SKU |
505
+ | `country_name` | `COUNTRY_TO_SKU` | 国家 → 公司 SKU |
506
+ | `channel_name` | `CHANNEL_TO_SKU` | 渠道 → 公司 SKU |
507
+ | `team_name` | `TEAM_TO_SKU` | 销售小组 → 公司 SKU |
508
+ | `team_username` | `TEAM_USER_TO_SKU` | 销售人员 → 公司 SKU |
509
+ | `develop_username` | `DEVELOP_USER_TO_SKU` | 开发人员 → 公司 SKU |
510
+ | `asin` | `ASIN_TO_MSKU` | ASIN → 渠道 SKU |
511
+ | `asin` | `ASIN_TO_SKU` | ASIN → 公司 SKU |
512
+ | `ed_sku`(公司SKU)| `SKU_TO_ASIN` | 公司 SKU → ASIN |
513
+ | `ed_sku`(公司SKU)| `SKU_TO_MSKU` | 公司 SKU → 渠道 SKU |
514
+ | `sell_sku`(渠道SKU)| `MSKU_TO_ASIN` | 渠道 SKU → ASIN |
515
+ | `sell_sku`(渠道SKU)| `MSKU_TO_SKU` | 渠道 SKU → 公司 SKU |
516
+ | `product_name` | `PRODUCT_NAME_TO_ASIN` | 产品名称 → ASIN |
517
+ | `product_name` | `PRODUCT_NAME_TO_MSKU` | 产品名称 → 渠道 SKU |
518
+ | `model` | `MODEL_TO_ASIN` | 产品型号 → ASIN |
519
+ | `model` | `MODEL_TO_MSKU` | 产品型号 → 渠道 SKU |
520
+
521
+ > **注意**:`translate` 是可选字段。当过滤值本身就是底层表字段的原始值时(如直接按 `date_id` 过滤日期),无需传 translate。
522
+
523
+ ### 5.4 小计/总计查询中的 WHERE 特殊处理
524
+
525
+ 当交叉表或透视表做小计、总计的补充查询时,PHP 层会在原始 WHERE 基础上追加**当前分页行维度的 IN 条件**,以确保结果仅涵盖当前页的维度值:
526
+
527
+ ```json
528
+ {
529
+ "operator": "AND",
530
+ "conditions": [
531
+ {
532
+ "...原始 WHERE 条件(日期、维度等)..."
533
+ },
534
+ {
535
+ "operator": "AND",
536
+ "negate": false,
537
+ "case_sensitive": true,
538
+ "conditions": [
539
+ {
540
+ "field": "ds_xxx.large_team_name",
541
+ "operator": "in",
542
+ "value": ["OR-C", "OR-E"] // 当前页出现的大团队名
543
+ },
544
+ {
545
+ "field": "ds_xxx.develop_username",
546
+ "operator": "in",
547
+ "value": ["向建权", "张茜荛"] // 当前页出现的开发人员名
548
+ }
549
+ ]
550
+ }
551
+ ]
552
+ }
553
+ ```
554
+
555
+ ---
556
+
557
+ ## 六、dataComparison 数据对比
558
+
559
+ ### 6.1 基本原理
560
+
561
+ 当 `dataComparison.switch = true` 时,Python 服务不会执行两次独立查询,而是将当期和对比期的数据**合并到一次 SQL** 中执行。
562
+
563
+ 核心机制是通过条件聚合(Conditional Aggregation)实现:
564
+
565
+ ```sql
566
+ -- Python 生成的对比 SQL 核心逻辑(示意)
567
+ SELECT
568
+ ds_xxx.large_team_name AS f_dim001,
569
+ -- 当期聚合(只累加日期在当期范围内的行)
570
+ SUM(IF(ds_xxx.date_id BETWEEN '2026-03-13' AND '2026-04-11', ds_xxx.price, 0)) AS f_metric001,
571
+ -- 对比期聚合(只累加日期在对比期范围内的行)
572
+ SUM(IF(ds_xxx.date_id BETWEEN '2026-02-11' AND '2026-03-12', ds_xxx.price, 0)) AS last_f_metric001
573
+ FROM (...) AS ds_xxx
574
+ WHERE
575
+ -- WHERE 日期范围自动扩展为当期 OR 对比期的并集
576
+ (ds_xxx.date_id BETWEEN '2026-03-13' AND '2026-04-11'
577
+ OR ds_xxx.date_id BETWEEN '2026-02-11' AND '2026-03-12')
578
+ GROUP BY f_dim001
579
+ ```
580
+
581
+ ### 6.2 字段裂变规则
582
+
583
+ 开启 dataComparison 后,每个度量字段(指标字段)会自动裂变为 4 个字段:
584
+
585
+ | 字段名格式 | 含义 | 说明 |
586
+ |-----------|------|------|
587
+ | `f_xxx` | 当期值 | 原始别名,主查询当期聚合结果 |
588
+ | `last_f_xxx` | 上期值 | 对比期聚合结果 |
589
+ | `diff_f_xxx` | 绝对差值 | `f_xxx - last_f_xxx` |
590
+ | `pct_f_xxx` | 环比变化率 | `(f_xxx - last_f_xxx) / ABS(last_f_xxx)`;当上期为 0 时返回 `null` |
591
+
592
+ > 维度字段(groupBy 中的字段)不会裂变,只有度量字段(有 aggregation 的字段)才会裂变。
593
+
594
+ ### 6.3 date 字段填写规则
595
+
596
+ | 数据集类型 | dataComparison.field 的值 |
597
+ |-----------|--------------------------|
598
+ | 非子查询类型 | `数据集别名.date_id`,如 `ds_6fbfb45edd2a.date_id` |
599
+ | 子查询类型 | 同上,`数据集别名.date_id`(日期过滤通过外层 `where` 的 translate 逻辑处理) |
600
+
601
+ ### 6.4 配置完整示例
602
+
603
+ **请求(交叉表开启数据对比)**:
604
+
605
+ ```json
606
+ {
607
+ "query": {
608
+ "from": {
609
+ "table": "(...子查询SQL...)",
610
+ "database": "",
611
+ "alias": "ds_6fbfb45edd2a",
612
+ "permission": ["channel_uuid", "listing_uuid"]
613
+ },
614
+ "select": [
615
+ {
616
+ "expr": "ds_6fbfb45edd2a.large_team_name",
617
+ "alias": "f_f3969fbc48264125"
618
+ },
619
+ {
620
+ "expr": "ds_6fbfb45edd2a.price",
621
+ "alias": "f_754ed2fb474f09f9",
622
+ "aggregation": "SUM"
623
+ }
624
+ ],
625
+ "groupBy": ["f_f3969fbc48264125"],
626
+ "where": {
627
+ "conditions": [
628
+ {
629
+ "field": "ds_6fbfb45edd2a.date_id",
630
+ "operator": "between",
631
+ "value": ["2026-03-13", "2026-04-11"]
632
+ }
633
+ ],
634
+ "operator": "AND"
635
+ },
636
+ "limit": 20,
637
+ "offset": 0
638
+ },
639
+ "dataSource": "doris_analytics",
640
+ "userEmail": "zhangpeiliang@aukeys.com",
641
+ "dataComparison": {
642
+ "switch": true,
643
+ "field": "ds_6fbfb45edd2a.date_id", // 数据集别名.date_id
644
+ "startDate": "2026-02-11", // 对比期开始
645
+ "endDate": "2026-03-12" // 对比期结束
646
+ }
647
+ }
648
+ ```
649
+
650
+ **响应数据样例(裂变字段)**:
651
+
652
+ ```json
653
+ {
654
+ "success": true,
655
+ "data": [
656
+ {
657
+ "f_f3969fbc48264125": "OR-C",
658
+ "f_754ed2fb474f09f9": "115654.6169", // 当期值
659
+ "last_f_754ed2fb474f09f9": "108601.5413", // 上期值
660
+ "diff_f_754ed2fb474f09f9": "7053.0756", // 绝对差值
661
+ "pct_f_754ed2fb474f09f9": "0.0649" // 环比变化率(约 6.49%)
662
+ },
663
+ {
664
+ "f_f3969fbc48264125": "OR-E",
665
+ "f_754ed2fb474f09f9": "98320.1100",
666
+ "last_f_754ed2fb474f09f9": "102500.0000",
667
+ "diff_f_754ed2fb474f09f9": "-4179.8900",
668
+ "pct_f_754ed2fb474f09f9": "-0.0408" // 负值表示下降
669
+ }
670
+ ],
671
+ "meta": {
672
+ "rowCount": 2,
673
+ "totalCount": 15,
674
+ "executionTimeMs": 1045,
675
+ "cached": false
676
+ }
677
+ }
678
+ ```
679
+
680
+ ---
681
+
682
+ ## 七、多次查询场景说明
683
+
684
+ PHP 层(`QueryBuilder`)根据图表类型会对 Python API 发起多次查询,然后合并结果。
685
+
686
+ ### 7.1 多次查询汇总表
687
+
688
+ | 场景 | 适用图表类型 | Python API 调用次数 | 说明 |
689
+ |------|------------|-------------------|------|
690
+ | 标准单次查询 | 所有普通图表 | 1 | 一次 API 调用获取所有数据 |
691
+ | 堆叠图二阶段查询 | 堆叠柱状图、堆叠折线图 | 2 | 第一次取分页维度,第二次取完整堆叠数据 |
692
+ | 交叉表列枚举 | crosstab / pivot_table | 2 | 第一次枚举列维度所有组合(上限由 `CROSSTAB_COLUMN_ENUM_LIMIT` 控制,默认 10000)|
693
+ | 交叉表矩阵取数 | crosstab / pivot_table | 2 | 按行×列笛卡尔矩阵取数 |
694
+ | 行小计 | crosstab / pivot_table | +1 | 按首维度 + 列维度分组聚合 |
695
+ | 行总计 | crosstab / pivot_table | +1 | 按列维度分组或无分组 |
696
+ | 全局指标总计 | crosstab / pivot_table(有列维度)| +1 | `groupBy=[]`,`limit=1` |
697
+ | 列总计 | crosstab / pivot_table(showColTotal=true)| +1 | 按行维度分组 |
698
+ | dim_summary(维度汇总)| crosstab / pivot_table(多维 + 列维度)| +1 | 按首维度分组 |
699
+ | 右轴独立查询 | combo_bar_line* 系列 | 2 | 右轴指标仅按 xAxis 聚合,不含其他分组 |
700
+ | 指标卡汇总 | metric_trend | 2 | 第二次查询 `groupBy=[]`,`limit=1`,获取全局汇总值 |
701
+
702
+ ### 7.2 各场景 WHERE 特殊处理说明
703
+
704
+ **堆叠图二阶段查询**:
705
+ - 第一次查询:正常分页,获取当前页的维度值列表
706
+ - 第二次查询:WHERE 中追加第一次查询结果的维度 IN 条件,去掉 limit/offset
707
+
708
+ **交叉表列枚举查询**:
709
+ - 只 SELECT 列维度字段,DISTINCT 去重
710
+ - limit 由环境变量 `CROSSTAB_COLUMN_ENUM_LIMIT` 控制
711
+ - 不传 `dataComparison`(无需对比期数据)
712
+
713
+ **小计/总计补充查询**:
714
+ - WHERE 中额外追加当前页行维度的 IN 条件(详见第五章 5.3 节)
715
+ - orderBy 仅保留维度相关的排序(去掉指标排序避免结果错位)
716
+
717
+ **指标卡汇总查询**:
718
+ - `groupBy` 传空数组 `[]`
719
+ - `limit` 传 `1`
720
+ - `offset` 传 `0`
721
+
722
+ ---
723
+
724
+ ## 八、SELECT 字段开发规范
725
+
726
+ ### 8.1 两种字段格式
727
+
728
+ **格式一:简化格式(推荐,适用于普通字段 + 标准聚合)**
729
+
730
+ ```json
731
+ { "expr": "ds_xxx.price", "alias": "f_metric001", "aggregation": "SUM" }
732
+ ```
733
+
734
+ 生成 SQL:`SUM(ds_xxx.price) AS f_metric001`
735
+
736
+ **格式二:完整表达式格式(适用于复杂计算场景)**
737
+
738
+ ```json
739
+ {
740
+ "expr": "ROUND(SUM(ds_xxx.ads_total_spend_cny) / SUM(ds_xxx.ads_sales_cny), 4)",
741
+ "alias": "f_metric002"
742
+ }
743
+ ```
744
+
745
+ 生成 SQL:`ROUND(SUM(ds_xxx.ads_total_spend_cny) / SUM(ds_xxx.ads_sales_cny), 4) AS f_metric002`
746
+
747
+ > 完整表达式格式中,`aggregation` 字段不传(或传 `null`),Python 服务直接将 `expr` 透传到 SQL 中,不做额外包裹。
748
+
749
+ ### 8.2 聚合函数参考列表
750
+
751
+ | 聚合函数 | 说明 | 示例 |
752
+ |----------|------|------|
753
+ | `SUM` | 求和 | 销售额、广告花费 |
754
+ | `COUNT` | 计数 | 订单数 |
755
+ | `AVG` | 平均值 | 平均单价 |
756
+ | `MAX` | 最大值 | 最高价格 |
757
+ | `MIN` | 最小值 | 最低价格 |
758
+ | `DISTINCT_COUNT` | 去重计数(COUNT DISTINCT) | 店铺数、SKU 数 |
759
+ | `STDDEV` | 样本标准差 | |
760
+ | `STDDEV_POP` | 总体标准差 | |
761
+ | `VARIANCE` | 样本方差 | |
762
+ | `VAR_POP` | 总体方差 | |
763
+ | `MEDIAN` | 中位数 | |
764
+ | `PERCENTILE` | 百分位数 | |
765
+ | `FIRST` | 组内第一个值 | |
766
+ | `LAST` | 组内最后一个值 | |
767
+ | `GROUP_CONCAT` | 字符串拼接 | |
768
+ | `ROUND` | 四舍五入(通常在 expr 中使用) | |
769
+ | `ABS` | 绝对值 | |
770
+ | `CEIL` | 向上取整 | |
771
+ | `FLOOR` | 向下取整 | |
772
+ | `SUBSTRING` | 字符串截取 | |
773
+
774
+ ### 8.3 维度字段与指标字段区分
775
+
776
+ | 字段类型 | 是否传 aggregation | 是否出现在 groupBy | dataComparison 是否裂变 |
777
+ |----------|-------------------|-------------------|------------------------|
778
+ | 维度字段(维度) | 不传 | 出现在 groupBy | 不裂变 |
779
+ | 指标字段(度量) | 传聚合函数 | 不出现在 groupBy | 裂变为 4 个字段 |
780
+
781
+ ### 8.4 高级计算算法(comparison 字段)
782
+
783
+ 当 select 字段中包含 `comparison` 属性时,Python 服务将对该字段启用对应的高级计算算法,在 SQL 层通过窗口函数或二次聚合完成复杂运算。
784
+
785
+ 目前支持三种算法:
786
+
787
+ | `comparison` 值 | 算法名称 | 说明 |
788
+ |----------------|---------|------|
789
+ | `MOY` | 同环比 | 将当期值与历史对应期做差值/百分比计算 |
790
+ | `ACC` | 累加 | 按时间序列做滚动累计值(Running Total) |
791
+ | `PPT` | 占比 | 将当前指标值除以全局总量(Percentage of Total) |
792
+
793
+ #### 8.4.1 同环比(MOY)
794
+
795
+ **完整字段结构**:
796
+
797
+ ```json
798
+ // 前提:groupBy 数组中必须同时包含日期字段和非日期维度字段
799
+ // 例如:
800
+ // "groupBy": ["dept_name", "DATE_FORMAT(date_id, '%Y-%m-%d')"]
801
+
802
+ {
803
+ "expr": "price",
804
+ "alias": "f_692e9ad694bcd_3240",
805
+ "comparison": "MOY",
806
+ "params": {
807
+ "date": "DATE_FORMAT(date_id, '%Y-%m-%d')", // groupBy 中的日期字段(带格式)
808
+ "dim": ["dept_name"], // groupBy 中除日期外的所有维度字段
809
+ "type": "MOM_DAY", // 同环比类型枚举值(见下表)
810
+ "cacl_type": "ORIGINAL", // 计算类型:ORIGINAL/COMPARE/PERCENT
811
+ "aggregation": "SUM" // 先聚合,再做同环比计算
812
+ }
813
+ }
814
+ ```
815
+
816
+ **params 字段说明**:
817
+
818
+ | 字段 | 名称 | 说明 | 示例 |
819
+ |------|------|------|------|
820
+ | `date` | 日期字段(含格式) | 必须与 `groupBy` 中的日期格式完全一致 | `DATE_FORMAT(date_id, '%Y-%m-%d')` |
821
+ | `dim` | 非日期维度字段列表 | `groupBy` 中除日期字段之外的所有字段 | `["dept_name"]` |
822
+ | `type` | 同环比类型枚举值 | 见下方类型枚举表 | `MOM_DAY` |
823
+ | `cacl_type` | 同环比计算类型 | `ORIGINAL`=原值,`COMPARE`=差值,`PERCENT`=百分比 | `ORIGINAL` |
824
+ | `aggregation` | 聚合函数 | 先按此函数聚合,再进行同环比计算 | `SUM` |
825
+
826
+ **type 枚举值完整列表**:
827
+
828
+ | 日期粒度 | 名称 | `type` 枚举值 | groupBy 中日期格式示例 |
829
+ |---------|------|-------------|----------------------|
830
+ | 天粒度 | 日环比 | `MOM_DAY` | `DATE_FORMAT(date_id, '%Y-%m-%d')` |
831
+ | 天粒度 | 周同比 | `YOY_WEEK` | `DATE_FORMAT(date_id, '%Y-%m-%d')` |
832
+ | 天粒度 | 月同比 | `YOY_MONTH` | `DATE_FORMAT(date_id, '%Y-%m-%d')` |
833
+ | 天粒度 | 年同比 | `YOY_YEAR` | `DATE_FORMAT(date_id, '%Y-%m-%d')` |
834
+ | 周粒度 | 周环比 | `MOM_WEEK` | `DATE_FORMAT(date_id, '%x-%v')` |
835
+ | 周粒度 | 年同比 | `YOY_YEAR` | `DATE_FORMAT(date_id, '%x-%v')` |
836
+ | 月粒度 | 月环比 | `MOM_MONTH` | `DATE_FORMAT(date_id, '%Y-%m')` |
837
+ | 月粒度 | 年同比 | `YOY_YEAR` | `DATE_FORMAT(date_id, '%Y-%m')` |
838
+ | 季粒度 | 季环比 | `MOM_QUARTER` | `DATE_FORMAT(date_id, '%Y-%q')` |
839
+ | 季粒度 | 年同比 | `YOY_YEAR` | `DATE_FORMAT(date_id, '%Y-%q')` |
840
+ | 年粒度 | 年环比 | `MOM_YEAR` | `DATE_FORMAT(date_id, '%Y')` |
841
+
842
+ **交叉表 / 透视表特殊规则**:
843
+
844
+ | 场景 | 同环比处理规则 |
845
+ |------|-------------|
846
+ | 行小计(首维度为日期时) | **需要计算**同环比 |
847
+ | 行小计(首维度非日期时) | **不计算**,显示 `-` |
848
+ | 行总计 | **不计算**,显示 `-` |
849
+
850
+ **特殊备注**:
851
+
852
+ 1. 计算公式字段(自定义 expr 表达式)暂不兼容同环比,需后续开发支持。
853
+ 2. 时间维度存在多个颗粒度时(如同时有年、年月、年月日),`params.date` 传**最小颗粒度**的日期格式(如年月日),且 `groupBy` 中的时间维度也只传最小颗粒度字段。
854
+
855
+ ---
856
+
857
+ #### 8.4.2 累加(ACC)
858
+
859
+ 按时间序列做滚动累计值(Running Total):先对每行数据执行聚合函数,再在时间维度上做滚动求和。
860
+
861
+ **完整字段结构**:
862
+
863
+ ```json
864
+ {
865
+ "expr": "price",
866
+ "alias": "f_692e9ad694bcd_3240",
867
+ "comparison": "ACC",
868
+ "params": {
869
+ "dim": [], // 分组累加维度,暂不支持,固定传 []
870
+ "aggregation": "SUM" // 先聚合再累加
871
+ }
872
+ }
873
+ ```
874
+
875
+ **params 字段说明**:
876
+
877
+ | 字段 | 名称 | 说明 |
878
+ |------|------|------|
879
+ | `dim` | 分组累加维度 | 暂不支持分组累加,固定传空数组 `[]` |
880
+ | `aggregation` | 聚合函数 | 先按此函数聚合,再在时间轴上做累加 |
881
+
882
+ **交叉表 / 透视表特殊规则**:
883
+
884
+ | 场景 | 累加处理规则 |
885
+ |------|------------|
886
+ | 行小计 | **不计算**,显示 `-` |
887
+ | 行总计 | **不计算**,显示 `-` |
888
+
889
+ ---
890
+
891
+ #### 8.4.3 占比(PPT)
892
+
893
+ 将当前维度的指标值除以全局(或组内)总量,计算占比百分比。先聚合,再除以总量。
894
+
895
+ **完整字段结构**:
896
+
897
+ ```json
898
+ {
899
+ "expr": "price",
900
+ "alias": "f_692e9ad694bcd_3240",
901
+ "comparison": "PPT",
902
+ "params": {
903
+ "dim": [], // 分组占比维度,暂不支持,固定传 []
904
+ "aggregation": "SUM" // 先聚合再算占比
905
+ }
906
+ }
907
+ ```
908
+
909
+ **params 字段说明**:
910
+
911
+ | 字段 | 名称 | 说明 |
912
+ |------|------|------|
913
+ | `dim` | 分组占比维度 | 暂不支持分组占比,固定传空数组 `[]` |
914
+ | `aggregation` | 聚合函数 | 先按此函数聚合,再计算占比 |
915
+
916
+ **交叉表 / 透视表特殊规则**:
917
+
918
+ | 场景 | 占比处理规则 |
919
+ |------|------------|
920
+ | 行小计 | **需要计算**占比 |
921
+ | 行总计 | **需要计算**占比 |
922
+
923
+ > ⚠️ **注意**:PPT 与 ACC 在小计/总计处理上规则相反。PPT 的小计/总计需展示合计行的占比;ACC 则不适合在汇总行展示滚动累加值,均显示 `-`。
924
+
925
+ ---
926
+
927
+ #### 8.4.4 三种算法对比速查
928
+
929
+ | 对比维度 | MOY(同环比) | ACC(累加) | PPT(占比) |
930
+ |----------|-------------|-----------|-----------|
931
+ | `comparison` 值 | `MOY` | `ACC` | `PPT` |
932
+ | `params.date` | 必填(日期格式字段) | 不需要 | 不需要 |
933
+ | `params.dim` | 必填(非日期维度列表) | 固定 `[]` | 固定 `[]` |
934
+ | `params.type` | 必填(枚举值) | 不需要 | 不需要 |
935
+ | `params.cacl_type` | 必填 | 不需要 | 不需要 |
936
+ | `params.aggregation` | 必填 | 必填 | 必填 |
937
+ | 小计是否计算 | 首维度为日期时计算 | 不计算(显示 `-`) | 计算 |
938
+ | 总计是否计算 | 不计算(显示 `-`) | 不计算(显示 `-`) | 计算 |
939
+ | groupBy 要求 | 必须含日期字段 | 无特殊要求 | 无特殊要求 |
940
+
941
+ ---
942
+
943
+ ## 九、分页与排序规范
944
+
945
+ ### 9.1 分页参数
946
+
947
+ | 参数 | 类型 | 说明 |
948
+ |------|------|------|
949
+ | `limit` | integer | 每页返回行数 |
950
+ | `offset` | integer | 跳过行数(从 0 开始) |
951
+
952
+ ```json
953
+ // 第 1 页,每页 20 条
954
+ { "limit": 20, "offset": 0 }
955
+
956
+ // 第 2 页,每页 20 条
957
+ { "limit": 20, "offset": 20 }
958
+ ```
959
+
960
+ **不传分页的场景**(矩阵查询、小计/总计补充查询):不传 `limit` 和 `offset` 字段,Python 服务返回全量数据。
961
+
962
+ ### 9.2 排序规范
963
+
964
+ ```json
965
+ "orderBy": [
966
+ { "expr": "f_metric001", "desc": true }, // 按指标降序
967
+ { "expr": "f_dim001", "desc": false } // 按维度升序
968
+ ]
969
+ ```
970
+
971
+ **规范要点**:
972
+
973
+ - `expr` 的值必须是 `select` 中某个字段的 `alias`,不能使用原始字段名
974
+ - 多个排序条件按数组顺序依次生效(先排第一个,再排第二个)
975
+ - `desc: true` 表示降序(DESC),`desc: false` 表示升序(ASC)
976
+ - 小计/总计查询中,orderBy 仅保留与维度字段对应的排序条件(指标排序会被自动移除)
977
+
978
+ ---
979
+
980
+ ## 十、开发注意事项
981
+
982
+ ### 10.1 alias 命名规范
983
+
984
+ PHP 端生成字段别名的规则如下:
985
+
986
+ - **格式**:`f_[随机哈希]`,例如 `f_XdPACWQYZuZBZTBGN9ZL`、`f_754ed2fb474f09f9`
987
+ - **维度字段和指标字段**使用相同的命名规则,均为 `f_` 前缀
988
+ - **dataComparison 裂变字段**命名规则(以原始别名 `f_xxx` 为例):
989
+ - `last_f_xxx`:上期值
990
+ - `diff_f_xxx`:绝对差值
991
+ - `pct_f_xxx`:环比变化率
992
+
993
+ > 开发时不要在业务逻辑中硬编码 alias 值,应以图表配置的 fieldId 或字段映射关系来识别字段。
994
+
995
+ ### 10.2 子查询类型判断方法
996
+
997
+ ```php
998
+ // PHP 伪代码示例
999
+ function isInnerWhereEnabled(string $tableSQL): bool {
1000
+ return str_contains($tableSQL, '{where_sub_placeholder_')
1001
+ || str_contains($tableSQL, '{and_sub_placeholder_');
1002
+ }
1003
+
1004
+ // 使用示例
1005
+ if (isInnerWhereEnabled($dataset['table'])) {
1006
+ // 子查询类型:过滤条件放 innerWhere,日期放 where
1007
+ $request['query']['innerWhere'] = buildInnerWhere($filters);
1008
+ $request['query']['where'] = buildDateWhere($dateRange);
1009
+ } else {
1010
+ // 非子查询类型:所有条件放 where
1011
+ $request['query']['where'] = buildWhere($filters, $dateRange);
1012
+ }
1013
+ ```
1014
+
1015
+ ### 10.3 日期条件处理
1016
+
1017
+ | 数据集类型 | 日期条件位置 | 字段格式 |
1018
+ |-----------|------------|---------|
1019
+ | 非子查询类型 | `where.conditions` 中 | `数据集别名.date_id` |
1020
+ | 子查询类型(inner_where_enabled)| `where.conditions` 中(translate 逻辑处理)| `数据集别名.date_id` |
1021
+ | dataComparison 开启后 | Python 自动扩展 WHERE 日期范围为 `当期 OR 对比期` 的并集 | 无需 PHP 手动处理 |
1022
+
1023
+ > 子查询类型的 `innerWhere` 中**不放**日期条件,日期通过外层 `where` 的 translate 逻辑注入,Python 服务会自动将其转换为内层的日期过滤。
1024
+
1025
+ ### 10.4 错误处理
1026
+
1027
+ **错误响应格式**:
1028
+
1029
+ ```json
1030
+ {
1031
+ "success": false,
1032
+ "data": null,
1033
+ "meta": null,
1034
+ "error": {
1035
+ "code": "VALIDATION_ERROR",
1036
+ "message": "字段 from.table 不能为空"
1037
+ }
1038
+ }
1039
+ ```
1040
+
1041
+ **常见错误码**:
1042
+
1043
+ | 错误码 | 原因 | 处理建议 |
1044
+ |--------|------|---------|
1045
+ | `VALIDATION_ERROR` | 请求参数校验失败(字段缺失或类型错误)| 检查请求体结构是否符合规范 |
1046
+ | `DATA_SOURCE_ERROR` | 数据源连接失败或超时 | 检查 dataSource 标识是否正确,重试 |
1047
+ | `SQL_EXECUTION_ERROR` | SQL 执行报错(语法错误、字段不存在等)| 开启 dryRun 调试生成的 SQL |
1048
+ | `PERMISSION_DENIED` | userEmail 对应用户权限为空 | 检查用户权限配置 |
1049
+ | `TIMEOUT_ERROR` | 查询超时 | 缩小时间范围或添加更多过滤条件 |
1050
+
1051
+ **调试技巧**:
1052
+
1053
+ ```json
1054
+ // 使用 dryRun 模式仅生成 SQL,不执行,方便排查 SQL 问题
1055
+ { "dryRun": true, "..." }
1056
+
1057
+ // 响应中的 meta.generatedSql 包含最终执行的 SQL 字符串
1058
+ ```
1059
+
1060
+ ### 10.5 缓存使用建议
1061
+
1062
+ ```json
1063
+ "cacheControl": {
1064
+ "enabled": true, // 生产环境建议开启缓存
1065
+ "forceRefresh": false, // 手动刷新时传 true
1066
+ "ttl": 300 // 缓存 5 分钟(秒)
1067
+ }
1068
+ ```
1069
+
1070
+ - 交互式筛选场景(用户频繁切换筛选条件):建议 `ttl` 设置为 `60`(1 分钟)
1071
+ - 定时刷新的报表场景:建议 `ttl` 设置为 `300`(5 分钟)
1072
+ - 小计/总计补充查询:可复用主查询的缓存配置
1073
+
1074
+ ### 10.6 列枚举上限控制
1075
+
1076
+ 交叉表的列维度枚举上限通过环境变量控制:
1077
+
1078
+ ```
1079
+ CROSSTAB_COLUMN_ENUM_LIMIT=10000 # 默认值 10000
1080
+ ```
1081
+
1082
+ 当列维度的组合数超出上限时,多余的列不会展示,建议在前端提示用户缩小筛选范围。
1083
+
1084
+ ---
1085
+
1086
+ ## 附录:opscli 本地数据集 CSV 列说明
1087
+
1088
+ > 以下为 `opscli skills install ops-dataset-query` 安装后 `data/` 目录中 CSV 文件的列定义,供字段索引检索时参考。
1089
+
1090
+ ### `data/datasets.csv` 列
1091
+
1092
+ | 列名 | 说明 |
1093
+ |------|------|
1094
+ | `table_id` | 数据集在系统中的唯一 ID |
1095
+ | `dataset_alias` | 数据集别名(英文,用于 `--dataset` 参数) |
1096
+ | `dataset_name` | 数据集中文名 |
1097
+ | `dataset_type` | 数据集类型(table / query) |
1098
+ | `dataset_category` | 业务分类(如:销售、库存、物流) |
1099
+ | `from_table` | 源数据库表名 |
1100
+ | `database` | 数据库标识 |
1101
+ | `data_source` | 数据源标识 |
1102
+ | `main_dttm_col` | 主时间字段 |
1103
+ | `description` | 数据集描述 |
1104
+ | `keywords` | 搜索关键词标签 |
1105
+
1106
+ ### `data/dataset_fields.csv` 列
1107
+
1108
+ | 列名 | 说明 |
1109
+ |------|------|
1110
+ | `dataset_alias` | 所属数据集别名 |
1111
+ | `dataset_name` | 所属数据集中文名 |
1112
+ | `dataset_type` | 数据集类型 |
1113
+ | `dataset_category` | 业务分类 |
1114
+ | `field_name` | 字段名(英文,用于 `--dimension` / `--metric` 参数) |
1115
+ | `verbose_name` | 字段中文名 |
1116
+ | `field_type` | 字段类型:`dimension`(维度)/ `metric`(指标) |
1117
+ | `data_type` | 数据类型:`STRING`、`INTEGER`、`DECIMAL`、`BOOLEAN` 等 |
1118
+ | `is_dttm` | 是否为时间字段(`true`/`false`) |
1119
+ | `is_restricted` | 是否受权限限制 |
1120
+ | `expression` | 计算表达式(派生字段) |
1121
+ | `description` | 字段描述 |
1122
+ | `keywords` | 搜索关键词标签 |
1123
+
1124
+ ---
1125
+
1126
+ *文档由 auto-scheduler 开发团队维护,如有疑问请联系数据平台组。*