# 活动 MQL 查询完整实录（2026-07-10，第二次修正）

## 关键发现：OFFSET + DESC + 日期范围 = 分页假死

**第一次查询**（错误的）：
```sql
SELECT ... FROM 销售管理.活动
WHERE 创建时间 >= "2026-04-01" AND 创建时间 <= "2026-09-30"
ORDER BY 创建时间 DESC LIMIT 50 OFFSET 0   -- 第1页：50条（全部7月）
-- OFFSET 50: 返回0条！误判为"只有50条"
-- 结论：Q2=0, Q3=50 → 错误！
```

**第二次查询**（正确的 — 分Q2/Q3分别查 + ASC排序）：
```sql
-- Q2单独查
SELECT ... FROM 销售管理.活动
WHERE 创建时间 >= "2026-04-01" AND 创建时间 <= "2026-06-30"
ORDER BY 创建时间 ASC LIMIT 50 OFFSET 0  -- 成功翻页111次
-- Q3单独查
WHERE 创建时间 >= "2026-07-01" AND 创建时间 <= "2026-09-30"
ORDER BY 创建时间 ASC LIMIT 50 OFFSET 0  -- 成功翻页7次
-- 结论：Q2=1,890, Q3=210 → 真实数据！
```

**第三次查询**（正确 — 合并查询 + ASC排序）：
```sql
WHERE 创建时间 >= "2026-04-01" AND 创建时间 <= "2026-09-30"
ORDER BY 创建时间 ASC LIMIT 50 OFFSET 0  -- 成功翻页
-- 结论：总计2,817条，去重前
```

## OFFSET 假死根因分析

当使用 `ORDER BY 创建时间 DESC` + 含上界的日期范围过滤时，MCP的OFFSET分页会跳过一个区间后返回空。**推测**MCP在DESC排序下按内部索引翻页，日期过滤改变了结果集但索引未调整，导致OFFSET落在结果集之外。

**铁律**：
1. **全量拉取用 `ORDER BY 创建时间 ASC`**，不用 DESC
2. **分开Q2和Q3查询**比合并更可靠（减少OFFSET偏移量）
3. 不能依赖 `len(items) < 50` 作为停止条件（第2页可能返回空但后面还有数据）
4. 安全停止条件：连续2页返回0，或累计页数>正常预期的3倍

## 分页安全模板

```python
def query_all_safe(mql, project_key, max_expected=10000):
    all_items = []
    page = 0
    consecutive_empties = 0
    while page * 50 < max_expected:
        offset = page * 50
        full_mql = f"{mql} LIMIT 50 OFFSET {offset}"
        result = mcp("search_by_mql", {"project_key": project_key, "mql": full_mql})
        items = parse_items(result)
        if not items:
            consecutive_empties += 1
            if consecutive_empties >= 2:
                break
            page += 1
            continue
        consecutive_empties = 0
        all_items.extend(items)
        page += 1
        if len(items) < 50:
            break
    return all_items
```

## 最终统计（2026-07-10）

| 月份 | 活动数 |
|------|--------|
| 4月 | 930 |
| 5月 | 827 |
| 6月 | 862 |
| 7月 | 198 |
| **合计** | **2,817** |

## 部门编码解析

通过 `get_workitem_brief` → `create_by.name` 反推部门归属：

| 编码 | 典型创建者 | 部门名 | 活动数 |
|------|-----------|--------|--------|
| `2o467stav` | 杨天雄, 刘虹晶, 林潇, 黄文才, 路要峰, 李江辉, 宋学伟, 冯贤平, 陈溪子, 龙友波, 任鑫 | 诊断原料销售部 | 2,190 |
| `i4l86_tn6` | 袁殿果, 王慧洁, 曾琪 | 生命科学-浙江 | 378 |
| `591s4k2uz` | 黄建华, 王诗香, 梁莹莹, 段玉 | 生命科学-广东 | 118 |
| `sp46mkhje` | 赵云浩, 黄明月 | 大客户部 | 128 |
| `xnm4v7thp` | 未知 | 未知 | 2 |
| `y23lpl4ex` | 未知 | 未知 | 1 |

**反推方法**：
```python
# 1. MQL采样每个dept code的几条活动
mql = f'SELECT name, work_item_id, `创建者` FROM 销售管理.活动 WHERE `跟进部门` = "{code}" LIMIT 5'
# 2. 用 work_item_id 调 get_workitem_brief 拿创建者姓名
brief = mcp("get_workitem_brief", {"project_key": pk, "work_item_id": wid})
creator_name = brief['work_item_attribute']['create_by']['name']
# 3. 根据创建者姓名推断部门（已知人员-部门映射）
```

⚠️ **MQL WHERE `跟进部门` = "code"** 不工作（option_id编码≠MQL值），需用创建者反推。

## 创建者姓名解析

飞书活动的 `创建者` 字段返回数字ID（如 `7611723867362577613`），非姓名。

```python
# 解析方法：get_workitem_brief → create_by.name
brief = mcp("get_workitem_brief", {"project_key": SALES_PK, "work_item_id": wid})
name = brief['work_item_attribute']['create_by']['name']
# → 7611723867362577613 → 王诗香
# → 7317550916222091265 → 杨天雄
```

⚠️ `search_user_info` 对销售管理项目的 user_key 返回空数组，**不可用**。

## 数据质量问题

| 问题 | 详情 |
|------|------|
| `work_item_id` 常为空 | 大量活动无work_item_id，不可用于去重或构造URL |
| `description` 常为空 | 跟进内容字段大部分为空，仅`name`有值 |
| `创建者` 为数字ID | 需逐条 `get_workitem_brief` 解析 |
| `跟进部门` 为编码 | 需创建者反推或 meta-fields 查 options |
| 活动名称重复 | 大量"活动跟进"等通用名 |

## 日期过滤格式

MQL WHERE 支持 ISO 日期字符串：
```sql
WHERE `创建时间` >= "2026-04-01"
WHERE `创建时间` >= "2026-04-01" AND `创建时间` <= "2026-09-30"
```
时间戳格式（`1743436800000`）**不工作**。
