SELECT
In Polars SQL, the SELECT statement is used to retrieve data from a table into a DataFrame. The
basic syntax of a SELECT statement in Polars SQL is as follows:
SELECT column1, column2, ...
FROM table_name;
Here, column1, column2, etc. are the columns that you want to select from the table. You can
also use the wildcard * to select all columns. table_name is the name of the table or that you
want to retrieve data from. In the sections below we will cover some of the more common SELECT
variants
df = pl.DataFrame(
{
"country": ["USA", "USA", "USA", "USA", "USA", "Netherlands"],
"city": [
"New York",
"Los Angeles",
"Chicago",
"Houston",
"Phoenix",
"Amsterdam",
],
"population": [8399000, 3997000, 2705000, 2320000, 1680000, 900000],
}
)
ctx = pl.SQLContext(population=df, eager=True)
print(ctx.execute("SELECT * FROM population"))
shape: (6, 3)
┌─────────────┬─────────────┬────────────┐
│ country ┆ city ┆ population │
│ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 │
╞═════════════╪═════════════╪════════════╡
│ USA ┆ New York ┆ 8399000 │
│ USA ┆ Los Angeles ┆ 3997000 │
│ USA ┆ Chicago ┆ 2705000 │
│ USA ┆ Houston ┆ 2320000 │
│ USA ┆ Phoenix ┆ 1680000 │
│ Netherlands ┆ Amsterdam ┆ 900000 │
└─────────────┴─────────────┴────────────┘
GROUP BY
The GROUP BY statement is used to group rows in a table by one or more columns and compute
aggregate functions on each group.
result = ctx.execute(
"""
SELECT country, AVG(population) as avg_population
FROM population
GROUP BY country
"""
)
print(result)
shape: (2, 2)
┌─────────────┬────────────────┐
│ country ┆ avg_population │
│ --- ┆ --- │
│ str ┆ f64 │
╞═════════════╪════════════════╡
│ USA ┆ 3.8202e6 │
│ Netherlands ┆ 900000.0 │
└─────────────┴────────────────┘
ORDER BY
The ORDER BY statement is used to sort the result set of a query by one or more columns in
ascending or descending order.
result = ctx.execute(
"""
SELECT city, population
FROM population
ORDER BY population
"""
)
print(result)
shape: (6, 2)
┌─────────────┬────────────┐
│ city ┆ population │
│ --- ┆ --- │
│ str ┆ i64 │
╞═════════════╪════════════╡
│ Amsterdam ┆ 900000 │
│ Phoenix ┆ 1680000 │
│ Houston ┆ 2320000 │
│ Chicago ┆ 2705000 │
│ Los Angeles ┆ 3997000 │
│ New York ┆ 8399000 │
└─────────────┴────────────┘
JOIN
income = pl.DataFrame(
{
"country": [
"USA",
"USA",
"USA",
"USA",
"Netherlands",
"Netherlands",
"Netherlands",
],
"city": [
"New York",
"Los Angeles",
"Chicago",
"Houston",
"Amsterdam",
"Rotterdam",
"Utrecht",
],
"income": [55000, 62000, 48000, 52000, 42000, 38000, 41000],
}
)
ctx.register_many(income=income)
result = ctx.execute(
"""
SELECT income.*, population.population
FROM population
LEFT JOIN income ON population.city = income.city
"""
)
print(result)
shape: (6, 4)
┌─────────────┬─────────────┬────────┬────────────┐
│ country ┆ city ┆ income ┆ population │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 ┆ i64 │
╞═════════════╪═════════════╪════════╪════════════╡
│ USA ┆ Los Angeles ┆ 62000 ┆ 3997000 │
│ USA ┆ New York ┆ 55000 ┆ 8399000 │
│ USA ┆ Houston ┆ 52000 ┆ 2320000 │
│ USA ┆ Chicago ┆ 48000 ┆ 2705000 │
│ Netherlands ┆ Amsterdam ┆ 42000 ┆ 900000 │
│ null ┆ null ┆ null ┆ 1680000 │
└─────────────┴─────────────┴────────┴────────────┘
Functions
Polars provides a wide range of SQL functions, including:
- Mathematical functions:
ABS,EXP,LOG,ASIN,ACOS,ATAN, etc. - String functions:
LOWER,UPPER,LTRIM,RTRIM,STARTS_WITH,ENDS_WITH. - Aggregation functions:
SUM,AVG,MIN,MAX,COUNT,STDDEV,FIRSTetc. - Array functions:
EXPLODE,UNNEST,ARRAY_SUM,ARRAY_REVERSE, etc.
For a full list of supported functions go the API documentation. The example below demonstrates how to use a function in a query
result = ctx.execute(
"""
SELECT city, population
FROM population
WHERE STARTS_WITH(country,'U')
"""
)
print(result)
shape: (5, 2)
┌─────────────┬────────────┐
│ city ┆ population │
│ --- ┆ --- │
│ str ┆ i64 │
╞═════════════╪════════════╡
│ New York ┆ 8399000 │
│ Los Angeles ┆ 3997000 │
│ Chicago ┆ 2705000 │
│ Houston ┆ 2320000 │
│ Phoenix ┆ 1680000 │
└─────────────┴────────────┘
Table Functions
In the examples earlier we first generated a DataFrame which we registered in the SQLContext.
Polars also support directly reading from CSV, Parquet, JSON and IPC in your SQL query using table
functions read_xxx.
result = ctx.execute(
"""
SELECT *
FROM read_csv('docs/assets/data/iris.csv')
"""
)
print(result)
shape: (150, 5)
┌──────────────┬─────────────┬──────────────┬─────────────┬───────────┐
│ sepal_length ┆ sepal_width ┆ petal_length ┆ petal_width ┆ species │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 ┆ f64 ┆ str │
╞══════════════╪═════════════╪══════════════╪═════════════╪═══════════╡
│ 5.1 ┆ 3.5 ┆ 1.4 ┆ 0.2 ┆ Setosa │
│ 4.9 ┆ 3.0 ┆ 1.4 ┆ 0.2 ┆ Setosa │
│ 4.7 ┆ 3.2 ┆ 1.3 ┆ 0.2 ┆ Setosa │
│ 4.6 ┆ 3.1 ┆ 1.5 ┆ 0.2 ┆ Setosa │
│ 5.0 ┆ 3.6 ┆ 1.4 ┆ 0.2 ┆ Setosa │
│ … ┆ … ┆ … ┆ … ┆ … │
│ 6.7 ┆ 3.0 ┆ 5.2 ┆ 2.3 ┆ Virginica │
│ 6.3 ┆ 2.5 ┆ 5.0 ┆ 1.9 ┆ Virginica │
│ 6.5 ┆ 3.0 ┆ 5.2 ┆ 2.0 ┆ Virginica │
│ 6.2 ┆ 3.4 ┆ 5.4 ┆ 2.3 ┆ Virginica │
│ 5.9 ┆ 3.0 ┆ 5.1 ┆ 1.8 ┆ Virginica │
└──────────────┴─────────────┴──────────────┴─────────────┴───────────┘
The path can also be a glob, or a list of paths or globs, as in
read_parquet(['a.parquet', 'b.parquet']).
read_csv also accepts named options after the path, with the same names and meaning as the options
of scan_csv. Write them as name => value (or name := value):
result = ctx.execute(
"""
SELECT *
FROM read_csv('docs/assets/data/iris.csv', infer_schema => false, skip_rows_after_header => 147)
"""
)
print(result)
shape: (3, 5)
┌──────────────┬─────────────┬──────────────┬─────────────┬───────────┐
│ sepal_length ┆ sepal_width ┆ petal_length ┆ petal_width ┆ species │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ str ┆ str ┆ str │
╞══════════════╪═════════════╪══════════════╪═════════════╪═══════════╡
│ 6.5 ┆ 3 ┆ 5.2 ┆ 2 ┆ Virginica │
│ 6.2 ┆ 3.4 ┆ 5.4 ┆ 2.3 ┆ Virginica │
│ 5.9 ┆ 3 ┆ 5.1 ┆ 1.8 ┆ Virginica │
└──────────────┴─────────────┴──────────────┴─────────────┴───────────┘
The supported options are:
- Layout:
has_header,separator,quote_char,comment_prefix,skip_rows,skip_rows_after_header,skip_lines,encoding. - Names and types:
new_columns,schema,schema_overrides,infer_schema,infer_schema_length,infer_schema_files,try_parse_dates,decimal_comma. - Values and errors:
null_values,ignore_errors. - Ragged rows and multiple files:
truncate_ragged_lines,missing_columns,extra_columns,include_file_paths.
Values are SQL literals:
NULLstands forNone, as ininfer_schema_length => NULLto infer the schema from the whole file, orquote_char => NULLto turn quoting off.- Lists use brackets:
new_columns => ['a', 'b']. schema,schema_overridesand per-columnnull_valuestake a dictionary, with types written as SQL type names:schema_overrides => {'id': 'INT', 'name': 'VARCHAR'}.schema_overridesalso accepts a list of type names, by position.- Escape sequences need an
E''string, as inseparator => E'\t'for tab-separated files.
Unlike scan_csv, read_csv tries to parse dates by default (try_parse_dates => true).
Some options are unstable: those marked as such in the
scan_csv API
reference are unstable in read_csv too, and may be changed at any point without it being
considered a breaking change.
read_ndjson reads newline-delimited JSON (one object per line) and accepts the options of
scan_ndjson in the same way: schema, schema_overrides, infer_schema_length, ignore_errors
and include_file_paths.
result = ctx.execute(
"""
SELECT *
FROM read_ndjson('docs/assets/data/orders.ndjson', schema_overrides => {'date': 'DATE'})
"""
)
print(result)
shape: (3, 4)
┌─────┬────────────┬─────────────────┬───────────┐
│ id ┆ date ┆ customer ┆ items │
│ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ date ┆ struct[2] ┆ list[i64] │
╞═════╪════════════╪═════════════════╪═══════════╡
│ 1 ┆ 2024-03-01 ┆ {"Ann","Ghent"} ┆ [3, 7] │
│ 2 ┆ 2024-03-04 ┆ {"Bo","Leuven"} ┆ [1] │
│ 3 ┆ 2024-03-09 ┆ {"Cy","Ghent"} ┆ [2, 5, 8] │
└─────┴────────────┴─────────────────┴───────────┘
- The schema is inferred from the first 100 rows, so a key that first appears later is dropped. Use
infer_schema_length => NULLto infer it from the whole file. - A nested object read as
'VARCHAR'keeps its JSON text. read_jsonreads the same files, but takes only the path. For a JSON array or a pretty-printed JSON document, usepl.read_jsoninstead.
read_parquet accepts the options of scan_parquet in the same way: schema, missing_columns,
extra_columns, hive_partitioning, hive_schema, try_parse_hive_dates and
include_file_paths.
result = ctx.execute(
"""
SELECT id, bool_col, path
FROM read_parquet('docs/assets/data/alltypes_plain.parquet', include_file_paths => 'path')
LIMIT 3
"""
)
print(result)
shape: (3, 3)
┌─────┬──────────┬─────────────────────────────────┐
│ id ┆ bool_col ┆ path │
│ --- ┆ --- ┆ --- │
│ i32 ┆ bool ┆ str │
╞═════╪══════════╪═════════════════════════════════╡
│ 4 ┆ true ┆ docs/assets/data/alltypes_plai… │
│ 5 ┆ false ┆ docs/assets/data/alltypes_plai… │
│ 6 ┆ true ┆ docs/assets/data/alltypes_plai… │
└─────┴──────────┴─────────────────────────────────┘
- Unlike
scan_parquet,read_parquetreads hive partitions by default when given one path, for a glob or a single file too: a path such asdata/k=1/part.parquetadds a columnk. Everykey=valuefolder in such a path counts, so an absolute path can also pick up columns from its parent folders. A list of paths reads them only when it is a single directory, as inscan_parquet. Sethive_partitioningtotrueorfalseto choose either way. - When reading several files, the first one defines the columns, unless
schemais given. Usemissing_columns => 'insert'andextra_columns => 'ignore'for files that differ from it. - As for
read_csv, options marked unstable in thescan_parquetAPI reference are unstable inread_parquettoo.
read_ipc accepts the options of scan_ipc in the same way: hive_partitioning, hive_schema,
try_parse_hive_dates and include_file_paths.
pl.read_csv("docs/assets/data/iris.csv").write_ipc("docs/assets/data/iris.feather")
result = ctx.execute(
"""
SELECT species, sepal_length, path
FROM read_ipc('docs/assets/data/iris.feather', include_file_paths => 'path')
LIMIT 3
"""
)
print(result)
shape: (3, 3)
┌─────────┬──────────────┬───────────────────────────────┐
│ species ┆ sepal_length ┆ path │
│ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ str │
╞═════════╪══════════════╪═══════════════════════════════╡
│ Setosa ┆ 5.1 ┆ docs/assets/data/iris.feather │
│ Setosa ┆ 4.9 ┆ docs/assets/data/iris.feather │
│ Setosa ┆ 4.7 ┆ docs/assets/data/iris.feather │
└─────────┴──────────────┴───────────────────────────────┘
- Hive partitions are read as for
read_parquet: by default for one path, and for a list only when it is a single directory. - As for
read_parquet, options marked unstable in thescan_ipcAPI reference are unstable inread_ipctoo.