-- 抖音账号日报:今天的账号情况 + 与昨天比对 + 推送有没有发出去 -- -- 分两段,因为它们是**两个维度**,硬塞进一张表会难看: -- -- 【一】账号维度:每个博主一行,今天的账号指标、相对昨天的增减、今天的作品情况。 -- 【二】任务维度:推送到底有没有发出去,以及从上次成功之后漏了几次。 -- -- 口径说明: -- * 「今天」= 该博主**最新一条**账号快照;「昨天」= 最新一条**日期早于今天**的快照。 -- 快照是一轮一条(任务一天跑好几轮),所以按日期取,不按轮次。 -- * 差值为 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;