Index B+ trees for faster joins, computing the optimal join order of a query by dynamic programming over subsets of tables, and dynamic table updates with transactions and the ACID properties.
Joins in SQLite3. SQLite3 iterates over one join column and looks up each value in the B+ tree of the other join column. Hence an index on one join column suffices, which explains Observation 2 above.
Iterating over the leaves (Slide 11). Scanning the linked leaves trivially yields all pairs (value, row index) in sorted order. With a B+ tree on both join columns, this is exactly the input of an efficient merge join (Lecture 5, Slide 13).
A clustered B+ tree stores the full table rows in the leaves (instead of only the row index). The join result can then be produced directly from the tree, but it costs more space and is less IO-friendly.
A B tree (as opposed to a B+ tree) also stores full rows or (value, row index) pairs in the inner nodes. With one IO operation per inner node this reduces the number of children and hence increases the depth; also some data is stored twice (leaf and inner node).
In practice, updates of tables and indexes are important; the lecture only returns to updates of tables (see ACID below).
Recap (Slide 13). A query plan is a sequence of operations that computes the result table from the input tables. Its cost is estimated per operation from the size estimates of its inputs (Lectures 4 and 5, ES4 and ES5). Goal now: find the best plan among all possible plans automatically (Query Optimization).
Only binary join operations are considered; this problem alone is hard enough.
Tables are named by capital letters A,B,C,D,E.
A query plan is written as a parenthesised expression, e.g. (((AB)(CD))E): first join A with B and C with D, then join the two results, then join that result with E. Parenthesised expressions correspond to binary trees with tables as leaves and joins as inner nodes (Lecture 4, Slide 18).
A straightforward program evaluates this formula with a recursive function. This works badly, as the following interlude shows.
Interlude: Fibonacci numbers (Slide 18). F(0)=0, F(1)=1, F(n)=F(n−1)+F(n−2), i.e. 0,1,1,2,3,5,8,… The direct recursive function computes F(30) reasonably fast, but F(50) seems to take forever: its running time is O(2n), because the same subproblems are recomputed over and over. Computing the values bottom-up, each from the two previous ones, takes linear time. The same idea (dynamic programming) applies to cost∗.
In round k, do not iterate over all subsets of size k; only consider the combinations of plans from previous rounds whose union has size k (already part of the algorithm above).
Two plans with table sets S1,S2 are joinable iff S1∩S2=∅ and the join graph has an edge between a table of S1 and a table of S2.
The lecture code checks this with a nested loop in time O(∣S1∣⋅∣S2∣); in practice one uses bit masks.
# public/code/lecture-06/query-planning.py (excerpt)for k in range(2, len(self.input_plans) +1):
optimal_plans[k] = {}
for k1 in range(1, k //2+1):
k2 = k - k1
for plan1 in optimal_plans[k1].values():
for plan2 in optimal_plans[k2].values():
if self.join_graph.joinable(plan1, plan2):
plan = QueryPlan.join(plan1, plan2) # size = min, cost = c1 + c2 + sizeif (plan.tables notin optimal_plans[k]
or plan.cost < optimal_plans[k][plan.tables].cost):
optimal_plans[k][plan.tables] = plan
Dynamically changing tables (Slide 22). So far tables were read once and then only queried. This is a frequent use case (e.g. a new Wikidata dump once per month, for which one simply builds a new database). In many applications, however, the values change all the time: bank data (who has how much money), product data (which products at which price), HISinOne (who has which grade).
CREATETABLE movies(title TEXT, year INT, rating REAL);
INSERTINTO movies VALUES('Forrest Gump', 1994, 8.8);
UPDATE movies SET rating =8.9WHERE title ='Inception';
DELETEFROM movies WHEREyear<2000;
CREATETABLE movies(title TEXT, year INT, score REAL);
ALTERTABLE movies ADDCOLUMN genre;
ALTERTABLE movies DROPCOLUMN genre;
ALTERTABLE movies RENAMECOLUMN score TO rating;
ALTERTABLE movies ALTERCOLUMN rating TYPE INT;
Transactions (Slide 25). With changing contents, errors become dangerous. In a read-only database a failed query just has to be asked again; but if in a money transfer from account X to account Y the amount was subtracted from X but not added to Y, the data is wrong.
How is an index B+ tree updated on INSERT/DELETE (keeping all leaves on one level and nodes at most d entries)? The scratchpad’s “Updates” section is empty; Slide 12 announces updates, but the lecture only covers updates of table rows, not of the index.