Obsidian用久了,大概都会经历这么个阶段:笔记越攒越多,双链连成了一张网,关系图谱密密麻麻,可真想找"上个月读的那本书的评分"或者"这周还没完成的任务"时,却只能一篇篇点开笔记翻。自带搜索能搜关键词,但搜不出"状态为未读且评分高于8分的书"。这时候Dataview就出现了。它把Markdown笔记当成数据库表,用类似SQL的查询语法生成动态列表、表格、任务清单,Obsidian瞬间从"个人维基"变成了"个人数据中台"。
这篇是Obsidian 0x系列的第5篇,专门聊Dataview插件。不管你是刚接触Obsidian、听说Dataview很强大但不知道怎么入门,还是已经开始用基础查询、但遇到性能或语法问题,这篇文章都能给到可落地的方案。我争取讲清楚:它解决了什么本质问题,查询语法怎么组织,实战场景怎么套,以及那些文档里不常提但实测很关键的性能大坑。
1. 为什么是Dataview:笔记一旦变多,查找效率才是第一痛点
1.1 笔记堆成山之后,搜索和标签为什么就不够用了
我见过不少朋友,刚开始装Obsidian时觉得自带的Quick Switcher和全局搜索已经很爽了——毕竟能全文检索,还能高亮。但用上半年、存了上千篇笔记后,问题就来了:搜索只能做"关键词匹配",做不了"条件组合"。
举个例子,你给读书笔记打了#book标签,又在正文里写了"状态:在读"。现在你想找"所有2025年标记为'在读'且评分超过8分的书",用标签和搜索就得先筛出所有#book标签的笔记,再一篇篇看状态和评分。运气好可能10篇,运气不好100篇,纯体力活。
标签系统本身也撑不住复杂查询。单个标签可以,但"多个标签的交集""按日期范围筛选"这类需求,靠标签面板就撸不动了。关系图谱很有视觉冲击力,但它从来不是查询工具——你能看出哪些笔记有联系,却看不出"哪些书还没读完"。
这才是Dataview存在的根本原因:Obsidian存储的是Markdown纯文本,天然结构化程度低,当笔记数量上去,缺少一层"查询层"来把数据从正文里提出来,按条件聚合展示。Dataview补的正是这一层。
1.2 Dataview在Obsidian生态里的实际定位
Dataview的定位,不是又一个"美化笔记的插件",而是一个数据查询与展示引擎。它读你的笔记文件,读取每篇笔记的YAML frontmatter、行内字段、标签、文件名、创建时间等元数据,然后把它们当成表格的行列,通过查询语句生成新的视图。
这个视图是动态的——你之后改了笔记里的字段,Dataview结果会自动跟着更新(大部分情况下)。换句话说,你不用反复维护目录页,只要在库的根目录放一个Books.md,用Dataview写一句"列出所有状态为'未读'的书",这个列表每次打开都会自动同步最新数据。
它适合谁?日记党想做月度回顾、读书党想管阅读进度、项目党想聚合任务和记录、还有喜欢做个人知识库仪表盘的朋友。它不适合谁?如果你拿Obsidian纯当写作工具,笔记之间不需要统计归类,那用不上Dataview,装了反而多一个要学的东西。
1.3 元数据才是核心:先把笔记结构设计好
Dataview说白了是"先有数据,后有查询"。数据从哪来?主要是两处:
- YAML frontmatter:笔记开头用三个横线包起来的部分,定义字段如
status、author、date。 - 行内字段(Inline fields):正文里写成
字段:: 值,Dataview也能识别。
我的建议是,只要是打算被Dataview查询的笔记,就尽量用YAML frontmatter统一开头。这样做的好处是字段规范、不易出错,而且肉眼一看就知道这篇笔记记录了哪些维度。
--- type: book title: 置身事内 author: 兰小欢 status: 在读 rating: 9 started: 2026-01-10 cover: https://example.com/cover.jpg ---这就像你先给表格设计了表头,Dataview才能给你生成像样的报表。如果每篇笔记字段命名三天两头变,查询时就会很痛苦。本章先建立这个认知,后面所有语法都建立在这套数据模型之上。
2. 读懂Dataview查询语法:把笔记当成数据库来提问
2.1 四种查询块:LIST、TABLE、TASK、CALENDAR
Dataview的查询块,是在笔记里插入一个代码块,语言指定为dataview。下面这张表可以让你快速理解四种块分别适合什么场景:
| 查询类型 | 输出形状 | 典型场景 |
|---|---|---|
LIST | 简单列表,一行一条 | 快速列出符合条件的笔记 |
TABLE | 表格,多列展示字段 | 对比多个字段,如书名、作者、状态 |
TASK | 任务列表 | 聚合多个笔记里的待办事项 |
CALENDAR | 按日期展示的日历视图 | 按创建时间/自定义日期展示笔记 |
日常使用里,LIST和TABLE占到了90%以上。TASK专门用于聚合任务,CALENDAR则适合日记、时间日志类内容。
2.2 FROM:划定数据范围
查询第一件事是划定范围,告诉Dataview"从哪里找数据"。三种最常用的来源:
TABLE status, rating FROM "Books"这是按文件夹找,双引号里是文件夹路径。FROM #book是按标签找,FROM [[索引笔记]]是找链接到某篇笔记的内容,FROM "Books" AND #book还能组合范围和标签。
不加FROM就是全库查询,例如:
LIST FROM #todo这里建议:能限定范围就限定范围。全库扫描在笔记量几千篇的时候还感受不到问题,一旦上万篇,任何查询都开始变慢。这个后面讲性能时还会展开。
2.3 WHERE与SORT:筛选和排序的骨架
WHERE是查询的灵魂,它决定哪些笔记进入结果。语法上支持常见的比较运算符和逻辑组合:
TABLE author, rating FROM "Books" WHERE status = "在读" AND rating >= 8 SORT rating DESC这里面有几个关键点:
- 字符串比较:用双引号包裹,比如
status = "在读"。 - 数字比较:直接写数字,
rating >= 8。 - 日期比较:最稳妥的写法是
date(2026-01-01),后面调用WHERE started >= date(2026-01-01)。 SORT后面跟字段名,多个字段用逗号分隔,DESC是倒序,ASC是正序。
有一个新手很容易踩的坑:字段名必须严格匹配YAML里的拼写。status和Status会被当成两个不同字段,中文名字也尽量保持统一。Dataview的字段匹配是大小写敏感的,这一点真的能卡住人半小时。
2.4 GROUP BY、FLATTEN与LIMIT:分组、展开与限量
GROUP BY把结果按字段分组,常用来做统计视图。比如按状态分组:
LIST FROM "Books" GROUP BY status这会把"未读""在读""读完"的书分别列成一组。注意,GROUP BY之后,原来字段的引用方式会变化,比如想显示分组的键,要用rows来代表组内所有条目。
FLATTEN适合处理数组字段。YAML里如果写了tags: [a, b]或者行内字段tags:: a, b,FLATTEN可以把数组展开成多行。典型场景是一个项目笔记里存了多个子任务标签,FLATTEN后可以分别展示。
LIMIT就是限制输出条数,适合只需要最近N条的场景:
TABLE title, rating FROM "Books" WHERE status = "读完" SORT rating DESC LIMIT 10这一节的内容足够应付80%日常需求了。如果还想了解更复杂的表达式、函数(比如length()、replace()、dateformat()),可以在笔记里输入dataview代码块后用插件自带的帮助文档查阅,语法是带着完整说明的。
3. 三个拿来即用的实战模板:阅读清单、任务聚合、日记回顾
3.1 用Dataview管阅读进度:一本书到底读完没有
我以前读书笔记都是散着的,看没看完得点开目录才知道。后来我统一在每本书的笔记frontmatter里加一组固定字段,结构类似这样:
--- type: book title: 人类简史 author: 尤瓦尔·赫拉利 status: 在读 rating: 0 started: 2026-01-15 finished: cover: ---然后建一个阅读清单.md,放这样三块查询:
TABLE author, started AS "开始时间" FROM "Books" WHERE status = "在读" SORT started ASCTABLE author, rating FROM "Books" WHERE status = "读完" SORT rating DESCTABLE author, started FLATTEN ("0:" + choice(status = "读完", "1", "0")) AS dummy FROM "Books" WHERE status = "在读" SORT date(started) ASC LIMIT 5第一块是当前在读的书,第二块是按评分排序的已读书单,第三块是我用来做"最近在读TOP5"界面的一块小技巧——通过choice生成一个虚拟字段来控制显示顺序,这个思路可以迁移到很多场景。
这里要特别强调:为什么要用YAML字段来记录状态,而不是把"在读"写在正文里?因为Dataview读结构化字段的效率远高于扫描正文,而且不容易匹配错。你正文里写了"在读,还是觉得一般吧",如果正文可以全文匹配,WHERE "在读"就可能误伤,用YAML字段则完全不存在这个问题。
3.2 任务看板:把散落各处的待办聚合到一个页面
Obsidian原生的任务列表(- [ ])在每篇笔记里是独立的,没法跨文件汇总。如果你把任务打散在不同笔记里,想统一看今天要干什么,用Dataview的TASK查询块最合适:
TASK FROM "Projects" WHERE !completed SORT due ASC这会把Projects文件夹下所有未完成任务列出来,并按截止日期排序。如果任务行里写明了⏳ 2026-01-20或使用Dataview的行内字段[due:: 2026-01-20],排序会非常准确。
我的一个习惯是给任务标签分优先级,比如#P0、#P1,然后在查询里直接用标签过滤:
TASK FROM "Projects" WHERE !completed AND contains(tags, "#P0")contains()函数很常用,它判断一个列表是否包含某个值。任务的tags属性在Dataview里默认是数组,contains(tags, "#P0")就能精确匹配。
不过提醒一句:如果你只做纯任务管理,且任务数量特别大(几百上千条),我更推荐配合Tasks插件来管理,因为Tasks插件对任务的解析更完整,支持优先级图标、自定义任务状态、周期任务。Dataview的TASK更适合轻量聚合,两者不冲突,可以配合用。
3.3 日记聚合:过去一周到底干了什么
很多人的Obsidian里有日记文件夹,每天写一篇,内容包括心情、时间记录、今日完成的事。这些日记如果一篇篇翻,复盘效率极低,但Dataview可以轻松把最近7天的日记聚合到一个页面:
TABLE file.day AS "日期", mood AS "心情", focus AS "今日重点" FROM "Daily" WHERE file.day >= date(today) - dur(7 days) SORT file.day DESC这里有两个核心知识点:
file.day:如果日记文件名是日期格式(比如2026-01-20.md),Dataview会自动把这个日期解析为file.day字段,可以直接拿来比较和排序。这是Obsidian和Dataview的"约定优于配置",非常省事。date(today) - dur(7 days):Dataview内置了日期运算,dur(7 days)就是时长7天,可以相减。这个语法写熟了,很多时间查询都很优雅。
再进一步,如果你在日记的YAML里记了time_spent这样的字段(例如工作了4小时写成time: 4),可以用SUM()聚合出一周总工时。
这里要分享一个我在真实使用中摸索出的操作经验:日期的书写一定要遵循YYYY-MM-DD格式,比如2026-01-08,不要写2026年1月8日或26-1-8,否则file.day的自动解析会失败或出错,查询结果会直接为空。很多人折腾半天查不到数据,八成就是栽在日期格式上。
4. 当内置查询不够用:用Dataviewjs写自己的逻辑
4.1 声明式语法解决不了问题时,就该上Dataviewjs了
dataview块里写的是类SQL声明式语法,优势是简单直观,但边界也很明显:没法做复杂循环、动态变量、自定义DOM结构、访问文件系统API。比如你想"统计过去30天每天写了多少条笔记"并输出成折线图数据,或者想输出一个按月份分组的卡片式表格,声明式语法就累了。
这时候就需要dataviewjs代码块。它允许你写JavaScript,利用Dataview提供的dv对象直接操作页面数据。
4.2 最常用的API速览
初学Dataviewjs,掌握这几个核心API就够用:
dv.pages('"Books"'):获取数据,等价于声明式里的FROM。dv.current():获取当前笔记的字段。dv.table(headers, values):渲染表格,headers是表头数组,values是二维数组。dv.list(values):渲染列表。dv.paragraph(text):输出普通段落文本。.sort()、.where()、.groupBy():Js数组或Dataview数组自带的方法。
注意,dv.pages()返回的不是普通数组,而是一个DataArray对象,它有自己的.where()、.sort()、.groupBy()方法,用法更贴近数据库操作。如果用惯了ES6的Array.prototype.filter,这两种方式可以互转(.array()方法转普通数组),不过DataArray的方法本身大多数场景更简洁。
4.3 一个真实案例:统计近30天的日记数量并绘图
这里给一个我实际用过的例子,工作日每天写日记,周末断更,我想看看近期写作频率有没有下降:
const days = 30; const files = dv.pages('"Daily"').where(p => p.file.day); const today = dv.date('today'); const results = []; for (let i = days - 1; i >= 0; i--) { const d = today.minus({ days: i }); const hasNote = files.some(p => p.file.day.toFormat('yyyy-MM-dd') === d.toFormat('yyyy-MM-dd')); results.push([d.toFormat('MM-dd'), hasNote ? '有' : '无']); } dv.table(["日期", "是否有日记"], results);这段代码做了四件事:
- 获取
Daily文件夹下所有笔记,并通过.where(p => p.file.day)过滤出能正确解析日期的笔记。 - 用
dv.date('today')得到今天的日期对象。 - 循环30天,用
today.minus({ days: i })往前推日期,然后判断当天是否有日记。 - 用
dv.table()输出。
坦白讲,这段代码拿到浏览量最大的知识库论坛上也够用了。但我要强调一个坑:p.file.day拿到的日期是Luxon DateTime对象,直接和字符串比较是永远不等的,必须用.toFormat()方法格式化成字符串再比较。这个坑我当年踩了半小时,极度崩溃。
4.4 Dataviewjs性能需要自己负责
Dataviewjs自由度大,代价是性能容易失控。比如在循环里调用dv.pages()就是大忌,应该先把它提到循环外面存成变量。而且你每次用dv.table()渲染几百行数据时,Obsidian的视图会明显卡顿。
我的几条经验:
- 尽量使用
dv.pages()一次拉取,然后内存中处理,不要反复查。 - 渲染条数超过200条时,考虑分组或限制展示,必要时用
LIMIT或者.take(50)。 - 只对需要的字段做处理,不要打印整个
p对象,那会拖着大量数据。 - 出错时在Obsidian开发者工具(Ctrl+Shift+I)的Console里看报错信息,比对着屏幕干猜强得多。
5. 高频踩坑现场:查询不出来、卡顿、数据不同步
5.1 字段大小写和命名不统一,查了个寂寞
这是Dataview最频发的错误,没有之一。YAML里写了status,结果查询写成了Status,出来的结果就是空。还有人在不同笔记之间字段写法飘忽不定,一篇用rating: 8,另一篇用Rate: 8,查询时只能命中一半。
我的解决思路是:建立一套笔记字段规范,把它写进自己的模板里。比如读书笔记统一使用title、author、status、rating、started、finished;日记统一使用mood、energy、time_spent。让Templater自动生成模板,这样每篇笔记的字段结构天生一致。字段名尽量全小写+下划线,比如time_spent,避免大小写问题。
5.2 日期格式不规范,WHERE怎么都匹配不上
日期筛选查不到结果,90%是日期格式问题。Obsidian希望日期用YYYY-MM-DD,如果你的YAML里写了2026.01.15甚至2026/01/15,Dataview解析会出各种意外。
我的建议是,凡是表示日期的字段,统一用YYYY-MM-DD格式,例如:
--- started: 2026-01-15 finished: 2026-02-03 ---然后查询时用date(started)包裹字段参与比较,这样最稳:
TABLE title FROM "Books" WHERE date(started) >= date(2026-01-01)5.3 数据更新不及时:为什么改了字段,视图没变
Dataview依赖索引。理论上你改了YAML后,页面刷新就会更新,但实际使用中偶尔会出现视图迟迟不刷新的情况。特别是批量改了很多文件、或者长时间没重启Obsidian时。
我的处理方法是:先重开那个包含查询块的笔记(切换页面再切回来),如果还不行,就打开设置里的Dataview配置,点击一下"Reindex"强制重建索引。这个操作基本能解决99%的"明明改了却不更新"问题。
5.4 性能优化:全库扫描是大忌
笔记量超过5000篇后,一个没有FROM限定、包含大量计算的Dataview查询块会明显拖慢打开速度。Dataview默认会缓存索引,但每次查询仍然需要遍历、过滤、排序,数据量大了自然会慢。
优化思路优先级从高到低:
- 加
FROM限定范围,不要全库扫描。比如只查"Books"文件夹,别让Dataview去扫"Daily"。 - 精简查询块数量。一个页面塞五六个Dataview块,且每个都扫全库,再快的机器也受不了。合并同类查询、拆到不同页面去。
- 使用Dataview设置里的"Refresh Enabled"和"Refresh Interval",把自动刷新周期调长,减少实时索引压力。
- 对Dataviewjs,缓存结果。可以用模块级变量保存计算结果,在一天内不重复计算。
- 考虑换用Datacore(Dataview的重写版本)如果库继续膨胀。但这不是本节重点,先不做展开;新用户完全可以从Dataview起步。
5.5 插件联动:不只是装饰,而是自动化
Dataview的威力乘以其他插件才真正被放大:
- Templater:新建读书笔记时自动插入YAML模板,字段零散落。配合Templater的JS能力,甚至可以在模板里自动获取当前日期、书名,让录入过程台窗化。
- Tasks插件:把任务管理做到极致,再用Dataview兜底做多维度聚合。
- Homepage插件:把启动页面做成一个掌控面板,放上核心查询块,一天开始就知道该做什么。
- QuickAdd插件:用快捷命令快速录入日记、任务项时,自动写入对应字段,避免手动敲YAML。
个人体会:别一口气把所有插件流程全自动化。先把Dataview的字段基础和查询练熟,用两周时间手动录入,确认哪些字段用得最多、哪些查询看得到价值,再上Templater和QuickAdd自动化。否则很容易出现"自动化模板做了一大堆,实际查询需求一变,模板全部要返工"的窘境。
最后分享一点我自己的折腾心得:Dataview最反直觉的地方在于,它不是让你"多记",而是逼着你想清楚"哪些笔记以后会被我怎么问"。每次动手写YAML字段前,先问自己一句"将来我会按什么条件找这篇笔记"——这个问题想明白了,字段设计自然就对了。我见过太多人一开始堆了二十多个字段,后来发现80%的字段从来没被查询过一次,全是无效成本。从最少字段起步,按真实查询需求慢慢加,才是用Dataview最舒服的路径。