如果你能够为每个问题类型承担一次数据库访问的成本,可以尝试以下未经测试的代码片段:
def lacking_configs():
for issue_type in IssueType.objects.all():
for customer in Customer.objects.filter(
issuetypeconfigpercustomer__issue_type=None
):
yield customer, issue_type
missing = list(lacking_configs())
这可能是可以接受的,除非您有很多问题类型,或者如果您每秒要执行此操作几次,但您也可以考虑设置一个明智的默认值,而不是使配置对象成为每种问题类型和客户组合的必需品(在我看来,这有点像设计上的问题)。
【更新】
我更新了问题:我想避免在Python中使用循环。最好是在数据库中解决此问题的解决方案。
在Django中,每个Queryset都是Model实例的列表或dict(values查询集),因此无法返回所需的格式(Model元组列表)而不进行一些Python处理(可能需要多次访问数据库)。最接近交叉积的方法是使用“extra”方法而没有where参数,但它涉及原始SQL并且需要知道其他模型的底层表名:
missing = Customer.objects.extra(
select={"issue_type_id": 'appname_issuetype.id'},
tables=['appname_issuetype']
)
因此,每个客户对象将具有额外的属性“issue_type_id”,其中包含一个IssueType的id。您可以使用“where”参数基于“NOT EXISTS(SELECT 1 FROM appname_issuetypeconfigpercustomer WHERE issuetype_id = appname_issuetype.id AND customer_id = appname_customer.id)”进行过滤。使用“values”方法,您可以获得接近您想要的东西 - 这可能足以验证规则并创建缺少的记录。如果您需要IssueType中的其他字段,请在“select”参数中包含它们。
为了组装一个
(Customer,IssueType)列表,您需要类似以下内容的东西:
cross_product = [
(customer, IssueType.objects.get(pk=customer.issue_type_id))
for customer in
Customer.objects.extra(
select={"issue_type_id": 'appname_issuetype.id'},
tables=['appname_issuetype'],
where=["""
NOT EXISTS (
SELECT 1
FROM appname_issuetypeconfigpercustomer
WHERE issuetype_id=appname_issuetype.id
AND customer_id=appname_customer.id
)
"""]
)
]
我认为这种方法不仅需要与“生成器”版本相同数量的数据库访问,而且不够便携,难以阅读,并且违反了DRY原则。 我想你可以使用类似以下代码将数据库查询次数降低到两次:
missing = Customer.objects.extra(
select={"issue_type_id": 'appname_issuetype.id'},
tables=['appname_issuetype'],
where=["""
NOT EXISTS (
SELECT 1
FROM appname_issuetypeconfigpercustomer
WHERE issuetype_id=appname_issuetype.id
AND customer_id=appname_customer.id
)
"""]
)
issue_list = dict(
(issue.id, issue)
for issue in
IssueType.objects.filter(
pk__in=set(m.issue_type_id for m in missing)
)
)
cross_product = [(c, issue_list[c.issue_type_id]) for c in missing]
总之,最好的情况是你只需要进行两个查询,但这可能会牺牲可读性和可移植性。与为每个客户和问题类型组合强制配置相比,拥有合理的默认设置可能是更好的设计。
这些都没有经过测试,如果还有一些问题需要解决,抱歉让你费心了。