SQL Query Builder Documentation

Installation

pip install sql-query-builder-py

Requirements: Python 3.12+

Welcome to SQL Query Builder

Dynamic SQL query builder for Python that eliminates complex if/else logic for dynamic WHERE clauses and JOINs. Build queries using a constructor-based API with automatic table aliasing and relationship management.

The Problem This Solves

Before: Complex Dynamic Query Logic

# Traditional approach - messy and error-prone
def build_user_query(include_orders=False, active_only=False, min_age=None):
    sql = "SELECT u.name"
    params = []

    if include_orders:
        sql += ", o.total"

    sql += " FROM users u"

    if include_orders:
        sql += " LEFT JOIN orders o ON u.id = o.user_id"

    conditions = []
    if active_only:
        conditions.append("u.active = %s")
        params.append(True)

    if min_age:
        conditions.append("u.age >= %s")
        params.append(min_age)

    if conditions:
        sql += " WHERE " + " AND ".join(conditions)

    return sql, params

After: Clean Constructor-Based API

from sql_generator import QueryBuilder, Table, TableJoinAttribute

# Define relationships once
users = Table('users', joins={
    'orders': TableJoinAttribute('id', 'user_id')
})
orders = Table('orders')

# Build queries declaratively
def build_user_query(include_orders=False, active_only=False, min_age=None):
    where_conditions = {}
    if active_only:
        where_conditions['users.active__eq'] = True
    if min_age:
        where_conditions['users.age__gte'] = min_age

    return QueryBuilder(
        tables=[users, orders] if include_orders else [users],
        select=['users.name'] + (['orders.total'] if include_orders else []),
        joins=['orders'] if include_orders else None,
        where=where_conditions or None
    ).build()

Key Features

🚀 Constructor-Based API

Perfect for dynamic query generation - no method chaining required

🏷️ Automatic Table Aliasing

Generates unique 3+ character aliases with conflict resolution

🔗 Flexible JOIN System

Direct joins, via chains, and mixed join types

🎯 Django-Style WHERE Conditions

{'users.id__eq': 1, 'or__age__gt': 18}

🛡️ Parameterized Queries

Safe SQL with automatic parameter binding

Hybrid Input Support

Use strings or objects for all query components

Quick Start Examples

Basic Query

from sql_generator import QueryBuilder, Table, TableJoinAttribute

# Define table relationships
users = Table('users', joins={
    'orders': TableJoinAttribute('id', 'user_id')
})
orders = Table('orders')

# Simple query
qb = QueryBuilder([users], ['users.name', 'users.email'])
sql, params = qb.build()

print(sql)
# SELECT use.name, use.email
# FROM users use

Query with JOINs and WHERE

# Complex query with relationships
qb = QueryBuilder(
    tables=[users, orders],
    select=['users.name', 'orders.total'],
    joins=['orders'],
    where={
        'users.active__eq': True,
        'orders.total__gte': 100
    }
)

sql, params = qb.build()
print(sql)
# SELECT use.name, ord.total
# FROM users use
# INNER JOIN orders ord ON use.id = ord.user_id
# WHERE use.active = %s AND ord.total >= %s

print(params)
# [True, 100]

Advanced Features

from sql_generator import SelectColumn, AggFunction, Join, ViaStep, JoinType

# Aggregation with custom aliases
qb = QueryBuilder(
    tables=[users, orders],
    select=[
        'users.name',
        SelectColumn('COUNT(*)', alias='order_count'),
        SelectColumn('total', table='orders', agg_function=AggFunction.SUM, alias='revenue')
    ],
    joins=['orders'],
    where={'users.active__eq': True},
    group_by=['users.id', 'users.name'],
    order_by=['revenue DESC'],
    limit=10
)

Dynamic Query Generation

# Perfect for APIs and dynamic filtering
def get_users(filters=None, include_orders=False, sort_by=None):
    tables = [users]
    select_cols = ['users.name', 'users.email']
    joins = []

    if include_orders:
        tables.append(orders)
        select_cols.append('orders.total')
        joins.append('orders')

    return QueryBuilder(
        tables=tables,
        select=select_cols,
        joins=joins or None,
        where=filters,
        order_by=[sort_by] if sort_by else None
    ).build()

# Usage
sql, params = get_users(
    filters={'users.active__eq': True, 'or__users.role__eq': 'admin'},
    include_orders=True,
    sort_by='users.name ASC'
)

Strings vs Objects - Flexible Input

The library supports both string and object inputs for all components, giving you flexibility to choose the approach that fits your use case.

SELECT Columns

# Using strings (simple and concise)
qb = QueryBuilder([users], ['users.name', 'users.email', 'COUNT(*)'])

# Using objects (more control and type safety)
from sql_generator import SelectColumn, AggFunction
qb = QueryBuilder([users], [
    SelectColumn('name', table='users'),
    SelectColumn('email', table='users'),
    SelectColumn('id', table='users', agg_function=AggFunction.COUNT, alias='total_users')
])

WHERE Conditions

# Using dictionary format (Django-style, concise)
qb = QueryBuilder([users], ['users.name'], where={
    'users.active__eq': True,
    'users.age__gte': 18,
    'or__users.role__eq': 'admin'
})

# Using objects (explicit control over logic)
from sql_generator import WhereCondition, Operator
qb = QueryBuilder([users], ['users.name'], where=[
    WhereCondition('active', Operator.EQ, True, table='users'),
    WhereCondition('age', Operator.GE, 18, table='users'),
    WhereCondition('role', Operator.EQ, 'admin', table='users', logical_operator='OR')
])

JOIN Operations

# Using strings (simple joins)
qb = QueryBuilder([users, orders], ['users.name', 'orders.total'],
                  joins=['orders'])

# Using objects (complex via chains with custom join types)
from sql_generator import Join, ViaStep, JoinType
qb = QueryBuilder([users, orders, order_items, products],
                  ['users.name', 'products.name'], joins=[
    Join('products', via_steps=[
        ViaStep('orders', JoinType.LEFT),
        ViaStep('order_items', JoinType.INNER)
    ])
])

ORDER BY Clauses

# Using strings (quick and readable)
qb = QueryBuilder([users], ['users.name'],
                  order_by=['users.name ASC', 'users.created_at DESC'])

# Using objects (explicit control)
from sql_generator import OrderBy
qb = QueryBuilder([users], ['users.name'], order_by=[
    OrderBy('name', table='users', direction='ASC'),
    OrderBy('created_at', table='users', direction='DESC')
])

Mixed Approach

# You can mix strings and objects in the same query
qb = QueryBuilder(
    tables=[users, orders],
    select=[
        'users.name',  # String
        SelectColumn('total', table='orders', agg_function=AggFunction.SUM, alias='revenue')  # Object
    ],
    joins=['orders'],  # String
    where={'users.active__eq': True},  # Dictionary
    order_by=['revenue DESC']  # String
)

When to Use Each Approach

Use Strings When: - Building simple, straightforward queries - Rapid prototyping and development - You prefer concise, readable code - Working with standard SQL patterns

Use Objects When: - You need explicit control over query components - Building complex queries with custom logic - You want full type safety and IDE support - Creating reusable query components - Working with complex JOIN chains or aggregations

Why Choose This Library?

Eliminates Complex Logic - No more nested if/else for dynamic queries

Type-Safe - Catch errors at development time, not runtime

Readable Code - Declarative syntax that’s easy to understand

Flexible - Works with simple queries and complex multi-table joins

Safe - Built-in SQL injection protection with parameterized queries

Maintainable - Changes to table relationships update all queries automatically

Perfect for building APIs, admin interfaces, reporting systems, and any application that needs dynamic SQL generation.