Obsidian 核心插件数据库(Bases)之公式使用教程

数据库(Bases)是 Obsidian 官方推出的核心数据库插件,无需任何第三方工具就能把一批笔记聚合成可排序、可筛选的多种视图布局。

公式属性让数据库具备了真正的计算能力:可以直接在数据库中创建由其他属性派生而来的计算列,把评分和完成度合并成综合指数、把文件修改时间转为「3 天前」这样的相对描述、根据截止日期和进度自动标注逾期状态——这些原本只能靠 Dataview 或手动维护的功能,现在都可以用一行公式搞定。本文将完整覆盖数据库公式的核心用法:从公式属性的创建流程,到属性引用、运算符与函数的具体语法,再到常见的实用示例与进阶技巧。


目录

  1. 创建公式属性
  2. 公式语法:引用属性
  3. 公式语法:运算符
  4. 公式语法:函数
  5. 数据类型详解
  6. 引用其他公式:派生计算
  7. 实用公式示例
  8. 常见问题与注意事项
  9. 进阶用法与个性化调整
  10. 与其他插件或功能的联动

1. 创建公式属性

公式属性通过数据库文件的工具栏菜单来创建,操作步骤如下。

第一步:打开属性菜单。 在已打开的数据库(.base)文件中,点击顶部工具栏中的「笔记属性」按钮,这会弹出一个属性管理菜单,列出当前数据库中已有的所有属性列。

第二步:添加公式属性。 在属性管理菜单的底部,点击「添加公式」选项,菜单会展开为一个公式编辑区域。

第三步:为公式属性命名。 在名称输入框中为这个公式属性取一个有意义的名称,例如 综合得分剩余天数完整书名 等。名称在同一个 Bases 文件中必须唯一,且不能与已有的 frontmatter 属性重名,建议使用清晰的中文或英文短语,方便后续在筛选和排序时辨认。

第四步:输入公式。 在「公式」字段中输入你的计算表达式。公式编辑器会在你输入时提供自动补全,包括已识别的笔记属性名称和可用的函数名称,输入 . 后会自动列出可用的方法,输入函数名的前几个字母后会弹出候选列表。当公式语法正确时,输入框右侧会出现一个绿色勾号;如果语法有错误,则会显示红色提示。

第五步:关闭对话框。 确认公式无误后,关闭属性管理菜单,数据库视图会立即显示新增的公式列,并对每一行笔记计算出对应的结果。

💡 说明 公式属性创建后,可以像其他属性列一样通过拖拽调整它在视图中的列顺序,也可以在视图设置中选择是否显示该列。公式列的值不可手动编辑(单元格呈灰色),因为它的值完全由公式决定。


2. 公式语法:引用属性

公式的核心能力之一是引用当前行笔记的属性值,再对这些值进行计算或组合。Bases 支持引用三类属性。

2.1 笔记属性(frontmatter 属性)

直接写属性名即可引用笔记 frontmatter 中的字段,不需要任何前缀。例如,如果笔记的 frontmatter 中有一个叫 difficulty 的字段,公式中直接写 difficulty 就能取到这个值。

difficulty * importance

属性名区分大小写,Difficultydifficulty 是两个不同的属性。如果属性名中包含空格或特殊字符,需要使用方括号语法引用,例如:note["my property"]

2.2 文件属性(内置属性)

Obsidian 为每个笔记文件内置了一批系统级属性,统一以 file. 为前缀。常用的内置文件属性包括:

  • file.name — 文件名(不含扩展名)
  • file.path — 文件在库中的完整路径
  • file.size — 文件大小(字节数)
  • file.ctime — 文件创建时间(日期时间对象)
  • file.mtime — 文件最后修改时间(日期时间对象)

示例:将文件名与 frontmatter 中的作者字段拼接成一个组合标识:

file.name + "(" + author + ")"

2.3 公式属性(引用其他公式)

如果已经定义了其他公式属性,可以在新公式中引用它们,必须使用 formula. 前缀加上公式属性的名称。

formula.base_score * weight_factor

关于公式引用公式的更多细节,见第七节「引用其他公式:派生计算」。

⚠️ 注意(特定设置) 以下属性名仅为示例,请根据你自己的笔记属性替换。

项目示例属性名说明
笔记属性difficultyimportanceauthor替换为你在 frontmatter 中实际使用的字段名
内置文件属性file.namefile.mtime这些是固定的内置属性,名称不需要修改
公式属性formula.base_score替换为你自己已定义的公式属性名称

公式属性名在创建时必须与现有的 frontmatter 属性名不重复,否则 Bases 无法区分两者。


3. 公式语法:运算符

3.1 算术运算符

算术运算符对数字类型的属性进行数学计算,支持标准的四则运算和括号控制运算优先级:

  • score + bonus — 加法
  • total - deduction — 减法
  • unit_price * quantity — 乘法
  • total_score / item_count — 除法
  • (mastery + fluency) * 0.5 — 括号控制运算顺序,此例计算两项均值

除法时注意分母为零的情况,Bases 会在单元格中显示错误,可以通过 if() 函数判断后处理(见 4.1 节)。

3.2 比较运算符

比较运算符对两个值进行比对,返回布尔值(truefalse),常用于筛选条件或 if() 函数的判断分支:

  • score > 80 — 大于
  • word_count < 500 — 小于
  • stage == "已完成" — 等于(注意是两个等号)
  • stage != "已搁置" — 不等于
  • file.mtime > now() - "30d" — 最近 30 天内修改过的文件

3.3 布尔运算符

布尔运算符用于组合多个条件:

  • !is_archived — 非(对布尔属性取反)
  • score > 60 && attendance > 0.8 — 且(两个条件均须满足)
  • is_urgent || is_blocked — 或(任一条件满足即可)

💡 说明 比较运算符的结果为布尔值,可以直接作为公式属性的输出(单元格会显示 truefalse),也可以作为 if() 函数的判断条件。将复杂的布尔表达式封装成一个公式属性,再在其他公式或筛选器中引用,是保持公式简洁的常用做法。


4. 公式语法:函数

函数对属性值执行更复杂的操作,使用时以 属性.函数名() 的链式写法调用(类型函数),或以 函数名(参数) 的形式直接调用(全局函数)。以下按函数类别介绍。

4.1 全局函数

全局函数不依附于特定的值类型,直接调用即可:

  • if(条件, 满足时的值, 不满足时的值) — 条件判断,第三个参数可省略(省略时默认为空)
  • now() — 返回当前日期时间
  • today() — 返回当前日期(不含时间)
  • date("2026-01-01") — 将字符串解析为日期对象
  • link("路径/文件名") — 返回一个可渲染的链接对象
  • max(a, b, c) — 返回多个数值中的最大值
  • min(a, b, c) — 返回多个数值中的最小值

if() 函数是公式中使用频率最高的全局函数之一,几乎所有需要「根据条件显示不同内容」的场景都会用到它。基本用法:

if(stage == "已发布", "✅ 已上线", "⏳ 进行中")

4.2 字符串函数

字符串函数作用于文本类型的值,以链式方式调用:

  • text.contains("关键词") — 判断文本是否包含某个子字符串,返回布尔值
  • text.replace("旧内容", "新内容") — 替换文本中的指定内容
  • text.split("分隔符") — 按分隔符拆分字符串,返回列表
  • text.lower() — 转为全小写
  • text.upper() — 转为全大写
  • text.title() — 转为标题格式(每个单词首字母大写)

示例:将笔记的文件名统一转为小写,方便与其他字段做不区分大小写的比较:

file.name.lower()

4.3 数字函数

数字函数作用于数值类型的属性:

  • num.round() — 四舍五入到整数
  • num.ceil() — 向上取整
  • num.floor() — 向下取整
  • num.abs() — 取绝对值
  • num.toFixed(2) — 保留指定位数的小数(返回字符串)

示例:将一个浮点数评分格式化为保留一位小数的显示值:

if(score, score.toFixed(1) + " 分", "未评分")

4.4 日期函数

日期函数作用于日期/时间类型的值,这类属性通常是通过 file.mtimefile.ctime 或 frontmatter 中的日期字段获取:

  • date.format("YYYY-MM-DD") — 将日期格式化为指定字符串(使用 Moment.js 格式)
  • date.relative() — 返回相对于当前时间的描述,如「3 天前」、「2 个月后」
  • date.date() — 提取日期部分(去除时间)
  • date.time() — 提取时间部分

示例:将文件修改时间显示为对人友好的相对描述:

file.mtime.relative()

示例:将 frontmatter 中的 publish_date 字段格式化输出:

publish_date.format("YYYY 年 MM 月 DD 日")

4.5 列表函数

列表函数作用于多值属性(如标签、链接列表等):

  • list.filter(条件) — 筛选列表中满足条件的元素
  • list.map(表达式) — 对列表中的每个元素应用表达式,返回新列表
  • list.sort() — 对列表排序
  • list.join("分隔符") — 将列表合并为一个字符串
  • list.unique() — 去除列表中的重复值
  • list.length — 返回列表中的元素数量(注意:length 是属性,不是函数,不带括号)

示例:计算一篇综述笔记中引用的参考文献数量:

references.length

示例:将标签列表中所有包含 project/ 前缀的标签提取出来,用逗号连接成字符串:

tags.filter(value.startsWith("project/")).join("、")

💡 说明 列表函数中的 filter()map() 使用 value 关键字代指当前正在遍历的列表元素,这一点和常见编程语言的 lambda 语法不同,初次使用时容易忘记。记住:凡是在 filter()map() 的括号内写条件或表达式时,用 value 来表示「当前这个元素」。


5. 数据类型详解

公式能处理的值有固定的类型,不同类型支持的操作和函数也不同。了解类型系统有助于避免常见的类型不匹配错误。

字符串(String):文本类型,用单引号或双引号括起来定义字面量,例如 "已完成"'project'。字符串支持拼接(+ 运算符)和所有字符串函数。大多数 frontmatter 文本属性都是字符串类型。

数字(Number):数值类型,直接写数字字面量,如 423.14(10 + 5)。数字支持四则运算和数字函数。frontmatter 中未加引号的纯数字属性通常会被识别为数字类型。

布尔值(Boolean):只有 truefalse 两个值,是比较运算符和布尔运算符的运算结果,也可以作为 if() 函数的条件参数。frontmatter 中类型为「复选框」的属性是布尔类型。

日期(Date):日期时间类型,由 date()today()now() 函数创建,或从 frontmatter 的日期类型属性读取。日期对象支持加减时间段(如 review_date + "14d")和所有日期函数。时间段字面量用字符串表示,支持 d(天)、w(周)、M(月)、h(小时)等单位。

列表(List):多值集合,可以用方括号定义字面量,如 [1, 2, 3],或由 frontmatter 的多值属性自动识别。列表支持 filter()map()sort()join() 等函数,以及通过 [索引] 访问特定位置的元素(索引从 0 开始)。

对象(Object):键值对结构,用花括号定义,如 {"name": "value"}。主要用于处理嵌套属性,如通过 file.properties 访问文件属性对象内的子字段。在一般日常公式中用到较少。

💡 说明 公式的输出类型由表达式的最终返回值决定,不需要显式声明。如果公式返回一个字符串,单元格就显示文本;如果返回一个布尔值,单元格就显示 true/false;如果返回一个日期,单元格就以日期格式渲染。当不同类型混合运算时(例如用 + 拼接数字和字符串),通常需要先用 .toFixed() 或字符串转换函数将非字符串类型显式转为字符串,否则可能出现 [object Object] 这样的渲染异常。


6. 引用其他公式:派生计算

公式属性可以相互引用,形成多层派生计算的链条。引用其他公式时,必须使用 formula. 前缀加上目标公式属性的名称。

假设已定义了一个名为 weighted_score 的公式:

(knowledge * 0.4) + (skill * 0.6)

然后可以在另一个公式中引用它,进一步计算等级标签:

if(formula.weighted_score >= 90, "优秀", if(formula.weighted_score >= 75, "良好", if(formula.weighted_score >= 60, "及格", "待提升")))

这种嵌套 if() 的写法实现了类似多分支判断的效果,适合需要把连续数值映射为离散分类的场景。

⚠️ 警告:避免循环引用 公式不能直接或间接地引用自身。例如,公式 A 引用了公式 B,公式 B 又引用了公式 A,就构成循环引用,Bases 会在单元格中显示错误并拒绝计算。设计多层公式时,应确保引用关系是单向的有向无环图(DAG),即每个公式只依赖于其他已存在的、不反过来依赖自己的公式。

派生计算的好处不仅是避免重复写相同的表达式,更重要的是让逻辑分层变得清晰:底层公式计算基础数值,中层公式对基础数值加工,顶层公式负责展示和分类。当底层逻辑需要调整时,只改一处即可,其他公式自动更新。


7. 实用公式示例

以下示例覆盖常见的使用场景,每个示例均可直接参考或按需修改后使用。

7.1 计算复习提醒日期

将笔记的「上次复习日期」加上指定的复习间隔天数,得到下次应当复习的日期:

last_review + "21d"

⚠️ 注意(特定设置) 以上公式依赖名为 last_review 的日期类型属性。时间段单位 "21d" 为示例值,请根据你自己的复习周期替换(如 "7d""1M" 等)。

项目示例值替换建议
上次复习属性last_review替换为你实际使用的日期字段名
复习间隔"21d"替换为你的记忆曲线复习间隔

7.2 标注待复习状态

如果下次复习日期已过且笔记状态不是「已掌握」,则标注为「待复习」,否则显示为空:

if(next_review < now() && mastery_level != "已掌握", "⚠️ 待复习", "")

7.3 格式化评分显示

将数值评分属性以带星号符号的格式显示,如果属性为空则显示「暂未评分」:

if(rating, "★ " + rating.toFixed(1), "暂未评分")

7.4 统计关联笔记数量

计算一个列表属性中的元素总数,适用于统计参考文献数、关联项目数等:

linked_notes.length

7.5 合并系列与卷册信息

将书目数据库中的系列名和卷册编号拼接为完整的显示标题:

series_name + " · 第 " + volume_number + " 卷"

7.6 计算综合优先级指数

用重要程度和紧迫程度的乘积除以预计所需精力,得出一个优先级排序依据:

(importance * urgency) / estimated_effort

💡 说明(按需调整) 此公式的设计逻辑是:重要且紧急的任务排在前面,但所需精力高的任务会被降权。三个属性均需为数字类型,且建议使用统一的量纲(例如均为 1~5 的整数),否则计算结果的可比性较差。

7.7 计算文件创建至今的天数

用当前时间减去文件创建时间,得到该笔记已创建多少天,适合用于追踪笔记的「年龄」:

(now() - file.ctime) / 86400000

💡 说明 两个日期相减的结果是毫秒数,86400000 是一天的毫秒数(24 × 60 × 60 × 1000),除以它即可换算为天数。可在外层套用 .floor() 取整:((now() - file.ctime) / 86400000).floor()

7.8 提取文件名中的日期部分

对于按日期命名的笔记(如 2026-05-03 读书摘要),从文件名中提取前 10 个字符作为日期字符串,适合在表格中显示一个统一的日期列:

file.name.split(" ")[0]

⚠️ 注意(特定设置) 以上公式假设文件名的格式为「日期 空格 其他内容」,且日期部分作为第一个空格分隔的片段。如果你的文件命名规则不同(例如日期在文件名末尾、或使用下划线而非空格分隔),需要调整 split() 的分隔符和索引值。

7.9 学习卡片综合熟练度

将知识点的「理解程度」和「记忆强度」加权平均,得出一个整体熟练度分数(满分 10 分):

(comprehension * 0.4 + retention * 0.6).toFixed(1)

7.10 项目健康度红绿灯

根据进度百分比和截止日期距离天数综合判断项目状态,返回颜色标记:

if(progress >= 1, "🟢 已完成",
  if((due_date - now()) / 86400000 < 0, "🔴 已逾期",
    if((due_date - now()) / 86400000 < 7 && progress < 0.5, "🟡 有风险", "🟢 正常")))

7.11 书单完整书目标签

将书名、作者和出版年份组合为一行标准引用格式,当作者或出版年份缺失时退回到仅显示书名:

if(author && publish_year,
  title + "(" + author + "," + publish_year + ")",
  title)

7.12 距离截止日的剩余天数(取整)

计算今天到截止日期之间的完整天数,负数表示已逾期:

((due_date - now()) / 86400000).floor()

7.13 统计有内容的可选属性个数

对于一组非必填属性,统计其中已填写(非空)的数量,用于衡量笔记的「完整度」:

[title, abstract, keywords, source_url].filter(value).length

7.14 将文件修改时间格式化为友好显示

根据文件最后修改时间距今的远近,切换显示格式——近期用相对描述,较早用绝对日期:

if(now() - file.mtime < 604800000,
  file.mtime.relative(),
  file.mtime.format("YYYY-MM-DD"))

8. 常见问题与注意事项

8.1 公式单元格显示 [object Object]

这通常发生在用 + 拼接字符串时,右侧的操作数不是字符串类型。例如把一个数字或日期直接拼接到字符串中,会触发此问题。解决方法是在拼接前先将非字符串值转换为字符串:数字类型使用 .toFixed(0).round().toString(),日期类型使用 .format("YYYY-MM-DD") 转为格式化字符串。

8.2 公式单元格显示为空但没有报错

属性为空(null)时,公式的计算结果可能是空值而非错误,单元格什么都不显示。如果希望在属性为空时也显示一个默认值,可以用 if() 处理空值情况:

if(topic, topic.upper(), "(未分类)")

这样,当 topic 属性有值时显示其大写形式,当属性为空时显示「(未分类)」。

8.3 日期加减运算报错

时间段(如 "14d")必须是字符串字面量,且写在日期值的右侧。时间段本身不能直接参与乘除运算,如果需要计算动态的时间段(例如「属性值 × 天」),需要先用 duration() 函数将数字转为时间段对象,并且时间段必须写在乘法的左侧:

review_date + duration(interval_days + "d")

这里 interval_days 是一个表示天数的数字属性,通过字符串拼接构成时间段字符串后传入 duration() 解析。

8.4 filter()map() 中报错

确认是否使用了 value 关键字来引用当前遍历的元素。以下是常见的错误写法与正确写法对比:

// 错误:直接写属性名,没有用 value
tags.filter(tags.startsWith("area/"))
 
// 正确:用 value 代表当前遍历的元素
tags.filter(value.startsWith("area/"))

8.5 升级 Obsidian 后公式全部失效

参见本文「版本说明」章节。在 Obsidian 1.9.1 版本中,Bases 对所有内置函数名进行了命名规范调整,snake_case 函数名全部改为 camelCase,例如 to_fixed 变为 toFixedis_empty 变为 isEmpty。升级后需要手动检查并更新 .base 文件中的所有函数名,否则涉及这些函数的公式将无法运行。

推荐做法 在 Obsidian 大版本升级前,建议先在测试库中确认功能兼容性,再迁移到主库。如果已有多个 Bases 文件,可以在文本编辑器中批量搜索替换函数名,效率更高。

8.6 公式运算结果保留了过多小数位

数字运算产生浮点数时,显示结果可能出现很多位小数(例如 0.3333333…)。用 .toFixed(n) 可以将其格式化为保留 n 位小数的字符串,或用 .round() 四舍五入为整数:

(completed_count / total_count * 100).toFixed(1) + "%"

9. 进阶用法与个性化调整

9.1 在筛选器中使用公式属性

公式属性创建后,不仅可以作为显示列,还可以直接用于视图的筛选条件。例如,先定义一个名为 is_overdue 的布尔型公式:

due_date < now() && completion_rate < 1

然后在视图的筛选设置中,添加筛选条件「formula.is_overdue 等于 true」,这个视图就只会显示已逾期且未完成的笔记,无需每次手动输入复杂的日期比较条件。这种「把复杂逻辑封装进公式,再用公式来筛选」的模式是保持视图配置简洁的关键技巧。

9.2 将公式作为排序依据

数值类型的公式属性可以直接作为视图的排序列。例如,定义一个综合热度评分公式:

(view_count * 0.3) + (citation_count * 0.7)

再在视图设置中按该公式属性降序排列,就能把「被引用多且浏览量高」的笔记排在最前面,比手动整理高效得多。

9.3 利用 image() 函数在单元格中渲染图片

image() 是一个全局函数,可以将图片路径或 URL 渲染为单元格内的预览图:

image(cover_image)

其中 cover_image 是一个存储了图片路径(如 Assets/book-covers/some-cover.jpg)或图片 URL 的 frontmatter 属性。这对构建书单、电影库等有视觉需求的数据库非常有用。

link() 函数可以基于属性值动态生成一个笔记链接。例如,根据项目代码属性自动生成指向对应项目笔记的链接:

link("Projects/" + project_code)

这样在数据库中点击该单元格就会直接跳转到对应的项目笔记,而不需要在 frontmatter 中手动维护一个链接字段。

💡 说明(按需调整) link() 的参数是笔记的路径字符串(相对于库根目录),路径中的 "Projects/" 前缀为示例,请替换为你实际的文件夹结构。如果目标笔记直接位于库根目录,省略文件夹前缀即可:link(project_code)

9.5 多条件的复杂逻辑公式

if() 函数可以嵌套以实现多分支逻辑,但层数过多会使公式难以阅读。可以借助布尔运算符简化:

if(priority == "高" && deadline_days < 3, "🔴 紧急", 
  if(priority == "高" || deadline_days < 7, "🟡 关注", "🟢 正常"))

这个公式的逻辑是:如果优先级为高且截止日还有不到 3 天,显示红色紧急;如果优先级高或截止日不到 7 天,显示黄色关注;其他情况显示绿色正常。


10. 与其他插件或功能的联动

10.1 与 Dataview 的关系

Bases 和 Dataview 在功能定位上有一定重叠,但侧重不同。Dataview 更擅长跨库全局查询和输出高度自定义的表格,支持 DQL 和 DataviewJS 两种查询语言,灵活度极高;Bases 更侧重于为特定的笔记集合提供可交互、可直接编辑属性的数据库视图,公式计算是其原生能力。

两者可以并存于同一个库中,互不干扰。如果你已经有大量 Dataview 查询,不需要迁移,Bases 可以作为一种补充:在需要直接编辑数据、或需要原生视图(而非嵌入式查询块)的场景下,Bases 是更合适的工具。

10.2 与 Templater 的联动

Templater 可以在新建笔记时自动填充 frontmatter 属性,而 Bases 的公式计算建立在这些属性之上。两者组合使用的典型工作流是:用 Templater 模板保证每类笔记都有完整、规范的属性集合(字段名统一、类型一致),再在 Bases 中对这些属性建立公式计算层。

如果 frontmatter 字段名不统一(例如有些笔记用 due_date,有些用 dueDate),公式只能识别其中一种,计算结果会出现大量空值。因此,在推广 Bases 之前,建议先用 Templater 或属性重命名工具统一笔记属性的命名规范。

10.3 与 Properties(属性面板)的联动

Obsidian 的内置属性面板支持直接在笔记中编辑 frontmatter,与 Bases 的数据库视图是同一套数据的两个入口。在数据库视图中修改一个笔记的属性值(非公式列),会立即反映到该笔记的 frontmatter 中;反之,在笔记的属性面板中修改属性,数据库视图也会在刷新后更新。公式列不可直接编辑,但其计算结果会随源属性的变更实时更新。