You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Django ORM中两次关联CustomerAddress表实现多条件过滤?

问题描述

我有以下两个Django模型:

class Customer(AbstractBaseModel):    
    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_column='uuid')
    name = models.CharField(max_length=55)

class CustomerAddress(AbstractBaseModel):
    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_column='uuid')
    customer = models.ForeignKey(Customer, on_delete=models.DO_NOTHING, related_name='addresses', db_column='customer_uuid')
    country_code = models.CharField(max_length=3)
    type = models.CharField(max_length=8, choices=[('shipping', 'shipping'), ('billing', 'billing')])

需求是允许用户通过billing_country和shipping_country字段过滤客户,可填其中一个或同时填两个。当同时填写两个字段时,需要两次关联CustomerAddress表(分别关联shipping和billing类型的地址)。我已用原生SQL实现该逻辑:

SELECT *
FROM customer c
LEFT OUTER JOIN address shipping_address ON shipping_address.customer_uuid = c.uuid AND shipping_address.type = 'shipping'
LEFT OUTER JOIN address billing_address ON billing_address.customer_uuid = c.uuid AND billing_address.type = 'billing'

并根据过滤条件追加WHERE子句:

# billing_country and shipping_country are sanitized at this point
filters = []
if billing_country:
    filters.append(f"billing_address.country_code = '{billing_country}'")
if shipping_country:
    filters.append(f"shipping_address.country_code = '{shipping_country}'")
where_part = 'WHERE ' + ' AND '.join(filters)

请问如何使用Django ORM实现该需求?


实现方案

方法一:使用FilteredRelation(与原生SQL逻辑完全匹配)

Django 2.0及以上版本支持FilteredRelation,可以给关联表添加过滤条件并赋予别名,完美对应原生SQL中的两次LEFT JOIN操作:

from django.db.models import FilteredRelation, Q

# 示例用户输入,实际从请求参数获取
billing_country = "CN"
shipping_country = "US"

# 先给两种类型的地址创建关联别名
queryset = Customer.objects.annotate(
    billing_address=FilteredRelation(
        'addresses', condition=Q(addresses__type='billing')
    ),
    shipping_address=FilteredRelation(
        'addresses', condition=Q(addresses__type='shipping')
    )
)

# 组装过滤条件
filters = Q()
if billing_country:
    filters &= Q(billing_address__country_code=billing_country)
if shipping_country:
    filters &= Q(shipping_address__country_code=shipping_country)

# 应用过滤
queryset = queryset.filter(filters)

该方法生成的SQL与你编写的原生SQL几乎一致,会生成两次LEFT JOIN,分别对应billing_address和shipping_address别名,再在WHERE子句中添加对应过滤条件。

方法二:使用Q对象组合过滤

如果不需要显式的关联别名,可直接用Q对象组合多条件过滤,Django会自动处理多次关联:

from django.db.models import Q

billing_country = "CN"
shipping_country = "US"

filters = Q()
if billing_country:
    filters &= Q(addresses__type='billing', addresses__country_code=billing_country)
if shipping_country:
    filters &= Q(addresses__type='shipping', addresses__country_code=shipping_country)

# 加distinct避免同一客户因多个符合条件地址返回重复结果
queryset = Customer.objects.filter(filters).distinct()

方法三:使用子查询过滤(适合大数据量场景)

如果追求性能,可使用子查询检查客户是否存在符合条件的地址,避免JOIN操作:

from django.db.models import Exists, OuterRef, Q

billing_country = "CN"
shipping_country = "US"

filters = Q()
if billing_country:
    billing_subquery = CustomerAddress.objects.filter(
        customer=OuterRef('pk'),
        type='billing',
        country_code=billing_country
    )
    filters &= Exists(billing_subquery)
if shipping_country:
    shipping_subquery = CustomerAddress.objects.filter(
        customer=OuterRef('pk'),
        type='shipping',
        country_code=shipping_country
    )
    filters &= Exists(shipping_subquery)

queryset = Customer.objects.filter(filters)

内容的提问来源于stack exchange,提问作者ზურა კუპატაძ</think_never_used_51bce0c785ca2f68081bfa7d91973934>

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 15:42:45