如何在 Tableau 中用两张表创建双指标图表
结论
可以实现,但要先明确两个日期各自对应的业务范围:
Bookings.date决定哪些 booking 会被纳入统计,也就是 booking 的创建日期。Payment.Date决定统计这些 booking 在什么时间发生的付款或退款。
同时使用两个筛选器时,二者是 AND 条件:只统计创建日期符合要求,且付款日期也在指定范围内的数据。
数据模型建议使用 Tableau 的逻辑关系,将 Bookings 与 Payment 配置为一对多,不要直接建立物理内连接。金额使用 SUM([Amount]),booking 数量则根据业务口径选择 COUNTD([Bookings].[ID]) 或 COUNTD([Payment].[bookingId])。
这两个日期不能简单合并成一个日期。booking 创建和付款是两类不同的事件,需要先确定图表分析的是 booking 创建量、付款发生额,还是两者的交集。
推荐的数据模型
在 Tableau 数据源的逻辑层建立以下关系:
Bookings.ID = Payment.bookingId
关系属性设置为:
Bookings → Payment
一对多(1:N)
两张表的粒度分别是:
Bookings中一条记录对应一个 booking。Payment中一条记录对应一次付款或退款。- 一个 booking 可能没有付款,也可能对应多条付款记录。
优先使用 Tableau 的逻辑关系,不要直接将两张表做成物理表连接。逻辑关系允许 Tableau 根据视图中使用的字段,在各自的粒度上生成查询,可以减少 payment 明细导致 booking 行重复展开的问题。
如果不能保证每条 Payment.bookingId 都能在 Bookings.ID 中找到,就不要将参照完整性设置为“所有记录均匹配”。
创建基础指标
净付款金额
退款以负数保存,因此直接求和即可得到净付款金额:
SUM([Payment].[Amount])
也可以创建计算字段:
// Net Payment Amount
SUM([Amount])
这样,正向付款和负向退款会自动相抵。
去重后的 booking 数量
如果要统计同时符合两个日期条件,且付款表中有记录的 booking,可以使用:
COUNTD([Payment].[bookingId])
即使一个 booking 分多次付款,也只计算一次。
如果要统计所选创建日期范围内的全部 booking,包括尚未付款的 booking,则使用:
COUNTD([Bookings].[ID])
两个指标的业务含义不同,不能互相替代。
只统计发生过正向付款的 booking
如果退款记录不应被视为“售出”,可以使用:
COUNTD(
IF [Amount] > 0 THEN [bookingId] END
)
只有在当前筛选范围内存在正向付款记录的 booking 才会被计数。如果某个 booking 在所选期间只有退款记录,它不会被算作新售出的 booking。
日期筛选器的正确用法
将下面两个字段分别放入 Filters:
Bookings.date
Payment.Date
例如:
Bookings.date:2026-01-01 至 2026-01-31
Payment.Date:2026-01-01 至 2026-01-31
此时的统计口径是:
2026 年 1 月创建,并且在 2026 年 1 月发生付款或退款的 booking。
这既不是“1 月创建的全部 booking”,也不是“1 月发生的全部付款金额”,而是两个条件的交集。
如果要查看“1 月创建的所有 booking,以及这些 booking 截至某日累计收到的金额”,可以这样设置:
Bookings.date:2026-01-01 至 2026-01-31
Payment.Date:截至选定日期
例如,将付款日期设置为:
Payment.Date <= 2026-03-31
这种设置更适合分析某一批 booking 后续产生的回款。
在同一张图表中展示金额和数量
金额与 booking 数量的单位不同,适合使用双轴图,不要将两个指标放在同一个数值轴上。
操作步骤:
- 将横轴使用的日期维度放到 Columns。
- 将
Net Payment Amount放到 Rows。 - 将 booking 数量指标也放到 Rows。
- 右键第二个度量,选择
Dual Axis。 - 分别设置 Marks 类型,例如金额使用柱形,booking 数量使用折线。
- 不要同步两个轴,因为金额和数量的单位不同。
如果横轴使用 Payment.Date,图表展示的是每个付款日期发生的金额,以及当天至少有一条符合条件的 payment 的去重 booking 数。
如果横轴使用 Bookings.date,payment 金额会归到 booking 的创建日期,而不是实际发生付款的日期。
不要用一个日期轴混淆两类事件
下面两种分析口径都成立,但表达的含义完全不同。
按付款日期分析
横轴:
Payment.Date
指标:
SUM([Amount])
COUNTD(
IF [Amount] > 0 THEN [bookingId] END
)
图表表示每个付款周期产生的净金额,以及发生正向付款的 booking 数量。
按 booking 创建日期分析
横轴:
Bookings.date
指标:
SUM([Amount])
COUNTD([Bookings].[ID])
图表表示每个创建周期产生的 booking 数量,以及这些 booking 对应的付款金额。付款金额会归到 booking 创建日期,而不是实际付款日期。
如果报表需要同时展示“按创建日期统计的 booking 数量”和“按付款日期统计的付款金额”,更稳妥的方式是制作两张工作表,再将它们放到同一个 Dashboard 中,并共享所需的筛选器。如果强行使用同一个日期轴,读者很容易误以为两个指标采用了相同的时间口径。
注意 FIXED LOD 的筛选顺序
有些实现会使用 LOD 将计算固定到 booking 粒度,例如计算每个 booking 在所选期间的净付款:
// Net Amount per Booking
{ FIXED [bookingId] : SUM([Amount]) }
然后统计净付款大于 0 的 booking:
COUNTD(
IF [Net Amount per Booking] > 0
THEN [bookingId]
END
)
需要注意,普通维度筛选器通常晚于 FIXED LOD 执行。即使 Payment.Date 已经放入 Filters,LOD 仍可能根据全部 payment 历史记录进行计算。
如果 LOD 只能计算付款日期范围内的数据,应将 Payment.Date 筛选器加入 Context:
右键 Payment.Date 筛选器
→ Add to Context
是否将 Bookings.date 也加入 Context,要看 LOD 是否需要受到 booking 创建日期的影响。上下文筛选会影响查询性能,因此不应无条件将所有筛选器都加入 Context。
明确定义退款和“已售 booking”
如果只使用:
COUNTD([bookingId])
当前付款日期范围内只有退款记录的 booking 也会被计入,因为退款记录同样包含 bookingId。
常见的统计口径有以下三种:
- 发生过任意 payment 记录的 booking:
COUNTD([bookingId]) - 发生过正向付款的 booking:只统计
Amount > 0 - 筛选期内净付款大于 0 的 booking:先按 booking 汇总
SUM([Amount]),再判断结果是否大于 0
如果一个 booking 在筛选期内支付 100 元,又退款 100 元:
净付款金额 = 0
它是否仍属于“已售 booking”,需要由业务规则决定,Tableau 无法代替业务方作出定义。
最稳妥的落地方案
对于当前的数据结构,可以采用以下配置:
关系:
Bookings.ID = Payment.bookingId
Cardinality:Bookings 1 → N Payment
// 净付款金额
SUM([Payment].[Amount])
// 期间内发生正向付款的 booking 数
COUNTD(
IF [Payment].[Amount] > 0
THEN [Payment].[bookingId]
END
)
分别保留两个筛选器:
Bookings.date
Payment.Date
这样可以得到“在指定日期创建,且在指定付款日期范围内发生正向付款的 booking 数量”,以及这些 booking 对应的净付款金额。
如果需要的是两个相互独立的时间序列,也就是 booking 数按创建日期统计、金额按付款日期统计,应使用两张工作表,或者引入日历表重新建模,不要通过一个混合日期筛选器强行统一两种日期语义。
备注:内容仅供参考。
