KDB knowledge

Joins

Put rows of two or more tables side by side: JOIN, LEFT / RIGHT / FULL JOIN, USING, CROSS JOIN, and ASOF / LT / SPLICE JOIN for time series.

A join puts rows of two tables side by side. For example, a trade in trade_log has only the broker number (buyer = 58); a join with brokers adds the broker's name.

ON says which rows belong together. Give each table a short name (an alias) and write alias.column for the columns.

JoinRows you get
JOIN (or INNER JOIN)only the rows that have a match on both sides
LEFT JOIN (or LEFT OUTER JOIN)every row of the left table; NULL columns when the right side has no match
RIGHT JOIN (or RIGHT OUTER JOIN)every row of the right table; NULL columns when the left side has no match
FULL JOIN (or FULL OUTER JOIN)every row of both tables, side by side where they match
CROSS JOINevery left row with every right row
ASOF JOINfor each left row, the last right row at or before its time
LT JOINfor each left row, the last right row strictly before its time
SPLICE JOINevery row of both time-series tables in time order, each with the other table's last row so far

Example: The 5 biggest trades of the day, with the buyer's and the seller's names (sample data: sample)

SELECT f.transaction_id, f.symbol, b.name AS buyer, s.name AS seller, f.quantity, f.rate, f.amount
FROM trade_log f
JOIN brokers b ON f.buyer = b.id
JOIN brokers s ON f.seller = s.id
WHERE f.date IN '2026-09-10'
ORDER BY f.amount DESC
LIMIT 5;

Result:

transaction_id (LONG)symbol (SYMBOL)buyer (STRING)seller (STRING)quantity (LONG)rate (DOUBLE)amount (DOUBLE)
100017TELCOOnline SecuritiesVision Securities500881440500
100031COASTImperial SecuritiesNorthgate Securities1000402402000
100015COASTOnline SecuritiesImperial Securities1000398398000
100018CITYImperial SecuritiesKeystone Securities500516258000
100023COASTNorthgate SecuritiesKeystone Securities500399199500

(5 rows)

JOIN, INNER JOIN

Syntax:

FROM left_table [[AS] alias]
[INNER] JOIN right_table [[AS] alias] { ON condition | USING (column, ...) }
[[INNER] JOIN ... ON ...] ...

For each row of the left side, KDB finds the rows of the right table where the ON condition is true, and gives one output row for each match. A row with no match is left out.

PartMeaning
right_tablea table, a view, (SELECT ...) alias, or a table of another database: otherdb.table (see queries across databases)
ON conditionwhich rows match; usually a.key = b.key, and more conditions with AND

Example: Security names with the close price of the day (sample data: sample)

SELECT s.symbol, s.name, s.sector, d.close
FROM ochl_day d
JOIN securities s ON s.symbol = d.symbol
WHERE d.time IN '2026-09-10'
ORDER BY s.symbol;

Result:

symbol (SYMBOL)name (STRING)sector (STRING)close (DOUBLE)
CITYCity Bank LimitedBanking504.9
COASTCoastal Bank LimitedBanking376.1
VOLTVolt River Hydropower LimitedEnergy191.2

(3 rows)

Example: Turnover by sector (sample data: sample)

SELECT s.sector, count(*) AS trades, sum(f.quantity) AS shares, sum(f.amount) AS turnover
FROM trade_log f
JOIN securities s ON f.symbol = s.symbol
WHERE f.date IN '2026-09-10'
GROUP BY s.sector
ORDER BY turnover DESC;

Result:

sector (STRING)trades (LONG)shares (LONG)turnover (DOUBLE)
Banking1637601609170
Telecom4770678260
Energy440073950

(3 rows)

Example: Accounting: totals per account type (a small trial balance) (sample data: query-app)

SELECT a.type, sum(l.debit) AS debit, sum(l.credit) AS credit
FROM ledger l
JOIN accounts a ON a.code = l.account
GROUP BY a.type
ORDER BY a.type;

Result:

type (STRING)debit (DECIMAL(2))credit (DECIMAL(2))
asset806350.00323830.00
equityNULL500000.00
expense26030.00NULL
incomeNULL8550.00
liability257500.00257500.00

(5 rows)

Example: Forum posts with the author's app plan (sample data: query-app, content-forum)

SELECT p.id, p.author, u.plan, u.city, p.body
FROM posts p
INNER JOIN app_users u ON u.username = p.author
WHERE p.thread_id = 1
ORDER BY p.id;

Result:

id (LONG)author (STRING)plan (STRING)city (STRING)body (STRING)
1ramproKathmanduCITY dividend looks good this year.
2sitafreePokharaI agree, the profit grew 18%.
4gitaproDharanWhat about the bonus shares?
5ramproKathmanduBonus is 10% this time.
6sitafreePokharaसिटी बैंकको नाफा राम्रो छ।

(5 rows)

Example: ON is required (sample data: sample; shows an error)

SELECT * FROM trade_log f JOIN brokers b;

Result:

Error: line 1, column 41: expected ON or USING (columns) and a join condition after the joined table but found ';'

Join speed: key lookup or hash

KDB picks one of two ways to find the rows of a joined table (JOIN and LEFT JOIN):

WayWhenCost
key lookupThe a = b parts of ON give the first columns of the joined table's key: its PRIMARY KEY, or for a time-series table its SORT BY / DEDUP KEY columns including the time column. Or they give the first column of an index of the joined table.Each left row reads only its own rows. A login row joined to its user among 100,000 users reads one user.
hashAny other ON, or a left side with many rows (more than about a quarter of the joined table)The joined table is read once (only the columns the query uses), then every left row is matched in it. Its rows are not copied: the hash points at them where they already are in memory, and a row is built only when a left row matches it.

Example: Each trade looks up its buyer by the brokers' PRIMARY KEY (sample data: sample)

EXPLAIN SELECT f.transaction_id, b.name
FROM trade_log f
JOIN brokers b ON b.id = f.buyer
WHERE f.date IN '2026-09-10';

Result:

plan (STRING)
scan table trade_log (1 of 2 partitions, about 32 rows)
join (key lookup on id) table brokers (1 of 1 partitions, about 5 rows)
build 3 of 7 stored column(s) per row: date, transaction_id, buyer
filter while scanning: 1 condition(s)
engine: row (a join)

(5 rows)

GROUP BY over a join: the column engine

Since 2026-10-02 a GROUP BY (or a plain count(*), sum, ...) over two tables joined on one pair of columns runs on the column engine: the joined table is read once into a hash, the first table is read column by column on all threads, and no joined row is ever made. On 400,000 rows joined to 40,000 that is about 10 times faster than the row engine.

It is used when all of these hold:

WhatAllowed
Tablestwo stored tables (not a subquery or view), JOIN or LEFT JOIN
ONone a.x = b.y of whole-number columns (BYTE .. LONG) or of text columns (SYMBOL, STRING), plus conditions on the joined table alone
WHEREconditions the column engine does (see WHERE: fast conditions) on either table; with LEFT JOIN, on the first table only
GROUP BYup to 4 plain columns of either table; no SAMPLE BY
Aggregatescount(*), count(col), sum, avg, min, max of plain columns (sum/avg of numbers, min/max also of times and dates)
Elsewherethe SELECT list, HAVING and ORDER BY read only the group columns and the aggregates

LEFT JOIN, LEFT OUTER JOIN

Syntax:

FROM left_table [[AS] alias]
LEFT [OUTER] JOIN right_table [[AS] alias] ON condition

Like JOIN, but every row of the left table stays. When the right table has no matching row, its columns are NULL. OUTER is an optional word.

Example: Every security, also the ones without trades (sample data: sample)

SELECT s.symbol, count(f.transaction_id) AS trades
FROM securities s
LEFT JOIN trade_log f ON f.symbol = s.symbol
GROUP BY s.symbol
ORDER BY trades DESC, s.symbol;

Result:

symbol (SYMBOL)trades (LONG)
CITY11
COAST11
TELCO6
VOLT4
ACME0

(5 rows)

Example: The day's close for every security: the condition on the day is in ON (sample data: sample)

SELECT s.symbol, d.close
FROM securities s
LEFT JOIN ochl_day d ON d.symbol = s.symbol AND d.time IN '2026-09-10'
ORDER BY s.symbol;

Result:

symbol (SYMBOL)close (DOUBLE)
ACMENULL
CITY504.9
COAST376.1
TELCONULL
VOLT191.2

(5 rows)

Example: The same condition in WHERE removes ACME and TELCO (sample data: sample)

SELECT s.symbol, d.close
FROM securities s
LEFT JOIN ochl_day d ON d.symbol = s.symbol
WHERE d.time IN '2026-09-10'
ORDER BY s.symbol;

Result:

symbol (SYMBOL)close (DOUBLE)
CITY504.9
COAST376.1
VOLT191.2

(3 rows)

Example: App users and how many forum posts they wrote (sample data: query-app, content-forum)

SELECT u.username, count(p.id) AS posts, max(p.created_at) AS last_post
FROM app_users u
LEFT OUTER JOIN posts p ON p.author = u.username
GROUP BY u.username
ORDER BY posts DESC, u.username;

Result:

username (STRING)posts (LONG)last_post (TIMESTAMP)
gita22026-09-02T03:25:00.000Z
ram22026-09-01T05:45:00.000Z
sita22026-09-01T06:15:00.000Z
anita0NULL
bikash0NULL
hari_990NULL
suman0NULL
test_user0NULL

(8 rows)

Example: Find what is missing: brokers who sold nothing on 2026-09-09 (sample data: sample)

SELECT b.id, b.name
FROM brokers b
LEFT JOIN trade_log f ON f.seller = b.id AND f.date IN '2026-09-09'
WHERE f.transaction_id IS NULL;

Result:

id (INT)name (STRING)
58Northgate Securities

(1 row)

RIGHT JOIN, RIGHT OUTER JOIN

Syntax:

FROM left_table [[AS] alias]
RIGHT [OUTER] JOIN right_table [[AS] alias] { ON condition | USING (column, ...) }

The mirror of LEFT JOIN: every row of the right table stays. When no left row matches it, the left side's columns are NULL. It is the same as swapping the two tables in a LEFT JOIN, but keeps the column order you wrote.

Example: Every security, also the ones nobody traded on 2026-09-10 (sample data: sample)

SELECT s.symbol, count(f.transaction_id) AS trades
FROM (SELECT * FROM trade_log WHERE date IN '2026-09-10') f
RIGHT JOIN securities s ON f.symbol = s.symbol
GROUP BY s.symbol
ORDER BY s.symbol;

Result:

symbol (SYMBOL)trades (LONG)
ACME0
CITY7
COAST9
TELCO4
VOLT4

(5 rows)

Example: Only the right rows without a match (sample data: sample)

SELECT s.symbol
FROM ochl_minutes m
RIGHT JOIN securities s ON m.symbol = s.symbol
WHERE m.symbol IS NULL
ORDER BY s.symbol;

Result:

symbol (SYMBOL)
ACME
TELCO
VOLT

(3 rows)

FULL JOIN, FULL OUTER JOIN

Syntax:

FROM left_table [[AS] alias]
FULL [OUTER] JOIN right_table [[AS] alias] { ON condition | USING (column, ...) }

Every row of both tables: the pairs that match side by side, then the left rows without a match (right side NULL) and the right rows without a match (left side NULL). Use it to compare two lists.

Example: Symbols with live prices, symbols that are not banks, or both (sample data: sample)

SELECT l.symbol AS live, s.symbol AS not_a_bank
FROM (SELECT DISTINCT symbol FROM live_data) l
FULL JOIN (SELECT symbol FROM securities WHERE sector <> 'Banking') s ON l.symbol = s.symbol
ORDER BY coalesce(l.symbol, s.symbol);

Result:

live (SYMBOL)not_a_bank (SYMBOL)
NULLACME
CITYNULL
COASTNULL
TELCOTELCO
VOLTVOLT

(5 rows)

Example: FULL JOIN after a comma is refused (sample data: sample; shows an error)

SELECT count(*) FROM brokers b, securities s FULL JOIN live_data l ON l.symbol = s.symbol;

Result:

Error: line 1, column 46: FULL JOIN after a comma join is ambiguous: PostgreSQL joins it before the comma, KDB after. Write CROSS JOIN instead of the comma to join left to right, or put the joins after the comma in a subquery: FROM a, (SELECT ... FROM b RIGHT JOIN c ON ...) s

USING

Syntax:

JOIN right_table [[AS] alias] USING (column, ...)

USING (id) is ON left.id = right.id for columns that have the same name in both tables (PostgreSQL). It works with JOIN, LEFT JOIN, RIGHT JOIN and FULL JOIN.

What you writeMeans
SELECT *each USING column once, first, then the other columns of each table
id (no table name)the joined id: the left one for JOIN and LEFT JOIN, the right one for RIGHT JOIN, coalesce(left.id, right.id) for FULL JOIN
a.id, b.ideach table's own column, as always; a.* still shows all of a

Example: The day's candle for each security, joined on the column both tables have (sample data: sample)

SELECT * FROM securities JOIN (SELECT symbol, close FROM ochl_day WHERE time IN '2026-09-10') d USING (symbol)
ORDER BY symbol;

Result:

symbol (SYMBOL)name (STRING)sector (STRING)close (DOUBLE)
CITYCity Bank LimitedBanking504.9
COASTCoastal Bank LimitedBanking376.1
VOLTVolt River Hydropower LimitedEnergy191.2

(3 rows)

Example: A column that is not in both tables (sample data: sample; shows an error)

SELECT * FROM securities s JOIN brokers b USING (symbol);

Result:

Error: line 1, column 28: column 'symbol' in USING is not in the right table 'b'

CROSS JOIN

Syntax:

FROM left_table [[AS] alias]
CROSS JOIN right_table [[AS] alias]

Every row of the left side with every row of the right table. There is no ON.

Example: How many pairs (sample data: sample)

SELECT count(*) AS pairs FROM brokers b CROSS JOIN securities s;

Result:

pairs (LONG)
25

(1 row)

Example: Broker buy and sell summary of the day (sample data: sample)

SELECT b.id, b.name,
       sum(CASE WHEN f.buyer  = b.id THEN f.quantity ELSE 0 END) AS bought,
       sum(CASE WHEN f.seller = b.id THEN f.quantity ELSE 0 END) AS sold
FROM brokers b
CROSS JOIN trade_log f
WHERE f.date IN '2026-09-10'
GROUP BY b.id, b.name
ORDER BY bought DESC;

Result:

id (INT)name (STRING)bought (LONG)sold (LONG)
49Online Securities1740340
45Imperial Securities15601520
58Northgate Securities7601270
1Keystone Securities5701100
34Vision Securities300700

(5 rows)

Example: CROSS JOIN has no ON (sample data: sample; shows an error)

SELECT b.name FROM brokers b CROSS JOIN securities s ON s.symbol = 'TELCO';

Result:

Error: line 1, column 54: expected JOIN, LEFT JOIN, RIGHT JOIN, FULL JOIN, ASOF JOIN, LT JOIN, SPLICE JOIN, CROSS JOIN, WHERE, LATEST ON, SAMPLE BY, GROUP BY, UNION ALL, ORDER BY, LIMIT, ';' or the end of the input but found 'ON'

ASOF JOIN

Syntax:

FROM left_table [[AS] alias]
ASOF JOIN right_table [[AS] alias] [ON condition]

A join by time. For each row of the left table, KDB takes the last row of the right table whose time is at or before the left row's time. With ON, the right row must also match the ON condition (usually the same symbol).

This answers questions like "what was the last candle when this live price came in?" or "what was the price when this trade happened?".

PartMeaning
left_tablea time-series table (its TIMESERIES column is the time)
right_tablea time-series table, or a subquery whose result has a time column
ON a.symbol = b.symbolmatch only rows of the same symbol; other AND conditions are checked too
ON (symbol)QuestDB's short form of the same: the listed columns are equal in both tables

Example: Live prices with the last minute candle at or before each snapshot (sample data: sample)

SELECT l.timestamp, l.symbol, l.ltp, m.time AS candle_time, m.close AS candle_close
FROM live_data l
ASOF JOIN ochl_minutes m ON l.symbol = m.symbol
WHERE l.symbol IN ('CITY', 'COAST')
ORDER BY l.symbol, l.timestamp;

Result:

timestamp (TIMESTAMP)symbol (SYMBOL)ltp (DOUBLE)candle_time (TIMESTAMP)candle_close (DOUBLE)
2026-09-10T05:20:00.000ZCITY5132026-09-10T05:20:00.000Z504.6
2026-09-10T07:15:00.000ZCITY515.62026-09-10T07:15:00.000Z502.3
2026-09-10T09:14:00.000ZCITY527.12026-09-10T09:14:00.000Z504.9
2026-09-10T05:20:00.000ZCOAST390.62026-09-10T05:20:00.000Z380.9
2026-09-10T07:15:00.000ZCOAST391.42026-09-10T07:15:00.000Z377.8
2026-09-10T09:14:00.000ZCOAST401.92026-09-10T09:14:00.000Z376.1

(6 rows)

Example: No match: there are no minute candles for TELCO, so the right side is NULL (sample data: sample)

SELECT l.timestamp, l.symbol, l.ltp, m.time AS candle_time, m.close
FROM live_data l
ASOF JOIN ochl_minutes m ON l.symbol = m.symbol
WHERE l.symbol = 'TELCO';

Result:

timestamp (TIMESTAMP)symbol (SYMBOL)ltp (DOUBLE)candle_time (TIMESTAMP)close (DOUBLE)
2026-09-10T05:20:00.000ZTELCO860.5NULLNULL
2026-09-10T07:15:00.000ZTELCO869NULLNULL
2026-09-10T09:14:00.000ZTELCO897.7NULLNULL

(3 rows)

Example: Trade rate compared with the day's close (sample data: sample)

SELECT f.transaction_id, f.symbol, f.rate, d.close AS day_close, round(f.rate - d.close, 2) AS above_close
FROM trade_log f
ASOF JOIN ochl_day d ON f.symbol = d.symbol
WHERE f.date IN '2026-09-10' AND f.symbol = 'COAST'
ORDER BY f.transaction_id
LIMIT 4;

Result:

transaction_id (LONG)symbol (SYMBOL)rate (DOUBLE)day_close (DOUBLE)above_close (DOUBLE)
100009COAST400376.123.9
100013COAST398376.121.9
100014COAST399.5376.123.4
100015COAST398376.121.9

(4 rows)

Example: An extra ON condition: the last minute with more than 1400 shares (sample data: sample)

SELECT l.timestamp, l.ltp, m.time AS big_minute, m.volume
FROM live_data l
ASOF JOIN ochl_minutes m ON l.symbol = m.symbol AND m.volume > 1400
WHERE l.symbol = 'CITY';

Result:

timestamp (TIMESTAMP)ltp (DOUBLE)big_minute (TIMESTAMP)volume (DOUBLE)
2026-09-10T05:20:00.000Z5132026-09-10T05:20:00.000Z1420
2026-09-10T07:15:00.000Z515.62026-09-10T07:10:00.000Z1440
2026-09-10T09:14:00.000Z527.12026-09-10T08:53:00.000Z1480

(3 rows)

Example: Both tables need a time column (sample data: sample; shows an error)

SELECT l.symbol, s.name FROM live_data l ASOF JOIN securities s ON l.symbol = s.symbol;

Result:

Error: line 1, column 42: ASOF JOIN needs a time column in both tables (TIMESERIES tables)

LT JOIN

Syntax:

FROM left_table [[AS] alias]
LT JOIN right_table [[AS] alias] [ON condition | ON (column, ...)]

Like ASOF JOIN, but the right row must be strictly earlier than the left row (QuestDB). A right row at the same time is skipped. Use it to pair a row with the one before it, for example each day's candle with the previous day's.

Example: Each day's close with the close of the day before (sample data: sample)

SELECT d.time, d.close, p.time AS prev_day, p.close AS prev_close
FROM ochl_day d
LT JOIN ochl_day p ON (symbol)
WHERE d.symbol = 'CITY' AND d.time >= '2026-09-06'
ORDER BY d.time;

Result:

time (TIMESTAMP)close (DOUBLE)prev_day (TIMESTAMP)prev_close (DOUBLE)
2026-09-06T00:00:00.000Z4992026-09-03T00:00:00.000Z497.9
2026-09-07T00:00:00.000Z498.72026-09-06T00:00:00.000Z499
2026-09-08T00:00:00.000Z497.52026-09-07T00:00:00.000Z498.7
2026-09-09T00:00:00.000Z504.62026-09-08T00:00:00.000Z497.5
2026-09-10T00:00:00.000Z504.92026-09-09T00:00:00.000Z504.6

(5 rows)

SPLICE JOIN

Syntax:

FROM left_table [[AS] alias]
SPLICE JOIN right_table [[AS] alias] [ON a.key = b.key | ON (column, ...)]

A full time join (QuestDB): the rows of both tables are merged in time order. Each row comes out once, with the latest row of the other table so far (of the same key with ON). Rows of the two tables with the same time (and key) come out as one pair.

Example: Live prices and a few minute candles on one time line (05:20 has both: one pair) (sample data: sample)

SELECT l.timestamp, l.ltp, m.time AS candle, m.close
FROM (SELECT * FROM live_data WHERE symbol = 'CITY') l
SPLICE JOIN (SELECT * FROM ochl_minutes
             WHERE symbol = 'CITY' AND time >= '2026-09-10T05:17:00Z' AND time < '2026-09-10T05:23:00Z') m;

Result:

timestamp (TIMESTAMP)ltp (DOUBLE)candle (TIMESTAMP)close (DOUBLE)
NULLNULL2026-09-10T05:17:00.000Z504.2
NULLNULL2026-09-10T05:18:00.000Z504.4
NULLNULL2026-09-10T05:19:00.000Z504.5
2026-09-10T05:20:00.000Z5132026-09-10T05:20:00.000Z504.6
2026-09-10T05:20:00.000Z5132026-09-10T05:21:00.000Z505.1
2026-09-10T05:20:00.000Z5132026-09-10T05:22:00.000Z504.9
2026-09-10T07:15:00.000Z515.62026-09-10T05:22:00.000Z504.9
2026-09-10T09:14:00.000Z527.12026-09-10T05:22:00.000Z504.9

(8 rows)

Table aliases

Syntax:

FROM table [AS] alias
JOIN table [AS] alias ON ...
alias.column
alias.*

An alias is a short name for a table in one query. After you give one, use it for the columns: f.symbol. AS is optional.

Example: One table joined twice, with AS and long aliases: who sold to whom (sample data: sample)

SELECT f.transaction_id, buyer_broker.name AS buyer, seller_broker.name AS seller
FROM trade_log AS f
JOIN brokers AS buyer_broker ON buyer_broker.id = f.buyer
JOIN brokers AS seller_broker ON seller_broker.id = f.seller
WHERE seller_broker.name = 'Keystone Securities'
ORDER BY f.transaction_id
LIMIT 3;

Result:

transaction_id (LONG)buyer (STRING)seller (STRING)
100002Imperial SecuritiesKeystone Securities
100013Northgate SecuritiesKeystone Securities
100016Vision SecuritiesKeystone Securities

(3 rows)

Example: A column in two tables needs the alias (sample data: sample; shows an error)

SELECT symbol, name FROM trade_log f JOIN securities s ON s.symbol = f.symbol;

Result:

Error: line 1, column 8: column 'symbol' is in more than one table; write it as table.column

Joining a subquery

Syntax:

JOIN (SELECT ...) alias ON condition
LEFT JOIN (SELECT ...) alias ON condition
ASOF JOIN (SELECT ...) alias ON condition

The right side of a join can be the result of another query in ( ). Give it an alias, so you can write alias.column. This is how you join a table with totals or with the latest row of something.

Example: Each security with its latest live price (sample data: sample)

SELECT s.symbol, s.name, l.ltp, l.pct_change
FROM securities s
LEFT JOIN (SELECT symbol, ltp, pct_change FROM live_data LATEST ON timestamp PARTITION BY symbol) l
  ON l.symbol = s.symbol
ORDER BY s.symbol;

Result:

symbol (SYMBOL)name (STRING)ltp (DOUBLE)pct_change (DOUBLE)
ACMEAcme Investment CompanyNULLNULL
CITYCity Bank Limited527.12.71
COASTCoastal Bank Limited401.90.7
TELCONorthwind Telecom Limited897.71.9
VOLTVolt River Hydropower Limited184-0.86

(5 rows)

Example: Each trade as a share of the symbol's total quantity of the day (sample data: sample)

SELECT f.transaction_id, f.symbol, f.quantity, t.total,
       round(f.quantity * 100.0 / t.total, 1) AS pct_of_day
FROM trade_log f
JOIN (SELECT symbol, sum(quantity) AS total FROM trade_log WHERE date IN '2026-09-10' GROUP BY symbol) t
  ON t.symbol = f.symbol
WHERE f.date IN '2026-09-10' AND f.symbol = 'CITY'
ORDER BY pct_of_day DESC;

Result:

transaction_id (LONG)symbol (SYMBOL)quantity (LONG)total (LONG)pct_of_day (DOUBLE)
100018CITY50091054.9
100029CITY20091022
100024CITY10091011
100010CITY509105.5
100022CITY209102.2
100027CITY209102.2
100030CITY209102.2

(7 rows)

Example: Account balances with the account names (sample data: query-app)

SELECT a.code, a.name, b.balance
FROM accounts a
JOIN (SELECT account, coalesce(sum(debit), 0) - coalesce(sum(credit), 0) AS balance
      FROM ledger GROUP BY account) b
  ON b.account = a.code
WHERE a.type = 'asset'
ORDER BY a.code;

Result:

code (INT)name (STRING)balance (DECIMAL(2))
1000Cash5000.00
1100Bank - City230320.00
1200Shares at cost247200.00

(3 rows)

Example: Without an alias it still works, but the columns have no name to use (sample data: sample)

SELECT b.id, b.name, count(*) AS trades
FROM brokers b JOIN (SELECT buyer FROM trade_log) ON buyer = b.id
GROUP BY b.id, b.name
ORDER BY b.id;

Result:

id (INT)name (STRING)trades (LONG)
1Keystone Securities9
34Vision Securities5
45Imperial Securities6
49Online Securities6
58Northgate Securities6

(5 rows)