Combining QuerySets¶
Edgy's QuerySet API supports SQL-style set operations such as UNION, UNION ALL, INTERSECT, and EXCEPT.
Overview¶
| Operation | Description | SQL Equivalent |
|---|---|---|
.union(qs2) |
Combines both querysets, removing duplicates. | UNION |
.union_all(qs2) |
Combines both querysets, keeping duplicates. | UNION ALL |
.intersect(qs2) |
Returns only rows appearing in both querysets. | INTERSECT |
.except_(qs2) |
Returns rows from the first queryset that aren't in the second. | EXCEPT |
All methods return a CombinedQuerySet, which behaves just like a normal queryset.
You can continue chaining methods such as:
.order_by()
.limit()
.offset()
.only()
.defer()
.values()
.exists()
.count()
.first()
.last()
Example Models¶
import edgy
class Customer(edgy.StrictModel):
name = edgy.CharField(max_length=100)
country = edgy.CharField(max_length=2)
class Meta:
registry = models
class Order(edgy.StrictModel):
customer = edgy.ForeignKey(Customer, related_name="orders")
total = edgy.DecimalField(max_digits=10, decimal_places=2)
status = edgy.CharField(max_length=20)
class Meta:
registry = models
Union — Combine QuerySets¶
Combine multiple querysets of the same model.
# Customers from Switzerland and Germany
swiss = Customer.query.filter(country="CH")
german = Customer.query.filter(country="DE")
# Combine them into a single queryset
european = swiss.union(german).order_by("name")
customers = await european.all()
Generated SQL:
SELECT * FROM customer WHERE country = 'CH'
UNION
SELECT * FROM customer WHERE country = 'DE'
ORDER BY name;
This deduplicates automatically, preserving unique customers.
Union All — Keep Duplicates¶
recent_orders = Order.query.filter(status="recent")
priority_orders = Order.query.filter(status="priority")
combined_orders = recent_orders.union_all(priority_orders)
orders = await combined_orders.order_by("-total").limit(10)
This returns all matching rows, even duplicates.
Intersect — Shared Records Between Sets¶
active_customers = Customer.query.filter(status="active")
premium_customers = Customer.query.filter(plan="premium")
shared = active_customers.intersect(premium_customers)
customers = await shared.order_by("name")
Returns only customers that satisfy both filters.
Except — Subtract One Set from Another¶
all_customers = Customer.query.all()
has_orders = Customer.query.filter(orders__isnull=False)
new_customers = all_customers.except_(has_orders)
await new_customers.count()
Returns only customers without any orders.
Chaining and Nesting¶
You can freely chain set operations — Edgy handles them as combined SQL subqueries.
# (CH ∪ DE) ∪ FR
union_3 = (
Customer.query.filter(country="CH")
.union(Customer.query.filter(country="DE"))
.union(Customer.query.filter(country="FR"))
)
customers = await union_3.order_by("name")
You can also mix operations:
# (CH ∪ DE) - FR
qs = (
Customer.query.filter(country="CH")
.union(Customer.query.filter(country="DE"))
.except_(Customer.query.filter(country="FR"))
)
Behavior Details¶
Ordering, Limit, and Offset¶
Outer modifiers apply to the entire combined result:
top_customers = (
swiss.union(german)
.order_by("-id")
.limit(10)
)
Deferred Fields and only()¶
These propagate safely across combined querysets:
qs1 = Customer.query.only("id", "name")
qs2 = Customer.query.defer("country")
combined = qs1.union(qs2).order_by("name")
rows = await combined.values(["id", "name"])
Counting and Existence¶
await combined.exists()
await combined.count()
await combined.offset(5).limit(5)
Model and Registry Safety¶
Both sides of the operation must come from the same model and registry.
from edgy.exceptions import QuerySetError
with pytest.raises(QuerySetError):
await Customer.query.union(Order.query)
Complex Example — Filtering for Reporting¶
eu_customers = (
Customer.query.filter(country="DE")
.union(Customer.query.filter(country="CH"))
.except_(Customer.query.filter(orders__isnull=False))
.order_by("name")
.limit(20)
)
for c in await eu_customers:
print(c.name, c.country)
Find customers in the EU who are German or Swiss but haven't ordered yet.
Tip
Always include .order_by() when comparing lists to ensure deterministic results.
Summary¶
| Feature | Supported |
|---|---|
union() |
Deduplicates results |
union_all() |
Keeps duplicates |
intersect() |
Common records |
except_() |
Subtracts records |
| Cross-model operations | ❌ Raises QuerySetError |