Recipe Tableau and SQL

Two values tie. Which rank function should I use?

When two values tie for second, RANK gives 1, 2, 2, 4; RANK_DENSE gives 1, 2, 2, 3; RANK_MODIFIED gives 1, 3, 3, 4; RANK_UNIQUE gives 1, 2, 3, 4. In SQL: RANK(), DENSE_RANK(), a COUNT(*) window and ROW_NUMBER(). The choice decides who makes a cut: in this climbing league, a 'top 6' final takes 4, 6, 7 or 10 climbers from the same scores, depending on the function.

Updated

The data

Northwall climbing gym, autumn bouldering league, round 3

Synthetic dataclimbs.csv

48 climbers in two divisions try 8 boulders each. A top scores 25 points, a zone 10. The top 6 in each division go to the final, and scores in steps of 5 tie all the time.

climberdivisionboulderresultpointsattempts
Aiko QuistOpenB1none05
Aiko QuistOpenB2none05
Aiko QuistOpenB3top254
Aiko QuistOpenB4none05

Step 1

Total each climber's points

Scores move in steps of 5, so flat runs of equal bars appear everywhere. Every flat run is a tie the rank function has to decide.

Tableau

SUM([points])
// Rows: division, climber; sorted by SUM(points) descending

SQL

SELECT division, climber, SUM(points) AS points
FROM input
GROUP BY division, climber
ORDER BY division, points DESC, climber

Rows

DivisionClimberPoints
MastersJade Varga200
MastersSigne Jansen185
OpenWes Young185
OpenPavel Zielinski175
MastersKian Okafor170
OpenLars Rossi170
OpenRosa Holm170
MastersTomas Kowal160
MastersZara Costa160
OpenNils Lind160
OpenUgo Grau160
OpenXimena Dahl160
OpenOdette Toivonen155
MastersBodhi Ito145
MastersOtto Toivonen145
MastersRemy Holm145
MastersCyrus Berg135
MastersHedda Ueda135
MastersMateo Weiss135
OpenCleo Berg135
OpenKofi Okafor135
MastersIsak Sato120
OpenDario Novak120
OpenHana Ueda120
OpenJuno Varga120
OpenVera Moreau120
OpenYusuf Costa120
MastersGus Fontaine115
MastersDina Novak105
MastersElio Pires105
MastersXander Dahl105
OpenFemi Ekberg105
OpenGreta Fontaine105
MastersFaye Ekberg100
OpenMira Weiss100
OpenSami Jansen100
MastersUna Grau95
OpenAiko Quist95
MastersViggo Moreau80
OpenTess Kowal80
MastersAlba Quist60
MastersNell Lind60
OpenIlya Sato60
MastersWanda Young55
MastersPriya Zielinski45
OpenBram Ito45
OpenEsme Pires45
MastersLotte Rossi35

Step 2

Rank the same totals four ways

Look at the three Open climbers on 160. RANK puts them all 5th and skips to 8th. RANK_DENSE puts them 4th and carries on at 5th. RANK_MODIFIED puts them 7th, the number of climbers at or above them. RANK_UNIQUE gives 5, 6 and 7 in alphabetical order. COUNT(*) works as the modified rank because a window with ORDER BY counts every row tied with the current one.

Tableau

RANK(SUM([points]), 'desc')   RANK_DENSE(SUM([points]), 'desc')   RANK_MODIFIED(SUM([points]), 'desc')   RANK_UNIQUE(SUM([points]), 'desc')
// Compute Using: climber

SQL

WITH t AS (
  SELECT division, climber, SUM(points) AS points
  FROM input
  GROUP BY division, climber
)
SELECT
  division,
  climber,
  points,
  RANK() OVER w AS rank,
  DENSE_RANK() OVER w AS rank_dense,
  COUNT(*) OVER w AS rank_modified,
  ROW_NUMBER() OVER (PARTITION BY division ORDER BY points DESC, climber) AS rank_unique
FROM t
WINDOW w AS (PARTITION BY division ORDER BY points DESC)
ORDER BY division, points DESC, climber

Rows

DivisionClimberPointsRankRank denseRank modifiedRank unique
MastersJade Varga2001111
MastersSigne Jansen1852222
MastersKian Okafor1703333
MastersTomas Kowal1604454
MastersZara Costa1604455
MastersBodhi Ito1456586
MastersOtto Toivonen1456587
MastersRemy Holm1456588
MastersCyrus Berg13596119
MastersHedda Ueda135961110
MastersMateo Weiss135961111
MastersIsak Sato1201271212
MastersGus Fontaine1151381313
MastersDina Novak1051491614
MastersElio Pires1051491615
MastersXander Dahl1051491616
MastersFaye Ekberg10017101717
MastersUna Grau9518111818
MastersViggo Moreau8019121919
MastersAlba Quist6020132120
MastersNell Lind6020132121
MastersWanda Young5522142222
MastersPriya Zielinski4523152323
MastersLotte Rossi3524162424
OpenWes Young1851111
OpenPavel Zielinski1752222
OpenLars Rossi1703343
OpenRosa Holm1703344
OpenNils Lind1605475
OpenUgo Grau1605476
OpenXimena Dahl1605477
OpenOdette Toivonen1558588
OpenCleo Berg13596109
OpenKofi Okafor135961010
OpenDario Novak1201171511
OpenHana Ueda1201171512
OpenJuno Varga1201171513
OpenVera Moreau1201171514
OpenYusuf Costa1201171515
OpenFemi Ekberg1051681716
OpenGreta Fontaine1051681717
OpenMira Weiss1001891918
OpenSami Jansen1001891919
OpenAiko Quist9520102020
OpenTess Kowal8021112121
OpenIlya Sato6022122222
OpenBram Ito4523132423
OpenEsme Pires4523132424

How many climbers make a top-6 final?

Anywhere from 4 to 10, from the same scores. In the Open division, RANK sends 7 climbers to the final, RANK_DENSE 10, RANK_MODIFIED 4, and only RANK_UNIQUE exactly 6. Pick a function and a number of spots: with RANK and 6 spots, 7 Open and 8 Masters climbers make it.

Rank function
310

Step 3

Draw the line for the final

Switch the rank function and watch the line move. The scores never change; only the rule for ties does.

Tableau

IF RANK(SUM([points]), 'desc') <= 6 THEN "Final"
ELSE "Out"
END
// Compute Using: climber

SQL

WITH t AS (
  SELECT division, climber, SUM(points) AS points
  FROM input
  GROUP BY division, climber
), k AS (
  SELECT
    *,
    RANK() OVER w AS r1,
    DENSE_RANK() OVER w AS r2,
    COUNT(*) OVER w AS r3,
    ROW_NUMBER() OVER (PARTITION BY division ORDER BY points DESC, climber) AS r4
  FROM t
  WINDOW w AS (PARTITION BY division ORDER BY points DESC)
)
SELECT
  division,
  climber,
  points,
  CASE
    WHEN (CASE '{knob.method}' WHEN 'RANK' THEN r1 WHEN 'RANK_DENSE' THEN r2 WHEN 'RANK_MODIFIED' THEN r3 ELSE r4 END) <= 6 THEN 'Final'
    ELSE 'Out'
    END AS status
FROM k
ORDER BY division, points DESC, climber

Rows

DivisionClimberPointsStatus
MastersJade Varga200Final
MastersSigne Jansen185Final
OpenWes Young185Final
OpenPavel Zielinski175Final
MastersKian Okafor170Final
OpenLars Rossi170Final
OpenRosa Holm170Final
MastersTomas Kowal160Final
MastersZara Costa160Final
OpenNils Lind160Final
OpenUgo Grau160Final
OpenXimena Dahl160Final
OpenOdette Toivonen155Out
MastersBodhi Ito145Final
MastersOtto Toivonen145Final
MastersRemy Holm145Final
MastersCyrus Berg135Out
MastersHedda Ueda135Out
MastersMateo Weiss135Out
OpenCleo Berg135Out
OpenKofi Okafor135Out
MastersIsak Sato120Out
OpenDario Novak120Out
OpenHana Ueda120Out
OpenJuno Varga120Out
OpenVera Moreau120Out
OpenYusuf Costa120Out
MastersGus Fontaine115Out
MastersDina Novak105Out
MastersElio Pires105Out
MastersXander Dahl105Out
OpenFemi Ekberg105Out
OpenGreta Fontaine105Out
MastersFaye Ekberg100Out
OpenMira Weiss100Out
OpenSami Jansen100Out
MastersUna Grau95Out
OpenAiko Quist95Out
MastersViggo Moreau80Out
OpenTess Kowal80Out
MastersAlba Quist60Out
MastersNell Lind60Out
OpenIlya Sato60Out
MastersWanda Young55Out
MastersPriya Zielinski45Out
OpenBram Ito45Out
OpenEsme Pires45Out
MastersLotte Rossi35Out

Step 4

Count the finalists under each rule

Only the unique rank always fills exactly the spots on offer. RANK lets in everyone tied on the last place, RANK_MODIFIED shuts them all out, and RANK_DENSE can let in far more than the cutoff, because it counts distinct scores, not people.

Tableau

WINDOW_SUM(IF RANK(SUM([points]), 'desc') <= 6 THEN 1 ELSE 0 END)
// one calc per rank function

SQL

WITH t AS (
  SELECT division, climber, SUM(points) AS points
  FROM input
  GROUP BY division, climber
), k AS (
  SELECT
    *,
    RANK() OVER w AS r1,
    DENSE_RANK() OVER w AS r2,
    COUNT(*) OVER w AS r3,
    ROW_NUMBER() OVER (PARTITION BY division ORDER BY points DESC, climber) AS r4
  FROM t
  WINDOW w AS (PARTITION BY division ORDER BY points DESC)
)
SELECT
  division,
  m.method,
  COUNT(*) FILTER (WHERE CASE m.method WHEN 'RANK' THEN r1 WHEN 'RANK_DENSE' THEN r2 WHEN 'RANK_MODIFIED' THEN r3 ELSE r4 END <= 6) AS finalists
FROM k CROSS JOIN (VALUES ('RANK'), ('RANK_DENSE'), ('RANK_MODIFIED'), ('RANK_UNIQUE')) AS m(method)
GROUP BY division, m.method
ORDER BY division, m.method

Rows

DivisionMethodFinalists
MastersRANK8
MastersRANK_DENSE11
MastersRANK_MODIFIED5
MastersRANK_UNIQUE6
OpenRANK7
OpenRANK_DENSE10
OpenRANK_MODIFIED4
OpenRANK_UNIQUE6

Which rank should I use?

Use RANK when tied people should share a place, RANK_DENSE when you want score tiers, and a unique rank when the list must have exactly N rows. A unique rank needs a tie-break that means something; the alphabet is not one.

Step 5

Break ties with a rule that means something

Climbing breaks ties on attempts: fewer tries for the same score ranks higher. Tableau's rank functions take one expression, so fold the tie-break into it: points times 1,000, minus attempts, keeps points first. In SQL, add attempts as a second sort key, and the name last so the order is always the same.

Tableau

RANK_UNIQUE(SUM([points]) * 1000 - SUM([attempts]), 'desc')
// Compute Using: climber

SQL

WITH t AS (
  SELECT
    division,
    climber,
    SUM(points) AS points,
    SUM(attempts) AS attempts
  FROM input
  GROUP BY division, climber
)
SELECT
  division,
  climber,
  points,
  attempts,
  ROW_NUMBER() OVER (PARTITION BY division ORDER BY points DESC, attempts ASC, climber) AS place,
  CASE
    WHEN ROW_NUMBER() OVER (PARTITION BY division ORDER BY points DESC, attempts ASC, climber) <= 6 THEN 'Final'
    ELSE 'Out'
    END AS status
FROM t
ORDER BY division, place

Rows

DivisionClimberPointsAttemptsPlaceStatus
MastersJade Varga200231Final
MastersSigne Jansen185212Final
OpenWes Young185161Final
OpenPavel Zielinski175242Final
MastersKian Okafor170233Final
OpenRosa Holm170163Final
OpenLars Rossi170264Final
MastersTomas Kowal160234Final
MastersZara Costa160305Final
OpenNils Lind160225Final
OpenUgo Grau160266Final
OpenXimena Dahl160287Out
OpenOdette Toivonen155258Out
MastersRemy Holm145206Final
MastersBodhi Ito145287Out
MastersOtto Toivonen145318Out
MastersHedda Ueda135259Out
MastersMateo Weiss1352510Out
MastersCyrus Berg1352611Out
OpenCleo Berg135279Out
OpenKofi Okafor1352710Out
MastersIsak Sato1202912Out
OpenHana Ueda1202311Out
OpenYusuf Costa1202812Out
OpenJuno Varga1202913Out
OpenVera Moreau1202914Out
OpenDario Novak1203115Out
MastersGus Fontaine1152413Out
MastersXander Dahl1052214Out
MastersDina Novak1052615Out
MastersElio Pires1052816Out
OpenGreta Fontaine1052516Out
OpenFemi Ekberg1053317Out
MastersFaye Ekberg1002817Out
OpenMira Weiss1002918Out
OpenSami Jansen1003019Out
MastersUna Grau952718Out
OpenAiko Quist953320Out
MastersViggo Moreau803219Out
OpenTess Kowal803021Out
MastersAlba Quist603420Out
MastersNell Lind603421Out
OpenIlya Sato603222Out
MastersWanda Young553422Out
MastersPriya Zielinski453323Out
OpenBram Ito453623Out
OpenEsme Pires453624Out
MastersLotte Rossi353424Out

Common mistake

A unique rank with an arbitrary tie-break

Three Masters climbers tie on 145 for 6th. Broken alphabetically, the last final spot goes to Bodhi Ito, who used 28 attempts. Remy Holm, on the same points in 20 attempts, is cut. A unique rank is only as fair as its tie-break, and in SQL ROW_NUMBER() without one can even change between runs.

WrongStarts with Jade Varga, Signe Jansen, Kian Okafor…
Point at the line to read it.
02468Climbers in this order →

Tableau

RANK_UNIQUE(SUM([points]), 'desc')
// ties fall in name order

SQL

WITH t AS (
  SELECT
    division,
    climber,
    SUM(points) AS points,
    SUM(attempts) AS attempts
  FROM input
  GROUP BY division, climber
)
SELECT
  climber,
  points,
  attempts,
  ROW_NUMBER() OVER (PARTITION BY division ORDER BY points DESC, climber) AS place
FROM t
WHERE division = 'Masters'
ORDER BY place
LIMIT 8
RightStarts with Jade Varga, Signe Jansen, Kian Okafor…
Point at the line to read it.
02468Climbers in this order →

Tableau

RANK_UNIQUE(SUM([points]) * 1000 - SUM([attempts]), 'desc')

SQL

WITH t AS (
  SELECT
    division,
    climber,
    SUM(points) AS points,
    SUM(attempts) AS attempts
  FROM input
  GROUP BY division, climber
)
SELECT
  climber,
  points,
  attempts,
  ROW_NUMBER() OVER (PARTITION BY division ORDER BY points DESC, attempts ASC, climber) AS place
FROM t
WHERE division = 'Masters'
ORDER BY place
LIMIT 8

Why does each division get fewer finalists than it should?

The rank ran over the whole table, not within each division.

Common mistake

Ranking across the whole table instead of per division

With Compute Using set to Table (down), Tableau ranks all 48 climbers together, so 'top 6' is the top 6 overall: 4 Open climbers and 3 Masters, ties included. Compute Using climber restarts the rank in each division.

WrongStarts with Masters, Open…
Point at a bar to read it.
01234MastersOpen

Tableau

RANK(SUM([points]), 'desc')
// Compute Using: Table (down)

SQL

WITH t AS (
  SELECT division, climber, SUM(points) AS points
  FROM input
  GROUP BY division, climber
), k AS (
  SELECT *, RANK() OVER (ORDER BY points DESC) AS place
  FROM t
)
SELECT
  division,
  COUNT(*) FILTER (WHERE place <= 6) AS finalists
FROM k
GROUP BY division
ORDER BY division
RightStarts with Masters, Open…
Point at a bar to read it.
02468MastersOpen

Tableau

RANK(SUM([points]), 'desc')
// Compute Using: climber

SQL

WITH t AS (
  SELECT division, climber, SUM(points) AS points
  FROM input
  GROUP BY division, climber
), k AS (
  SELECT
    *,
    RANK() OVER (PARTITION BY division ORDER BY points DESC) AS place
  FROM t
)
SELECT
  division,
  COUNT(*) FILTER (WHERE place <= 6) AS finalists
FROM k
GROUP BY division
ORDER BY division

Run it on your own data

Any table with an item, an optional group and a score works. Drop your CSV and pick the columns; the steps above recompute in your browser, and nothing is uploaded.

Drop a CSV anywhere on this page

Up to 10,000 rows. The sample, climbs.csv, shows the shape.

Questions

Which rank should a leaderboard use?

RANK (competition rank, 1, 2, 2, 4) for most leaderboards: tied people share a place and the next place shows how many are ahead. Use RANK_DENSE when you want tiers, not places. Use a unique rank only with a tie-break that means something.

What is the difference between INDEX() and RANK_UNIQUE()?

INDEX() numbers rows in the order the view happens to sort them. RANK_UNIQUE sorts by the expression inside it, whatever the view's sort.

How do I rank within each group?

Rank along the item and restart for every group. In Tableau, set Compute Using to the item (climber here) so the other dimension (division) partitions it. In SQL, PARTITION BY division.

What about percentile ranks?

Tableau's RANK_PERCENTILE sorts ascending by default, so the highest value gets the top percentile. SQL's PERCENT_RANK() is (RANK - 1) / (rows - 1) within the partition. Both run from 0 to 1, and tied values share one value.

Two totals look equal but rank differently. Why?

They differ in a decimal place the view doesn't show. Round inside the rank, e.g. RANK(ROUND(SUM([amount]), 2)), if equal-looking totals should tie.

Sources