Skip to content

SELECT

Every builder starts as a SELECTQueryBuilder::<Dialect>::table("users") with no other method calls compiles to SELECT * FROM "users". This page covers everything that shapes the SELECT list: plain columns, aliases, aggregates, raw expressions, subquery columns, and DISTINCT / DISTINCT ON. Filtering belongs to WHERE, combining tables to JOIN.

select — plain columns

select takes any iterable of column names and replaces the column list. Every name is escaped per dialect; dotted identifiers are escaped per segment; a literal "*" passes through unquoted; an empty list selects *:

rust
let (sql, binds) = QueryBuilder::<Postgres>::table("users")
    .select(["id", "name"])
    .where_eq("status", "active")
    .to_sql();
// SELECT "id", "name" FROM "users" WHERE "status" = $1
rust
let (sql, _) = QueryBuilder::<Postgres>::table("users")
    .select(["users.id"])
    .to_sql();
// SELECT "users"."id" FROM "users"

Escaping is not optional decoration — a hostile column name is neutralized by quote-doubling rather than spliced in:

rust
let (sql, _) = QueryBuilder::<Postgres>::table("users")
    .select([r#"id" ; DROP TABLE users; --"#])
    .to_sql();
// SELECT "id"" ; DROP TABLE users; --" FROM "users"

select_as — column aliases

select_as(col, alias) emits col AS alias, with both identifiers escaped:

rust
let (sql, _) = QueryBuilder::<Postgres>::table("users")
    .select_as("created_at", "joined")
    .to_sql();
// SELECT "created_at" AS "joined" FROM "users"

Aggregates

Five aggregate helpers — select_count, select_sum, select_avg, select_min, select_max — each with an _as twin that adds an escaped alias. The column is escaped; for select_count (and select_count_as) the special column "*" passes through as COUNT(*):

rust
let (sql, _) = QueryBuilder::<Postgres>::table("users")
    .select_count("*")
    .to_sql();
// SELECT COUNT(*) FROM "users"
rust
let (sql, _) = QueryBuilder::<Postgres>::table("orders")
    .select_sum("amount")
    .select_avg("amount")
    .select_min("amount")
    .select_max("amount")
    .to_sql();
// SELECT SUM("amount"), AVG("amount"), MIN("amount"), MAX("amount") FROM "orders"

Aggregates mix freely with plain columns and GROUP BY (see GROUP BY · HAVING · ORDER · LIMIT):

rust
let (sql, _) = QueryBuilder::<Postgres>::table("orders")
    .select(["status"])
    .select_count_as("*", "cnt")
    .select_sum_as("amount", "total")
    .group_by(["status"])
    .to_sql();
// SELECT "status", COUNT(*) AS "cnt", SUM("amount") AS "total" FROM "orders" GROUP BY "status"

Qualified columns work inside aggregates too: select_sum("orders.amount")SUM("orders"."amount").

select_raw — verbatim expressions

select_raw(sql, binds) is the escape hatch for anything the structured API does not cover (functions, casts, window expressions). The fragment is appended to the column list after any select columns and aggregate/alias expressions; multiple calls accumulate; binds is an Option<Vec<Value>> (None for no binds).

⚠️ Positional placeholder contract

The sql fragment is emitted verbatim — it is NOT escaped and NOT renumbered. Its binds are appended to the running bind list in order. On Postgres you must hand-write $N numbers that match the actual bind position (number of binds already accumulated + 1, + 2, …); on MySQL/SQLite use ?. A wrong $N produces a malformed query. The same contract applies to every *_raw method — see the Security Model for the full escape-hatch inventory.

rust
let (sql, _) = QueryBuilder::<Postgres>::table("orders")
    .select_raw("COUNT(*)", None)
    .to_sql();
// SELECT COUNT(*) FROM "orders"

select_subquery — scalar subquery columns

select_subquery(alias, sub) takes another builder by value and emits (<sub>) AS "alias" after the regular columns and any select_raw expressions. The subquery is compiled with placeholder continuity: since the SELECT list renders before WHERE, the subquery's binds take the earlier $N numbers — automatically, no manual numbering involved:

rust
let (sql, binds) = QueryBuilder::<Postgres>::table("users")
    .select(["id"])
    .select_subquery(
        "cnt",
        QueryBuilder::<Postgres>::table("orders")
            .select_raw("COUNT(*)", None)
            .where_eq("status", 1i64),
    )
    .where_eq("active", true)
    .to_sql();
// SELECT "id", (SELECT COUNT(*) FROM "orders" WHERE "status" = $1) AS "cnt" FROM "users" WHERE "active" = $2
// binds == [Value::I64(1), Value::Bool(true)]

Correlated subqueries use where_column to reference the outer table — see WHERE.

distinct

distinct() emits SELECT DISTINCT … and works on all dialects:

rust
let (sql, _) = QueryBuilder::<Postgres>::table("t")
    .distinct()
    .select(["a"])
    .to_sql();
// SELECT DISTINCT "a" FROM "t"

distinct_on — Postgres only

distinct_on(cols) emits Postgres' SELECT DISTINCT ON (cols) …; the columns are escaped like any identifier:

rust
let (sql, _) = QueryBuilder::<Postgres>::table("t")
    .distinct_on(["a", "b"])
    .select(["a"])
    .to_sql();
// SELECT DISTINCT ON ("a", "b") "a" FROM "t"

Dialect note — MySQL and SQLite have no DISTINCT ON. Compiling a distinct_on builder against either dialect fails: try_to_sql() returns BuildError::DistinctOnRequiresPostgres, and to_sql() panics with DISTINCT ON requires PostgreSQL. If the dialect is decided at runtime, use the try_ form.