关于 n8n-data-tables
n8n-data-tables 是面向 Agent 的技能,介绍如何使用 n8n 内置表格存储保存工作流状态,并说明表结构设计、CRUD 操作及其在查找、去重和事件记录中的适用场景。
主要功能
- 01用于持久状态的表格存储
- 02CRUD 操作指南
- 03表结构与列设计
- 04查找、去重和事件记录模式
- 05发布前验证 Data Table
适用场景
- 01为工作流保存查找数据
- 02跟踪去重或幂等状态
- 03记录并查询近期事件
MCP 相关
该技能介绍用于创建和操作表格的 Data Table MCP 工具及 n8n `dataTable` 节点。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ n8n-io/ skills --skill n8n-data-tables安装后在 Claude Code 中输入/n8n-data-tables确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/DEDUP_PATTERNS.mdOPERATIONS.mdSCHEMA_DESIGN.md详细介绍
n8n 数据表
数据表是 n8n 内置的表格存储:位于 n8n 实例内的真实表格,包含列、类型和行,并可通过 dataTable 节点和数据表 MCP 工具执行 CRUD 操作。
可用它们存储本地持久状态:查找表、近期事件、每个会话的库存、计数器、幂等性跟踪,以及在需要逐行逻辑或外部可见性时使用的去重状态(单纯判断“是否见过这个值?”的去重应使用 Remove Duplicates 节点)。适用于中小规模数据量(数万行没问题,数百万行则应使用真正的数据库)。
不可妥协的规则
- 系统管理的列 + 外部 ID。 每张表都会自动包含三个列:
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中也指出了相同的 Set 节点反模式。) -
列名大小写遵循 n8n 的规范:camelCase。 自动管理的列采用 camelCase(
createdAt、updatedAt),因此用户列采用相同风格更清晰:arxivId、paperId、taxRate。在同一查询中混用大小写(createdAt >= ... AND arxiv_id eq ...)看起来像拼写错误。字符串化数据块列无论如何都应保留_object后缀(keyInsights_object),下划线是约定标记,而非大小写风格。 -
创建或更新后,通过
get_workflow_details验证columns参数。 UI 在手动映射模式下有显示问题(显示“目前不存在任何项目”,但实际数据并未丢失)。检查 JSON 可确认哪些内容已持久化。 -
数据结构适合时,可以使用关系型设计。 对于真正的父子数据(论文 → 摘要、客户 → 订单),通过
id引用父记录,明确命名列(paperId、customerId),并在工作流逻辑中确保完整性。对于无需建模关系的扁平场景(去重、查找、审计),不要强行使用关系型设计。 -
存储格式不等于接口格式。 从子工作流返回
_object字段之前,应先解析它们。调用方不应收到还需要自行解析的字符串化内容。参见references/SCHEMA_DESIGN.md中的“存储格式 ≠ 接口格式”。
默认列
每张数据表都包含以下列,无论你是否声明:
| 列 | 类型 | 行为 |
|---|---|---|
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” UI 显示问题
当 SDK 保存手动模式的列映射(mappingMode: 'defineBelow')时,即使运行时能正确持久化数据,n8n UI 的 “Values to insert” 面板也可能显示为空(“Currently no items exist”)。如果用户反馈 Insert 节点“看起来坏了”或“没有字段”,请告知他们这是 UI 显示问题:点击列参数上的重新加载(刷新)按钮,即可重新填充架构并显示映射。不会丢失数据,可随时操作。
常见模式
按外部 ID 去重
对于简单的“是否见过这个值?”去重,默认使用 Remove Duplicates 节点的“之前执行中见过的项目”模式。它只需一个节点,使用内部存储,无需维护架构。只有在有理由让去重状态存放在真实表中时,才应使用 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 UI 编辑,工作流在执行时读取:
[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。有关 Merge 汇聚和同形分支,请参阅 n8n-node-configuration 的 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 和 n8n-node-configuration 中的 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 或后续的 Get 操作检查实际的 Data Table 内容,确认各列没有被静默写入 NULL。
第 4 步尤其重要,特别是首次连接新的 Insert 时。剥离上下文的中间节点 + 手动映射 + UI 小问题,会导致列被静默写入 NULL。