SQLAlchemy Basics#

SQLAlchemy is the most popular database toolkit and Object-Relational Mapping (ORM) library for Python. It provides a full suite of well-known enterprise-level persistence patterns, designed for efficient and high-performing database access. SQLAlchemy is divided into two main components: the Core (low-level SQL abstraction) and the ORM (high-level object mapping). This cheat sheet covers the Core API, which provides a SQL Expression Language that allows you to construct SQL statements in Python code while remaining database-agnostic. The Core is ideal when you need fine-grained control over SQL queries or when working with existing database schemas.

Create an Engine#

The Engine is the starting point for any SQLAlchemy application. It represents the connection pool and dialect for a particular database, managing connectivity and translating Python code into database-specific SQL. The create_engine() function takes a database URL that specifies the database type, credentials, host, and database name. SQLAlchemy supports many databases including SQLite, PostgreSQL, MySQL, Oracle, and Microsoft SQL Server through different dialects.

>>> from sqlalchemy import create_engine
>>> # SQLite in-memory database (great for testing)
>>> engine = create_engine("sqlite:///:memory:")
>>> # SQLite file-based database
>>> engine = create_engine("sqlite:///mydb.sqlite")
>>> # PostgreSQL
>>> engine = create_engine("postgresql://user:pass@localhost/dbname")
>>> # MySQL
>>> engine = create_engine("mysql+pymysql://user:pass@localhost/dbname")

Database URL Format#

SQLAlchemy uses RFC-1738 style URLs to specify database connections. The URL format provides a standardized way to specify all connection parameters including the database driver, authentication credentials, host address, port number, and database name. Understanding this format is essential for configuring connections to different database systems. The make_url() function can parse and construct these URLs programmatically.

>>> from sqlalchemy import make_url
>>> # Format: dialect+driver://username:password@host:port/database
>>> url = make_url("postgresql://user:pass@localhost:5432/mydb")
>>> url.drivername
'postgresql'
>>> url.username
'user'
>>> url.host
'localhost'
>>> url.database
'mydb'

Connect and Execute Raw SQL#

While SQLAlchemy encourages using its SQL Expression Language, you can also execute raw SQL strings directly. This is useful for complex queries that are difficult to express in SQLAlchemy’s API, or when migrating existing SQL code. The text() function wraps raw SQL strings and allows parameter binding for security. Always use parameter binding instead of string formatting to prevent SQL injection attacks.

>>> from sqlalchemy import create_engine, text
>>> engine = create_engine("sqlite:///:memory:")
>>> with engine.connect() as conn:
...     conn.execute(text("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)"))
...     conn.execute(text("INSERT INTO test (name) VALUES (:name)"), {"name": "Alice"})
...     conn.commit()
...     result = conn.execute(text("SELECT * FROM test"))
...     print(result.fetchall())
[(1, 'Alice')]

Transaction Management#

Transactions ensure that a series of database operations either all succeed or all fail together, maintaining data integrity. SQLAlchemy provides several ways to manage transactions: implicit transactions with begin(), context managers for automatic commit/rollback, and manual control with commit() and rollback(). The begin() method starts a transaction that will automatically rollback on exceptions and commit on successful completion when used as a context manager.

>>> from sqlalchemy import create_engine, text
>>> engine = create_engine("sqlite:///:memory:")
>>> # Using begin() for automatic commit/rollback
>>> with engine.begin() as conn:
...     conn.execute(text("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)"))
...     conn.execute(text("INSERT INTO users (name) VALUES ('Bob')"))
...     # Commits automatically if no exception

>>> # Manual transaction control
>>> with engine.connect() as conn:
...     trans = conn.begin()
...     try:
...         conn.execute(text("INSERT INTO users (name) VALUES ('Carol')"))
...         trans.commit()
...     except:
...         trans.rollback()
...         raise

Define Tables with Metadata#

MetaData is a container that holds information about database tables and other schema constructs. You can define tables programmatically using the Table class, specifying columns with their types and constraints. This approach is part of SQLAlchemy Core and gives you explicit control over the table structure. The metadata can then create all defined tables in the database with create_all(), which generates the appropriate DDL statements for your database dialect.

>>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String
>>> engine = create_engine("sqlite:///:memory:")
>>> metadata = MetaData()
>>> users = Table(
...     "users", metadata,
...     Column("id", Integer, primary_key=True),
...     Column("name", String(50)),
...     Column("email", String(100))
... )
>>> metadata.create_all(engine)
>>> # Check table columns
>>> [c.name for c in users.columns]
['id', 'name', 'email']

Reflect Existing Tables#

Table reflection allows SQLAlchemy to load table definitions from an existing database schema automatically. This is useful when working with legacy databases or when you want to avoid duplicating schema definitions. The reflect() method on MetaData reads the database schema and creates Table objects for all tables found. You can also reflect individual tables using autoload_with parameter.

>>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String
>>> engine = create_engine("sqlite:///:memory:")
>>> # Create a table first
>>> with engine.begin() as conn:
...     conn.execute(text("CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL)"))

>>> # Reflect the table
>>> metadata = MetaData()
>>> metadata.reflect(bind=engine)
>>> list(metadata.tables.keys())
['products']
>>> products = metadata.tables['products']
>>> [c.name for c in products.columns]
['id', 'name', 'price']

Inspect Database Schema#

The inspect() function provides a powerful way to examine database schema details at runtime. The inspector can retrieve information about tables, columns, indexes, foreign keys, and other database objects. This is particularly useful for database administration tasks, schema migrations, and debugging. The inspector works with any database supported by SQLAlchemy and provides a consistent API across different database systems.

>>> from sqlalchemy import create_engine, inspect, MetaData, Table, Column, Integer, String
>>> engine = create_engine("sqlite:///:memory:")
>>> metadata = MetaData()
>>> users = Table("users", metadata,
...     Column("id", Integer, primary_key=True),
...     Column("name", String(50)))
>>> metadata.create_all(engine)

>>> inspector = inspect(engine)
>>> inspector.get_table_names()
['users']
>>> inspector.get_columns('users')  
[{'name': 'id', ...}, {'name': 'name', ...}]

Insert Data#

The insert() construct creates an INSERT statement for a table. You can specify values using the values() method or pass them as keyword arguments. For bulk inserts, pass a list of dictionaries to execute(). SQLAlchemy will generate efficient multi-row INSERT statements when possible. The returning() method can retrieve auto-generated values like primary keys after insertion.

>>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, insert
>>> engine = create_engine("sqlite:///:memory:")
>>> metadata = MetaData()
>>> users = Table("users", metadata,
...     Column("id", Integer, primary_key=True),
...     Column("name", String(50)))
>>> metadata.create_all(engine)

>>> # Single insert
>>> with engine.begin() as conn:
...     conn.execute(insert(users).values(name="Alice"))
...     # Bulk insert
...     conn.execute(insert(users), [{"name": "Bob"}, {"name": "Carol"}])

>>> with engine.connect() as conn:
...     result = conn.execute(users.select())
...     print(result.fetchall())
[(1, 'Alice'), (2, 'Bob'), (3, 'Carol')]

Select Data#

The select() construct creates SELECT statements with a Pythonic API. You can specify which columns to retrieve, add WHERE clauses with where(), order results with order_by(), and limit results with limit() and offset(). The SQL Expression Language uses Python operators like ==, !=, >, < which are overloaded to generate SQL conditions. This provides type safety and prevents SQL injection.

>>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, select, insert
>>> engine = create_engine("sqlite:///:memory:")
>>> metadata = MetaData()
>>> users = Table("users", metadata,
...     Column("id", Integer, primary_key=True),
...     Column("name", String(50)),
...     Column("age", Integer))
>>> metadata.create_all(engine)

>>> with engine.begin() as conn:
...     conn.execute(insert(users), [
...         {"name": "Alice", "age": 30},
...         {"name": "Bob", "age": 25},
...         {"name": "Carol", "age": 35}])

>>> with engine.connect() as conn:
...     # Select all
...     result = conn.execute(select(users))
...     print(result.fetchall())
...     # Select with condition
...     result = conn.execute(select(users).where(users.c.age > 28))
...     print(result.fetchall())
[(1, 'Alice', 30), (2, 'Bob', 25), (3, 'Carol', 35)]
[(1, 'Alice', 30), (3, 'Carol', 35)]

Update Data#

The update() construct creates UPDATE statements. Use where() to specify which rows to update and values() to set new column values. Without a WHERE clause, all rows in the table will be updated. The returning() method can retrieve the updated values. For bulk updates with different values per row, use bindparam() to create parameterized statements.

>>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String
>>> from sqlalchemy import select, insert, update
>>> engine = create_engine("sqlite:///:memory:")
>>> metadata = MetaData()
>>> users = Table("users", metadata,
...     Column("id", Integer, primary_key=True),
...     Column("name", String(50)))
>>> metadata.create_all(engine)

>>> with engine.begin() as conn:
...     conn.execute(insert(users), [{"name": "Alice"}, {"name": "Bob"}])
...     conn.execute(update(users).where(users.c.name == "Alice").values(name="Alicia"))

>>> with engine.connect() as conn:
...     result = conn.execute(select(users))
...     print(result.fetchall())
[(1, 'Alicia'), (2, 'Bob')]

Delete Data#

The delete() construct creates DELETE statements. Always use where() to specify which rows to delete, unless you intend to delete all rows. Like other DML statements, delete() supports returning() to retrieve deleted rows. Be careful with DELETE statements as they permanently remove data from the database.

>>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String
>>> from sqlalchemy import select, insert, delete
>>> engine = create_engine("sqlite:///:memory:")
>>> metadata = MetaData()
>>> users = Table("users", metadata,
...     Column("id", Integer, primary_key=True),
...     Column("name", String(50)))
>>> metadata.create_all(engine)

>>> with engine.begin() as conn:
...     conn.execute(insert(users), [{"name": "Alice"}, {"name": "Bob"}, {"name": "Carol"}])
...     conn.execute(delete(users).where(users.c.name == "Bob"))

>>> with engine.connect() as conn:
...     result = conn.execute(select(users))
...     print(result.fetchall())
[(1, 'Alice'), (3, 'Carol')]

SQL Expression Language#

SQLAlchemy’s SQL Expression Language provides a Pythonic way to construct SQL statements. Column objects support comparison operators (==, !=, >, <), logical operators (& for AND, | for OR), and methods like in_(), like(), between(), and is_(). These expressions are composable and can be combined to build complex queries while maintaining readability and type safety.

>>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String
>>> from sqlalchemy import select, insert, and_, or_
>>> engine = create_engine("sqlite:///:memory:")
>>> metadata = MetaData()
>>> users = Table("users", metadata,
...     Column("id", Integer, primary_key=True),
...     Column("name", String(50)),
...     Column("age", Integer))
>>> metadata.create_all(engine)

>>> with engine.begin() as conn:
...     conn.execute(insert(users), [
...         {"name": "Alice", "age": 30},
...         {"name": "Bob", "age": 25},
...         {"name": "Carol", "age": 35}])

>>> with engine.connect() as conn:
...     # AND condition
...     stmt = select(users).where(and_(users.c.age > 25, users.c.age < 35))
...     print(conn.execute(stmt).fetchall())
...     # OR condition
...     stmt = select(users).where(or_(users.c.name == "Alice", users.c.name == "Bob"))
...     print(conn.execute(stmt).fetchall())
...     # IN clause
...     stmt = select(users).where(users.c.name.in_(["Alice", "Carol"]))
...     print(conn.execute(stmt).fetchall())
[(1, 'Alice', 30)]
[(1, 'Alice', 30), (2, 'Bob', 25)]
[(1, 'Alice', 30), (3, 'Carol', 35)]

Join Tables#

The join() method creates JOIN clauses between tables. SQLAlchemy can automatically determine join conditions based on foreign key relationships, or you can specify them explicitly. Use select_from() to specify the joined tables in a SELECT statement. SQLAlchemy supports INNER JOIN (default), LEFT OUTER JOIN, RIGHT OUTER JOIN, and FULL OUTER JOIN through the isouter and full parameters.

>>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, ForeignKey
>>> from sqlalchemy import select, insert
>>> engine = create_engine("sqlite:///:memory:")
>>> metadata = MetaData()
>>> users = Table("users", metadata,
...     Column("id", Integer, primary_key=True),
...     Column("name", String(50)))
>>> orders = Table("orders", metadata,
...     Column("id", Integer, primary_key=True),
...     Column("user_id", Integer, ForeignKey("users.id")),
...     Column("product", String(50)))
>>> metadata.create_all(engine)

>>> with engine.begin() as conn:
...     conn.execute(insert(users), [{"name": "Alice"}, {"name": "Bob"}])
...     conn.execute(insert(orders), [
...         {"user_id": 1, "product": "Book"},
...         {"user_id": 1, "product": "Pen"},
...         {"user_id": 2, "product": "Laptop"}])

>>> with engine.connect() as conn:
...     stmt = select(users.c.name, orders.c.product).select_from(
...         users.join(orders))
...     print(conn.execute(stmt).fetchall())
[('Alice', 'Book'), ('Alice', 'Pen'), ('Bob', 'Laptop')]

Aggregate Functions#

SQLAlchemy provides functions for SQL aggregates like count(), sum(), avg(), min(), and max() in the sqlalchemy.func namespace. These can be used in SELECT statements and combined with group_by() for grouped aggregations. The func object is a special namespace that generates SQL function calls for any function name you access on it.

>>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String
>>> from sqlalchemy import select, insert, func
>>> engine = create_engine("sqlite:///:memory:")
>>> metadata = MetaData()
>>> sales = Table("sales", metadata,
...     Column("id", Integer, primary_key=True),
...     Column("product", String(50)),
...     Column("amount", Integer))
>>> metadata.create_all(engine)

>>> with engine.begin() as conn:
...     conn.execute(insert(sales), [
...         {"product": "A", "amount": 100},
...         {"product": "A", "amount": 150},
...         {"product": "B", "amount": 200}])

>>> with engine.connect() as conn:
...     # Count all rows
...     result = conn.execute(select(func.count()).select_from(sales))
...     print(result.scalar())
...     # Sum with group by
...     stmt = select(sales.c.product, func.sum(sales.c.amount)).group_by(sales.c.product)
...     print(conn.execute(stmt).fetchall())
3
[('A', 250), ('B', 200)]

Drop Tables#

Tables can be dropped using the drop() method on a Table object or drop_all() on MetaData to drop all tables. The checkfirst parameter prevents errors if the table doesn’t exist. Be careful with these operations in production as they permanently delete data and schema. Always backup your database before dropping tables.

>>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, inspect
>>> engine = create_engine("sqlite:///:memory:")
>>> metadata = MetaData()
>>> users = Table("users", metadata, Column("id", Integer, primary_key=True))
>>> products = Table("products", metadata, Column("id", Integer, primary_key=True))
>>> metadata.create_all(engine)

>>> inspector = inspect(engine)
>>> sorted(inspector.get_table_names())
['products', 'users']

>>> # Drop single table
>>> users.drop(engine)
>>> sorted(inspect(engine).get_table_names())
['products']

>>> # Drop all tables
>>> metadata.drop_all(engine)
>>> inspect(engine).get_table_names()
[]