diff --git a/docs/queries/account-daily-report.sql b/docs/queries/account-daily-report.sql new file mode 100644 index 0000000..cc58b1e --- /dev/null +++ b/docs/queries/account-daily-report.sql @@ -0,0 +1,122 @@ +-- 抖音账号日报:今天的账号情况 + 与昨天比对 + 推送有没有发出去 +-- +-- 分两段,因为它们是**两个维度**,硬塞进一张表会难看: +-- +-- 【一】账号维度:每个博主一行,今天的账号指标、相对昨天的增减、今天的作品情况。 +-- 【二】任务维度:推送到底有没有发出去,以及从上次成功之后漏了几次。 +-- +-- 口径说明: +-- * 「今天」= 该博主**最新一条**账号快照;「昨天」= 最新一条**日期早于今天**的快照。 +-- 快照是一轮一条(任务一天跑好几轮),所以按日期取,不按轮次。 +-- * 差值为 NULL 表示**没有可比的昨天**(昨天没跑或没采到这个号),不是 0。 +-- * 时间戳是毫秒,日期换算统一走 FROM_UNIXTIME(x/1000)。 +-- * MySQL 5.7 没有窗口函数,所以「最新一条」还是相关子查询。 + +-- =========================================================================== +-- 【一】账号日报 +-- =========================================================================== +SELECT + al.alias AS 博主备注, + COALESCE(cur.nickname, yest.nickname, '') AS 博主昵称, + cur.fans AS 粉丝数, + cur.fans - yest.fans AS 粉丝增减, + cur.total_favorited AS 总获赞, + cur.total_favorited - yest.total_favorited AS 获赞增减, + cur.works_count AS 主页作品数, + cur.works_count - yest.works_count AS 作品数增减, + (SELECT COUNT(*) FROM monitor_note n + JOIN monitor_task tn ON tn.id = n.task_id + WHERE tn.platform = 'dy' AND n.creator_hash = base.creator_hash) AS 监控中作品, + (SELECT COUNT(*) FROM monitor_note n + JOIN monitor_task tn ON tn.id = n.task_id + WHERE tn.platform = 'dy' AND n.creator_hash = base.creator_hash + AND n.first_seen_at >= UNIX_TIMESTAMP(CURDATE()) * 1000) AS 今日新采到, + FROM_UNIXTIME((SELECT MAX(n.published_at) FROM monitor_note n + JOIN monitor_task tn ON tn.id = n.task_id + WHERE tn.platform = 'dy' + AND n.creator_hash = base.creator_hash) / 1000) AS 最近作品发布, + FROM_UNIXTIME(cur.captured_at / 1000) AS 最近快照, + CASE WHEN yest.id IS NULL THEN '昨天无快照' ELSE '有' END AS 昨日可比 +FROM ( + -- 账号清单:作品里出现过的 ∪ 有账号资料的(含一条作品都没有的号) + SELECT n1.creator_hash FROM monitor_note n1 + JOIN monitor_task t1 ON t1.id = n1.task_id WHERE t1.platform = 'dy' + UNION + SELECT s1.creator_hash FROM monitor_creator_stat s1 + JOIN monitor_task t2 ON t2.id = s1.task_id WHERE t2.platform = 'dy' +) base +-- 今天(最新一条快照) +LEFT JOIN monitor_creator_stat cur + ON cur.id = (SELECT s.id FROM monitor_creator_stat s + JOIN monitor_task t3 ON t3.id = s.task_id + WHERE t3.platform = 'dy' AND s.creator_hash = base.creator_hash + ORDER BY s.captured_at DESC, s.id DESC LIMIT 1) +-- 昨天(最新一条日期早于今天的快照) +LEFT JOIN monitor_creator_stat yest + ON yest.id = (SELECT s.id FROM monitor_creator_stat s + JOIN monitor_task t4 ON t4.id = s.task_id + WHERE t4.platform = 'dy' AND s.creator_hash = base.creator_hash + AND DATE(FROM_UNIXTIME(s.captured_at / 1000)) < CURDATE() + ORDER BY s.captured_at DESC, s.id DESC LIMIT 1) +LEFT JOIN monitor_creator_alias al + ON al.platform = 'dy' AND al.creator_hash = base.creator_hash +ORDER BY (cur.fans - yest.fans) IS NULL, cur.fans - yest.fans DESC, cur.fans DESC; + + +-- =========================================================================== +-- 【二】推送有没有发出去(任务维度) +-- =========================================================================== +-- +-- 判定办法,两步: +-- +-- 1. `last_notified_at` **只在企业微信返回 errcode=0 之后才写**,所以它是「最近一次 +-- 推送成功的时刻」。推送就发生在那一轮跑完之后几秒内 —— 拿它去和一串 run 的结束 +-- 时间比,落在两分钟内的那一轮,就是**最后一次真正发出去的那轮**。 +-- 2. 那一轮**之后**、本该推送却没有的轮次,就是漏掉的。`漏掉几次` 就是这么数的。 +-- +-- 为什么不能更精确:失败的推送没有留下任何记录(原因只打到容器 stdout),`last_notified_at` +-- 也只有一个值,会被后来的成功覆盖。要逐轮可查得让它落一条事件或一列原因 —— 那是改代码, +-- 不是这条 SQL 能补的。 +-- +-- 「本该推送」= 那轮 new_notes > 0,或者出了失败类事件(和 notify.build_run_message 的 +-- 判断一致:它按任务上的两个开关过滤事件,没有可推的就返回 None、压根不发)。 +SELECT + t.id AS 任务, + t.name AS 名称, + t.notify_enabled AS 推新作品, + t.notify_failures AS 推异常, + FROM_UNIXTIME(t.last_notified_at / 1000) AS 上次推送成功于, + hit.run_id AS 成功的是这轮, + -- 没有匹配行(= 一次没漏)时 LEFT JOIN 给的是 NULL,直接显示会是一片空白, + -- 分不清「零」和「查不到」。这里补成 0。 + COALESCE(leaks.漏掉几次, 0) AS 那之后漏掉, + leaks.最近漏掉的那轮 AS 最近漏掉的轮次, + FROM_UNIXTIME(leaks.最近漏掉的时间) AS 它结束于 +FROM monitor_task t +-- 找「推送成功时刻」对应的那一轮:结束时间落在它之前两分钟内、且最近的那一轮。 +LEFT JOIN ( + SELECT r.task_id, MAX(r.id) AS run_id + FROM monitor_run r + JOIN monitor_task t5 ON t5.id = r.task_id + WHERE t5.last_notified_at IS NOT NULL + AND r.finished_at <= t5.last_notified_at + AND t5.last_notified_at - r.finished_at <= 120000 + GROUP BY r.task_id +) hit ON hit.task_id = t.id +-- 上次成功之后,本该推送却没推的轮次。 +LEFT JOIN ( + SELECT r.task_id, + COUNT(*) AS 漏掉几次, + MAX(r.id) AS 最近漏掉的那轮, + MAX(r.finished_at) AS 最近漏掉的时间 + FROM monitor_run r + JOIN monitor_task t6 ON t6.id = r.task_id + WHERE r.finished_at > COALESCE(t6.last_notified_at, 0) + AND (r.new_notes > 0 + OR EXISTS (SELECT 1 FROM monitor_event e + WHERE e.run_id = r.id + AND e.type IN ('suspected_auth_failure', 'run_failed', 'no_data_found'))) + GROUP BY r.task_id +) leaks ON leaks.task_id = t.id +WHERE t.platform = 'dy' +ORDER BY t.id;