TessariDBDocs
  • 0.34.2-betalatest
  • 0.34.1-beta
  • 0.34.0-beta
  • 0.33.4-beta
  • 0.33.3-beta
  • 0.33.2-beta
  • 0.33.1-beta
  • 0.33.0-beta
  • 0.32.0-beta

Reading from a read

A read can be the source of another read — it must state its own ceiling, and it tells you when that ceiling bit.

The two tables this page reads:

DEFINE TABLE users (name string, city string);
DEFINE TABLE orders (total int, who string);
CREATE users = { name: 'ada', city: 'london' };
CREATE users = { name: 'grace', city: 'york' };
CREATE orders = { total: 7, who: 'ada' };

Anywhere a table can be read, a parenthesised read can stand instead:

SELECT * FROM (SELECT * FROM users WHERE city = 'london' LIMIT 100);

The inner read runs, its answer is held, and the outer read treats that as its source. The plan says so:

(1 record(s), via materialised)

That word is the one to know. Materialised means the records were collected into memory before the outer read saw them — unlike a scan or an index seek, which stream.

The inner read must state a ceiling

SELECT * FROM (SELECT * FROM users WHERE city = 'london');

Refused — UnexpectedToken.

error: expected `LIMIT n` on the inner read — a materialised source states how
much it may hold, so that a truncated answer is never mistaken for a whole one,
found `)`

This is the rule the rest of the page is about, and it is worth being plain about why it exists.

A source that is collected into memory has to stop somewhere. There are three things a database can do about that, and two of them are bad. It can collect without limit, and fall over on the day the table grew — a failure that arrives in production, at the worst size, having passed every test. Or it can impose a private ceiling and quietly answer about a prefix, which is worse, because the answer looks complete: a count is a number, a MAX is a value, and neither carries a mark saying of the first ten thousand.

The third option is to make the author say it. LIMIT on a materialised read is not a restriction, it is a declaration — this many is enough for the question I am asking — and it moves the decision to the one person who knows the answer.

And it says when the ceiling bit

Declaring a bound would be no better than a private one if you never learned it had been reached. So:

SELECT * FROM (SELECT * FROM users LIMIT 2);
1: { city: 'london', name: 'ada' }
2: { city: 'york', name: 'grace' }
(2 record(s), via materialised)
note: the materialised source reached its ceiling of 2, so this answers about a
prefix of what it would hold unbounded

The note travels with the answer, not to a log. It fires on reaching the ceiling, so it can fire when nothing was actually lost — two records under a LIMIT 2 is indistinguishable, from inside, from the first two of a thousand. That is the safe direction for a warning to be wrong in.

It fires no matter what the outer read then does with the records:

SELECT * FROM (SELECT * FROM users ORDER BY name LIMIT 2) WHERE name = 'ada';
1: { city: 'london', name: 'ada' }
(1 record(s), via materialised)
note: the materialised source reached its ceiling of 2, so this answers about a
prefix of what it would hold unbounded

One record answered and the note is still there, correctly. The outer WHERE filtered a prefix; whether the record it was looking for was inside that prefix is exactly what you cannot tell from the answer.

Joining a read

A materialised read can be one side of a join, and AS names it so the two sides do not collide:

SELECT * FROM users JOIN (SELECT * FROM orders WHERE total > 5 LIMIT 100) AS big
  ON users.name = big.who;
1: { big: { total: 7, who: 'ada' }, users: { city: 'london', name: 'ada' } }

AS is for exactly this. Outside a join there is nothing for the name to distinguish, so it is refused there rather than accepted and ignored.

A self-join is two reads of one table

Naming both sides is what makes it possible to say which is which:

SELECT * FROM (SELECT * FROM users LIMIT 100) AS a
  JOIN (SELECT * FROM users LIMIT 100) AS b
  ON a.city = b.city;
1: { a: { city: 'london', name: 'ada' }, b: { city: 'london', name: 'ada' } }
2: { a: { city: 'york', name: 'grace' }, b: { city: 'york', name: 'grace' } }

Note that every record matched itself, which is nearly never what a self-join is for — add the condition that excludes it, usually a comparison on the two ids.

What to reach for instead

A materialised read holds records in memory, so it is the wrong tool when the inner answer is large and the outer read only needs a little of it. Two alternatives usually apply:

  • Push the condition down. SELECT * FROM (SELECT * FROM users LIMIT 1000) WHERE city = 'london' collects a thousand records to keep a few; writing the WHERE on the inner read collects the few.
  • If you are following a link somebody wrote down rather than matching on a shared value, that is a traversal and not a read of a read — see Graphs.
Reading from a read — TessariDB