Module · Data
SQL Builder
Lesson 8 of 11 · 9 min
The previous lesson ran SQL. This one writes it, without requiring you to.
SQL Builder is a visual composer: pick the tables, tick the fields, add filters and an order, set a limit. It produces a statement — and then, crucially, lets you take it over.
It produces SQL; it does not run it
This is the thing to understand first, because it explains why the node feels incomplete on its own. SQL Builder's output is text. Nothing has touched the database yet. The query node beside it does that.
Builder, then query
Two nodes, one job: compose, then execute.
Scroll for all 4 steps →
Watch out: Feeds database_query: wire query: "{{alias.final_sql}}" into the database_query step.
So the pairing is always the same shape: the query node's query field holds {{sqlbuilder_1.final_sql}}. Forget that wiring and you get a builder that composes a perfectly good statement nobody ever runs — no error, no rows, just a workflow that does nothing.
What you fill in
| Field | What it holds |
|---|---|
selected_tables | Tables to include in the query |
select_fields | Fields to select with optional functions |
joins | Table join configurations |
where_conditions | WHERE clause filter conditions (legacy simple mode) |
filter_groups | Advanced filter groups for complex WHERE clause logic |
group_by | Fields to group by (auto-suggested based on aggregates) |
order_by | Fields to order results by |
limit | Maximum number of rows to return Defaults to 100. |
selected_tables
- What it holds
- Tables to include in the query
select_fields
- What it holds
- Fields to select with optional functions
joins
- What it holds
- Table join configurations
where_conditions
- What it holds
- WHERE clause filter conditions (legacy simple mode)
filter_groups
- What it holds
- Advanced filter groups for complex WHERE clause logic
group_by
- What it holds
- Fields to group by (auto-suggested based on aggregates)
order_by
- What it holds
- Fields to order results by
limit
- What it holds
- Maximum number of rows to return Defaults to
100.
There are two ways to filter, and the difference matters once a query gets real. where_conditions is a flat list joined by one operator. filter_groups nests them — groups combined with AND or OR, which is how you express “this and that, or the other thing” without parentheses in your head.
The three SQL fields, and the one-way door
| Field | Holds |
|---|---|
generated_sql | What the visual builder composed. Rewritten whenever you change a picker. |
final_sql | What actually gets used. Normally a copy of the above. |
is_customized | Whether you have edited the SQL by hand. |
generated_sql
- Holds
- What the visual builder composed. Rewritten whenever you change a picker.
final_sql
- Holds
- What actually gets used. Normally a copy of the above.
is_customized
- Holds
- Whether you have edited the SQL by hand.
Editing the SQL is a one-way door. The moment you change final_sql yourself, is_customized flips and the builder stops overwriting your statement. That is the right behaviour — it is what stops a stray click destroying a hand-tuned query — but it does mean the pickers above are now decoration. Someone editing the filters later will see nothing change, and will not know why.
So decide which mode a query is in and leave it there. If it needs hand-written SQL, consider skipping the builder entirely and typing the statement into the Database Query node, where there is no second surface to be misled by.
When the builder is the right tool
- You do not write SQL often. The pickers show you the tables and columns that exist, which beats guessing at names.
- The query is straightforward — a few fields, a filter or two, an order, a limit. That is most reporting queries.
- Someone else will maintain it. A visual filter list is readable by a colleague who does not write SQL; a 40-line statement is not.
And against: window functions, CTEs, anything with a subquery. The builder does not express them, and forcing it to via the escape hatch gives you the worst of both — a visual config that lies about what runs.
Try it
- Add a SQL Builder, pick
contacts, tick three fields, add one filter, and set a limit of 5. - Look at
generated_sql— that is your picks, as SQL. - Wire
{{sqlbuilder_1.final_sql}}into a Database Query and run it. - Now edit the SQL by hand, go back and change a filter, and watch the statement not update. That is
is_customized, and now you have seen it.
That completes Logic & Data. You can branch, route, loop, wait, pause for a person, call another workflow, name values, reshape them and query the database directly — which is every mechanism a workflow has for deciding and computing.

