Custom SQL Queries
Custom SQL queries let you define free-form SELECT statements that produce virtual tables in the target. Unlike structured data filters (which modify how existing tables are read), queries create entirely new result sets from arbitrary SQL — including JOINs, CTEs, aggregations, and expressions.
Available in Load mode only. For cross-source SQL, use Federated Queries.
How queries work
Each query entry in the queries array defines:
name— the virtual table name created in the targetquery— a fullSELECTstatement executed against the source
The backend executes queries via DuckDB, writing the result set to the target under the given name.
Example
{
"name": "users-transform",
"mode": "load",
"source": {
"connections": [
{
"connectionId": "conn_SOURCE_ID",
"database": "app_db",
"queries": [
{
"name": "active_customers",
"query": "SELECT id, UPPER(full_name) AS full_name, LOWER(email) AS email FROM customers WHERE active = true"
}
]
}
]
},
"target": {
"id": "conn_TARGET_ID",
"spec": {
"db": { "database": "warehouse" }
}
}
}
This creates a active_customers table in the target warehouse database with the filtered and transformed result.
Practical rules
- Use
SELECTstatements only. - The
namefield becomes the target table name — keep it stable across runs. - Return only the columns the target needs.
- If the target depends on primary keys, include them in the result.
- Keep column aliases stable so the resulting schema is predictable.
- Validate the query in the SQL Console before running a full stream.
Mixing tables and queries in one stream
A single database connection can include both tables and queries in the same stream. The backend runs this as a mixed stream:
- Tables are streamed in parallel using native database readers.
- Custom queries are executed sequentially via DuckDB.
- Results from both phases are aggregated into the target.
This lets you combine straightforward table copies with transformed query extracts in a single Load run.
{
"mode": "load",
"source": {
"connections": [
{
"connectionId": "conn_PG_ID",
"database": "app_db",
"tables": [
{ "name": "orders" },
{ "name": "products" }
],
"queries": [
{
"name": "active_customers",
"query": "SELECT id, email, plan FROM customers WHERE active = true"
}
]
}
]
}
}
In multi-source streams, each connection defines its own tables and queries independently. You can also mix connection types — for example, a PostgreSQL connection with tables + queries alongside an S3 connection with file selections.
Queries vs data filters
| Feature | Custom SQL Queries (queries[]) | Data Filters (tables[].selection) |
|---|---|---|
| Input | Free-form SELECT statement | Visual column/row/sort/limit controls |
| Target table name | Defined by name field | Same as source table name |
| JOINs, CTEs, aggregations | Yes | No |
| CDC support | No (Load only) | No (Load only) |
For simple column selection and row filtering without writing SQL, use Data Filters.
Validation checklist
- Verify the source connection in Data Explorer.
- Run the query in the SQL Console.
- Confirm the column names and types match the target expectation.
- Start with a limited run (
limits.numberOfEvents) before applying broadly. - After the first run, verify the stream reached finished and the target table has the expected rows.