Skip to content

Commit 4d22359

Browse files
committed
fix: address review feedback — match Tiangolo's docs style
1 parent 14cd392 commit 4d22359

File tree

1 file changed

+19
-112
lines changed

1 file changed

+19
-112
lines changed

docs/advanced/constraints.md

Lines changed: 19 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,156 +1,63 @@
11
# Database Constraints
22

3-
Database constraints are rules that ensure data integrity and prevent invalid data from being inserted into your database. **SQLModel** supports several types of constraints through **SQLAlchemy**.
3+
In some cases you might want to enforce rules about your data directly at the **database level**. For example, making sure that a hero's name is unique, or that their age is never negative.
44

5-
These constraints are enforced at the **database level**, which means they work regardless of which application is inserting the data. This is particularly important for data consistency and reliability in production systems.
5+
These rules are called **constraints**, and because they live in the database, they work regardless of which application is inserting the data. This is particularly important for data consistency in production systems.
66

77
/// info
88

9-
**SQLModel** uses <a href="https://docs.sqlalchemy.org/en/20/core/constraints.html" class="external-link" target="_blank">SQLAlchemy's constraint system</a> under the hood, giving you access to all the powerful constraint options available in SQLAlchemy.
9+
**SQLModel** uses <a href="https://docs.sqlalchemy.org/en/20/core/constraints.html" class="external-link" target="_blank">SQLAlchemy's constraint system</a> under the hood, so you have access to all the powerful constraint options available in SQLAlchemy.
1010

1111
///
1212

1313
## Unique Constraints
1414

15-
Unique constraints ensure that certain fields or combinations of fields have unique values across all rows in the table.
15+
Let's say you want to make sure that no two heroes can have the same name. The simplest way to do this is with the `unique` parameter in `Field()`:
1616

17-
### Single Column Unique Constraints
17+
{* ./docs_src/advanced/constraints/tutorial001_py310.py ln[4:8] hl[5] *}
1818

19-
The simplest way to add a unique constraint is using the `unique` parameter in the `Field()` function:
19+
Now the `name` field must be unique across all heroes. If you try to insert a hero with a name that already exists, the database will raise an error.
2020

21-
{* ./docs_src/advanced/constraints/tutorial001_py310.py ln[4:8] hl[6] *}
21+
So two heroes named "Deadpond" and "Spider-Boy" would work fine, but trying to add a second "Deadpond" would fail.
2222

23-
In this example, the `name` field must be unique across all heroes. If you try to insert a hero with a name that already exists, the database will raise an error.
23+
## Multi-Column Unique Constraints
2424

25-
**Valid examples:**
26-
* Two heroes with names "Deadpond" and "Spider-Boy"
27-
* Heroes with the same age but different names
25+
Sometimes you don't need each individual field to be unique, but you want a **combination** of fields to be unique. For example, you might allow multiple heroes named "Spider-Boy" as long as they have different ages.
2826

29-
🚫 **Invalid examples:**
30-
* Two heroes both named "Deadpond"
31-
* Inserting a hero with a name that already exists in the database
32-
33-
### Multi-Column Unique Constraints
34-
35-
Sometimes you want a combination of fields to be unique, even though each individual field can have duplicate values. You can achieve this using `__table_args__` with `UniqueConstraint`:
27+
You can do this using `__table_args__` with a `UniqueConstraint`:
3628

3729
{* ./docs_src/advanced/constraints/tutorial002_py310.py ln[5:11] hl[6] *}
3830

39-
In this example, the combination of `name` and `age` must be unique. This means you can have multiple heroes with the same name (as long as they have different ages), and you can have multiple heroes with the same age (as long as they have different names).
40-
41-
**Valid examples:**
42-
* "Spider-Boy" aged 16 and "Spider-Boy" aged 25 (same name, different ages)
43-
* "Spider-Boy" aged 16 and "Iron Man" aged 16 (different names, same age)
44-
45-
🚫 **Invalid examples:**
46-
* Two heroes both named "Spider-Boy" and both aged 16
31+
With this setup, "Spider-Boy" aged 16 and "Spider-Boy" aged 25 are both allowed, because the **combination** of name and age is different. But two heroes both named "Spider-Boy" and both aged 16 would be rejected.
4732

4833
/// tip
4934

50-
You can include as many fields as needed in a `UniqueConstraint`. For example: `UniqueConstraint("name", "age", "team")` would make the combination of all three fields unique.
35+
You can include as many fields as needed in a `UniqueConstraint`. For example, `UniqueConstraint("name", "age", "team")` would require the combination of all three fields to be unique.
5136

5237
///
5338

5439
## Check Constraints
5540

56-
Check constraints allow you to define custom validation rules using SQL expressions. These are more flexible than basic type validation and can enforce business rules at the database level.
41+
Check constraints let you define custom validation rules using SQL expressions. This is handy for enforcing business rules, like making sure a hero's age is never negative:
5742

5843
{* ./docs_src/advanced/constraints/tutorial003_py310.py ln[5:11] hl[6] *}
5944

60-
In this example, the check constraint ensures that the `age` field cannot be negative. The constraint has a name (`age_non_negative`) which makes error messages clearer and allows you to reference it later if needed.
61-
62-
**Valid examples:**
63-
* Heroes with age 0, 16, 25, 100, etc.
64-
* Any non-negative integer for age
65-
66-
🚫 **Invalid examples:**
67-
* Heroes with negative ages like -5, -1, etc.
68-
69-
/// info
70-
71-
Check constraints can use any valid SQL expression supported by your database. Common examples include:
45+
Here we're saying that `age` must be greater than or equal to zero. The `name` parameter gives the constraint a descriptive label, which makes error messages much easier to understand.
7246

73-
- **Range checks:** `age BETWEEN 0 AND 150`
74-
- **String length:** `LENGTH(name) >= 2`
75-
- **Pattern matching:** `email LIKE '%@%'`
76-
- **Value lists:** `status IN ('active', 'inactive', 'pending')`
77-
78-
///
79-
80-
### Naming Check Constraints
81-
82-
It's a good practice to always give your check constraints descriptive names using the `name` parameter. This makes debugging easier when constraint violations occur:
83-
84-
```python
85-
CheckConstraint("age >= 0", name="age_non_negative")
86-
CheckConstraint("LENGTH(name) >= 2", name="name_min_length")
87-
CheckConstraint("email LIKE '%@%'", name="email_format")
88-
```
47+
So heroes with age 0, 16, or 100 would all be fine, but trying to insert a hero with age -5 would fail.
8948

9049
## Combining Multiple Constraints
9150

92-
You can combine different types of constraints in the same table by adding multiple constraint objects to `__table_args__`:
51+
You can mix different types of constraints in the same model by adding multiple constraint objects to `__table_args__`:
9352

9453
{* ./docs_src/advanced/constraints/tutorial004_py310.py ln[5:15] hl[6:10] *}
9554

96-
This example combines:
97-
- A unique constraint on the combination of `name` and `age`
98-
- A check constraint ensuring age is non-negative
99-
- A check constraint ensuring name has at least 2 characters
100-
101-
/// tip
102-
103-
When combining constraints, remember that **all constraints must be satisfied** for data to be inserted successfully. Design your constraints carefully to ensure they work together and don't create impossible conditions.
104-
105-
///
106-
107-
## Constraint Violation Errors
108-
109-
When constraints are violated, SQLAlchemy will raise exceptions. It's good practice to handle these in your application:
110-
111-
```python
112-
from sqlalchemy.exc import IntegrityError
113-
114-
try:
115-
with Session(engine) as session:
116-
# Trying to insert duplicate data
117-
hero = Hero(name="Deadpond", age=48, secret_name="Dive Wilson")
118-
session.add(hero)
119-
session.commit()
120-
except IntegrityError as e:
121-
print(f"Constraint violation: {e}")
122-
session.rollback()
123-
```
124-
125-
## Database Support
55+
This model has three constraints working together: the combination of `name` and `age` must be unique, age cannot be negative, and the name must be at least 2 characters long. All constraints must be satisfied for data to be inserted successfully.
12656

12757
/// warning
12858

129-
Not all databases support all types of constraints equally:
130-
131-
- **SQLite:** Supports unique constraints and basic check constraints, but has limitations with some complex SQL expressions
132-
- **PostgreSQL:** Full support for all constraint types with rich SQL expression support
133-
- **MySQL:** Good support for most constraints, with some syntax differences in check constraints
134-
- **SQL Server:** Full support for all constraint types
59+
Not all databases support all types of constraints equally. In particular, **SQLite** has limitations with some complex SQL expressions in check constraints. Make sure to test your constraints with your target database.
13560

136-
Always test your constraints with your target database to ensure compatibility.
61+
Most other SQL databases like **PostgreSQL** and **MySQL** have full or near-full support. 🎉
13762

13863
///
139-
140-
## Best Practices
141-
142-
🎯 **Use meaningful constraint names** - This makes debugging easier when violations occur
143-
144-
🎯 **Combine field-level and table-level constraints** - Use `Field(unique=True)` for simple cases and `__table_args__` for complex ones
145-
146-
🎯 **Consider performance** - Unique constraints automatically create indexes, which can improve query performance
147-
148-
🎯 **Handle constraint violations gracefully** - Always wrap database operations in try-catch blocks when constraints might be violated
149-
150-
🎯 **Document your constraints** - Make sure your team understands what business rules the constraints enforce
151-
152-
/// info
153-
154-
Remember that **SQLModel** constraints are implemented using **SQLAlchemy**, so you have access to all the power and flexibility of SQLAlchemy's constraint system. For more advanced use cases, check the <a href="https://docs.sqlalchemy.org/en/20/core/constraints.html" class="external-link" target="_blank">SQLAlchemy constraints documentation</a>.
155-
156-
///

0 commit comments

Comments
 (0)