关于 n8n-data-tables-official
面向 n8n 工作流设计者,介绍内置 Data Tables 的表结构、操作方式和设计规范,适用于小到中等规模的持久化状态。涵盖查找表、事件记录和按行逻辑去重,并说明何时应改用真实数据库。
主要功能
- 01通过列和行存储持久化数据
- 02支持插入、更新、查询与删除
- 03提供 upsert 和行存在性判断
- 04说明去重与查找表模式
- 05涵盖列类型及关联设计
适用场景
- 01保存稳定的查找数据
- 02记录近期事件或审计信息
- 03需要检查或查询的去重状态
- 04在工作流中维护父子数据关系
MCP 相关
介绍 n8n Data Tables 的 data-table MCP 工具,可供 MCP 客户端操作表数据。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ n8n-io/ skills --skill n8n-data-tables-official安装后在 Claude Code 中输入/n8n-data-tables-official确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/DEDUP_PATTERNS.mdOPERATIONS.mdSCHEMA_DESIGN.md详细介绍
n8n Data Tables
Data Tables 是 n8n 的内置表格存储:n8n 实例中的真实数据表,包含列、类型、行,并可通过 dataTable 节点和数据表 MCP 工具执行 CRUD 操作。
适用于本地持久化状态:查找表、近期事件、按会话划分的库存、计数器、幂等性跟踪,以及在需要逐行处理逻辑或外部可见性时进行去重(单纯“是否见过这个值?”的去重应使用 Remove Duplicates 节点)。适用于中小规模数据量(数万行没问题;数百万行应使用真正的数据库)。
必须遵守的规则
- 系统管理的列 + 外部 ID。 每张表都会自动包含 3 列:
id(bigserial)、createdAt、updatedAt。不要在create_data_table中声明这些列(否则会报错或遮蔽系统列)。插入时不要写入这些列。对于外部的领域标识符(arxivId、stripeCustomerId、requestId),请添加单独的列,并基于该列进行去重或查找。 - 列中只能存放基本类型,嵌套数据使用
string+_object后缀。 不支持 JSON/object/array 列类型。对于嵌套数据(数组、解析后的对象),使用string列,写入时调用JSON.stringify(...),读取时调用JSON.parse(...)。为列名添加_object标记(例如keyInsights_object)。该后缀是要求读取方进行解析的约定。参见references/SCHEMA_DESIGN.md。
强烈建议的默认做法
-
不要在 Data Table 节点前添加 Set 节点来修改字段。 Data Table 节点的每列表达式输入框与 Set 字段功能同样强大,因此 Set 节点无法完成任何 Data Table 节点本身做不到的工作。(
n8n-expressions-official中也指出了同样的 Set 节点反模式。) -
列名大小写遵循 n8n 的风格:camelCase。 自动管理的列采用 camelCase(
createdAt、updatedAt),因此用户列采用同样的风格会更清晰:arxivId、paperId、taxRate。在同一查询中混用大小写(createdAt >= ... AND arxiv_id eq ...)看起来像是拼写错误。字符串化数据块列名仍应保留_object后缀(keyInsights_object);下划线是约定标记,不是大小写风格。 -
创建或更新后,通过
get_workflow_details验证columns参数。 UI 在手动映射模式下有显示问题(会显示“Currently no items exist”,但实际上没有数据丢失)。检查 JSON 可以确认已持久化的内容。 -
数据结构适合时,关系型设计同样可行。 对于真正的父子数据(论文 → 摘要、客户 → 订单),通过
id引用父项,明确命名列(paperId、customerId),并在工作流逻辑中确保完整性。对于没有关系需要建模的扁平场景(去重、查找、审计),不要强行采用关系型设计。 -
存储格式不等于接口格式。 从子工作流返回
_object字段前,应先解析。调用方不应收到还需要自行解析的字符串化内容。参见references/SCHEMA_DESIGN.md中的“Storage format ≠ interface format”。
默认列
无论是否声明,每张 Data Table 都包含以下列:
| 列 | 类型 | 行为 |
|---|---|---|
id | bigserial / number | 自增主键。插入时由 n8n 分配,不能写入该列。插入响应中会返回此列。 |
createdAt | timestamp | 插入时自动设置。 |
updatedAt | timestamp | 每次更新时自动刷新。 |
实际使用时:
- 不要在
create_data_table中声明这些列。 它们已经存在。 - 查询时直接使用这些列,无需添加自己的时间戳列。“今天创建”:
createdAt >= '<today ISO>'。“自上次同步后更新”:updatedAt >= $('Last Sync').item.json.timestamp。 - 不要将这些列用作跨系统标识符。 自动生成的
id是内部标识符,在重建表或迁移实例时会重置。对于领域标识符,请使用自己的列。
在数据形状确实需要时采用关系型设计
Data Tables 不会强制执行外键,但如果数据确实具有这种结构,你仍然可以跨表建模父子数据。需要注意的是:完整性由你负责,而不是 n8n。
- 通过
id引用父行。 子表在某一列中保存父行的id。 - 在列名中说明引用关系。
paperId、customerId、eventId能清楚表明关系。 - 在工作流逻辑中确保完整性。 插入子项之前,先查找父项。删除父项之前,决定如何处理子项(删除、保留为孤立项或归档)。n8n 不会级联处理。
- 留意过期引用。 指向已删除父项的子项会造成不易察觉的错误。可以软删除,或运行清理工作流。
对于复杂的关系结构(3 张以上的表并需要联接、事务性写入),请使用真正的 SQL 数据库。
操作:分别适用于哪些场景
| 操作 | 适用场景 |
|---|---|
insert | 始终新增。新建行,n8n 会分配 id。 |
upsert | “不存在则新增,存在则更新。”需要 matchType 和筛选条件来判断是否已存在。 |
update | “修改符合此筛选条件的行。”没有匹配项时不会插入。 |
get | 获取符合筛选条件的行(返回 0 行或多行)。支持 orderBy、limit、returnAll。 |
deleteRows | 删除符合筛选条件的行。 |
rowExists / rowNotExists | 针对传入项进行布尔式筛选。常用于去重分支。 |
完整的操作范围(筛选语法、matchType、排序模式)请参阅 references/OPERATIONS.md。
“Currently no items exist”界面显示问题
SDK 保存手动模式的列映射(mappingMode: 'defineBelow')时,即使运行时能正确持久化数据,n8n 界面的 “Values to insert” 面板也可能显示为空(“Currently no items exist”)。如果用户反映 Insert 节点“看起来坏了”或“没有字段”,请告诉他们:这是界面显示问题,点击列参数旁的重新加载(刷新)按钮,即可重新填充架构并显示映射。不会丢失数据,可随时操作。
常见模式
按外部 ID 去重
对于简单的“我是否见过这个值?”去重,默认使用 Remove Duplicates 节点(“items seen in previous executions”模式)。这是只需一个节点的解决方案,使用内部存储,无需维护架构。只有在有理由让去重状态存放在真实表中时,Data Tables 才值得使用:
- 你需要查询或检查去重状态。 例如仪表板、审计,或“过去一周处理过哪些内容?”
- 命中时需要行级逻辑。 例如按类别设置 TTL(“类别 A 在 30 天后过期,类别 B 在 7 天后过期”)、根据已存储状态有条件地重新处理,或根据状态列进行分支。
- 需要按租户或用户分别命名空间, 而
Remove Duplicates的历史存储无法实现这一点。
符合这些条件时:
[Source: { arxivId, ... }]
↓
[Data Table Get: filter arxivId eq $json.arxivId, limit 1]
↓
[IF: result has items?]
├── Yes → [Skip, or apply row-level logic from the stored row]
└── No → [Process] → [Data Table Insert: { arxivId, ...rest }]
完整的模式说明(upsert、rowNotExists、Get+IF、幂等键)请参阅 references/DEDUP_PATTERNS.md。
查找表
稳定的参考数据(国家 → 税率,套餐 → 功能标记)。通过 n8n 界面编辑,工作流在执行时读取:
[Data Table Get: filter country eq $json.country, limit 1]
↓
[Use the looked-up row's taxRate, etc.]
最近事件 / 审计跟踪
仅追加插入,之后再查询:
[Workflow event] → [Data Table Insert: { userId, eventType, payloadSummary }]
createdAt 使得“过去一小时内的最近事件”无需自备时间戳即可轻松实现。
参考文件
| 文件 | 何时阅读 |
|---|---|
references/SCHEMA_DESIGN.md | 设计列和类型、无外键的关系型模式、映射模式(defineBelow 与 autoMapInputData),以及 Data Tables 不适用的场景 |
references/OPERATIONS.md | 操作范围(insert/upsert/update/get/delete/rowExists)、筛选语法、matchType、orderBy |
references/DEDUP_PATTERNS.md | 幂等键、RemoveDuplicates 节点与 Data Table 去重、先搜索再插入与 upsert |
关于表达式规范($json 与 $('Node Name').item.json、Set 节点反模式),请参阅 n8n-expressions-official。关于 Merge 汇合和形状相同的分支,请参阅 n8n-node-configuration-official 中的 references/MERGE_NODE.md。
反模式
| 反模式 | 问题所在 | 修复方法 |
|---|---|---|
| 在 Insert 上游添加 Set「以调整输入形状」 | 平白多出一个节点,这是典型的 Set 反模式;添加列后字段形状会发生变化 | 直接在 Insert 节点的各列槽位中进行映射,或重命名上游字段以启用自动映射 |
在 create_data_table 中声明 id、createdAt、updatedAt | 会报错,或用不会自动更新的用户列遮蔽系统列 | 不要声明,这些列已经存在 |
| 在 Data Tables 中存储应用关键数据 | 如果 n8n 出现故障,你会失去访问权限 | 无法承受丢失的数据应存储在真正的数据库中 |
| 在 Data Tables 中存储跨应用的事实来源数据 | 难以与非 n8n 消费方共享,查询方式也不方便 | 使用真正的数据库 |
将自动生成的 id 视为稳定的跨实例标识符 | 重建表后会重置,无法移植 | 使用领域 ID 列(arxivId、requestId)进行跨系统引用 |
| 假设外键会级联处理 | n8n 不会级联处理,删除父记录后会留下孤立的子记录 | 使用软删除,或运行维护引用完整性的清理工作流 |
| 引用紧邻上游的节点,但中间节点剥离了 json | 对于那些「本该存在」的字段,Insert 会静默写入 NULL | 按名称引用稳定的上游节点,或使用 NoOp/Merge 汇聚锚点(参见 n8n-expressions-official 和 n8n-node-configuration-official 的 references/MERGE_NODE.md) |
| 使用手动映射模式 + Set 节点来修复「Currently no items exist」 | 这解决不了任何问题,只是 UI 的小问题;你添加了一个无用的 Set 节点 | 通过 get_workflow_details 验证 columns.value 中包含你的映射;运行时没有问题。告知用户按下列参数上的重新加载按钮,让 UI 显示这些字段。 |
发布前验证
创建或更新使用 Data Tables 的工作流后:
validate_workflow通过。- 运行
get_workflow_details,检查每个 Data Table 节点的columns。value和(手动映射模式下的)schema都已填充。 - 使用固定数据运行
test_workflow。Insert 响应应包含id、createdAt、updatedAt。 - 通过 UI 检查实际的 Data Table 内容,或执行后续 Get,确认各列没有被静默写入 NULL。
第 4 项尤其重要,特别是首次接入新的 Insert 时。剥离上下文的中间节点 + 手动映射 + UI 小问题会导致列被静默写入 NULL。