transact-rs/sqlx · error · syn::Error

expected exactly 1 column, got

Error message

expected exactly 1 column, got {}

What it means

The `query_scalar!` family requires the query to return exactly one column, since its output type is a single value. `quote_query_scalar` checks the described result columns and fails at compile time if the count differs, including the actual count in the message.

Solutions

  1. Change the SQL to select exactly one column (e.g. `SELECT count(*) FROM ...`)
  2. If you need multiple values, use `query_as!` with a record struct instead
  3. If it uses a query file, fix the .sql file so it returns a single column

Example fix

// before
let names = query_scalar!("SELECT id, name FROM users").fetch_all(&pool).await?;
// after
let names = query_as!(User, "SELECT id, name FROM users").fetch_all(&pool).await?;
// or for one value:
let count: i64 = query_scalar!("SELECT count(*) FROM users").fetch_one(&pool).await?;
Defensive patterns

Strategy: validation

Validate before calling

// ensure the SQL returns exactly one column before using query_scalar!
// e.g. inspect the described query in a test:
let d = sqlx::describe("SELECT count(*) FROM users", &pool).await?;
assert_eq!(d.columns().len(), 1);

Prevention

When it happens

Trigger: Using `query_scalar!`/`query_scalar_unchecked!` with SQL that returns 0 or 2+ columns — e.g. `SELECT id, name FROM users` or a query that returns no columns.

Common situations: Converting `query_as!` code to `query_scalar!` without trimming the select list; editing the .sql file to add a column; migrations changing a view so it now returns extra columns.

Related errors


AI-assisted analysis of transact-rs/sqlx@03af8bcc57 (2026-09-03). Data as JSON: /api/errors/9c63e26c7b2474bc. Report an issue: GitHub.

Appendix: source

Thrown at sqlx-macros-core/src/query/output.rs:217

            #(#instantiations)*

            ::std::result::Result::Ok(#out_ty { #(#ident: #var_name),* })
        })
    }
}

pub fn quote_query_scalar<DB: DatabaseExt>(
    input: &QueryMacroInput,
    config: &Config,
    warnings: &mut Warnings,
    bind_args: &Ident,
    describe: &Describe<DB>,
) -> crate::Result<TokenStream> {
    let columns = describe.columns();

    if columns.len() != 1 {
        return Err(syn::Error::new(
            input.src_span,
            format!("expected exactly 1 column, got {}", columns.len()),
        )
        .into());
    }

    // attempt to parse a column override, otherwise fall back to the inferred type of the column
    let ty = if let Ok(rust_col) = column_to_rust(describe, config, warnings, 0) {
        rust_col.type_.to_token_stream()
    } else if input.checked {
        let ty = get_column_type::<DB>(config, warnings, 0, &columns[0]);
        if describe.nullable(0).unwrap_or(true) {
            quote! { ::std::option::Option<#ty> }
        } else {
            ty
        }
    } else {
        quote! { _ }

View on GitHub (pinned to 03af8bcc57)