A comprehensive SQL-to-MongoDB converter that transforms complex SQL queries into MongoDB aggregation pipelines. Supports 25+ SQL functions, advanced analytics, and full query capabilities.
Download cli from here Click here
🚀 New: 25+ SQL Functions Supported - String manipulation, math operations, date functions, and conditional expressions!
Download the latest release from GitHub Releases.
go get github.com/synehq/zero-sql/pkg/zerosqlimport "github.com/synehq/zero-sql/pkg/zerosql"
converter := zerosql.New(nil)
pipeline, err := converter.ConvertSQLToMongo("SELECT * FROM users WHERE age > 18")Zero-SQL supports a comprehensive set of SQL features:
- SELECT statements with column selection, aliases, and DISTINCT
- Quoted identifiers (column names with spaces or special characters)
- FROM clauses with table references
- WITH clauses (Common Table Expressions/CTEs) with UNNEST support
- JOIN operations (INNER, LEFT, RIGHT)
- WHERE clauses with complex conditions (AND, OR, nested conditions)
- Comparison operators (=, !=, >, <, >=, <=, LIKE, ILIKE, IN, NOT IN)
- NULL checks (IS NULL, IS NOT NULL)
- BETWEEN clauses with date ranges and numeric ranges
- ORDER BY clauses with ASC/DESC
- LIMIT and OFFSET
- GROUP BY with aggregation functions (COUNT, SUM, AVG, MIN, MAX, STDDEV, VARIANCE)
- HAVING clauses
- Case-insensitive pattern matching with ILIKE
- UPPER(text) - Convert text to uppercase
- LOWER(text) - Convert text to lowercase
- CONCAT(str1, str2, ...) - Concatenate strings
- SUBSTRING(text, start, length) / SUBSTR(text, start, length) - Extract substring
- LENGTH(text) / LEN(text) - Get string length
- REPLACE(text, find, replace) - Replace substrings
- UNNEST(array) - Expand array elements into separate rows (used in CTEs)
- ABS(number) - Absolute value
- CEIL(number) - Ceiling (round up)
- FLOOR(number) - Floor (round down)
- POWER(base, exponent) / POW(base, exponent) - Power function
- SQRT(number) - Square root
- MOD(dividend, divisor) - Modulo operation
- YEAR(date) - Extract year from date
- MONTH(date) - Extract month from date
- DAY(date) - Extract day of month from date
- DATEADD(date, interval, unit) - Add time interval to date
- DATEDIFF(end_date, start_date, unit) - Calculate date difference
- STRFTIME(date, format) - Format date using strftime patterns
- CAST(expression AS type) - Type conversion (preprocessed to remove CAST wrapper)
- COALESCE(val1, val2, ...) - Return first non-null value
- NULLIF(expr1, expr2) - Return null if expressions are equal
git clone https://github.com/synhq/zero-sql
cd zero-sql
go build -o zero-sql buildgo install github.com/synhq/zero-sqlzero-sql "SELECT name, age FROM users WHERE age > 18"Usage:
zero-sql [SQL_QUERY] [flags]
Flags:
-f, --format string Output format (json, bson) (default "json")
-h, --help help for zero-sql
-c, --include-collection Include collection information in the output
-p, --pretty Pretty print the output (default true)
-v, --verbose Verbose outputzero-sql "SELECT name, email FROM users WHERE active = true"Output:
[
{
"$match": {
"active": true
}
},
{
"$project": {
"_id": 0,
"name": "$name",
"email": "$email"
}
}
]Zero-SQL fully supports quoted identifiers for column names with spaces or special characters:
zero-sql 'SELECT "User Name", "Email Address" FROM users WHERE "Created Date" > '\''2023-01-01'\'''Output:
[
{
"$match": {
"Created Date": {
"$gt": "2023-01-01"
}
}
},
{
"$project": {
"_id": 0,
"User Name": "$User Name",
"Email Address": "$Email Address"
}
}
]zero-sql 'SELECT YEAR("Order Date") as order_year, MONTH("Order Date") as order_month, UPPER("Category") as category_upper, COUNT(*) as total_orders, ROUND(SUM("Total Amount"), 2) as revenue, ROUND(AVG("Total Amount"), 2) as avg_order_value, CONCAT('\''$'\'', ROUND(SUM("Total Amount"), 0)) as formatted_revenue FROM orders WHERE "Order Date" BETWEEN '\''2023-01-01'\'' AND '\''2023-12-31'\'' AND "Status" IN ('\''completed'\'', '\''shipped'\'') GROUP BY YEAR("Order Date"), MONTH("Order Date"), "Category" ORDER BY revenue DESC LIMIT 20;'Output:
[
{
"$match": {
"$and": [
{
"Order Date": {
"$gte": "2023-01-01",
"$lte": "2023-12-31"
}
},
{
"Status": {
"$in": ["completed", "shipped"]
}
}
]
}
},
{
"$group": {
"_id": {
"category": "$Category",
"group_0": {"$year": "$Order Date"},
"group_1": {"$month": "$Order Date"}
},
"avg_order_value": {"$avg": "$Total Amount"},
"revenue": {"$sum": "$Total Amount"},
"total_orders": {"$sum": 1}
}
},
{
"$project": {
"_id": 0,
"avg_order_value": {"$round": ["$avg_order_value", 2]},
"category_upper": {"$toUpper": "$_id.category"},
"formatted_revenue": {"$concat": ["$", {"$round": ["$revenue", 0]}]},
"order_month": "$_id.group_1",
"order_year": "$_id.group_0",
"revenue": {"$round": ["$revenue", 2]},
"total_orders": "$total_orders"
}
},
{
"$sort": {"revenue": -1}
},
{
"$limit": 20
}
]zero-sql "SELECT u.name, p.title FROM users u JOIN posts p ON u.id = p.user_id"Output:
[
{
"$lookup": {
"from": "posts",
"localField": "id",
"foreignField": "user_id",
"as": "p"
}
},
{
"$unwind": "$p"
},
{
"$project": {
"_id": 0,
"name": "$u.name",
"title": "$p.title"
}
}
]zero-sql "SELECT u.name, p.title FROM users u LEFT JOIN posts p ON u.id = p.user_id"Output:
[
{
"$lookup": {
"from": "posts",
"localField": "id",
"foreignField": "user_id",
"as": "p"
}
},
{
"$unwind": {
"path": "$p",
"preserveNullAndEmptyArrays": true
}
},
{
"$project": {
"_id": 0,
"name": "$u.name",
"title": "$p.title"
}
}
]zero-sql "SELECT u.name, p.title, c.name as category FROM users u JOIN posts p ON u.id = p.user_id JOIN categories c ON p.category_id = c.id"Output:
[
{
"$lookup": {
"from": "posts",
"localField": "id",
"foreignField": "user_id",
"as": "p"
}
},
{
"$unwind": "$p"
},
{
"$lookup": {
"from": "categories",
"localField": "category_id",
"foreignField": "id",
"as": "c"
}
},
{
"$unwind": "$c"
},
{
"$project": {
"_id": 0,
"category": "$c.name",
"name": "$u.name",
"title": "$p.title"
}
}
]zero-sql "WITH order_items AS (SELECT o._id as order_id, UNNEST(o.items) as item FROM orders o LIMIT 1000) SELECT oi.item.product_id, COUNT(*) as times_ordered FROM order_items oi GROUP BY oi.item.product_id"Output:
[
{
"$unwind": {
"path": "$o.items",
"preserveNullAndEmptyArrays": false
}
},
{
"$project": {
"item": "$o.items",
"order_id": "$order_id"
}
},
{
"$limit": 1000
},
{
"$group": {
"_id": {
"product_id": "$item.product_id"
},
"times_ordered": {
"$sum": 1
}
}
}
]zero-sql "SELECT status, COUNT(*) as total FROM orders GROUP BY status"Output:
[
{
"$group": {
"_id": {
"status": "$status"
},
"total": {
"$sum": 1
}
}
}
]zero-sql "SELECT * FROM products WHERE (price > 100 AND category = 'electronics') OR (price < 50 AND category = 'books')"Zero-SQL supports both case-sensitive (LIKE) and case-insensitive (ILIKE) pattern matching:
# Case-sensitive pattern matching
zero-sql "SELECT name FROM users WHERE email LIKE '%@gmail.com'"
# Case-insensitive pattern matching (PostgreSQL style)
zero-sql "SELECT name FROM users WHERE email ILIKE '%@GMAIL.COM'"ILIKE Output:
[
{
"$match": {
"email": {
"$regex": ".*@GMAIL.COM",
"$options": "i"
}
}
},
{
"$project": {
"_id": 0,
"name": "$name"
}
}
]zero-sql 'SELECT * FROM transactions WHERE "Date" BETWEEN '\''2023-01-01'\'' AND '\''2023-12-31'\'''Output:
[
{
"$match": {
"Date": {
"$gte": "2023-01-01",
"$lte": "2023-12-31"
}
}
}
]zero-sql "SELECT name, created_at FROM users ORDER BY created_at DESC LIMIT 10"# Convert case and concatenate names
zero-sql "SELECT UPPER(name), LOWER(email), CONCAT(first_name, ' ', last_name) as full_name FROM users"
# Extract substrings and get lengths
zero-sql "SELECT SUBSTRING(name, 1, 10) as short_name, LENGTH(description) as desc_length FROM products"
# Replace text in strings
zero-sql "SELECT REPLACE(description, 'old', 'new') as updated_desc FROM products"# Round prices and calculate absolute values
zero-sql "SELECT CEIL(price), FLOOR(discount), ABS(amount) FROM orders"
# Power and square root calculations
zero-sql "SELECT POWER(quantity, 2), SQRT(price), MOD(total, 100) FROM products"# Extract date components
zero-sql "SELECT YEAR(created_at), MONTH(created_at), DAY(created_at) FROM orders"
# Date arithmetic
zero-sql "SELECT DATEADD(created_at, 30, 'day') as due_date FROM invoices"
# Date formatting with STRFTIME
zero-sql "SELECT STRFTIME(created_at, '%Y-%m-%d') as date_formatted FROM orders"# Type casting (CAST expressions are preprocessed)
zero-sql "SELECT CAST(amount AS INTEGER) as int_amount, CAST(price AS STRING) as price_str FROM products"# Handle null values
zero-sql "SELECT COALESCE(nickname, first_name, 'Unknown') as display_name FROM users"
# Null comparisons
zero-sql "SELECT NULLIF(amount, 0) as valid_amount FROM transactions"
# Rounding with precision
zero-sql "SELECT ROUND(price, 2), ROUND(SUM(amount), 2) as total FROM orders GROUP BY category"# Get unique categories
zero-sql "SELECT DISTINCT category, status FROM products"
# Count distinct values
zero-sql "SELECT COUNT(DISTINCT category) as unique_categories FROM products"- SELECT Clause: Column selection, aliases, DISTINCT, aggregation functions
- FROM Clause: Table references with aliases and quoted identifiers
- JOIN Clause: INNER, LEFT, RIGHT joins with complex conditions
- WHERE Clause: Comparison operators, pattern matching, NULL checks, ranges
- GROUP BY Clause: Single/multiple columns with aggregation functions
- HAVING Clause: Filter aggregated results
- ORDER BY Clause: Ascending/descending with aliases and expressions
- LIMIT/OFFSET: Result pagination
- Column selection:
SELECT name, age - Wildcard:
SELECT * - DISTINCT:
SELECT DISTINCT category, status - Column aliases:
SELECT name AS full_name - Quoted identifiers:
SELECT "User Name", "Email Address" - Aggregation functions:
COUNT(),SUM(),AVG(),MIN(),MAX() - String functions:
UPPER(),LOWER(),CONCAT(),SUBSTRING(),LENGTH(),REPLACE() - Math functions:
ABS(),CEIL(),FLOOR(),POWER(),SQRT(),MOD(),ROUND() - Date functions:
YEAR(),MONTH(),DAY(),DATEADD(),DATEDIFF(),STRFTIME() - Conditional functions:
COALESCE(),NULLIF()
- Table references:
FROM users - Table aliases:
FROM users u - Quoted table names:
FROM "User Data"
- INNER JOIN:
INNER JOIN posts ON users.id = posts.user_id - LEFT JOIN:
LEFT JOIN posts ON users.id = posts.user_id - Table aliases in JOINs:
FROM users u JOIN posts p ON u.id = p.user_id - Multiple JOINs:
FROM users u JOIN posts p ON u.id = p.user_id JOIN categories c ON p.category_id = c.id - JOINs with WHERE conditions:
FROM users u JOIN posts p ON u.id = p.user_id WHERE u.active = true
- Comparison operators:
=,!=,<>,>,<,>=,<= - Pattern matching:
LIKE(case-sensitive),ILIKE(case-insensitive) - List operations:
IN,NOT IN - Range operations:
BETWEEN,NOT BETWEEN - NULL checks:
IS NULL,IS NOT NULL - Logical operators:
AND,OR,NOT - Parentheses for grouping:
(condition1 OR condition2) AND condition3 - Quoted column names:
WHERE "Created Date" > '2023-01-01'
- Single column:
GROUP BY status - Multiple columns:
GROUP BY category, status - Quoted column names:
GROUP BY "Product Category", "Region" - Works with aggregation functions in SELECT
- Filter aggregated results:
HAVING COUNT(*) > 5 - Supports same operators as WHERE clause
- Ascending:
ORDER BY nameorORDER BY name ASC - Descending:
ORDER BY created_at DESC - Multiple columns:
ORDER BY category, name DESC - Order by aliases:
ORDER BY Total_Amount DESC - Quoted column names:
ORDER BY "Created Date" DESC
- Limit results:
LIMIT 10 - Skip results:
OFFSET 20orLIMIT 20, 10
Zero-SQL fully supports PostgreSQL-style quoted identifiers, allowing you to use column names with:
- Spaces:
"User Name" - Special characters:
"Email@Address" - Reserved keywords:
"Order" - Mixed case:
"UserID"
The ILIKE operator provides PostgreSQL-compatible case-insensitive pattern matching:
-- These are equivalent for matching
WHERE name ILIKE '%john%' -- Matches: John, JOHN, john, JoHn
WHERE name LIKE '%john%' -- Matches only: johnZero-SQL handles sophisticated business intelligence queries with:
- Multiple aggregation functions in a single query
- Complex WHERE clauses with multiple conditions
- GROUP BY with multiple columns including quoted identifiers
- ORDER BY with computed column aliases
- Proper handling of date ranges and IN clauses
- String manipulation and formatting functions
- Mathematical computations and rounding
- Date extraction and arithmetic operations
- Conditional expressions and null handling
Zero-SQL now supports 25+ SQL functions for comprehensive data transformation:
String Functions: UPPER, LOWER, CONCAT, SUBSTRING, LENGTH, REPLACE Math Functions: ABS, CEIL, FLOOR, POWER, SQRT, MOD, ROUND Date Functions: YEAR, MONTH, DAY, DATEADD, DATEDIFF, STRFTIME Conditional Functions: COALESCE, NULLIF
These functions can be used in SELECT, WHERE, GROUP BY, HAVING, and ORDER BY clauses, enabling complex data transformations and calculations directly in your SQL queries.
The tool generates MongoDB aggregation pipelines using these stages:
$lookup- for JOIN operations$unwind- to flatten joined arrays$match- for WHERE and HAVING clauses$group- for GROUP BY clauses with proper aggregation handling$project- for SELECT column specification$sort- for ORDER BY clauses$skip- for OFFSET$limit- for LIMIT
Zero-SQL provides detailed error messages for:
- Invalid SQL syntax
- Unsupported SQL features
- Type mismatches in quoted identifiers
- Missing required clauses
- Malformed aggregation functions
Use the --verbose flag for additional debugging information.
Current limitations include:
- Only SELECT statements are supported (no INSERT, UPDATE, DELETE)
- Subqueries are not yet supported
- Window functions (ROW_NUMBER, RANK, etc.) are not supported
- UNION operations are not supported
- CTEs (Common Table Expressions) are not supported
- Some advanced PostgreSQL-specific features may not be available
Contributions are welcome! Please see the Contributing Guide for details.
This project is licensed under the MIT License - see the LICENSE file for details.
The project follows a clean architecture with clear separation of concerns:
zero-sql/
├── cmd/ # CLI command definitions
│ └── root.go # Main command setup
├── internal/converter/ # Core conversion logic
│ ├── converter.go # Main converter with pipeline building
│ ├── parser.go # SQL AST parsing with 25+ function handlers
│ └── operators.go # SQL to MongoDB operator mappings
├── main.go # Application entry point
├── go.mod # Go module definition
└── README.md # This file
The converter package is the heart of the application, handling the conversion from SQL Abstract Syntax Trees (AST) to MongoDB aggregation pipelines. It includes specialized handling for quoted identifiers, complex expressions, and 25+ SQL functions including string manipulation, mathematical operations, date functions, and conditional expressions.
# Basic usage
zero-sql "SELECT name, age FROM users WHERE age > 18"
# With quoted column names
zero-sql 'SELECT "User Name", "Email Address" FROM users WHERE "Created Date" > '\''2023-01-01'\'''
# With collection information (helps avoid MongoDB namespace errors)
zero-sql --include-collection "SELECT name, age FROM users WHERE age > 18 LIMIT 5"
# Pretty print JSON output
zero-sql --pretty "SELECT * FROM products"
# Verbose mode
zero-sql --verbose "SELECT name FROM users"
# Complex aggregation query
zero-sql 'SELECT "Department", AVG("Salary") as "Average Salary" FROM employees WHERE "Hire Date" > '\''2020-01-01'\'' GROUP BY "Department" ORDER BY "Average Salary" DESC'
# String functions
zero-sql "SELECT UPPER(name), CONCAT(first_name, ' ', last_name) as full_name FROM users"
# Math and date functions
zero-sql "SELECT ROUND(price, 2), YEAR(created_at), DATEADD(created_at, 30, 'day') FROM orders"
# DISTINCT queries
zero-sql "SELECT DISTINCT category, status FROM products ORDER BY category"If you encounter an error like (InvalidNamespace) {aggregate: 1} is not valid for '$limit'; a collection is required, this means that when executing the generated aggregation pipeline in MongoDB, the collection name is not being specified correctly.
Solution: Use the --include-collection flag to get both the collection name and the pipeline:
# This outputs collection name and pipeline
zero-sql --include-collection "SELECT name FROM users LIMIT 10"Output:
{
"collection": "users",
"pipeline": [
{
"$limit": 10
}
]
}Then, in your MongoDB client/driver, use the collection name when executing the aggregation:
// MongoDB shell
db.users.aggregate([{"$limit": 10}])
// Node.js with MongoDB driver
await db.collection("users").aggregate([{"$limit": 10}]).toArray()When using quoted column names in your SQL queries, make sure to:
- Use double quotes (
") for column names, not single quotes (') - Escape quotes properly in your shell commands
- Use the exact case as stored in your MongoDB collection
# Correct
zero-sql 'SELECT "User Name" FROM users'
# Incorrect - will be treated as a string literal
zero-sql "SELECT 'User Name' FROM users"