PAYATHON 2026

如何在 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 数量的单位不同,适合使用双轴图,不要将两个指标放在同一个数值轴上。

操作步骤:

  1. 将横轴使用的日期维度放到 Columns。
  2. 将 Net Payment Amount 放到 Rows。
  3. 将 booking 数量指标也放到 Rows。
  4. 右键第二个度量,选择 Dual Axis。
  5. 分别设置 Marks 类型,例如金额使用柱形,booking 数量使用折线。
  6. 不要同步两个轴,因为金额和数量的单位不同。

如果横轴使用 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 数按创建日期统计、金额按付款日期统计,应使用两张工作表,或者引入日历表重新建模,不要通过一个混合日期筛选器强行统一两种日期语义。

备注:内容仅供参考。