Notion 数据库自动同步技巧
1 引言
我最近重构了本站更新日志(Change Log)背后的 Notion 数据库。此前站点依赖单个数据库,其中大多是手动维护的属性。尽管单元格内容大多由 AI agent 生成,手动维护这个数据库依然繁琐。
由于我本就通过 Notion Calendar 在另一个数据库中记录开发任务,于是我将两个数据库关联起来,借助 Rollup 与 Formula 自动填充更新日志的属性。
2 数据库结构
本 Quarto 站点使用两个项目专属数据库——一个用于开发进度 (Dev Track),一个用于更新日志 (Change Log) ,外加一个统管所有项目的通用数据库。数据库填充完成后,Python 脚本 会拉取最新数据,再由 Lua 过滤器渲染出 更新日志 页面。工作流如下:
2.1 任务数据库
Dev Track 数据库将每项日常任务存为一条记录。其中一个 Relation 属性关联到 Change Log 数据库,此外还包含时间线、时长等基础属性。
值得一提的是,我并非通过 Notion 界面逐行添加记录,而是用 Notion Calendar 记录任务。它提供清晰的日历视图,也便于我在做事时随手记笔记。
2.2 进度数据库
Change Log 数据库按阶段记录站点的开发进度。每条记录关联了 Dev Track 数据库中的若干任务,并将它们汇总为 Rollup 属性。例如 Start (实际开始日期:开始时间早于凌晨 5 点的任务计入前一天) 和 Hours(该阶段内所有任务的总时间跨度)。
Formula 属性负责计算并返回格式化的结果。例如 Date label 属性会判断某条记录的时间跨度是一天还是多天,同时对于最新阶段返回 <start> - now。
此外还有一些隐藏属性支撑这些计算:_days 供 Days 使用,_end 与 _phase 供 Date label 使用。_BU 后缀则表示“备份” (backup),即原先手动编辑属性的备份。
2.3 项目数据库
All Projects 数据库汇集所有项目并展示整体 Rollup 统计。其中每一行同时也是一个汇总页面,用于管理各子数据库,并展示图表等信息。
3 用 Formula 搭建同步的技巧
3.1 时区
在用 Formula 与 Rollup 替换掉手动输入的属性后,我发现日期类 Formula 属性在 Notion 界面与 API 中返回的值并不一致。例如:
prop("Timeline").dateStart().formatDate("YYYY-MM-DD").parseDate()这个 Formula 会去掉 Timeline 起始日期中的时间部分,返回纯日期。无论工作区设为哪个时区,它在 Notion 界面中都正常,但 API 可能返回不同的日期。
原因在于 formatDate() 以 UTC+0 解析和输出。如果你的时区是 UTC-4,结果就会相差 4 小时。
解决办法是:只要你的时区不是 UTC+0,就在调用 formatDate() 时传入时区参数。
prop("Timeline").dateStart().formatDate("YYYY-MM-DD", UTC-4).parseDate()如果你所在地区实行夏令时,建议使用 IANA 时区名称,例如用 America/New_York 而非 UTC-4。
3.2 数据类型
Formula 属性在源属性为空时并不强制要求设置回退值(fallback)。若要设置,请使用与该属性相同的类型。
同时应避免用 empty() 作为回退值:它会导致图表视图(Chart View)无法使用。例如:
ifs(
/* set empty() as fallback if $Timeline is empty */
prop("Timeline") == empty(),
empty(),
/* if start time is before 5am, subtract 1 day */
prop("Timeline").dateStart().hours() <= 5,
prop("Timeline").dateStart().dateSubtract()
.formatDate("YYYY-MM-DD", UTC-4).parseDate(),
/* else: set start time */
prop("Timeline").dateStart().formatDate("YYYY-MM-DD", UTC-4).parseDate()
)这个 Formula 根据 Timeline 属性给出任务的实际日期,并将开始时间早于凌晨 5 点的任务计入前一天。
它在常规视图(表格、列表、日历等)中正常工作,但该属性无法用作图表视图的 X 轴。
解决办法是去掉 empty() 回退值。要么完全不设回退值——源属性为空时该属性也为空,同时仍可用于图表视图;要么使用一个日期作为回退,例如 1900-01-01 或 today()。
ifs(
/* date fallback if $Timeline is empty */
prop("Timeline") == empty(),
parseDate("1900-01-01"),
/* if start time is before 5am, subtract 1 day */
prop("Timeline").dateStart().hours() <= 5,
prop("Timeline").dateStart().dateSubtract()
.formatDate("YYYY-MM-DD", UTC-4).parseDate(),
/* else: set start time */
prop("Timeline").dateStart().formatDate("YYYY-MM-DD", UTC-4).parseDate()
)其他类型同理:字符串用 "",数字用 0,布尔用 false,列表用 []。
3.3 降低运行开销
每个 Rollup 属性都会重新从源数据库拉取全部内容,因此 Rollup 过多的数据库在维护时可能变得很慢。应尽量减少 Rollup 数量。与其用多个 Rollup 分别取关联数据,不如用一个 Rollup 配合 Formula 来实现。





