Chart guide Tableau and SQL
How do I build a KPI card that compares this month with last month?
Anchor the card to the latest date in the data with { FIXED : MAX([Date]) }, then compare this month to date with the same days of the prior month, not the whole prior month. Here the naive card says bookings fell 41%; the fair one says they grew 4%.
The data
Lantern Yoga daily classes, Sep 2024 to 17 Oct 2025
Three yoga studios in one city. One row per studio per day: classes booked, revenue and late cancellations. The data ends on Friday 17 October 2025.
class_date | studio | bookings | revenue | late_cancels |
|---|---|---|---|---|
| 2024-09-01 | Riverside | 58 | 1142.4 | 4 |
| 2024-09-01 | Old Town | 73 | 1265.4 | 4 |
| 2024-09-01 | Hillcrest | 37 | 626.4 | 1 |
| 2024-09-02 | Riverside | 81 | 1612.8 | 1 |
Step 1
Total by month: the last bar looks like a crash
October is only 17 days old, so its bar is short. A card built on this, "October vs September", reports a collapse that isn't happening.
Tableau
SUM([bookings])
// MONTH([class_date]) on ColumnsSQL
SELECT
CAST (date_trunc('month', class_date) AS DATE) AS month,
SUM(bookings) AS total
FROM input
GROUP BY 1
ORDER BY 1Rows
| Month | Bookings |
|---|---|
| 2024-09-01 | 5,926 |
| 2024-10-01 | 6,679 |
| 2024-11-01 | 6,015 |
| 2024-12-01 | 5,303 |
| 2025-01-01 | 7,432 |
| 2025-02-01 | 6,525 |
| 2025-03-01 | 6,891 |
| 2025-04-01 | 6,505 |
| 2025-05-01 | 6,203 |
| 2025-06-01 | 5,845 |
| 2025-07-01 | 5,853 |
| 2025-08-01 | 6,238 |
| 2025-09-01 | 7,120 |
| 2025-10-01 | 4,204 |
Step 2
Find the latest date in the data
Every period on the card is measured from this one date, so the card updates itself when a new file arrives.
Latest date
Oct 17, 2025
Tableau
{ FIXED : MAX([class_date]) }
// name it [Latest date]SQL
SELECT MAX(class_date) AS latest_date
FROM inputRows
| Class date |
|---|
| 2025-10-17 |
Step 3
Compare the same days, not the whole month
1 to 17 October against 1 to 17 September. Now both bars cover the same number of days, and the comparison is fair.
Tableau
// [Current MTD]
SUM(IF DATETRUNC('month', [class_date]) = DATETRUNC('month', [Latest date])
THEN [bookings] END)
// [Prior MTD]
SUM(IF DATETRUNC('month', [class_date]) = DATEADD('{knob.compare}', -1, DATETRUNC('month', [Latest date]))
AND DAY([class_date]) <= DAY([Latest date])
THEN [bookings] END)SQL
WITH b AS (
SELECT MAX(class_date) AS latest
FROM input
)
SELECT
'This month to date' AS period,
SUM(bookings) FILTER (WHERE date_trunc('month', class_date) = date_trunc('month', latest)) AS total
FROM input, b
UNION ALL
SELECT
'Same days, one {knob.compare} earlier',
SUM(bookings) FILTER (WHERE date_trunc('month', class_date) = date_trunc('month', latest) - INTERVAL 1 month AND day(class_date) <= day(latest))
FROM input, bRows
| Period | Total |
|---|---|
| This month to date | 4,204 |
| Same days, one month earlier | 4,043 |
Step 4
Assemble the card: headline, change, direction
Big number first, the change right under it, and the prior value small so nobody has to guess what it's compared with. Switch the measure to late cancellations: the arrow goes up and the color turns red, because more cancellations is bad news.
This month to date
4,204
+4% vs 4,043
Tableau
// [Change %]
([Current MTD] - [Prior MTD]) / [Prior MTD]
// Text: [Current MTD] large, [Change %] below it
// Color: [Change %] > 0, flipped for measures where up is badSQL
WITH b AS (
SELECT MAX(class_date) AS latest
FROM input
), p AS (
SELECT
SUM(bookings) FILTER (WHERE date_trunc('month', class_date) = date_trunc('month', latest)) AS CURRENT,
SUM(bookings) FILTER (WHERE date_trunc('month', class_date) = date_trunc('month', latest) - INTERVAL 1 month AND day(class_date) <= day(latest)) AS prior
FROM input, b
)
SELECT
CURRENT,
prior,
CURRENT - prior AS delta,
CURRENT / prior - 1 AS change
FROM pRows
| Current | Prior | Delta | Change |
|---|---|---|---|
| 4,204 | 4,043 | 161 | 4% |