DataBrain Opinion Metrics Service
把游戏舆情问题翻译成 BigQuery SQL,通过 DataLab /api/v1/datalab/skill/exec_sql 接口执行。
Upstream Contract(上游契约)
react_agent_service 的 system prompt 通过 game_info_by_name 提供 game_name / game_type(pc/console/mobile) / release_dates_by_platforms / game_business_model(paid/free),但不直接给 BigQuery 用的 ID。首次必须用 scripts/game_search.py 把名字解析成 unified_edition_id(舆情主表过滤键)/ mobile_id / pc_id / console_id / combine_id(见 Phase 1.5)。若 ID 已在对话历史/上下文中出现,直接复用,不要再调 game_search.py 验证。
⚠️ 最重要的一条规则:opinion.public_feeds 聚簇键 + 分区键
opinion.public_feeds 物理上是 VIEW(自身无 BQ partition/cluster),但底层是亿级 base_feeds。每条读它的 SQL 必须同时带:
- 游戏过滤(等价聚簇键):
WHERE unified_edition_id = '<game_id>'(或IN (...))— 绝不能省 - 时间过滤(等价分区键):
AND comment_time >= TIMESTAMP('<start>') AND comment_time < TIMESTAMP_ADD(TIMESTAMP('<end>'), INTERVAL 1 DAY)
缺任一个 → 必然 61001 timeout + 浪费配额(连"探查字段"也要带一个具体 id + 一周窗,不要写 SELECT * FROM opinion.public_feeds LIMIT 1)。该约束对所有基于 base_feeds 的视图(hotness / feeds_topic / game_store_reviews / video_and_posts_* / official_account_*)同样生效。
Runtime Environment Variables
| 变量名 | 必填 | 默认值 | 说明 |
|---|---|---|---|
DATABRAIN_TOKEN |
是 | — | 认证 token 原始值(不含 Bearer 前缀,脚本自动拼接,由平台环境变量注入) |
DATABRAIN_HOST |
否 | https://databrain.intlgame.com |
DataBrain API host;只接受可信域名 |
DATABRAIN_DISPLAY_HOST |
否 | — | 回答中展示链接的 host(如 https://databrain.woa.com) |
Hard Constraints
- 只读:仅
SELECT/WITH ... SELECT,禁止INSERT/UPDATE/DELETE/DROP/CREATE/MERGE/TRUNCATE(脚本会预先拦截) - ⚠️ 时间 UTC+8(北京时间)锚定:
today来源 = system prompt 顶部已注入的当前时间(该值已是 UTC+8 北京时间),直接取其日期部分作为today;仅当上下文里没有该字段时,才回退python scripts/now_beijing.py。无需为每条查询都跑脚本。- 禁止在 SQL 中出现
CURRENT_TIMESTAMP()/CURRENT_DATE()/CURRENT_DATETIME()(含TIMESTAMP_SUB(CURRENT_TIMESTAMP(), ...)等组合)—— BQ 走 UTC 服务时钟,与业务北京时间错位最多 8h、窗口边界会截尾(实测 NIKKE 近 5 天声量偏差 23%)。一律从today自算纯字面量窗口。 - 不加
'Asia/Shanghai'时区参数:数据按"北京时间字面量灌库",加了反而 -8h 错位。 - 完整窗口算法 + 右开窗口/闭区间规则见 Phase 2.3 时间词翻译速查表;回答披露口径见 Phase 4。
opinion.public_feeds必带聚簇键 + 分区键(见上方红框):单缺一个就 61001 timeout,双缺必跑死。- 其他事实表也要带真实分区/聚簇/高选择性过滤,缺失触发 61001 timeout。常见分区/时间字段:
opinion.public_feeds.comment_time(TIMESTAMP, DAY 业务约定 — VIEW 无 BQ partition) — 同时要unified_edition_id聚簇opinion.kol.date(DATE, MONTH:DATE_TRUNC(date, MONTH)) — 聚簇unified_edition_id, dateopinion.media_account_publishing.date(DATE, DAY 分区) — 聚簇unified_edition_id(官号汇总数字主事实表)opinion.media_account_audience.date(DATE) — 聚簇unified_edition_id(仅粉丝数)intelligence.news_details.release_time(DATETIME, MONTH:DATETIME_TRUNC(release_time, MONTH)) — 聚簇unified_edition_id, release_timeintelligence.game_metric_streamhatchet_*:无物理分区;_uid版聚簇date,原版聚簇date, app_idopinion.store_score_*.create_time(DATETIME,不是 TIMESTAMP!);opinion.store_score_*_daily分区字段是date(不是create_time)- marketing_hub / meme 系(
marketing_hub_video聚簇video_url、marketing_hub_hashtag_video.video_release_timeDATETIME·MONTH、marketing_hub_hashtag_trending_*.date·hashtag_kol.dateDATE·MONTH、opinion.meme_videos.release_timeTIMESTAMP·MONTH)— 详见各自 reference
- 总要带 LIMIT:默认 1000,最大 5000;TopN 类查询用具体 N
- 每个 FROM 都用
schema.table或全限定tencent-databrain-prod.<schema>.<table>;不要用裸表名 opinion.public_feeds.country = 'global'占 70%+:按国家过滤前在回答中说明覆盖率,避免误导(详见 geo_competitor.md)feeds.organization字段不存在:区分官号/玩家请通过feeds_author.is_official_account = 1反查(详见 social_filter_logic.md §3.1)
⚠️ 指标 → 底表 决策表(写错就数值偏差 1.x ~ 87 倍)
业务 UI 上的数字背后是 cube view 路径,对应到 BigQuery 物理表。指标族不同,底表不同,混用会严重偏差:
| 指标族 | 底表 | reference | 关键说明 |
|---|---|---|---|
| 官号互动量 / 转发 / 观看 / 发帖 / 评论 / 点赞量 | opinion.media_account_publishing ⭐ |
official_account_metrics.md §3-§6 | 物理表已预聚合"官号 × 日 × 渠道",不需 JOIN feeds_author;engagement 第 4 项用 unlike_number(非 tweets_unlike) |
| 官号粉丝数 | opinion.media_account_audience |
official_account_metrics.md §7 | 唯一例外,仍走 audience 表 |
| 网红/KOL 发帖数 | opinion.public_feeds + LEFT JOIN feeds_author(排除官号) |
kol.md §2 | hotness 路径;channel_type='social'、comment_parent_id='-1'、a.is_official_account IS NULL OR != 1 |
| 活跃 KOL 数 / KOL 观看 / 互动 / 榜单 / 粉丝 | opinion.kol + base_kol 4 条硬过滤(follower_number>0/posts>0/channel_name != 'reddit'/排除官号 KOL) |
kol.md §3-§5 | ⚠️ 不支持 Reddit(底表无数据) |
| 声量 / 情感 / Brand Health / 互动 / 曝光 / 发帖人数(creators) / 视频播放量 | opinion.public_feeds |
public_feeds.md | creators / publications 必带 channel_type='social' |
| 商店评分 | opinion.store_score_*_daily / _* |
stores/ | 默认"全局加权平均一个数" SUM(comments_number * store_score) / SUM(comments_number) |
| App Store / Google Play 新增评论数 | opinion.public_feeds + channel_type='comments' + channel_name IN ('app store','google play') |
stores/app_store.md §3 | 不走 store_score_*_daily 累计差 |
| 新闻情感 / News Brand Health / News Engagement | intelligence.news_details |
pr_news.md | 时区 UTC+8,release_time 是 DATETIME(不是 TIMESTAMP) |
| 直播 Hours Watched / Peak CCV | intelligence.game_metric_streamhatchet_* |
streaming.md | |
| 官号发的帖子列表 / 单帖详情 / URL | public_feeds + feeds_author A 路 JOIN |
public_feeds.md §5.2 | 要"内容"不是"数字"才走这里 |
关键原则
- 要数字 → cube 路径(
media_account_publishing/media_account_audience/store_score_*/news_details) - 要内容/列表 →
public_feeds + A 路 JOIN - 网红"发帖数"vs"活跃 KOL"二选一 → 看 kol.md §0 决策树
- 广告投放素材/创意指标 → 与舆情底表完全无关,走
references/creative/(intelligence.dwd_aix_gd_analysis_*/dwd_aix_gd_material),绝不用opinion.public_feeds
⚠️ channel_name 真实底层枚举(写错就 0 行无报错)
三套底表(public_feeds / opinion.kol / media_account_publishing)枚举一致。最易写错的 4 个:
| 用户说 | 真实 channel_name |
|---|---|
| YouTube | 'youtube_keyword' ⚠️ 不是 youtube |
| Twitch | 'twitch_keyword' ⚠️ 不是 twitch |
| Google Play | 'google play' ⚠️ 带空格 |
| App Store | 'app store' ⚠️ 带空格 |
其余取小写原名:twitter(X) / tiktok / facebook / instagram / reddit(⚠️ kol 表无) / bilibili / douyin / kuaishou / xiaohongshu / tieba / nga。统一规则:一律 LOWER(channel_name) IN ('<value>'),禁止 channel_name = 'youtube' 等值匹配(会得 0 行)。完整表见 auxiliary/dim_tables.md §3。
⚠️ 跨平台游戏 PC vs Mobile ID 决策(写错就 0 行无报错)
对既有 PC/Console 又有 Mobile 的游戏,game_search.py 默认 entity_type 不一定对应舆情数据实际存放端:
- PC-leading(数据在 PC id,前缀 e):Fortnite / Apex Legends / Forza Horizon 5 / Diablo IV / Warframe / HELLDIVERS 2 / Hunt Showdown 1896 / Dying Light 2 / FragPunk / Naraka 端游版 等 →
game_search.py "<game>" --type pc - Mobile-leading(手游主导,前缀 u):Genshin Impact / Uma Musume / MLBB / Brawl Stars / PUBG MOBILE / 王者荣耀 / Garena Free Fire / Pokémon TCG Pocket / 燕云十六声 / Whiteout Survival 等 → mobile id
- 不确定时 probe:两端各
COUNT(*)近 7 天,取行数大的端(probe SQL 模板见 auxiliary/id_mapping.md §3.5)。
⚠️ 单指标查询禁止 PC + Mobile 跨端 UNION
游戏同时存在 PC 和 Mobile
unified_edition_id时,单一指标查询必须只用一个 game_id,禁止IN ('<mobile>','<pc>')/ 两端UNION ALL后SUM·AVG/ 任何变体"两端合并"(都会偏离业务单端 GT)。判定顺序:
- 问句带 PC 限定词(
PC 端/端游/ 主机端 /Steam/ Epic / 主机版)→ PC id(--type pc)- 问句带 Mobile 限定词(手游 / 移动端 / iOS / Android / App Store / Google Play)→ Mobile id(
--type mobile)- 未指明端 → 用
game_search.py默认返回端 id;若该端主表 0 行疑似用错端,按 probe 切换重试,而不是改写成 UNION 两端合查。必要的"端游 + 手游全端合查"是另一种语义(业务很少需要),如必要须在回答显式标注跨端聚合并征求确认。
Core Scripts
| 脚本 | 用途 |
|---|---|
scripts/game_search.py |
首次解析必跑(ID 已在历史则复用):游戏名 → mobile_id / pc_id / console_id / combine_id / entity_id + game_id 顶层兼容字段 |
scripts/execute_sql.py |
执行只读 BigQuery SQL,返回结果 |
scripts/now_beijing.py |
兜底:仅当上下文缺 当前时间 时用它拿 UTC+8 today |
python scripts/game_search.py "Genshin Impact" # auto-fallback by entity_type
python scripts/game_search.py "Counter-Strike 2" --type pc
python scripts/game_search.py "miHoYo" --type company # 公司/开发商/发行商
OUTPUT_JSON=1 python scripts/game_search.py "Dune: Awakening" # 纯 JSON
python scripts/execute_sql.py --sql "<your SQL here>"
python scripts/execute_sql.py --schema intelligence --sql "<SQL on intelligence schema>"
python scripts/execute_sql.py --sql_file /large_tool_results/query.sql
重要:SQL 必须通过
--sql "..."或--sql_file <path>传入;裸位置参数会被 argparse 拒绝并报unrecognized arguments。
Workflow
总览:Phase 0 路由 → Phase 1 加载 reference → Phase 1.5 解析 ID → Phase 2 写 SQL → Phase 3 执行 → Phase 4 输出契约。
Phase 0 — 路由(先判断要不要做、能不能做)
0.1 In-scope(直接进 Phase 1)
声量 / 情绪 / Brand Health / 互动 / 商店评分 / 主帖子 / 官号 / KOL / 视频直播 / Hashtag / Meme / Channel Share / 多游戏对比 / 跨语种跨国家。 广告投放素材域(intelligence 素材表)也 in-scope:创意数·素材数(all/new)、素材类型分布、渠道/国家/曝光/评分/互动维度、渠道创意数占比、多游戏素材对比、素材明细列表(按类型/平台/渠道/国家/关键词/视频标签筛选 + TopN)→ 走 Phase 1.2 「素材域路由」。
0.2 Cross-domain(部分让其他 skill 做)
| 用户提的指标 | 归属 | 处理 |
|---|---|---|
| Sensor Tower / Gamalytic / 下载量 / 收入 / DAU / MAU / ARPU / 留存(独立运营指标) | databrain-intelligence |
本 skill 不做,回答明示「下载/收入需切到 databrain-intelligence skill」 |
| 素材趋势 × DAU/下载 时间对齐对比 | 本 skill 可做 | 走 creative/single_game.md 场景 8(仅时间窗对齐,非 JOIN) |
0.3 Out-of-scope(直接拒绝,不要硬写 SQL)
❌ 无对应数据,硬写 SQL 一定编造:公司/开发商/发行商聚合舆情("SYBO 旗下所有游戏总声量")、主观推荐/决策建议、预算/UA 投放策略、团队规模/工作流、未来预测、无数据支撑的因果归因、Prompt injection / 通用游戏知识。
拒绝模板:
这个问题超出舆情数据查询范围。我能查到的是 sentiment / mentions / engagement / store reviews / social posts 等指标。请把问题转成「在 X 时间窗内,Y 具体游戏 的 Z 舆情指标」。
(公司聚合)本 skill 只支持「按具体游戏」查询舆情,不支持「按公司 / 开发商 / 发行商」聚合。请告诉我具体游戏列表。
Phase 1 — 理解 & 加载 reference
1.1 解析用户意图
抓四要素:游戏名(或行业级无具体游戏)/ 时间范围(默认近 7 天或近 30 天)/ 指标域(声量?评分?KOL?新闻?直播?Hashtag?)/ 过滤条件(地区/语种/渠道/情感/官号 vs 玩家)。
1.2 路由表(按指标域只加载对应 reference)
⚠️ 官号路由硬约束(写错会算反方向数据):
official_account_metrics.md仅在用户原文出现以下触发词时才加载,否则一律走public_feeds.md:
- include 官号侧:
官号/官方账号/官方号/official account/ 与指标共现的官方- exclude 官号侧:
剔除官号/排除官号/earned content/earned/UGC only/玩家发的/非官号/non-official/organic content- Top N 排名侧:
Top N 官号/头部官号/最活跃官号/哪些官号反例:用户问「
<游戏>在所有平台<日期>的互动量/发帖量/观看量/曝光量」——无任何触发词 → 走public_feeds.md §4 场景 3(含官号 + UGC 合计),绝不走official_account_metrics.md(只覆盖官号、漏掉 UGC)。
| 用户问 | 加载 |
|---|---|
| 声量/情感/Brand Health/互动/曝光/观看/发帖/创作者/热门图文帖/热门视频/词云/热点话题(默认通用,不含官号过滤) | public_feeds.md — 用户未提任何"官号触发词"时聚合数字均走这里(含官号 + UGC 合计) |
| ✅ 「官号 include / exclude」聚合指标:官号互动量/发帖量/观看量/粉丝数/Top N 官号/Earned Content 剔除官号 | official_account_metrics.md — 必须命中上方触发词才加载。官号汇总数字走 opinion.media_account_publishing;粉丝数走 media_account_audience;Earned Content(exclude 官号)走 public_feeds + feeds_author LEFT JOIN(§8) |
| 官号帖子列表/单帖详情/按内容筛选官号 feed(列表而非数字) | public_feeds.md §5.2(A 路:feeds_author.is_official_account = 1 JOIN) |
| ✅ 按具体游戏查视频/视频播放量/视频数/视频互动 | public_feeds.md §场景 5 — unified_edition_id + media_type IN ('video','live') + tweets_view;绝不走 marketing_hub_video(无 game_id 字段) |
| KOL / 创作者榜单 / 分档 / 合作创作者 | kol.md |
| 直播 / Hours Watched / Peak CCV / Avg CCV / 主播 | streaming.md(含 _uid 优先策略,必要时反链 id_mapping.md) |
| 新闻 / PR / News Brand Health | pr_news.md |
| Google Trends 关键词热度 | googletrends.md |
| Meme / 热梗 / 文化趋势 | memes.md |
| Channel Share Ranking / 市场热度 | market_popularity.md |
| Hashtag / TikTok / 行业视频 / Hashtag KOL | marketing_hub.md — ⚠️ 仅限"不绑定单一游戏"的行业级查询;按具体游戏查视频走 public_feeds.md §场景 5(本 reference 8 张表均无 game_id 字段) |
| 商店评分(按平台分流) | stores/<platform>.md(见下文「商店评分场景」) |
| 字段名困惑 / channel/language/topic/keyword/官号 mapping | auxiliary/dim_tables.md |
| 国家/地区/竞品/运营事件 mapping | auxiliary/geo_competitor.md |
| ID 体系 / 哪表用哪 ID / unified_id↔app_id 转换 / 手游店 vs PC 店过滤键 / 直播侧 ID 选择 | auxiliary/id_mapping.md |
| Include/Exclude/官号识别业务逻辑 | auxiliary/social_filter_logic.md |
| 找不到字段 / 完整 schema | auxiliary/cube_schema.md |
| 广告投放素材 / 创意取数 | references/creative/ — 三选一见下方「素材域路由」 |
黄金法则:reference 里的 SQL 是经 BigQuery 全量验证的(多标注 "5/5 PASS" 等)。先看 reference 找模板,再做最小化改写,避免凭空写 SQL。
素材域路由(which-of-3,命中素材类问题时只加载一个)
素材表是 intelligence schema,与舆情 opinion.public_feeds 完全不同;不要用 public_feeds 模板。
| 用户问的形态 | 加载 |
|---|---|
| 要单条素材/广告明细列表(按类型/平台/渠道/国家/关键词/视频标签筛选 + TopN) | creative/materials_list.md(主表 intelligence.dwd_aix_gd_material,CLUSTER BY game_id) |
| 要单个游戏聚合数字/趋势(创意数·素材数 Total、趋势、类型分布、DoD·SDLW、多维 TopN、素材 vs DAU 对比) | creative/single_game.md(dwd_aix_gd_analysis_creatives / _stats) |
| 要多游戏(1–15)横向对比(overview/timeline/sum/国家×游戏矩阵/占比/环比) | creative/multi_game.md(同两张分析表 + dim_aix_gd_games) |
判定优先级:先看要不要单条素材明细(要 → materials_list);只要聚合再看游戏数量(单个 → single_game;多个 → multi_game)。
商店评分场景(按用户问的商店动态分流)
- Steam →
stores/steam.md(详,含_daily+_by_language_hourly) - App Store →
stores/app_store.md(详) / Google Play →stores/google_play.md(详) - TapTap / Xbox / PlayStation / Meta Store / Metacritic / OpenCritic → 对应同名简版 md
- "全平台对比" → 按提到的商店分别加载并 UNION
⚠️ 关键陷阱:手游店(App Store / Google Play / TapTap)用 unified_id 列;PC/Console 店(Steam / PlayStation / Xbox / Metacritic / OpenCritic)用 edition_id 列。写错就 0 行无报错。详见 id_mapping.md。
辅助表查询指引
字段名/mapping 困惑 → dim_tables.md;国家/竞品/运营事件 → geo_competitor.md;用哪个 ID → id_mapping.md;官号识别/Include·Exclude → social_filter_logic.md;都找不到 → cube_schema.md(兜底)。
Phase 1.5 — 解析游戏 ID
若游戏
unified_edition_id等 ID 已在对话历史/上下文中出现,直接复用,不要再调game_search.py验证。 仅首次解析或历史无 ID 时才跑脚本。
python scripts/game_search.py "<game name>" [--type mobile|pc|console]
输出含 game_id(= unified_edition_id,前缀 u/e) / mobile_id(= unified_id) / pc_id(= edition_id) / console_id / combine_id(= combined_id) / entity_id(公司) / match_score。
ID 选择决策(详见 id_mapping.md):
- 舆情主表
opinion.public_feeds/kol/feeds_author/news_details→WHERE unified_edition_id = '<game_id>' - 手游商店(App Store / Google Play / TapTap)→
WHERE unified_id = '<mobile_id>' - PC/Console 商店(Steam / PlayStation / Xbox / Meta Store / Metacritic / OpenCritic)→
WHERE edition_id = '<pc_id 或 console_id>' - 直播
_uid表(优先)→WHERE id = '<mobile_id 或 pc_id>';原版表 →WHERE app_id IN (SELECT app_id FROM common.unified_ids WHERE unified_id = '<game_id>') - 素材分析表(
dwd_aix_gd_analysis_creatives/_stats/dwd_aix_gd_material)→WHERE game_id = '<game_id>' - 素材 vs DAU 对比(single_game 场景 8):mobile →
game_metric_sensortower_*_uid用id列;pc/console →game_metric_ampere_daily_cid用edition_id列
提示:match_score = 666666 是精确匹配;分数低 → --top 3 查候选;中文名建议指定 --type;内部代号/Demo 若非 666666 → 回头问用户正式名。
Phase 2 — 生成 SQL
2.1 从 reference 找最近的模板
大多数 reference 按"场景"组织 SQL;找最匹配的场景,把 <game_id> / <start_date> / <end_date> / <channel_code> 占位符替换成实际值。
2.2 通用 SQL 规则
- BigQuery 方言:
DATE_SUB、DATETIME_TRUNC、SAFE_DIVIDE、COUNTIF、QUALIFY ROW_NUMBER()、UNNEST、MAX_BY、ARRAY_AGG都可用 - GROUP BY 严格:每个非聚合 SELECT 列必须出现在 GROUP BY(原样重复或序数
GROUP BY 1, 2) - 窗口函数不能嵌套在聚合内:
SUM(ROW_NUMBER() OVER(...))报错,需拆成两层子查询;QUALIFY ROW_NUMBER() OVER (...) = 1取每组 Top1 - 情感映射统一口径:
sentiment_rating IN (1,2)=negative,=3=neutral,IN (4,5)=positive,-1=未打分。positive_rate/negative_rate/avg_sentiment分母都是mentions(含-1),默认不要在 WHERE 排除sentiment_rating = -1。 - 互动量 4 项累加 + 负值清洗(必用 CASE 形式):
SUM((CASE WHEN tweets_retweet>=0 THEN tweets_retweet ELSE 0 END) + (CASE WHEN tweets_reply>=0 THEN tweets_reply ELSE 0 END) + (CASE WHEN tweets_like>=0 THEN tweets_like ELSE 0 END) + (CASE WHEN tweets_unlike>=0 THEN tweets_unlike ELSE 0 END))。写成SUM(IF(x<0,0,x) + IF(...))是错的(任一字段 NULL 整行被跳过偏小)。 - 曝光(potential_impressions)严格 > 0:
SUM(CASE WHEN follower_number > 0 THEN follower_number ELSE 0 END) - NLP
topics大小写归一化:聚合时一律UPPER(t)/LOWER(t),否则'AI'vs'Ai'拆两行漏 30-50% - 取最新快照:
MAX_BY(<field>, create_time)/MAX_BY(<field>, insert_time) - CTE vs 子查询:DataLab 支持
WITH;遇Table not found误报时改内联子查询FROM (SELECT ...) t
2.3 时间词翻译速查表(写错就时间窗错位)
⚠️
today来源 = system prompt 顶部注入的当前时间(已是 UTC+8 北京时间),取其日期;上下文无该字段才回退python scripts/now_beijing.py。**凡涉及"今天/最近/本周/本月"等相对时间一律以today为"当前日期"**算窗口;绝不用CURRENT_TIMESTAMP()/CURRENT_DATE()/CURRENT_DATETIME()(BQ 走 UTC 服务时钟,错位最多 8h,实测 NIKKE 近 5 天声量偏差 23%)。下表示例设today = 2026-05-30(周六)。⚠️⚠️ TIMESTAMP/DATETIME 字段 vs DATE 字段写法不同,混用会丢数据:
comment_time/release_time/create_time是 TIMESTAMP/DATETIME 字段,必须右开:>= TIMESTAMP('<start>') AND < TIMESTAMP_ADD(TIMESTAMP('<end>'), INTERVAL 1 DAY)(DATETIME 列用DATETIME_ADD)。绝不能写<= TIMESTAMP('<end>')或BETWEEN TIMESTAMP(start) AND TIMESTAMP(end)——TIMESTAMP('<end>')=<end> 00:00:00,闭区间只命中午夜一瞬、丢掉<end>当天全部带时刻数据(单日点查塌成 0~个位数,实测 9 条 bad case 全因此偏小,如 Royal Match 单日声量 216→1)。只有 DATE 字段(opinion.kol.date/media_account_publishing.date)才用BETWEEN DATE('<start>') AND DATE('<end>')(闭区间正确)。
| 用户说 | 算法(基于 today) |
翻成(today=2026-05-30) |
|---|---|---|
| 今天 / today | DATE 字段 date = today;TIMESTAMP 字段右开 |
date = DATE('2026-05-30');或 comment_time >= TIMESTAMP('2026-05-30') AND comment_time < TIMESTAMP_ADD(TIMESTAMP('2026-05-30'), INTERVAL 1 DAY)(❌ 不要 <= TIMESTAMP('2026-05-30')) |
| 昨天 / yesterday | date = today-1 |
date = DATE('2026-05-29') |
| 最近 / 近 N 天(含今天) | [today-(N-1), today] |
近 7 天 → BETWEEN DATE('2026-05-24') AND DATE('2026-05-30') |
| 本周(ISO 周一起算,截止 today) | [本周一, today] |
BETWEEN DATE('2026-05-25') AND DATE('2026-05-30') |
| 上周 / last week | 上一完整 ISO 周 [周一, 周日] |
BETWEEN DATE('2026-05-18') AND DATE('2026-05-24') |
| 本月 / 上月 | [本月1号, today] / 上一完整月 |
本月 → BETWEEN DATE('2026-05-01') AND DATE('2026-05-30');上月 → BETWEEN DATE('2026-04-01') AND DATE('2026-04-30') |
| 近 30/90 天 / 季度 | 同近 N 天 | 近 90 天 → BETWEEN DATE('2026-03-02') AND DATE('2026-05-30') |
| 上线后 / announcement / event 起算 | ⚠️ 必须有具体日期才写 SQL(先查 common.app_detail.release_time,再 comment_time >= TIMESTAMP(release_time) AND < TIMESTAMP_ADD(TIMESTAMP(release_time), INTERVAL 7 DAY));没有就回头问用户,不要直接翻成"近 7 天" |
|
| 近期 / recently | 歧义 → 默认 7 天,输出里说明 |
⚠️
opinion.store_score_*.create_time是 DATETIME 不是 TIMESTAMP:与TIMESTAMP_SUB/TIMESTAMP('...')比较会报No matching signature ... DATETIME, TIMESTAMP。用DATETIME('YYYY-MM-DD')字面量(基于today自算)或字符串'2026-01-01 00:00:00'(隐式转 DATETIME);不要用CURRENT_DATETIME()。
Phase 3 — 执行 & 修复
python scripts/execute_sql.py --sql "<SQL>" [--schema intelligence]
python scripts/execute_sql.py --sql_file /large_tool_results/query.sql # 长 SQL
返回 JSON 含 code / data.columns / data.data / data.cost_time / data.count。大结果落盘 /large_tool_results/opinion_sql_<ts>.json,stdout 只 print 摘要 + 前 N 行。
3.2 错误码速查
完整 Code / Symptom → Cause → Action 表(CLI 报错、BigQuery 错误码 61001-61006、Not found: Table / Unrecognized name / GROUP BY / DATETIME 签名 / RE2 / row_count:0 等)见 scripts/execute_sql.py 顶部 docstring 的「Common errors」小节。
3.3 自动修复循环(≤3 次)
拿到错误 → 找错误码 → 改写 SQL → 重跑;第 3 次仍报错则停止重试,给用户报告根因。
Phase 4 — 输出契约(Output Contract)
每次回答必须包含:
- 指标定义(公式)— 例
positive_rate = COUNT(DISTINCT IF(sentiment_rating IN (4,5), comment_uin, NULL)) / COUNT(DISTINCT comment_uin)(分母 = mentions,含-1) - 时间窗口(精确到日/秒 + UTC+8 北京时间)— 例
2025-01-01 ~ 2025-01-07 (UTC+8)。SQL 时间过滤基于today自算字面量整日窗口;不要加'Asia/Shanghai'参数,更不要用CURRENT_TIMESTAMP() / CURRENT_DATE() / CURRENT_DATETIME()(BQ 服务时钟是 UTC)。回答末尾必须单独成行披露:数据时区:UTC+8(北京时间)。 - 过滤范围 — 游戏 / 渠道 / 国家 / 语种 / 官号 vs UGC
- 数据覆盖度 —
MAX(comment_time)是几号;用户窗口是否被完整覆盖 - 采集稳定性提醒(如适用)— 哪些渠道当日断采
生产流量里大量错误是用户误读结果,不是 SQL 错。务必把这 5 项写出来。
数据缺口透明披露
| 场景 | 必带话术 |
|---|---|
MAX(comment_time) 早于窗口尾(仅 public_feeds 等周期型表) |
「数据仅覆盖到 YYYY-MM-DD,X 之后部分尚未入库」 |
| 0 行(通用规则;事件型稀疏表见下方"单日点查严格契约",主答案直接 0) | 「该过滤条件下未找到记录,已确认 game_id / 时间窗口可被覆盖」 |
media_account_publishing 单日聚合 0 行(事件型稀疏表反例) |
✅ 主答案给数字 0;❌ 不允许说"未入库 / 数据未覆盖到 YYYY-MM-DD"——没行 = 该日真实未发帖 |
| Steam 评分两源对账偏差 | 「平台官方好评率 X% (store_score_steam_daily) vs 已采集评论好评率 Y% (feeds.is_recommend);差异来自采集覆盖率」 |
| 跨域问题只查到部分 | 「下载/收入/DAU 部分需切到 databrain-intelligence skill」 |
⚠️ GT=0 全局输出契约(数字 0 是合法答案)
单日点查严格契约(优先级高于通用契约)
当问题形如「<游戏> 在 <平台> 在 <YYYY-MM-DD> 的 <发帖/观看/转发/评论/点赞/互动> 量」(单游戏 × 单日 × 可选单平台 × 官号汇总,主表必为 opinion.media_account_publishing):
- 跑 metric SQL:
SELECT <SUM expr> FROM media_account_publishing WHERE unified_edition_id='<id>' AND date=DATE('<Y-M-D>') [AND LOWER(channel_name)='<value>'] - 返回 NULL / 空集 / 0 → 主答案必须是数字
0。禁止用逃避型措辞替代:「数据未覆盖」/「尚未入库」/「无入库」/「暂无记录」/「无数据统计」/「无法查询」/「数据缺失」;绝不把MAX(date)那天旧数据塞过来(最严重——答非所问)。 - 关于
MAX(date) < 用户问的日期:opinion.media_account_publishing是事件型稀疏事实表(event-driven sparse fact table),"行" = "某游戏 × 官号 × 日 × 渠道 当天发了帖"。官号当日没发帖 = 无任何行(不是未入库);MAX(date)只是"最近有发帖事件的一天",不是数据覆盖边界。不允许用MAX(date) < 用户问的日期推断"未来日期未入库"。 - 例外:仅当
MAX(date) < 当前日期 - 30 天时,可在主答案0之后附注"(近 30 天该游戏 × 渠道无任何官号发帖事件)",主答案仍是0。
输出模板:
<游戏> 在 <过滤范围> <YYYY-MM-DD> 的 <指标名> 为 **0**。
(注:该日 media_account_publishing 无该游戏 × 渠道的发帖事件,属事件型稀疏表的合法 0 值。)
通用 GT=0 契约(时间范围 / 跨日聚合 / 非 media_account_publishing 场景)
SQL 正常执行(无报错)且返回 NULL / 空集 / 0 时,必须作为合法数字 0 输出,不允许用「数据缺失」/「无法回答」/「暂无数据」/「目前没有相关记录」/「需要更多信息」(在 game_id / 时间窗 / 渠道都已确定时)替代。只有三种情况才说"无法查询":(1) game_search.py 解析不到 game_id;(2) SQL 报 BigQuery 错误;(3) 指标根本不在本 skill 覆盖范围(Phase 0 out-of-scope)。
关键原则:业务 GT 把"实际为 0"和"暂无数据"区分清楚——前者合法事实,后者查询失败。SQL 返回什么数字就答什么,覆盖度备注另行说明。
Reference Documents Index
| 文件 | 主题 |
|---|---|
| public_feeds.md | 核心事实表:声量/情感/Brand Health/互动/曝光/Earned/热门图文帖/热门视频/词云/话题;§5.2 官号帖子列表(A 路) |
| official_account_metrics.md | 「官号 include/exclude」聚合指标专属(必须命中官号触发词才加载)。汇总数字走 media_account_publishing;粉丝数走 media_account_audience;Earned 走 public_feeds + feeds_author LEFT JOIN |
| kol.md | opinion.kol + kol_tag + feeds_author(创作者榜单/趋势/官号识别 — 非直播) |
| streaming.md | streamhatchet 6 张表(直播;优先 _uid 版本) |
| pr_news.md | intelligence.news_details |
| googletrends.md | opinion.googletrends_keyword |
| memes.md | opinion.memes + meme_videos |
| market_popularity.md | top_mobile_game + top_pconsole_game + public_feeds(Channel Share Ranking) |
| marketing_hub.md | marketing_hub.* 全部 8 张(Hashtag / 视频 / KOL Info) |
| stores/steam.md / app_store.md / google_play.md | 主商店评分(详) |
| stores/taptap.md / xbox.md / playstation.md / meta_store.md / metacritic.md / opencritic.md | 其他商店(简) |
| auxiliary/dim_tables.md | dim_channel / dim_keyword / dim_language / dim_topic_labels / dim_media_account |
| auxiliary/geo_competitor.md | country_region + unified_competitor + game_event |
| auxiliary/id_mapping.md | ID 体系单一真理源:5 个 ID 含义 + 各表过滤键 + 决策树 + join 模板 + PC/Mobile probe |
| auxiliary/social_filter_logic.md | Include/Exclude/官号识别业务逻辑 |
| auxiliary/cube_schema.md | 全表字段索引(兜底) |
| creative/single_game.md | 单游戏投放素材:四指标 Total/趋势/类型分布/多维 TopN/素材 vs DAU 对比 |
| creative/multi_game.md | 多游戏(1–15)投放素材横向对比 |
| creative/materials_list.md | 素材/广告明细列表 + 视频标签 opinion.aix_gd_video_tags_detail |
Common Patterns(Quick Recipe)
取一段时间内某游戏的整体声量(其余模板见对应 references/<topic>.md)
SELECT
FORMAT_DATE('%Y-%m-%d', DATE(comment_time)) AS date,
COUNT(DISTINCT comment_uin) AS mentions,
COUNT(DISTINCT CASE WHEN sentiment_rating IN (4,5) THEN comment_uin END) AS positive,
COUNT(DISTINCT CASE WHEN sentiment_rating IN (1,2) THEN comment_uin END) AS negative
FROM `tencent-databrain-prod.opinion.public_feeds`
-- 必带:聚簇键 unified_edition_id + 分区键 comment_time(右开窗口)
WHERE unified_edition_id = '<game_id>'
AND comment_time >= TIMESTAMP('<start_date>')
AND comment_time < TIMESTAMP_ADD(TIMESTAMP('<end_date>'), INTERVAL 1 DAY)
GROUP BY date
ORDER BY date
LIMIT 1000;
Cross-skill Coordination
本 skill 处理 舆情/声量/口碑/KOL/直播/新闻 类问题,与 databrain-intelligence(DAU/Revenue/Retention)互补。同一对话既有「玩家舆情」又有「游戏销量」时,分别调用两个 skill,由上层合并。
Pitfalls (TL;DR)
- ❌ 最严重:查
opinion.public_feeds不带unified_edition_id(聚簇键)或comment_time范围(分区键)→ 亿级全表扫 + 必然 61001 timeout(其他事实表忘加时间分区过滤同理) - 不解析游戏名直接写
unified_edition_id = '原神'→ 0 行(必须先game_search.py,ID 在历史则复用) - 手游店写
WHERE unified_edition_id = ...(实际列名unified_id)/ PC 店写unified_edition_id(实际edition_id)→ 0 行无报错 - 直播原版表(kol/sessions/profile 无 _uid 版)用
unified_edition_id过滤 → 0 行(先转app_id,详见 streaming.md) - 用
TIMESTAMP_SUB查store_score_*.create_time(DATETIME)→No matching signature报错 - 用
country='global'后认为是"全球数据"(其实是"无国家归属",按国查时要country IN ('<target>','global')) - 写
WHERE organization='official'区分官号(字段不存在!用dim_media_account.category反查) - "发帖作者数/creators" 用
COUNT(DISTINCT comment_uin)(那是帖子数口径)→ 必须COUNT(DISTINCT CONCAT(reviewer,'-',LOWER(channel_name)))(主帖 +channel_type='social') - 聚合
topics不归一化大小写 → 漏 30-50%;把"上线后"直接翻成"近 7 天"(必须先查 release_time 或问用户) - 按具体游戏查视频却用
marketing_hub.marketing_hub_video:该表是行业级 Feed,无任何 game_id 字段,LIKE '%游戏名%'反查标题大量误差 → 一律走 public_feeds.md §场景 5 - 官号路由两个方向都可能走反(生产主要错误模式):
- 方向 A:用户没说"官号"还走
official_account_metrics.md→ 漏全部玩家 UGC(如 Blood Strike 仅 28K 官号互动量)。无触发词 → 必走 public_feeds.md §4 场景 3。 - 方向 B:用户明说"官号/剔除官号/Earned/玩家发的"还走
public_feeds.md通用 §4 → 漏官号识别。命中触发词 → 必走 official_account_metrics.md。 - 中国渠道(douyin/bilibili/xiaohongshu/kuaishou/tieba/taptap/nga)→ 先按 official_account_metrics.md §0 渠道探针确认有无数据;唯一硬告知不覆盖的是 weibo(全域无 weibo 渠道)。
- 方向 A:用户没说"官号"还走