ExerciseChallenge
Challenge
Order the columns for six queries
25 minintermediate2–12 yrs
Edge cases. You have to reason, and two valid fixes differ.
What this teaches
- Equality columns go first, the range or ORDER BY column after them
- Seeking stops at the first range column
- One well-ordered index often replaces three narrow ones
- An index on (a) is redundant beside (a, b); one on (b) is not
- Covering columns go last and cost one write, not one per column
Starter
Starter.java
import java.util.*;
import java.util.stream.*;
/**
* Challenge: six queries on one table. Design the indexes.
*
* Not "which columns" — which columns, IN WHICH ORDER, and how few indexes
* can cover the six. A naive answer is six indexes and six writes per insert.
* A good answer is three or four.
*
* Work the order out from the rule before writing any code: every equality
* column first, then the range or ORDER BY column, then anything worth
* covering.
*/
public class Starter {
/*
* table orders (
* id, tenant_id, customer_id, status, channel,
* created_at, total, currency
* )
*
* Q1 WHERE tenant_id = ? AND customer_id = ?
* SELECT id, status, total
*
* Q2 WHERE tenant_id = ? AND status = ? AND created_at >= ?
* SELECT id, customer_id
*
* Q3 WHERE tenant_id = ? AND customer_id = ? AND status = ?
* SELECT id
*
* Q4 WHERE tenant_id = ? ORDER BY created_at DESC LIMIT 50
* SELECT id, customer_id, total
*
* Q5 WHERE channel = ? AND created_at >= ?
* SELECT id
*
* Q6 WHERE tenant_id = ? AND total > ?
* SELECT id, customer_id
*/
record Index(String name, List<String> columns) {
static Index of(String name, String... c) { return new Index(name, List.of(c)); }
}
// TODO 1: for each query write its equality columns and its range or
// ORDER BY column, before proposing anything.
//
// Q1 equality: ____________________ range/order: ____
// Q2 equality: ____________________ range/order: ____
// Q3 equality: ____________________ range/order: ____
// Q4 equality: ____________________ range/order: ____
// Q5 equality: ____________________ range/order: ____
// Q6 equality: ____________________ range/order: ____
// TODO 2: turn each row above into an ordered column list — that IS the
// index for that query. Put them in CANDIDATES below.
static final List<Index> CANDIDATES = List.of(
// Index.of("idx_q1", "tenant_id", "customer_id"),
// ... one per query, to start with
);
// TODO 3: merge them. Two rules:
// - drop any index that is a leading prefix of another
// - two queries can share an index when one equality set is a leading
// prefix of the other's
// Aim for four or fewer.
// TODO 4: exactly one of the six cannot share with the rest. Identify it
// and say why its leading column rules it out.
// TODO 5: pick the query most worth covering and append its selected
// columns. Say which read that removes, and why it does not cost an extra
// write.
// TODO 6: write the redundancy check and run it over your final set —
// an index is redundant when its columns are a leading prefix of another's.
public static void main(String[] args) {
System.out.println("candidates: " + CANDIDATES.size());
for (Index i : CANDIDATES) System.out.println(" " + i.name() + " " + i.columns());
System.out.println("write cost per insert: " + (1 + CANDIDATES.size()));
// TODO 7: print the final set and its write cost. Compare with six
// indexes, and state the read benefit you kept.
}
}Run it locally:
cd exercises/java/indexing/composite-index-order/02-challenge
javac Starter.java -d /tmp/out && java -cp /tmp/out StarterHints
Hint 1
For each query, list its equality columns, then its range or ORDER BY column. That list IS the index, in that order.
Hint 2
Two queries can share an index when one's equality set is a leading prefix of the other's.
Hint 3
Check every candidate against the others for redundancy before you propose the final set.
Hint 4
Count writes: the budget is per index, so three narrow indexes cost three writes and one wide one costs one.
Done when
- Every query has a proposed index, written as an ordered column list
- The proposals are merged so no index is a leading prefix of another
- The final set is four indexes or fewer, and you can justify each
- At least one query is covered, and you say which read it removes
← Back to Does an index on (a, b) help a query filtering only on b?