Skip to content

SQL Query Builder

Build a parameterised SELECT, INSERT, UPDATE or DELETE for Postgres, MySQL or SQL Server — with a warning when the query is valid but not what you meant.

Query

Double-quoted identifiers, $1 parameters

Columns

empty selects *

Joins

=

WHERE

GROUP BY

HAVING

ORDER BY

Limit

SQL

parameterised
SELECT
  u.id,
  u.email,
  COUNT(o.id) AS orders
FROM
  users AS u
LEFT OUTER JOIN orders AS o ON u.id = o.user_id
WHERE
  o.status = $1
GROUP BY
  u.id,
  u.email
ORDER BY
  orders DESC
LIMIT 10;

Values

$1
paid

Worth knowing

1
  • WHERE filters o.status from the LEFT JOINed table "o", which turns the outer join back into an inner one. Move the condition into the ON clause to keep the unmatched rows.

Built in your browser — no database is contacted and no schema is uploaded. Values become placeholders by default, because a query assembled from concatenated strings is how SQL injection happens.

About the SQL Query Builder

This SQL query builder assembles SELECT, INSERT, UPDATE and DELETE statements from a form, for PostgreSQL, MySQL or SQL Server. Values become placeholders — $1, ? or @p1 depending on the dialect — with the bound values listed separately, because a query assembled by concatenating strings is precisely how SQL injection happens.

It also argues with you. A LEFT JOIN whose right-hand table is filtered in WHERE quietly behaves as an inner join; a column selected next to an aggregate but missing from GROUP BY is rejected by Postgres and silently arbitrary in MySQL; LIMIT without ORDER BY returns an unpredictable subset. Each of those produces a warning next to the statement, with the reason and the fix.

Identifiers are quoted per dialect and escaped by doubling the quote character, which is the only protection available at a position where a placeholder cannot go. A table called order comes out as "order" rather than as a syntax error.

Output is passed through the same formatter as the SQL Formatter tool, so the layout is identical to what you would get by pasting a query there. Everything runs in your browser; no database is contacted and no schema is uploaded.

  • SELECT, INSERT, UPDATE and DELETE, with RETURNING where the dialect supports it
  • Parameterised output by default, with the ordered value list shown alongside
  • PostgreSQL, MySQL and SQL Server — including the OFFSET/FETCH form SQL Server needs instead of LIMIT
  • Joins with aliases; INNER, LEFT, RIGHT, FULL and CROSS
  • Aggregates with COUNT DISTINCT, GROUP BY and HAVING
  • IN lists expanded to one placeholder per item, and BETWEEN to two
  • OR runs parenthesised, so operator precedence cannot change what you meant
  • Warnings for the outer-join-filtered-in-WHERE trap, missing GROUP BY columns, and an UPDATE or DELETE with no WHERE

How to use it

  1. Pick the operation and type the table name. An alias is worth setting as soon as you add a join.
  2. Add the columns you want. Leave the list empty for SELECT * — and set an aggregate on any column that needs one.
  3. Add joins, then conditions. The connector dropdown on the second and later conditions controls AND versus OR.
  4. Read the SQL and the value list together. The placeholders are positional, so the order of that list is the order your driver expects.
  5. Check the warnings panel before copying. Everything it reports is legal SQL that probably does not mean what you intended.
  6. Switch to Inlined only to paste into psql or a SQL console — never to copy into application code.

Real-world use cases

Backend & API developers

Assemble a parameterised SELECT or UPDATE for a new endpoint without hand-counting placeholder positions, then paste the statement and its value list straight into the driver call.

Junior developers & SQL learners

Build a join or a GROUP BY through the form and read the generated SQL to see the shape a correct statement takes, with the outer-join and aggregate warnings explaining why a plausible query is wrong.

Backend developers reviewing a teammate's PR

Reassemble the query described in a ticket to check it against what actually shipped, without opening a database client just to sanity-check one WHERE clause.

QA & test engineers

Generate the exact parameterised statement and value list a fixture or integration test needs to seed or assert against, for a dialect that differs from the one they use day to day.

Engineers switching database engines

Rebuild a familiar query for a different dialect — SQL Server's OFFSET/FETCH instead of LIMIT, or MySQL's backtick identifiers instead of Postgres's double quotes — without relearning the syntax from scratch.

Examples

A parameterised SELECT with a join

Input
SELECT from users AS u, LEFT JOIN orders AS o ON u.id = o.user_id, WHERE u.country = SE
Output
SELECT
  u.id,
  u.email
FROM
  users AS u
  LEFT OUTER JOIN orders AS o ON u.id = o.user_id
WHERE
  u.country = $1;

$1 = SE

The value never enters the statement text. That is the whole point of the default mode.

An IN list becomes one placeholder per item

Input
WHERE country IN  SE, NO, DK
Output
WHERE
  country IN ($1, $2, $3);

$1 = SE
$2 = NO
$3 = DK

A single placeholder for a whole list does not work in any of these dialects — the driver would bind one string. Expanding it is the correct answer, and it is why the count of placeholders varies with your input.

An OR run is parenthesised

Input
active = true AND country = SE OR country = NO
Output
WHERE active = $1 AND (country = $2 OR country = $3);

Without the parentheses, AND binds tighter than OR and the query means something else entirely. This is the difference between active Swedes plus all Norwegians, and active users from either country.

The same query for SQL Server

Input
LIMIT 10 with ORDER BY id
Output
ORDER BY
  id ASC OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY;

SQL Server has no LIMIT, and it refuses to page at all without an ORDER BY. If you have not set one, the builder adds ORDER BY (SELECT NULL) and tells you it did.

Common errors

MessageCauseFix
column "x" must appear in the GROUP BY clause or be used in an aggregate functionA plain column is selected alongside an aggregate without being grouped.Add it to GROUP BY, or wrap it in an aggregate. MySQL will run this query without complaint and return an arbitrary row per group, which is worse than the error — so the warning appears here regardless of dialect.
A LEFT JOIN returns fewer rows than expectedThe right-hand table is filtered in WHERE. Unmatched rows have NULL there, and NULL fails every comparison, so the outer join is reduced to an inner one.Move the condition into the ON clause. The builder warns about this whenever a WHERE condition names a LEFT JOINed alias, except for IS NULL — which is the deliberate anti-join idiom.
syntax error at or near "order"A table or column shares a name with a reserved word.Quote it. This builder quotes reserved words automatically, using the right character for the dialect: "order" on Postgres, `order` on MySQL, [order] on SQL Server.
bind message supplies 1 parameters, but prepared statement requires 3An IN list was written as one placeholder, so the driver bound a single string where the statement wanted three values.Expand the list to one placeholder per item, which is what this builder emits. Some drivers offer array binding as an alternative — for Postgres, = ANY($1) with an array is the tidier form.
The statement is valid but Invalid usage of the option NEXT in the FETCH statementSQL Server OFFSET/FETCH used without ORDER BY.Add an ORDER BY. Paging without one is meaningless anyway — there is no defined order to take a page from.
An UPDATE modified every rowNo WHERE clause. The statement is valid and applies to the whole table.Add a condition. The builder warns whenever an UPDATE or DELETE has no WHERE, because this is the one mistake here with no undo.

Frequently asked questions

Does this connect to my database?

No. This SQL query builder runs entirely in your browser: it generates statement text and never executes anything. Nothing about your schema is uploaded, and there is no connection string to enter — which also means it cannot check that the columns you typed exist.

Why are the values placeholders instead of being written into the query?

Because that is the only reliable defence against SQL injection, and because a builder that demonstrated string concatenation would be teaching the wrong habit to exactly the people most likely to copy it. Escaping in application code fails on the input you did not anticipate; a parameterised statement never mixes the value with the SQL at all. The inline mode exists because you cannot paste $1 into psql, and it says as much every time you use it.

Which dialect should I pick?

The one you are querying — the differences are real. Identifier quotes differ (", ` and []), placeholders differ ($1, ? and @p1), MySQL has no RETURNING, and SQL Server has no LIMIT and needs OFFSET ... FETCH with a mandatory ORDER BY. Generating for the wrong dialect produces a statement that looks right and does not run.

Can it build subqueries, CTEs or window functions?

No. The form covers the shape most queries take — joins, filters, grouping, ordering, paging — and stops where a form stops being clearer than SQL. A CTE or a window function is easier to type than to configure. Build the outer query here, then paste it into the SQL Formatter and extend it by hand.

Why does it warn about things that are valid SQL?

Because valid is not the same as correct. Every warning here is a statement the database will happily run and which probably does not do what you intended — the filtered outer join is the clearest case, and it is the single most common cause of a query returning too few rows. Each warning names the reason so you can dismiss it when you do mean it.

How are identifiers protected if they cannot be parameterised?

By quoting them and doubling any occurrence of the closing quote character, which is the standard escape and the only option at that position. A table name of users"; DROP TABLE x -- becomes the single quoted identifier "users""; DROP TABLE x --" — a nonsense table name rather than two statements. This matters because dynamic table and column names are the one place parameterised queries genuinely cannot help you.

Is the generated SQL formatted the same way as the SQL Formatter tool?

Yes, byte for byte. The builder produces a single-line statement and passes it through the same formatSql engine the formatter page uses, so there is one layout implementation on the site rather than two that drift. Keyword case follows the setting you choose.

Can I use this to check whether a query I already wrote is correct?

Not directly — there's no field to paste an existing statement into, since the form only builds forward from a table, columns and conditions. To have an existing query checked for the same class of mistakes this builder warns about — the filtered outer join, the ungrouped aggregate column, LIMIT with no ORDER BY — paste it into the SQL Query Explainer instead, which reads a query rather than assembling one.

Last updated