仪表盘(Dashboard,.dash)
.dash 文件以数据描述仪表盘:source 块为数据文件命名,query 块存放 SQL,plot 块绘制查询结果,filter 块让点击一张图就能筛选其他图。不需要编写代码,人和 AI agent 都可以编写这类文件,通过 diff 审阅,并在打开前校验。
.dash 文件可以根据保存的查询绘制常规图表、表格、数字和地图。纯文本格式也方便与 AI agent 协作:agent 编写或修改文件,用 ducklocal check 验证能否运行,你再在图表上写评论,提出下一步修改意见。
完整示例
这个仪表盘读取教程样例文件。把 sales.dash 下载到 sales.csv 所在的文件夹,再打开它。
source "sales" {
path = "sales.csv"
}
query "totals" {
sql = <<SQL
SELECT round(sum(revenue), 2) AS revenue, sum(orders) AS orders,
count(DISTINCT region) AS regions
FROM sales
WHERE $channel
SQL
}
query "by_channel" {
sql = <<SQL
SELECT channel, round(sum(revenue), 2) AS revenue
FROM sales
GROUP BY channel
ORDER BY revenue DESC
SQL
}
query "daily" {
sql = <<SQL
SELECT date, region, round(sum(revenue), 2) AS revenue
FROM sales
WHERE $channel
GROUP BY date, region
ORDER BY date
SQL
}
plot "revenue" {
type = "card"
query = query.totals
value = revenue
title = "Revenue"
width = 4
}
plot "orders" {
type = "card"
query = query.totals
value = orders
title = "Orders"
width = 4
}
plot "regions" {
type = "card"
query = query.totals
value = regions
title = "Regions"
width = 4
}
plot "revenue_by_channel" {
type = "bar"
query = query.by_channel
x = channel
y = revenue
title = "Revenue by channel"
width = 5
}
plot "daily_revenue" {
type = "line"
query = query.daily
x = date
y = revenue
series = region
title = "Daily revenue, per region"
width = 7
}
plot "daily_table" {
type = "table"
query = query.daily
title = "The numbers behind it"
}
filter "channel" {
plot = plot.revenue_by_channel
}打开后,最上面一排是三个数字,下面柱状图和折线图并排,再下面是表格。每个 query 只执行一次,不管有多少个 plot 用它:totals 供给三张卡片,daily 同时供给折线图和表格。在 Revenue by channel 里点一根柱子,卡片、折线图和表格就只看这个渠道;柱状图本身保持完整,可以接着点别的柱子。
打开仪表盘
以下任一方式都能把 .dash 文件打开成仪表盘标签页,和查询标签页并列:
- 标签栏末尾的 +,再选 打开 Dashboard;
- 把文件拖到窗口上;
- 在命令行里指定:
ducklocal sales.dash; - 从脚本或 agent 打开到已经在运行的窗口里:
ducklocal open sales.dash(见 CLI 指南)。
plot 排在 12 列的网格上:每个 plot 占它的 width 列,从左往右排满一行,放不下的就换到下一行。拖动行之间的分隔条可以调整高度;仪表盘比标签页高时会滚动。画不出来的 plot(查询失败,或缺少某列)会在自己的格子里显示原因,其余部分照常绘制。打开的仪表盘下次启动时会恢复,最近打开过的会列在侧栏的 仪表盘 下。
鼠标移到 plot 上,右上角会出现操作按钮:
- SQL 把 plot 翻过来,显示它的查询上一次执行的 SQL(已经填好 source 和当前的筛选值,可以原样运行),附有 复制 和 在查询页打开;图表 再翻回去。
- 对话气泡打开这个 plot 的评论。
- 放大这张图 让它占满仪表盘标签页;点 放回看板(Esc) 或按 Esc 回到网格。
SQL 视图带语法高亮。右键点击仪表盘表格的单元格,可复制该单元格、用制表符分隔的整行,或按显示顺序复制整列。
点 table 图表的表头可以排序:先降序,再升序,再回到查询原来的顺序。数值按数值排,NULL 排在最后;被筛选选中的行,排序后仍保持选中。
工具栏的 查看源码 会把标签页切换成该文件的编辑器,带语法高亮(heredoc 里的 SQL 与 SQL 编辑器同样着色)、补全和行内诊断。保存 或 ⌘S 写回文件并重新绘制。如果保存的文件未通过校验,标签页会保留上一次能用的仪表盘,并在上方显示错误。其他程序修改了文件时也是如此。返回视图 回到图表。重新加载 会重读文件并重新执行所有查询。
用评论审阅
有了评论,审阅仪表盘就像审阅文档一样,还能把修改交给 AI agent。关于这套设计背后的产品思考,可阅读《Claude Dashboards 给 DuckLocal 的启发》。
在窗口里,鼠标移到 plot 上,点对话气泡。仪表盘上方会弹出一张卡片:顶部引用 plot 的标题,下面是已有的讨论,再下面是输入框。写下要改的地方(比如“按收入从高到低排”),点 评论(或按 ⌘/Ctrl+Enter)。每条讨论都有 回复 和 解决;解决了的讨论会变淡但仍然保留,有人回复时重新打开。有未解决讨论的 plot 会一直显示一个带数字的气泡,哪里还有事待办一眼就能看到。
agent 用 ducklocal comments 读取并回复评论,不需要窗口:
ducklocal comments sales.dash{
"file": "sales.dash",
"comments_file": "sales.dash.comments.json",
"open": 1,
"threads": [
{
"id": "c1",
"plot": "revenue_by_channel",
"plot_title": "Revenue by channel",
"plot_query": "by_channel",
"plot_lines": [68, 75],
"plot_missing": false,
"resolved": false,
"comments": [
{ "author": "user", "text": "按收入从高到低排", "at": "2026-10-10T14:03:00+08:00" }
]
}
]
}每条讨论都写明了它所在的 plot、plot 用的查询,以及 plot block 在文件里的起止行,agent 由此知道该改哪里。它修改规格文件,跑一遍 ducklocal check,然后回复:
ducklocal comments sales.dash --reply c1 --text "已按收入降序排列" --resolve窗口同时盯着这两个文件。一秒之内,plot 就按改过的规格重新绘制,agent 的回复也出现在讨论里,标着 Agent。
评论保存在仪表盘旁边的 名称.dash.comments.json 里,和它的 source 文件一样跟着仪表盘走。它们是工作笔记,不是仪表盘的一部分:别提交进版本库,审阅完就可以删掉。
官方 agent skill 教过这套流程,装了它的 agent 只要听到一句“处理 sales.dash 上的评论”就知道怎么做。
0.4.0 新增
agent 可以提议修改,而不是直接改文件。标签页上会出现 审阅,显示差异和检查结果,只有你能应用。
看见 agent 正在做什么
agent 对当前打开的仪表盘运行 check、comments 或 presence 时,角落会显示它的名字,带着它所属厂商的标志(Claude、OpenAI、Gemini、Cursor、DeepSeek),认不出的就显示首字母;点击可查看这次工作的记录。文件被修改后,改动和新增的 plot 会保持标记(Claude 改过、Claude 新增),直到你把它们放在视野里看过片刻;被删除的 plot 留下一条可手动关闭的提示。
agent 可以先运行 ducklocal presence sales.dash --doing editing --plot revenue_by_channel,说明接下来要改哪里。已有的 plot 会加上边框;尚不存在的名字会在仪表盘下方显示占位提示。30 秒没有活动,或显式传入 --doing idle 后,这次会话结束。agent 干完了、但它回复的评论还在等你回应时,它会留在角落,头像上显示等待的条数。评论会记下是哪个 agent 写的,并显示它的标志。参数见 CLI 参考。这些标记用于展示本机活动,不会自动向 agent 派发任务。
Block
文件由一连串 block 组成,每个 block 是类型、带引号的名字和主体:
source "name" { ... }
query "name" { ... }
plot "name" { ... }
filter "name" { ... }同一类中名字必须唯一:两个 query 不能同名,两个 plot 也不能,但 query 和 plot 之间可以同名。
source
| 属性 | 必填 | 取值 |
|---|---|---|
path | 是 | 一个 .csv、.tsv、.txt、.parquet、.json、.jsonl 或 .ndjson 文件,或者匹配多个文件的通配符(exports/orders-*.csv),合起来当作一张表读 |
source 给数据文件起一个名字,供查询读取:写了 source "sales",查询里就写 FROM sales。路径相对于 .dash 文件,而不是 DuckLocal 启动时所在的目录,所以仪表盘和数据可以一起挪;~ 开头的路径和绝对路径也可以。
source 不会动连接:它会变成每个用到它的查询前面的一段 WITH sales AS (…),所以侧栏里不会多出东西,标签页关掉也不留痕迹;同名的表只在这个仪表盘的查询里被遮住。名字必须是普通标识符(字母、数字和 _)。工作簿不能当 source,先在 DuckLocal 里打开它,再查询它变成的表。
query
| 属性 | 必填 | 取值 |
|---|---|---|
sql | 是 | 一条只读 SQL 语句,写成 heredoc 或字符串 |
query block 只包含 sql。SQL 里可以用 $name 读取某个 filter。
plot
| 属性 | 适用 | 取值 |
|---|---|---|
type | 全部,必填 | "line"、"bar"、"stacked_bar"、"area"、"scatter"、"pie"、"heatmap"、"histogram"、"map"、"table"、"card" 或 "text"——带引号的字符串 |
query | 除 text 外全部必填 | 指向某个 query block 的引用:query.by_channel |
x | 图表,必填 | 横轴上的列;pie 的扇区;histogram 要分箱的数值列。table 显示所有列,不需要 x |
y | 图表,必填 | 要画的列,必须是数值类型;pie 各扇区的大小。histogram 不需要 y |
series | line、bar、stacked_bar、area、scatter、heatmap | 按这一列的取值拆分行,每个取值一条序列;heatmap 的行 |
title | 全部 | plot 的标题,带引号的字符串;卡片的标签 |
width | 全部 | 在 12 列网格里占几列,1–12。默认 12,卡片默认 3 |
value | card | 要显示的列,取第一行。默认第一列 |
note | card | 数值下方的说明;带引号的字符串直接显示,裸列名则读取第一行对应列的值 |
tone | card | "accent"、"good"、"warning" 或 "critical",设置顶部横线与数值的颜色 |
format | 图表和 card | 数字的含义,见数字格式与变化。map 和 table 不支持 |
delta | card | 存放变化量的数值列,显示在数值下方 |
delta_format | card | 变化量的格式。默认与卡片相同 |
down_is_good | card | 设为 true 时,下降显示绿色、上升显示红色,适合成本、延迟、流失率 |
description | 全部,0.4.0 起 | 这张图显示什么、怎么读;鼠标移到标题旁的 ⓘ 上时显示 |
text | text | 正文,字符串或 heredoc;title 与 text 至少写一个,不写 query |
lat、lng | map | 纬度和经度列,单位是度。数值列的列名表明它是纬度或经度时可以不写,比如 lat、latitude、geo_lat,或 lng、lon、longitude |
color | map | 按这一列给点着色;不写时,如果有取值不多的文本列,会自动用它 |
size | map | 按这一数值列决定点的大小 |
size_scale | map | "sqrt"(默认)或 "log",决定 size 怎样缩放 |
tooltip | map | 点的提示框里显示哪些列:[name, passengers] |
列属性指的是查询结果中的列,匹配时不区分大小写。普通列名直接写(x = date);不是普通标识符的列名加引号(y = "revenue (EUR)")。某种类型用不上的属性,比如折线图的 lat、地图的 x、饼图的 series,会被当成错误,而不是悄悄忽略。
filter
| 属性 | 必填 | 取值 |
|---|---|---|
plot | 是 | 在哪张图上点选:plot.revenue_by_channel。只能是 bar、stacked_bar 或 table |
column | table 必填 | 点选时取哪一列的值。bar 和 stacked_bar 不写时就是它的 x |
filter 的作用见筛选。
图表类型
| 类型 | 画出 |
|---|---|
line | 每条序列一条线,不填充 |
area | 每条序列一块填充面积 |
bar | 每行一根柱子,每条序列一种颜色。带 series 时,每组里各序列的柱子并排。只有一条序列时,每根柱子上标出数值 |
stacked_bar | 每个 x 一根柱子,各 series 堆叠:正值从零向上,负值向下,顶部和提示框里显示合计 |
heatmap | x 横排、series 竖排,每个格子按 y 用同一种蓝色着色,值越大越深(深色模式下越浅)。查询没有返回的组合留空 |
histogram | 按数值列 x 的区间统计行数,箱宽取整。查询要直接返回行本身,不要先分组 |
scatter | 每个点一个圆点,互不相连,每条序列一种颜色 |
pie | 每个 x 值一个扇区,大小按 y,从大到小;超过 8 个时最小的几个合并成"其他"。画成环形,中间显示合计;点图例项可以单独突出一个扇区,再点一次恢复 |
text | 标题、说明文字,或两者兼有;不执行查询 |
map | 每行一个点,位置按 lat 和 lng;见地图 |
table | 把查询结果显示为表格,列宽按内容调整;点表头排序 |
card | 一个大号数字,title 是它的标签。按千位分组,过长的小数会舍入(11439704.020000005 → 11,439,704.02),放不下时缩小字号而不是截断 |
鼠标移到图表上可以查看数值,格式与坐标轴一致(显示 4.4M,不显示 4400000)。
柱状图会标出每一个类别。标签在柱子下面放不下时,图会横过来,标签排在左边一列。只有时间轴会抽稀标签,在不重叠的前提下尽量多标,第一个和最后一个总会标出;按 DATE 列画的柱状图,会把查询里没有数据的日子补成空位,一段冷清的日子看起来是个缺口,而不是直接消失。折线图、面积图和散点图的轴也这样抽稀标签。
所有图表类型的 x 轴都是分类轴:各个值按查询返回的顺序等距排开,不按数值间距排。所以用两个数值列画 scatter,得到的并不是真正的 x–y 散点图。
行按查询返回的顺序绘制,所以给图表供数的查询要以 ORDER BY 结尾。
0.4.0 新增
仪表盘里按时间画的 line 和 scatter 会按实际时间定位,间隔不均匀时看起来也不均匀,并且不管 ORDER BY 如何都按时间顺序画。时间为 NULL 的行不画,并有提示。查询返回的 NULL 读数会让线在那里断开。area 仍然等距排开,并会注明。序列按名称保持颜色;查询被截断时,每种图表都会注明。
卡片显示查询的第一行。查询返回多行时,数字下面会注明;一行都没有时显示 —。
卡片与文本面板
卡片的 note 用来解释数值,tone 用颜色表达状态。同一行的卡片共享数值字号。文本面板可以添加标题或说明,不执行 SQL:
plot "revenue_summary" {
type = "card"
query = query.totals
value = revenue
title = "收入"
note = "当前选中渠道"
tone = "accent"
width = 4
}
plot "reading_guide" {
type = "text"
title = "如何阅读这张仪表盘"
text = "点击渠道,筛选每日图表和收入卡片。"
width = 8
}文本面板只接受 type、title、text 和 width。较长的说明可以写成 text = <<TEXT,以单独一行 TEXT 结束。图表、饼图和地图使用八种不同色相,并为当前主题调整明暗;多条序列会显示图例。
数字格式与变化
format 决定卡片数值、数值轴、柱子标签和提示框的显示方式:
| 取值 | 显示为 |
|---|---|
"num0"、"num1"、"num2" | 带相应小数位的数字 |
"pct"(等于 "pct1")、"pct0"、"pct2" | 比例:0.421 显示为 42.1%,所以查询要返回小数,不要乘以 100 |
"usd"、"eur"、"gbp"、"cny" | 货币,两位小数 |
"usd0"、"eur0"、"gbp0"、"cny0"、"jpy" | 货币,取整 |
卡片用 delta 显示变化,在数值下方显示为 ▲ 12.3% 或 ▼ $125。向好时为绿色,否则为红色,后面的 note 说明是跟什么比。变化量要在卡片的查询里算好:
plot "revenue_total" {
type = "card"
query = query.totals
value = revenue
format = "usd0"
delta = growth
delta_format = "pct1"
note = "比上周"
}布局
width 是 plot 在仪表盘的 12 列里占几列。plot 按写的顺序从左往右排,下一个放不下就换行,所以四张卡片(各占 3)排成一行数字,两张 width = 6 的图在下面并排。一行不满 12 列时,剩下的地方空着。全是卡片的一行起始高度较矮;全是表格的一行,起始高度刚好放下最长那张表的所有行(最多一张图的高度);全是文本面板的一行按文字内容确定高度;其他行起始高度是一张图的高度。鼠标悬停时才显示行间的调整手柄。
地图
plot "stations" {
type = "map"
query = query.stations
lat = geo_lat
lng = geo_lng
color = line
size = passengers
size_scale = "log"
tooltip = [name, passengers]
}地图把每一行画成一个点,用 Web Mercator 投影,范围自动贴合这些点,背景是标了度数的经纬网。
color:最多五个取值各有一种颜色;取值更多时,最常见的四个各有颜色,其余共用"其他"。图例标出各颜色对应的值。不写时,如果结果里有取值不多的文本列,就按它着色。size:点的面积跟着数值走,数值翻倍,面积也翻倍。数值跨好几个数量级时用size_scale = "log"。角落的图例可以读回大小对应的数值。tooltip:不写时,提示框先列 size 列和其他数值列,再列坐标。
点的下面可以画一层 OpenStreetMap 底图。它默认关闭,要在 显示 → 在线底图(OpenStreetMap) 里打开,因为这是 DuckLocal 唯一会自己联网的地方。发出去的只是要哪些地图瓦片,大致能看出你在看哪片区域,但不会带上任何一行数据。瓦片在磁盘上缓存一周(见设置与应用数据);关闭或离线时,地图只画经纬网。查询结果 图表 标签页里的地图也受这个开关控制。
筛选
filter 让一张图可以点选,并用选中的值筛选其他查询:
filter "channel" {
plot = plot.revenue_by_channel # 点一根柱子,选中它的 channel
}
filter "day" {
plot = plot.days # 表格:选中一行,取……
column = day # ……这一列的值
}
query "daily" {
sql = "SELECT date, sum(orders) AS orders FROM sales WHERE $channel AND $day GROUP BY 1"
}查询里的 $name 是一个条件,写在该写条件的地方。什么都没选时它是 TRUE,所以仪表盘打开时和没有这个 filter 完全一样。选中一个值后,它变成 ("channel" = 'Web'),也就是 filter 的 column 列等于选中的值;选中显示为 NULL 的单元格时,会匹配 SQL NULL 或文本 NULL。多个 filter 怎么组合,由查询自己的写法决定。
- 柱状图:点一根柱子就选中它,它保持原色,其他柱子变淡。再点一次取消。
- 表格:选中一行,点击或用方向键都可以。
- 当前生效的选择以小标签显示在所有图的上方;点标签上的 ✕ 清除这个 filter,全部清除 清除所有选择。
- 做选择的那张图不会被自己的 filter 筛选。 即使选中了某个渠道,它的查询里
$channel仍然是TRUE;否则点一下,柱状图就只剩刚点的那根,没法再点别的。这就是交叉筛选,和 Mosaic、crossfilter 的做法一样。 - 只重跑变了的查询。 一次选择只重跑 SQL 因此改变的查询;不读任何 filter 的查询保留原来的结果。
每个 filter 同时只保留一个值。可点选的 plot 会在标题下显示点击筛选提示;筛选导致部分分组消失时,序列仍保持原来的颜色。使用 $channel 的查询,必须在该条件所在的位置能访问筛选器指定的列;ducklocal check 还会绑定一次带选中值的查询,在第一次点击前发现缺失的列。
0.4.0 新增
点选显示为 NULL 的单元格时,只选中 SQL NULL;文本 'NULL' 也只选中它自己。可点选的图表不再在标题下显示点击筛选提示。
选中的值以带引号的字面量写进 SQL,比较时由 DuckDB 转成列的类型,所以日期、数字和文本一样能用。选择在标签页开着期间一直有效,重新加载后只要 filter 还在文件里也会保留;关掉 DuckLocal 后不会记住。
取值
| 形式 | 示例 | 用于 |
|---|---|---|
| 字符串 | "Revenue by channel" | type、title、path、size_scale、tone、字面量 note、text、带引号的列名、简短的 sql |
| Heredoc | <<SQL … SQL | sql、text |
| 引用 | query.daily、plot.revenue_by_channel | plot 的 query、filter 的 plot |
| 裸名字 | channel | 列属性 |
| 数字 | 6 | width |
| 列表 | [name, "Unique IPs"] | tooltip |
字符串在行尾结束,支持 \n、\t、\" 和 \\。
# 或 // 之后直到行尾都是注释。属性之间不需要分隔符;换行只是空白。
这就是全部语法:没有变量、没有函数、没有条件,除了 filter 的 $name 也没有别的插值。
Heredoc
heredoc 用来写跨行文本,实际上就是写 SQL:
sql = <<SQL
SELECT 1
SQL<<后面的分隔符可以是任意单词(按惯例用SQL),同一行里它后面不能再有别的内容。- heredoc 在第一行内容恰好等于分隔符(允许前后有空格)的地方结束。
- 每一行文本都会去掉结束行那么多的缩进,所以 SQL 可以跟着 block 缩进,缩进本身不会带进 SQL。
SQL 规则
仪表盘在打开、重新加载、保存时,以及 DuckLocal 启动并恢复该标签页时都会执行查询,所以查询只能读:
- 允许:
SELECT、WITH …、FROM开头的查询、VALUES、SHOW、DESCRIBE、SUMMARIZE、PIVOT和UNPIVOT。 - 拒绝: 任何可能改动数据或文件的语句,如
CREATE、INSERT、UPDATE、DELETE、DROP、COPY、ATTACH、INSTALL、LOAD等。打开别人发来的.dash文件不会改动你的数据。 - 每个 query 一条语句。
- 嵌在别的查询里的
PIVOT要写明取值。 以PIVOT开头的查询可以让 DuckDB 自己找取值(PIVOT orders ON channel USING sum(amount));嵌在SELECT或WITH里的,要写出取值列表:ON channel IN ('Web', 'Store')。 $name必须对应一个 filter block。 只读检查时,每个$name都按TRUE处理。字符串、带引号的标识符、注释和$$ … $$字符串里的$不算。
查询在窗口自己的连接上执行,所以能看到 SQL 编辑器能看到的一切:打开的文件、挂载的数据库,以及 TEMP 表等连接级状态。
SQL 里的路径
从 v0.2.2 起,仪表盘 SQL 里的相对文件路径也会从 .dash 所在文件夹查找。例如,从别的目录打开仪表盘时,FROM 'sales.csv' 可以读取仪表盘旁的 CSV。窗口和 ducklocal check 都如此。
对于 SQL 里直接指定的单个文件,DuckDB 仍会先检查工作目录;那里有同名文件时,可能优先读到它。source block 会明确按仪表盘目录解析路径,可以避免这个歧义。仍可使用绝对路径或已注册的视图。
限制
| 限制 | 结果 |
|---|---|
| 每个 plot 12 条序列 | 多出的序列不画,并有提示说明原本有多少条 |
| 每条序列 1,500 个点 | 按分桶取平均压缩到上限以内 |
| 200 根柱子 | 多出的柱子不画 |
| 饼图 8 个扇区 | 最小的几个合并成一个"其他" |
| 热力图 200 列、12 行 | 多出的列和行不画 |
| 直方图 5–40 个箱 | 约为行数的平方根,箱宽是 1、2、2.5 或 5 乘以 10 的整数次幂 |
| 地图 20,000 个点 | 多出的点不画,并有提示 |
校验仪表盘
ducklocal check 不开窗口就能校验文件。把仪表盘交给别人之前,先跑一遍:
ducklocal check sales.dash
ducklocal check sales.dash --database warehouse.duckdb
ducklocal check sales.dash --summary先做静态检查:语法、名字与引用、必填属性、每个 $name 是否有对应的 filter,以及用 DuckDB 自己的 parser 检查每个查询的 SQL。然后真正执行每个查询(只读,带着 source,什么都没选),核对每个 plot 用到的列是否确实是查询返回的列,包括 y、地图坐标和 size 是否为数值,以及每个 filter 的 column 是否是它那张图的查询返回的列。之所以要真正执行,是因为转换失败、通配符一个文件都没匹配到这类问题,要读到行时才会暴露。
- 不带
--database时在内存里执行,只读自己source文件的仪表盘这样就够了。读取了某张 source 没定义的表的查询,列在unresolved下而不算失败,它的 plot 也不核对列:它多半是写给某个数据库的。 - 带
--database PATH时,在该数据库上执行(只读打开)。查询读的是数据库里的表时,一定要带上它。
每个使用 filter 的查询,还会带一个选中值做绑定检查,不执行这个变体,以发现该查询中不存在的筛选列。有错的 block 不会阻止其余有效 block 继续检查;同一次运行会汇总规格错误和查询失败。
成功时输出 source、查询(附带结果列,以及各自返回的行数 rows)、filter 与 plot 的 JSON 摘要,外加 empty:执行了但一行都没返回的查询。这不算错误,但通常是正则、筛选条件或 join 什么都没匹配上,交出仪表盘之前逐个看一眼。--summary 在成功时只输出一行 JSON:数量统计,以及未解析和返回空结果的查询名。包含规格诊断时以状态 2 退出,一次列出所有问题,每条一行 文件:行号: 信息;仅有 SQL 失败、数据库或文件错误时以 1 退出。细节见 CLI 指南。
0.4.0 新增
ducklocal check 文件 --restricted --allow-read 路径 可以在受限条件下校验不是你写的仪表盘。见限制不是你写的 SQL。
常见信息
| 信息 | 解决 |
|---|---|
type is a string in quotes | 写 type = "line",而不是 type = line |
Unknown plot type: "donut"; one of line, bar, area, scatter, pie, map, table, card, text | 使用列出的类型之一 |
plot block "p" requires y | 图表类型都需要 y;table、map、card 和 text 不需要 |
plot "p" draws query "q", which the file does not define | 补上这个 query block,或改正 query = query.… 里的名字 |
query names a query block: query = query.some_name | query 属性要写引用,不是字符串 |
plot "p" draws y = "channel" (VARCHAR), which is not numeric | 让 y 指向数值列,或改用 type = "table" |
A map plot places points by lat and lng; it takes no x | 地图没有坐标轴:写 lat 和 lng,或者让列名来判断 |
width is how many of the dashboard's 12 columns the plot spans | 写 1 到 12 之间的整数 |
query "q" reads $chanel, but no filter block is named "chanel" | 改正名字,或补上这个 filter block |
filter "f" picks on plot "p", a line; a value is picked on a bar or table plot | 让 filter 指向柱状图或表格 |
a dashboard query must be a single read-only statement | 写入操作放到 SQL 编辑器里做,或者给嵌套的 PIVOT 用 IN 写明取值;见 SQL 规则 |
Unknown block: "card"; a card is a plot: plot "x" { type = "card" … } | 每个面板都是 plot block,卡片和文字面板也一样 |
"minutes" is a SQL keyword, so as a column alias it needs AS | 写成 count(*) AS minutes,或者给别名加引号 |
Unterminated heredoc: no line is exactly SQL | 在单独一行写上结束分隔符 |
在你自己的编辑器里编辑
ducklocal lsp 是 .dash 文件的 language server,提供:输入时即时诊断;block 类型、属性、图表类型以及 query.、plot. 后面名字的补全;悬停文档;从 query.name 或 plot.name 跳到对应 block 的定义跳转。Neovim 与 VS Code 的配置见 CLI 指南。
