ScoutQL filters
WHERE is a boolean expression over a source’s columns, evaluated on raw rows
before they are grouped. HAVING is a boolean expression over the query’s
aggregates and their aliases, evaluated after.
Both accept the same operators. The difference is only what they can name, and
it decides what a filter means: WHERE kills >= 10 keeps ten-kill games,
while HAVING kills >= 10 keeps players whose total is ten.
Operators
Section titled “Operators”| Operator | Tests |
|---|---|
= <> |
Equality. Strings compare exactly, case and all |
> >= < <= |
Ordered comparison |
AND OR NOT ( … ) |
Boolean composition, in that precedence |
IN (a, b, …) |
Membership in a list |
BETWEEN a AND b |
Inclusive range |
LIKE / ILIKE |
Pattern match; % any run, _ one character |
IS NULL / IS NOT NULL |
Presence — = NULL is never true |
+ - * / |
Arithmetic inside a comparison |
:: |
Cast |
A boolean column stands on its own: WHERE win and WHERE NOT early_surrendered
need no comparison.
SELECT COUNT(*) AS games, AVG(win::INT) AS win_rateFROM match_participantsWHERE queue IN ('solo', 'flex') AND game_duration_seconds >= 900 AND NOT early_surrendered AND game_creation_at >= CURRENT_TIMESTAMP - INTERVAL 30 DAYGROUP BY championHAVING games >= 20ORDER BY win_rate DESCLIMIT 10Which columns each source exposes, and which of them WHERE may name, is in
Sources and columns.
Aggregate floors belong in HAVING
Section titled “Aggregate floors belong in HAVING”An aggregate in WHERE is refused rather than reinterpreted. WHERE games >= 10
has no meaning on a single row — a row is one game — and quietly promoting it
to a group filter would hide the fact that the query said something impossible.
HAVING games >= 10 is the guard that stops a 1–0 record topping a win-rate
leaderboard, and it is the single most common omission in a report that looks
wrong.
Naming a player
Section titled “Naming a player”player('…') resolves a tracked player’s accounts when the report runs:
SELECT COUNT(*) AS games, AVG(win::INT) AS win_rateFROM match_participantsWHERE player('Bob') AND game_creation_at >= CURRENT_TIMESTAMP - INTERVAL 90 DAYGROUP BY championORDER BY games DESCIt takes the server’s Scout alias in a guild report and the recorded Riot ID in global Explore. Because it resolves at run time it follows renames and newly linked accounts, which a stored name comparison cannot.
The player dimension can also be compared directly (player = 'Bob'), but
that matches the one label the row happens to carry, so prefer the call.
Naming a champion
Section titled “Naming a champion”champion('…') takes a champion’s display name and folds it to the numeric id
the match data stores, while the query compiles:
SELECT COUNT(*) AS games, AVG(win::INT) AS win_rateFROM match_participantsWHERE champion_id = champion('Kai''Sa') AND game_creation_at >= CURRENT_TIMESTAMP - INTERVAL 90 DAYGROUP BY patchORDER BY patch ASCA name it does not recognize fails with the closest match as a suggestion,
before the report runs. Note the doubled quote: 'Kai''Sa' is how a literal
apostrophe is written.
Time bounds
Section titled “Time bounds”A window is a WHERE conjunct on the source’s time column. The compiler
recognizes these shapes and executes them as the query’s window; anything else
is applied as an ordinary filter, and the window stays all of history.
| Form | Means |
|---|---|
t >= CURRENT_TIMESTAMP - INTERVAL <n> DAY |
A rolling window ending now |
… INTERVAL <n> WEEK / MONTH / YEAR |
The same, in a longer unit |
(t AT TIME ZONE '<zone>')::DATE BETWEEN '<from>' AND '<to>' |
Whole calendar days, both ends inclusive |
t::DATE BETWEEN '<from>' AND '<to>' |
The same, in UTC days |
t >= '<from>' AND t < '<to>' |
An explicit instant range |
| (omitted) | Every match Scout has ingested |
t is the source’s own time column — game_creation_at for matches,
observed_at for champion-select observations. The rank sources have none and
accept no window.
Omitting the bound is legal and warns (time-window-unbounded). It has to stay
legal, or “how many games have we ever played?” would be unaskable; it warns
because an all-history answer read as a monthly one looks exactly like a
correct answer.
Scoping to a competition
Section titled “Scoping to a competition”The competition sources require their competition as a top-level condition — they have no meaning without one, so the query will not compile if it is missing:
SELECT MAX(score) AS scoreFROM competition_rankWHERE competition_id = 42GROUP BY playerORDER BY score DESCRENDER leaderboardRelated
Section titled “Related”- ScoutQL
- Sources and columns
- Queues and regions — the values
queuetakes.