Skip to main content

Dynamically generate SQL queries

Project description

SQL Generator

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.

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

Installation

pip install sql-query-builder-py

Requirements: Python 3.12+

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'
)

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

Usage Examples

Table Definitions

from sql_generator import Table, TableJoinAttribute

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

orders = Table('orders', joins={
    'order_items': TableJoinAttribute('id', 'order_id')
})

order_items = Table('order_items', joins={
    'products': TableJoinAttribute('product_id', 'id')
})

products = Table('products')

JOIN Examples

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

# Join objects with via chains
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)
        ])
    ]
)

WHERE Conditions

# Dictionary format with Django-style operators
where = {
    'users.active__eq': True,           # AND users.active = %s
    'or__users.age__lt': 25,           # OR users.age < %s  
    'and__orders.total__gte': 100,     # AND orders.total >= %s
    'users.name__like': '%john%',      # AND users.name LIKE %s
    'users.id__in': [1, 2, 3]         # AND users.id IN (%s, %s, %s)
}

# WhereCondition objects for complex logic
from sql_generator import WhereCondition, Operator

conditions = [
    WhereCondition('active', Operator.EQ, True, table='users'),
    WhereCondition('age', Operator.GTE, 18, table='users', logical_operator='OR'),
    WhereCondition('status', Operator.IN, ['active', 'pending'], table='orders')
]

API Reference

QueryBuilder

QueryBuilder(
    tables: list[Table],                              # Required: Table definitions
    select: list[str | SelectColumn],                 # Required: Columns to select
    joins: list[str | Join] | None = None,           # Optional: JOIN clauses
    where: dict[str, Any] | list[WhereCondition] | None = None,  # Optional: WHERE conditions
    group_by: list[str | GroupBy] | None = None,     # Optional: GROUP BY columns
    order_by: list[str | OrderBy] | None = None,     # Optional: ORDER BY columns
    limit: int | None = None                         # Optional: LIMIT clause
)

WHERE Operators

Operator SQL Example
eq = {'id__eq': 1}
ne != {'status__ne': 'inactive'}
lt, le <, <= {'age__lt': 25}
gt, ge >, >= {'price__gte': 100}
like, ilike LIKE, ILIKE {'name__like': '%john%'}
in, not_in IN, NOT IN {'id__in': [1,2,3]}
is_null, is_not_null IS NULL, IS NOT NULL {'deleted_at__is_null': None}
between BETWEEN {'age__between': [18, 65]}

Key Classes

  • Table - Database table with optional join definitions
  • TableJoinAttribute - Defines relationship between tables
  • Join - JOIN clause with optional via chains
  • ViaStep - Step in a via chain with custom join type
  • SelectColumn - Column selection with aggregation and aliasing
  • WhereCondition - WHERE clause condition with logical operators
  • GroupBy/OrderBy - GROUP BY and ORDER BY clauses

Contributing

# Clone repository
git clone https://github.com/arthurm040/sql-generator.git
cd sql-generator

# Install with PDM
pdm install

# Run tests with coverage
pdm run test

License

MIT License - see LICENSE file for details.


Note: This library generates parameterized SQL queries using %s placeholders, compatible with PostgreSQL and MySQL. For SQLite, you may need to replace %s with ? in the generated SQL.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sql_query_builder_py-0.1.0rc2.tar.gz (28.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sql_query_builder_py-0.1.0rc2-py3-none-any.whl (17.3 kB view details)

Uploaded Python 3

File details

Details for the file sql_query_builder_py-0.1.0rc2.tar.gz.

File metadata

  • Download URL: sql_query_builder_py-0.1.0rc2.tar.gz
  • Upload date:
  • Size: 28.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: pdm/2.26.0 CPython/3.10.11 Windows/10

File hashes

Hashes for sql_query_builder_py-0.1.0rc2.tar.gz
Algorithm Hash digest
SHA256 f966d03de9a7f902c5ae3074b6da631034fb694300d1f5207aa9b147a0b83413
MD5 130687998b841232d8969dcffcca2fb4
BLAKE2b-256 a99a7fab0bdbbf9c909566c4d841c4e6378d73c175c7aa395157e69ba06151ba

See more details on using hashes here.

File details

Details for the file sql_query_builder_py-0.1.0rc2-py3-none-any.whl.

File metadata

File hashes

Hashes for sql_query_builder_py-0.1.0rc2-py3-none-any.whl
Algorithm Hash digest
SHA256 9bc9bc19e131dc130c3dd378bd187595deab394464ce3065fc31a2946e658a3c
MD5 1a2ac5b89b0720291a5f54380089e1f8
BLAKE2b-256 ec587cdc2d4ea018d6a7f271f169f2ec96439cbe96fe00d612709f524616f30d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page