# LATEST ON > Keep only the newest row of each symbol (or of each group of columns). - Category: Time-series - Keywords: LATEST, LATEST ON, PARTITION BY, newest row, last row, latest price, last price, LTP, live prices, last login, most recent, LATEST BY, QuestDB, top movers, gainers - See also: [SAMPLE BY](sample-by.md), [WHERE](where.md), [Time and time zones](time-and-time-zones.md), [Candles cookbook (top-gainers)](candles-cookbook.md#top-gainers), [Joins (asof-join)](joins.md#asof-join) `LATEST ON` keeps **one row per group: the row with the newest time**. The usual use is "the last price of every symbol" from a table of live price snapshots. - Write it after `WHERE` and before `SAMPLE BY`, `GROUP BY` and `ORDER BY`. - It works like QuestDB's `LATEST ON ... PARTITION BY ...`, so queries from the 006 app run unchanged. **Example: Live market: the last snapshot of every symbol** (sample data: sample) ```sql SELECT symbol, ltp, point_change, pct_change, timestamp FROM live_data LATEST ON timestamp PARTITION BY symbol ORDER BY pct_change DESC; ``` Result: | symbol (SYMBOL) | ltp (DOUBLE) | point_change (DOUBLE) | pct_change (DOUBLE) | timestamp (TIMESTAMP) | |---|---|---|---|---| | CITY | 527.1 | 13.9 | 2.71 | 2026-09-10T09:14:00.000Z | | TELCO | 897.7 | 16.7 | 1.9 | 2026-09-10T09:14:00.000Z | | COAST | 401.9 | 2.8 | 0.7 | 2026-09-10T09:14:00.000Z | | VOLT | 184 | -1.6 | -0.86 | 2026-09-10T09:14:00.000Z | (4 rows) ## LATEST ON Syntax: ```sql SELECT ... FROM table [WHERE condition] LATEST ON time_column PARTITION BY column [, column ...] [GROUP BY ...] [HAVING ...] [ORDER BY ...] [LIMIT n] ``` For each different value of the `PARTITION BY` columns, keeps only the row with the biggest `time_column`. All other rows are dropped before the SELECT list is computed. | Part | Type | Meaning | |---|---|---| | `time_column` | `TIMESTAMP` or `DATE` column | "newest" means the biggest value of this column | | `column, ...` | column names | the groups: one row is kept per different value (or per different combination of values) | - **Returns:** one row per group. Without `ORDER BY`, the rows come oldest first (by the kept time). Add `ORDER BY` when the order matters. - **NULL:** rows whose time is `NULL` are skipped. A `NULL` in a `PARTITION BY` column is a group of its own. - **Notes:** - `PARTITION BY` here means "per group". It has nothing to do with the `PARTITION BY` of `CREATE TABLE` (the files on disk). - It works on one table, a view, or a subquery with an alias. It does **not** work with joins. - It also works on relational tables that have a `TIMESTAMP` column (for example the last login of every user). - When two rows of a group have the same newest time, KarelaDB keeps the one it reads last. - On a table split by symbol (`PARTITION BY ..., SYMBOL(symbol)`, see [partitions](partitions-and-dedup.md#partition-by-symbol)), `LATEST ON time PARTITION BY symbol` reads only the last rows of each symbol's newest file, on all threads, not the whole table. With `PARTITION BY YEAR` or `MONTH` that holds without a `WHERE` or with only `symbol = ...` / `symbol IN (...)`; other conditions read the files in full. - `WHERE` runs **before** `LATEST ON` (see [the next part](latest-on.md#where-first)). - Aggregates, window functions, `ORDER BY` and `LIMIT` run **after** it, on the kept rows only. - The column names must be plain names: `PARTITION BY upper(symbol)` is not allowed. - QuestDB's older `LATEST BY symbol` works too: it is `LATEST ON` the table's own time column `PARTITION BY symbol`. It needs a time-series table (one with `TIMESERIES(...)`), and it may stand before or after `WHERE`; the `WHERE` still picks the rows first. - KarelaDB keeps one row per group while it reads. More groups than `--max-groups` (5,000,000 by default) stop the query with `the query makes more than N groups (max_groups)`; see [protection](protection.md). **Example: The newest daily candle of every symbol** (sample data: sample) ```sql SELECT time, symbol, close, volume FROM ochl_day LATEST ON time PARTITION BY symbol; ``` Result: | time (TIMESTAMP) | symbol (SYMBOL) | close (DOUBLE) | volume (DOUBLE) | |---|---|---|---| | 2026-09-10T00:00:00.000Z | CITY | 504.9 | 183040 | | 2026-09-10T00:00:00.000Z | COAST | 376.1 | 186200 | | 2026-09-10T00:00:00.000Z | VOLT | 191.2 | 41470 | (3 rows) **Example: Two columns: the last trade of each broker in each symbol** (sample data: sample) ```sql SELECT buyer, symbol, date, transaction_id, quantity, rate FROM trade_log LATEST ON date PARTITION BY buyer, symbol ORDER BY buyer, symbol LIMIT 6; ``` Result: | buyer (INT) | symbol (SYMBOL) | date (TIMESTAMP) | transaction_id (LONG) | quantity (LONG) | rate (DOUBLE) | |---|---|---|---|---|---| | 1 | CITY | 2026-09-10T00:00:00.000Z | 100024 | 100 | 514.5 | | 1 | COAST | 2026-09-10T00:00:00.000Z | 100026 | 200 | 400 | | 1 | TELCO | 2026-09-10T00:00:00.000Z | 100021 | 50 | 878 | | 1 | VOLT | 2026-09-10T00:00:00.000Z | 100020 | 100 | 187 | | 34 | CITY | 2026-09-09T00:00:00.000Z | 100004 | 500 | 514.5 | | 34 | COAST | 2026-09-10T00:00:00.000Z | 100009 | 50 | 400 | (6 rows) **Example: App users: the last person who logged in from each city** (sample data: query-app) ```sql SELECT city, username, last_login FROM app_users LATEST ON last_login PARTITION BY city ORDER BY last_login DESC; ``` Result: | city (STRING) | username (STRING) | last_login (TIMESTAMP) | |---|---|---| | Lalitpur | bikash | 2026-09-11T04:20:00.000Z | | Dharan | gita | 2026-09-11T00:45:00.000Z | | Kathmandu | ram | 2026-09-10T14:30:00.000Z | | Pokhara | suman | 2026-09-10T03:55:00.000Z | | NULL | anita | 2026-08-30T06:15:00.000Z | (5 rows) **Example: News: the newest article of each company** (sample data: content-news) ```sql SELECT symbol, published_at, title FROM news LATEST ON published_at PARTITION BY symbol ORDER BY published_at DESC; ``` Result: | symbol (STRING) | published_at (TIMESTAMP) | title (STRING) | |---|---|---| | COAST | 2026-09-02T03:15:00.000Z | Coastal Bank merger talks | | NULL | 2026-09-01T02:15:00.000Z | Market index closes at 2,840 | | VOLT | 2026-08-15T04:15:00.000Z | Hydropower company plans IPO | | CITY | 2026-08-01T05:45:00.000Z | सिटी बैंकको नाफा बढ्यो | (4 rows) **Example: Accounting: the last journal line of each account** (sample data: query-app) ```sql SELECT account, entry_date, debit, credit, memo FROM ledger LATEST ON entry_date PARTITION BY account ORDER BY account; ``` Result: | account (INT) | entry_date (TIMESTAMP) | debit (DECIMAL(2)) | credit (DECIMAL(2)) | memo (STRING) | |---|---|---|---|---| | 1000 | 2026-09-05T00:00:00.000Z | NULL | 25000.00 | September rent | | 1100 | 2026-09-10T00:00:00.000Z | 12600.00 | NULL | Sell 20 CITY at 630 | | 1200 | 2026-09-10T00:00:00.000Z | NULL | 10300.00 | Sell 20 CITY at 630 | | 2000 | 2026-09-03T00:00:00.000Z | 257500.00 | NULL | Pay the broker | | 3000 | 2026-09-01T00:00:00.000Z | NULL | 500000.00 | Owner puts money in the bank | | 4000 | 2026-09-08T00:00:00.000Z | NULL | 6250.00 | CITY cash dividend | | 4100 | 2026-09-10T00:00:00.000Z | NULL | 2300.00 | Sell 20 CITY at 630 | | 5000 | 2026-09-03T00:00:00.000Z | 1030.00 | NULL | Commission on the CITY buy | | 5100 | 2026-09-05T00:00:00.000Z | 25000.00 | NULL | September rent | (9 rows) ## WHERE with LATEST ON Syntax: ```sql SELECT ... FROM table WHERE condition LATEST ON time_column PARTITION BY column, ... ``` `WHERE` removes rows first. `LATEST ON` then picks the newest of the rows that are left. So `WHERE` changes **which** row is the newest. - **Notes:** - "The last price before 13:30 Nepal time" is `WHERE timestamp < '...13:30:00+05:45'`, then `LATEST ON`. - "The last price today" is `WHERE timestamp IN '2026-09-10'`: a symbol with no row today is not in the result at all. - To filter the **kept** rows (for example "only symbols that are up now"), put the LATEST ON query in a subquery and write the condition outside. A condition in `WHERE` would pick an older row instead. - A time condition in `WHERE` also makes the query fast: KarelaDB reads only the partitions of that time. **Example: The last price of each symbol before 13:30 Nepal time** (sample data: sample) ```sql SELECT symbol, ltp, timestamp FROM live_data WHERE timestamp < '2026-09-10T13:30:00+05:45' LATEST ON timestamp PARTITION BY symbol; ``` Result: | symbol (SYMBOL) | ltp (DOUBLE) | timestamp (TIMESTAMP) | |---|---|---| | CITY | 515.6 | 2026-09-10T07:15:00.000Z | | COAST | 391.4 | 2026-09-10T07:15:00.000Z | | TELCO | 869 | 2026-09-10T07:15:00.000Z | | VOLT | 185 | 2026-09-10T07:15:00.000Z | (4 rows) **Example: The same text as the 006 app (live_movers.go)** (sample data: sample) ```sql SELECT symbol, ltp, point_change, pct_change, volume FROM live_data WHERE symbol IN ('CITY', 'COAST') AND timestamp >= '2026-09-10T00:00:00.000000Z' LATEST ON timestamp PARTITION BY symbol; ``` Result: | symbol (SYMBOL) | ltp (DOUBLE) | point_change (DOUBLE) | pct_change (DOUBLE) | volume (LONG) | |---|---|---|---|---| | CITY | 527.1 | 13.9 | 2.71 | 75490 | | COAST | 401.9 | 2.8 | 0.7 | 35210 | (2 rows) **Example: Wrong and right: symbols whose LAST change is below zero** (sample data: sample) ```sql -- Wrong: WHERE runs first, so you get the last FALLING row of each symbol (old rows). SELECT symbol, pct_change, timestamp FROM live_data WHERE pct_change < 0 LATEST ON timestamp PARTITION BY symbol; -- Right: take the newest row first, then filter. SELECT symbol, pct_change, timestamp FROM (SELECT symbol, pct_change, timestamp FROM live_data LATEST ON timestamp PARTITION BY symbol) t WHERE pct_change < 0; ``` Result: `SELECT symbol, pct_change, timestamp FROM live_data WHERE pct_change < 0 LATEST ON timestamp PARTITI` | symbol (SYMBOL) | pct_change (DOUBLE) | timestamp (TIMESTAMP) | |---|---|---| | CITY | -0.04 | 2026-09-10T05:20:00.000Z | | COAST | -1.93 | 2026-09-10T07:15:00.000Z | | TELCO | -1.36 | 2026-09-10T07:15:00.000Z | | VOLT | -0.86 | 2026-09-10T09:14:00.000Z | (4 rows) `SELECT symbol, pct_change, timestamp FROM (SELECT symbol, pct_change, timestamp FROM live_data LATES` | symbol (SYMBOL) | pct_change (DOUBLE) | timestamp (TIMESTAMP) | |---|---|---| | VOLT | -0.86 | 2026-09-10T09:14:00.000Z | (1 row) **Example: The last close of 2025 (the year before)** (sample data: sample) ```sql SELECT time, symbol, close FROM ochl_day WHERE time IN '2025' LATEST ON time PARTITION BY symbol; ``` Result: | time (TIMESTAMP) | symbol (SYMBOL) | close (DOUBLE) | |---|---|---| | 2025-12-31T00:00:00.000Z | CITY | 542.4 | | 2025-12-31T00:00:00.000Z | COAST | 426.5 | | 2025-12-31T00:00:00.000Z | VOLT | 194.3 | (3 rows) ## LATEST ON with other clauses Syntax: ```sql SELECT ... FROM table [WHERE ...] LATEST ON time_column PARTITION BY column, ... [ORDER BY ...] [LIMIT n] ``` The other parts of the query see only the kept rows. - **Notes:** - `ORDER BY ... LIMIT n` after LATEST ON gives a "top n" list of the newest rows. - `count(*)` after LATEST ON counts the kept rows, so it is `1` per group. - A view or a subquery can be the source: `FROM (SELECT ...) t LATEST ON ...`. **Example: Top 2 gainers right now** (sample data: sample) ```sql SELECT symbol, ltp, pct_change FROM live_data LATEST ON timestamp PARTITION BY symbol ORDER BY pct_change DESC LIMIT 2; ``` Result: | symbol (SYMBOL) | ltp (DOUBLE) | pct_change (DOUBLE) | |---|---|---| | CITY | 527.1 | 2.71 | | TELCO | 897.7 | 1.9 | (2 rows) **Example: Up or down, with CASE** (sample data: sample) ```sql SELECT symbol, pct_change, CASE WHEN pct_change > 0 THEN 'up' WHEN pct_change < 0 THEN 'down' ELSE 'flat' END AS direction FROM live_data LATEST ON timestamp PARTITION BY symbol ORDER BY symbol; ``` Result: | symbol (SYMBOL) | pct_change (DOUBLE) | direction (STRING) | |---|---|---| | CITY | 2.71 | up | | COAST | 0.7 | up | | TELCO | 1.9 | up | | VOLT | -0.86 | down | (4 rows) **Example: Aggregates run after LATEST ON: one row per symbol is left** (sample data: sample) ```sql SELECT symbol, count(*) AS rows_left FROM live_data LATEST ON timestamp PARTITION BY symbol; ``` Result: | symbol (SYMBOL) | rows_left (LONG) | |---|---| | CITY | 1 | | COAST | 1 | | TELCO | 1 | | VOLT | 1 | (4 rows) ## Errors Syntax: ```sql LATEST ON time_column PARTITION BY column, ... ``` The common mistakes and their messages. | Mistake | Message | |---|---| | `LATEST ON timestamp` without `PARTITION BY` | `expected PARTITION BY after LATEST ON timestamp` | | the column after `LATEST ON` is not a time | `LATEST ON needs a TIMESTAMP or DATE column` | | an unknown column | `LATEST ON: unknown column '...'` | | a query with a `JOIN` | `LATEST ON works on a single table (no joins)` | | `LATEST BY` on a table without a time column | `LATEST BY needs a table with a time column` | - **Notes:** to use LATEST ON rows in a join, put the LATEST ON query in a subquery and join the subquery. **Example: PARTITION BY is needed** (sample data: sample; shows an error) ```sql SELECT symbol, ltp FROM live_data LATEST ON timestamp; ``` Result: Error: `line 1, column 54: expected PARTITION BY after LATEST ON timestamp, as in LATEST ON timestamp PARTITION BY symbol, but found ';'` **Example: The column must be a time** (sample data: sample; shows an error) ```sql SELECT symbol, ltp FROM live_data LATEST ON symbol PARTITION BY symbol; ``` Result: Error: `LATEST ON needs a TIMESTAMP or DATE column, 'symbol' is SYMBOL` **Example: No joins** (sample data: sample; shows an error) ```sql SELECT l.symbol, s.name, l.ltp FROM live_data l JOIN securities s ON s.symbol = l.symbol LATEST ON timestamp PARTITION BY symbol; ``` Result: Error: `LATEST ON works on a single table (no joins)` **Example: The fix: LATEST ON in a subquery, then the join** (sample data: sample) ```sql SELECT l.symbol, s.name, l.ltp, l.pct_change FROM (SELECT symbol, ltp, pct_change FROM live_data LATEST ON timestamp PARTITION BY symbol) l JOIN securities s ON s.symbol = l.symbol ORDER BY l.pct_change DESC; ``` Result: | symbol (SYMBOL) | name (STRING) | ltp (DOUBLE) | pct_change (DOUBLE) | |---|---|---|---| | CITY | City Bank Limited | 527.1 | 2.71 | | TELCO | Northwind Telecom Limited | 897.7 | 1.9 | | COAST | Coastal Bank Limited | 401.9 | 0.7 | | VOLT | Volt River Hydropower Limited | 184 | -0.86 | (4 rows) **Example: QuestDB's LATEST BY: the same as LATEST ON the time column** (sample data: sample) ```sql SELECT symbol, ltp FROM live_data LATEST BY symbol ORDER BY symbol; SELECT symbol, ltp FROM live_data LATEST ON timestamp PARTITION BY symbol ORDER BY symbol; ``` Result: `SELECT symbol, ltp FROM live_data LATEST BY symbol ORDER BY symbol` | symbol (SYMBOL) | ltp (DOUBLE) | |---|---| | CITY | 527.1 | | COAST | 401.9 | | TELCO | 897.7 | | VOLT | 184 | (4 rows) `SELECT symbol, ltp FROM live_data LATEST ON timestamp PARTITION BY symbol ORDER BY symbol` | symbol (SYMBOL) | ltp (DOUBLE) | |---|---| | CITY | 527.1 | | COAST | 401.9 | | TELCO | 897.7 | | VOLT | 184 | (4 rows) **Example: LATEST BY needs a time-series table** (sample data: sample; shows an error) ```sql SELECT id FROM brokers LATEST BY id; ``` Result: Error: `LATEST BY needs a table with a time column (a TIMESERIES table); for other tables write LATEST ON