Notion 数据库自动同步技巧

Notion
数据库
Quarto
Web
教程
通过 API 同步 Notion 数据库时,Formula 与 Rollup 属性的使用技巧。
作者

Pinn Xu

发布于

2026年7月8日

7 min read · 287 EN words · 1481 中文字

1 引言

我最近重构了本站更新日志(Change Log)背后的 Notion 数据库。此前站点依赖单个数据库,其中大多是手动维护的属性。尽管单元格内容大多由 AI agent 生成,手动维护这个数据库依然繁琐。

由于我本就通过 Notion Calendar 在另一个数据库中记录开发任务,于是我将两个数据库关联起来,借助 Rollup 与 Formula 自动填充更新日志的属性。

小节 2 介绍数据库结构与工作流,小节 3 分享我在搭建自动同步过程中总结的关键经验。

2 数据库结构

本 Quarto 站点使用两个项目专属数据库——一个用于开发进度 (Dev Track),一个用于更新日志 (Change Log) ,外加一个统管所有项目的通用数据库。数据库填充完成后,Python 脚本 会拉取最新数据,再由 Lua 过滤器渲染出 更新日志 页面。工作流如下:

2.1 任务数据库

Dev Track 数据库将每项日常任务存为一条记录。其中一个 Relation 属性关联到 Change Log 数据库,此外还包含时间线、时长等基础属性。

Dev 数据库总览

Dev 数据库总览

值得一提的是,我并非通过 Notion 界面逐行添加记录,而是用 Notion Calendar 记录任务。它提供清晰的日历视图,也便于我在做事时随手记笔记。

Notion Calendar 中的 Dev 数据库

Notion Calendar 中的 Dev 数据库

2.2 进度数据库

Change Log 数据库按阶段记录站点的开发进度。每条记录关联了 Dev Track 数据库中的若干任务,并将它们汇总为 Rollup 属性。例如 Start (实际开始日期:开始时间早于凌晨 5 点的任务计入前一天) 和 Hours(该阶段内所有任务的总时间跨度)。

Log 数据库总览

Log 数据库总览

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 来实现。

回到顶部