FormatterHQ

SQL to MongoDB Query Converter

Translate SQL SELECT, INSERT, UPDATE and DELETE statements into MongoDB shell queries: find(), aggregate(), countDocuments(), distinct() and more.

Input SQL
Output JAVASCRIPT
Ready.

How to use the SQL to MongoDB Query Converter

  1. Paste one or more SQL statements, separated by semicolons.
  2. Pick the SQL dialect your query is written in, such as MySQL or PostgreSQL.
  3. The MongoDB queries appear automatically; click Convert after changes.
  4. Copy the output into mongosh, Compass or your driver code.

Convert SQL to MongoDB queries

This SQL to MongoDB converter parses your SQL with a real parser and rewrites each statement as a MongoDB shell command. A simple SELECT becomes db.collection.find() with a filter, projection, sort, skip and limit. Queries with GROUP BY or aggregate functions become an aggregate() pipeline. It is a quick way to learn MongoDB's query language when you already think in SQL, or to port queries during a migration.

Example: SELECT to find()

SELECT name, email, age FROM users
WHERE age >= 18 AND status = 'active'
  AND (country IN ('US', 'CA') OR name LIKE 'Jo%')
ORDER BY age DESC
LIMIT 20 OFFSET 40;

becomes

db.users.find({
  age: { $gte: 18 },
  status: 'active',
  $or: [
    { country: { $in: ['US', 'CA'] } },
    { name: { $regex: '^Jo' } }
  ]
}, { name: 1, email: 1, age: 1, _id: 0 }).sort({ age: -1 }).skip(40).limit(20);

Conditions joined by AND are merged into one filter object when their field names do not collide, OR becomes $or, and LIKE 'Jo%' turns into an anchored regular expression. Because the column list was explicit, _id: 0 is added so the result matches the SQL columns.

Example: GROUP BY to aggregate()

SELECT city, COUNT(*) AS total, AVG(age) AS avg_age FROM users GROUP BY city HAVING total > 5 ORDER BY total DESC converts to:

db.users.aggregate([
  {
    $group: { _id: '$city', total: { $sum: 1 }, avg_age: { $avg: '$age' } }
  },
  { $match: { total: { $gt: 5 } } },
  { $project: { _id: 0, city: '$_id', total: 1, avg_age: 1 } },
  { $sort: { total: -1 } }
]);

Supported SQL features

  • Operators: =, !=/<>, <, <=, >, >=, IN, NOT IN, BETWEEN, IS NULL, IS NOT NULL, LIKE, NOT LIKE, ILIKE (case-insensitive regex) and REGEXP. NOT (...) becomes $nor.
  • Aggregates: COUNT, SUM, AVG, MIN and MAX map to $sum, $avg, $min and $max in a $group stage. COUNT(*) is $sum: 1, while COUNT(col) becomes a conditional $sum that skips documents where the field is null or missing; COUNT(DISTINCT col) is not supported and stops with an error. HAVING becomes a $match after the group, whether it uses an alias or an aggregate such as COUNT(*) > 5.
  • Shortcuts: a lone SELECT COUNT(*) becomes countDocuments(), and SELECT DISTINCT col becomes distinct('col', filter).
  • Writes: INSERT maps to insertOne or insertMany, UPDATE to updateMany with $set, and DELETE to deleteMany. SET visits = visits + 1 is recognized and turned into $inc.
  • Paging: LIMIT n OFFSET m, MySQL's LIMIT m, n, and with the SQL Server dialect TOP n and OFFSET m ROWS FETCH NEXT n ROWS ONLY, become skip() and limit().

The SQL dialect option controls how the input is parsed. Choose SQL Server for TOP or bracketed identifiers, PostgreSQL for ILIKE, or BigQuery for backtick-quoted project paths.

Limitations

  • JOINs are not converted. MongoDB uses $lookup or embedded documents, and the right design depends on your data model, so the tool stops with an explanatory error. Convert the single-table part and add $lookup by hand.
  • Subqueries, UNION, window functions and most SQL functions are not supported; subqueries and UNION stop with a message that says so. In a WHERE filter, NOW() is emitted as a $$NOW placeholder; in a plain find() filter, replace it with new Date(). In INSERT and UPDATE values it is already written as new Date().
  • Column names are kept as-is, so an SQL id column stays id. If your documents use _id, rename the field in the filter.
  • Only plain columns and aggregates are allowed in the SELECT list; computed expressions such as price * qty need a $project stage you write yourself.

Also keep types in mind: '42' in SQL is a string, and MongoDB will not match it against a stored number.

Privacy

Parsing and conversion run in your browser, so your queries and table names are never uploaded. To tidy the SQL first, run it through the SQL formatter, or check it with the SQL validator. If you are designing collections, JSON to Mongoose schema generates a schema from a sample document.

Frequently asked questions

Does the converter translate JOINs to $lookup?
No. JOINs are rejected with a message, because the right MongoDB design might be $lookup, an embedded array or a denormalized field. Convert the base query, then add a $lookup stage that matches your data model.
How is LIKE converted to MongoDB?
% becomes .*, _ becomes . and the pattern is anchored where SQL is. So 'Jo%' becomes ^Jo and '%son' becomes son$. ILIKE adds $options: 'i' for case-insensitive matching.
Can I use the output in Node.js or Python drivers?
The filter and pipeline objects work the same in drivers; only the call syntax differs. In Node.js you would write db.collection('users').find(...), and in PyMongo you would quote the $ operator keys as strings.
Why does UPDATE become updateMany instead of updateOne?
A SQL UPDATE changes every row that matches the WHERE clause, which is what updateMany does. If your filter targets a unique key, you can switch to updateOne.
Which SQL dialects can I paste?
MySQL, MariaDB, PostgreSQL, SQLite, SQL Server (T-SQL), BigQuery, Snowflake, Redshift, Hive, Db2, Flink SQL, Trino and Athena. Pick the one that matches your syntax so dialect-specific keywords parse correctly. Each dialect has its own reserved words: the MariaDB parser, for example, rejects a column named status, so MariaDB users can switch to MySQL if a query is rejected.

Last updated October 3, 2026

Guides

Related tools