Dune连接多张转账表后数据翻倍:重复行怎么排查
多表关联后数据翻倍,通常是因为两个表之间存在"一对多"关系,而你直接做了 JOIN,没有在关联条件里加唯一性约束。遇到这种情况,排查思路是:从匹配逻辑入手,找到导致笛卡尔积的环节,再用去重手段修正。
1. 先搞明白数据为什么会翻倍
Dune 里常见的转账表(如 token_transfers、dex.trades、bridges_evms.flows)之间不是简单的一一对应关系。
以跨链桥表 bridges_evms.flows 为例,它本身已经做了"存款-取款"配对,但官方文档里直接列了一个 duplicate_index 字段——说明连官方都知道,某些情况下数据会重复。跨链桥的配对逻辑是:一笔存款可能对应多笔取款,直接关联就会导致存款行被复制。
transaction.hash 在交易表中是唯一的,但在日志表和追踪表里,根据交易执行方式的不同,同一个哈希可能出现多次。不是所有表的关联键都是唯一的。
前置条件:你已经在 Dune 的 Query Editor 里写了一个带 JOIN 的查询,跑出来的结果行数明显多于预期。
2. 第一步:定位是哪一步导致了翻倍
步骤1:分拆查询,逐段检查行数
做什么:把最后的 JOIN 拆开,分别查每个子查询或 CTE 的行数。
怎么做:
情况A(拆成多个独立查询):分别执行 SELECT COUNT(*) FROM (你的子查询A) 和 B,记录各自行数。然后执行 SELECT COUNT(*) FROM A JOIN B ON ...,看关联后的行数是否等于 A行数 × B行数。
情况B(用 CTE 分步调试):在 Dune 里把每个中间结果写成 CTE,逐一运行 SELECT COUNT(*) 确认。
做到什么程度算完成:你确定了是哪个 JOIN 环节导致行数爆炸式增长。如果关联后行数远大于两个子查询的行数之和,说明存在笛卡尔积或一对多匹配错误。
步骤2:检查 JOIN 条件里的字段是否唯一
做什么:对 JOIN 条件中用到的字段(如 tx_hash、block_time、token_address),检查它们在各自表中是否有重复值。
怎么做:
查左表:SELECT COUNT(*) AS total, COUNT(DISTINCT join_key) AS unique FROM left_table
查右表:同样查 join_key 的唯一值数量。
如果 total > unique,说明这个字段在表里不是唯一的。用非唯一字段做 JOIN,就会产生一对多匹配。
做到什么程度算完成:你确认了 JOIN 条件中的字段在至少一张表里存在重复。
常见失败原因:直接把 tx_hash 当作唯一键去关联 logs 或 traces 表。一个交易可能触发多个事件、多次内部调用,tx_hash 在 logs 表里会出现多次,这是正常的——但如果你没意识到这一点,直接 JOIN 就会翻倍。
3. 第二步:选择适合的去重方法
步骤3:用 ROW_NUMBER() 在关联前做去重
做什么:在 JOIN 之前,把非唯一表里需要关联的那一行,通过窗口函数取唯一一条记录。
怎么做:
用 ROW_NUMBER() OVER (PARTITION BY tx_hash ORDER BY log_index),只取 rn = 1 的行。
用去重后的结果再去 JOIN 另一张表。
做到什么程度算完成:JOIN 后的行数不再大于预期,且关键指标(如总金额)未被错误放大。
步骤4:用 DISTINCT 做最终去重(代价较高)
做什么:在 SELECT 语句末尾加 DISTINCT,去掉完全相同的行。
怎么做:
如果数据重复是因为同一个匹配逻辑产生了两条完全一样的记录,DISTINCT 可以一次性去掉。
但注意:Dune 的 Trino 引擎下,COUNT(DISTINCT ...) OVER (PARTITION BY ...) 在某些边界情况下可能返回 0,这是 Trino 的已知 bug,不要用这个写法做窗口去重。
做到什么程度算完成:查出来的数据不再有肉眼可见的重复行。但 DISTINCT 计算成本很高,数据量大时慎用。
风险提醒:直接 DISTINCT 可能掩盖更根本的匹配逻辑错误。GitHub 上有一个 Seaport 交易匹配重复的案例——原因是 offer 和 consideration 的匹配是一对多的,直接 DISTINCT 虽然能去重,但计算成本极高,且根源问题依然存在。建议优先修正 JOIN 逻辑,而不是用 DISTINCT 兜底。
做完以上排查,怎么确认自己解决了?
把你的查询拆分成"去重前"和"去重后"两个版本,分别算一次核心指标(比如总转账金额)。如果去重前的金额明显虚高,去重后的金额与你从 Etherscan 手动查到的总和一致,说明你找对了重复行并成功去重了。下一步,把去重逻辑写成可复用的 CTE,避免每次关联都重复手写。
