Learn / Lesson 11 · 3 min

One Table From Many

The Daily Grind opened a second region, and the new region's till exports its orders to a separate file. The weekly report needs both regions in one table, and it needs each loyalty customer counted once however many times they appear. Two jobs, and both are about rows: putting rows together, and taking repeated rows out.

Watch it happen

The east region's export, and the west region's, whose system writes the columns the other way round:

order_iddrink
1oat latte
drinkorder_id
mocha2
both = east.unionByName(west)
order_iddrink
1oat latte
2mocha

unionByName stacks the rows and matches the columns by name, so the west export's reversed columns land in the right place.

union lines columns up by position and says nothing when they don't match; unionByName matches them by name.

union matches by position

The older union lines the columns up by position and asks no questions. Stack the same two tables with it and you get a table that looks fine and is wrong:

order_iddrink
1oat latte
mocha2

The second row has a drink in the id column and an id in the drink column, and no error was raised. The engine here turned the numbers into text to make it fit; another engine might refuse, which is better, or do the same, which is worse. Use unionByName; the extra word is the safety.

When the columns don't all exist

unionByName refuses a table that is missing a column the other has: stack an export with a city column onto one without, and the run stops with right column not found: city. That is the right default. When the missing column is expected, say so, and the gap is filled with nulls:

east.unionByName(west, allowMissingColumns=True)
order_iddrinkcity
1oat latteLeeds
2mochanull

Repeated rows

distinct() drops rows that are exact repeats of another row, every column the same. dropDuplicates(["customer_id"]) keeps one row per value of the columns you name, whatever the other columns hold.

signups.distinct()                       # drops exact duplicate rows
signups.dropDuplicates(["customer_id"])  # one row per customer

Those are different jobs. Four signup rows, two of them identical:

customer_idsource
1app
1app
1web
2app

distinct() returns three rows, because (1, web) is not a repeat of (1, app). dropDuplicates(["customer_id"]) returns two, one per customer, and for customer 1 it keeps one of the three without telling you which.

Which row dropDuplicates keeps is not yours to choose. Which row survives is the engine's choice, and sorting the table beforehand does not change that. Sort six plays by play_id descending, drop duplicates on user_id, and the engine here kept play 1, not play 6. When you need a specific row per key, the latest, the highest, the first by date, that is a window function's job, and the window functions lesson teaches it.

Two problems, in the order they build:

  1. Combine the Regions — stack two exports by name.
  2. Unique Listeners — count each person once.

Practice what you learned