Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Query data

: opens the command line in the footer: a row number, or a query in SQL or q over the table. The prefix says what Enter will do: row: while the line is digits alone, else sql: or q:. Ctrl+T switches between SQL and q, keeping what is typed, and the line opens in that language next time. With a query in effect the line opens on its text, in its language, selected: typing replaces it, the arrows edit it.

PrefixWhat you typeExample, on NYC flights (2013)
row:A row number1200
sql:SQL over the table named dfSELECT carrier, COUNT(*) AS flights FROM df GROUP BY carrier ORDER BY flights DESC
q:Datui’s short language, a subset of q, described belowselect flights: count flight by carrier
KeyAction
EnterGo to the row, or run the query; an empty query returns to the full table
TabComplete the column name being typed (in SQL, df too)
Ctrl+TSQL or q
EscCancel
↑ ↓The language’s history

To keep the rows that hold some text, find it with / and press Ctrl+G.

Running a query, or clearing one, starts a fresh view: sidebar filters, sort, frozen columns and pivot/melt are dropped. Apply them after the query. A SQL ORDER BY on columns marks their headers ▲ or ▼, as a sort does, until a sort from the sidebar replaces it. The command line stays open until the query’s first rows are in. A query that fails on the data is not applied: the reason shows under it, the table keeps what it showed, and the query stays to fix.

To start in q, set query.default_mode:

[query]
default_mode = "q"

A build without the sql feature has q alone.

Run a query

Open NYC flights (2013) from Example datasets on the home screen: 336,776 departures from JFK, LaGuardia and Newark, delays in minutes. When does a JFK departure leave late? At sql::

SELECT hour, AVG(dep_delay) AS mean_delay, COUNT(dep_delay) AS flights
FROM df
WHERE origin = 'JFK'
GROUP BY hour
ORDER BY hour

Statements are split over lines here to read; type them on one line, or press Alt+Enter for a new line. 19 rows, one per scheduled hour. The mean delay climbs from 0.5 minutes at 5:00 to 26.1 at 21:00. In q the same query is one line:

select mean_delay: avg dep_delay, flights: count dep_delay by hour where origin = "JFK"

Which airlines arrive late?

SELECT carrier, AVG(arr_delay) AS delay, COUNT(*) AS flights
FROM df
GROUP BY carrier
ORDER BY delay DESC

F9 is last at +21.9 minutes; AS, at −9.9, arrives early on average. Press Enter on AS to see its 714 flights: every one is Newark to Seattle. Esc comes back. See Drill down a GROUP BY.

The carrier ranking: F9 first at 21.92 minutes late on average, AS last at −9.93, with the cursor on AS

Which airlines arrive late? F9, by 21.9 minutes on average; AS arrives early. :, the query, Enter, then G to AS.

AS drilled down: Group: carrier=AS, 714 flights, origin EWR and dest SEA on every row

Where does AS fly? Enter on AS: its 714 flights, Newark (EWR) to Seattle (SEA). Esc goes back.

SQL

SQL mode runs the SQL that the bundled version of Polars supports, not the full SQL standard. A statement reads one table, df:

df isWhile
The group’s rowsDrilled down into a group
The pivot or melt resultA pivot or melt is in effect
The data as loadedOtherwise

Sidebar filters and sort are not part of df, and neither is the previous statement’s result: each statement starts from df again.

A statement’s rows come back in one order on every read: joins, unions, DISTINCT and groupings keep the order of the rows they read (a grouping that drills down, with no ORDER BY or LIMIT, is sorted by its keys instead), and rows that an ORDER BY ranks equal keep the order they come in. Paging through the result never repeats a row or skips one, and a LIMIT without ORDER BY keeps the same rows.

Write SQL

KeyIn SQL
TabComplete the column name, or df, being typed. Press again for the next match
Alt+EnterStart a new line
EnterRun the statement
↑ ↓Move between lines; past the first or last, walk the history
  • The columns of df are listed under the input in their types’ colors, narrowed to the name being typed. Names with spaces complete in double quotes: "Team 1"; in q, as col["Team 1"].
  • A long statement wraps onto a second line, then scrolls within the two.
  • An empty input shows an example to start from: SELECT * FROM df WHERE ....

When a statement fails on a value that will not convert, the reason names the column, the values that failed and SQL that gets past them:

FailureTry
STRPTIME meets text that does not match the formatTrim it first: STRPTIME(SUBSTR(Date, 1, 15), '%a %b %d %Y'), or REPLACE
CAST meets text that is not a numberTRY_CAST(col AS INT), which reads it as null
CAST(col AS DATE) meets a date not written YYYY-MM-DDSTRPTIME(col, '%d/%m/%Y') with the format it is written in

The count is exact when the whole column was checked before the run stopped. Otherwise it says “At least N”, from the rows read so far.

Dates and messy text

Premier League (2020-21) writes dates as Sat Sep 12 2020, and twelve postponed matches as Tue Jan 12 2021(P). Scores are text such as 4–3, with an en dash. SQL takes both apart:

SELECT Round,
       CAST(STRPTIME(SUBSTR(Date, 1, 15), '%a %b %d %Y') AS DATE) AS match_date,
       "Team 1" AS home, "Team 2" AS away,
       CAST(SPLIT_PART(FT, '–', 1) AS INT) + CAST(SPLIT_PART(FT, '–', 2) AS INT) AS goals
FROM df
ORDER BY goals DESC, match_date, home

SUBSTR drops the (P). Two matches had nine goals: Aston Villa 7–2 Liverpool on 2020-10-04 and Manchester Utd 9–0 Southampton on 2021-02-02.

Premier League 2020-21 matches by goals: Aston Villa against Liverpool on 2020-10-04 and Manchester Utd against Southampton on 2021-02-02 lead with 9

Which matches had the most goals? :, the query, Enter: match_date is a date and goals a number, two nine-goal matches first.

NYC flights has a time_hour timestamp, but it is in UTC, so a late-evening departure lands on the next day. The local date is in year, month and day:

SELECT DATE(CONCAT_WS('-', year, month, day)) AS flight_date,
       COUNT(*) AS flights, AVG(dep_delay) AS delay
FROM df
GROUP BY flight_date
ORDER BY flight_date

365 rows, one per day of 2013. Chart flight_date against delay as a line and 2013-03-08 stands out at 83.5 minutes.

A date or datetime past the calendar’s range, such as a sentinel of i64::MIN + 1 microseconds, has no calendar text:

In a queryA date past the calendar
.str, .format, like, .part, .slice, .replace, .strip, ^ with textIts stored number, as the table shows it: -9223372036854775807 us since 1970-01-01 UTC
SQL CAST(... AS VARCHAR), ||, CONCAT, STRFTIME; COALESCE, CASE or UNION with textIts stored number
.date, .time, .year, .month_start and the other date parts; SQL date functions and INTERVAL arithmeticNull
SQL CAST(... AS TIMESTAMP); ^, COALESCE, CASE, GREATEST or LEAST with a datetimeNull when the datetime cannot count it

A nanosecond datetime only spans 1677-09-21 to 2262-04-11. Near those ends, such as pandas’ Timestamp.max:

In a queryNear the ends of the nanosecond range
.month_startNull within a month of 1677-09-21
.month_endNull within a month of either end
SQL INTERVAL arithmeticNull when the interval could carry it past an end
With a time zone: .date, .time, .doy and the rows aboveNull within a day of either end
A date cast to a nanosecond datetime or met with one, as aboveNull before 1677-09-22 or after 2262-04-11

Drill down a GROUP BY

Press Enter on a row of a GROUP BY result to see the rows behind it: the rows of df that passed the WHERE and share the row’s keys, with every column of df, a key that is a column first. A null key shows the rows whose key is null. Esc comes back to the grouped rows, cursor and frozen columns as they were.

On NYC yellow taxis (January 2025), 3.5 million trips, group by a computed key:

SELECT EXTRACT(HOUR FROM tpep_pickup_datetime) AS pickup_hour,
       COUNT(*) AS trips, AVG(tip_amount) AS avg_tip, AVG(fare_amount) AS avg_fare
FROM df
GROUP BY pickup_hour
ORDER BY pickup_hour

24 rows: 18:00 is the busiest hour with 267,951 trips, 4:00 the quietest with 20,033. Enter on hour 4 shows those 20,033 trips.

DrillsDoes not drill
SELECT keys, aggregates FROM df [WHERE] GROUP BY keys, then any HAVING, ORDER BY, LIMITJoins, subqueries, WITH, UNION
A key named by its column, its select alias, its position (GROUP BY 1) or the same expression as selectedWindow functions (OVER), DISTINCT ON, GROUP BY ALL, UNNEST
Computed keys: EXTRACT(HOUR FROM ts) AS hA key that is not selected, or written differently in SELECT

Where a statement does not drill, Enter inspects the row instead. Where it drills, the footer says Enter Drill. A result that drills has the keys that lead it frozen, as a q by does. Without ORDER BY or LIMIT it comes back sorted by its keys, since Polars returns groups in no fixed order.

q

q is a subset of the q language, not a complete q or q-sql, and it evaluates right to left. Use it where it is shorter than the SQL:

DatasetqSQL
Palmer penguinsselect mean_mass_g: avg body_mass_g by speciesSELECT species, AVG(body_mass_g) AS mean_mass_g FROM df GROUP BY species
NYC flights (2013)select mean_delay: (avg dep_delay).round[1] by hourSELECT hour, ROUND(AVG(dep_delay), 1) AS mean_delay FROM df GROUP BY hour
NYC yellow taxisselect trips: count VendorID by tpep_pickup_datetime.hourSELECT EXTRACT(HOUR FROM tpep_pickup_datetime) AS hour, COUNT(VendorID) AS trips FROM df GROUP BY hour
US baby namesselect total: sum n by name where name in ["Emma", "Jennifer", "Olivia"]SELECT name, SUM(n) AS total FROM df WHERE name IN ('Emma', 'Jennifer', 'Olivia') GROUP BY name

from df is optional and accepted after the columns and by, as in q and like SQL’s FROM df: select mean_delay: avg dep_delay by hour from df where origin = "JFK".

Right to left means a * b + c is a * (b + c), and (a + b) * 2 > 100 is (a + b) * (2 > 100): put the comparison first, 100 < (a + b) * 2. Query syntax has the grammar, every accessor and more examples on the built-in datasets.

A by clause without aggregates gives one row per group; with aggregates, one summary row per group. Press Enter on a group to drill down to its rows; a row above the table names the group, and Esc comes back. A group without aggregates shows the columns you selected; an aggregated one shows every column of the rows behind it, after the query’s where, key columns first. The cursor, frozen columns and column order come back with Esc.

Save a query

A view saves the active query, in its language, with the filters and sort, to replay on the next file of the same shape.