sqlalchemy unique across multiple columns
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
Sometimes a single column is not enough to define uniqueness. A database may allow the same value to appear many times on its own, while still requiring the combination of two or more columns to be unique, such as one slug per account or one seat assignment per event.
Declaring a Composite Unique Constraint
In SQLAlchemy, uniqueness across multiple columns is defined at the table level with UniqueConstraint. This is different from unique=True on a single column, which only protects that individual field.
Here is a runnable SQLAlchemy 2.x example that prevents the same email from joining the same team twice:
The database is the authority here. Even if two requests race each other, the constraint prevents invalid duplicate rows from being committed.
Why the Constraint Belongs in the Schema
It is tempting to check for duplicates in application code before inserting:
This pre-check can improve error messages, but it should never replace the database constraint. Two transactions can both pass the lookup and then race to insert. Only the unique constraint closes that gap.
That is why the usual production pattern is:
- Define
UniqueConstraintin the model. - Optionally perform a lookup for a friendly message.
- Still handle
IntegrityErroron commit.
Naming and Migrating the Constraint
Give the constraint an explicit name. That makes database errors clearer and helps migration tools generate predictable schema changes. In SQLAlchemy projects that use Alembic, the schema change belongs in a migration, not only in the Python model definition.
A concise model declaration is often enough:
With that schema, each account can reuse common slugs only if the account id is different. That pattern is very common in multi-tenant systems.
Common Pitfalls
- Using
unique=Trueon both columns and expecting pairwise uniqueness. That would force each column to be globally unique, which is a different rule. - Relying only on an application-level existence check. Race conditions still allow duplicates unless the database enforces the rule.
- Forgetting to handle
IntegrityError. The constraint is working when the exception happens, so the application should roll back and respond cleanly. - Leaving constrained columns nullable without understanding database behavior. Some databases treat multiple
NULLvalues as distinct inside unique constraints. - Updating the ORM model without creating the matching migration. The Python class alone does not change an existing production table.
Summary
- Use
UniqueConstraintin__table_args__when uniqueness depends on a combination of columns. - Keep the constraint in the database schema, not only in application code.
- Catch
IntegrityErrorand roll back the session when a duplicate insert is rejected. - Give constraints explicit names so migrations and operational debugging are easier.
- Review nullability carefully, because unique constraint behavior around
NULLcan vary by database engine.
Related reading
- SQLAlchemy What's the difference between flush and commit?
- sqlalchemy.exc.NoSuchModuleError Can't load plugin sqlalchemy.dialectspostgres
- SqlCommand Close and Dispose - which to call?
- SqlDataAdapter vs SqlDataReader
- sqlite3.ProgrammingError Incorrect number of bindings supplied. The current statement uses 1, and there are 74 supplied
- SSL InsecurePlatform error when using Requests package
- SqlDataAdapter.Fill - Asynchronous approach
- SqlDateTime.MinValue DateTime.MinValue, why?

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.