Django ORM 交叉产品
Django ORM Cross Product
我有三个模型:
class Customer(models.Model):
pass
class IssueType(models.Model):
pass
class IssueTypeConfigPerCustomer(models.Model):
customer=models.ForeignKey(Customer)
issue_type=models.ForeignKey(IssueType)
class Meta:
unique_together=[('customer', 'issue_type')]
如何在没有 IssueTypeConfigPerCustomer 对象的情况下找到 (customer, issue_type) 的所有元组?
我想避免 Python 中的循环。首选在数据库中解决此问题的解决方案。
背景:对于每个客户和每个问题类型,数据库中都应该有一个配置。
如果您有能力为每种问题类型进行一次数据库访问,请尝试类似以下未经测试的代码段:
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())
这可能没问题,除非你有很多问题类型或者如果你每秒这样做几次,但你也可以考虑有一个合理的默认值,而不是为每个问题类型的组合强制配置对象和客户(恕我直言,这有点设计味道)。
[更新]
I updated the question: I want to avoid a loop in Python. A solution which solves this in the DB would be preferred.
在 Django 中,每个 Queryset
要么是一个模型实例列表,要么是一个 dict
(values
查询集),所以不可能 return 你的格式想要(模型的 tuples
的列表)而没有一些 Python(并且可能多次访问数据库)。
最接近叉积的是使用不带 where
参数的 "extra" 方法,但它涉及原始 SQL 并知道基础 table 名称对于其他型号:
missing = Customer.objects.extra(
select={"issue_type_id": 'appname_issuetype.id'},
tables=['appname_issuetype']
)
因此,每个 Customer 对象都会有一个额外的属性 "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
参数中即可。
为了 assemble (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
)
"""]
)
]
不仅这需要与基于 "generator" 的版本相同的数据库访问次数,而且恕我直言,它在 table 方面也更少,可读性更差并且违反了 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]
底线:在最好的情况下,您会以易读性和可移植性为代价进行两次查询。与 Customer 和 IssueType 的每个组合的强制配置相比,具有合理的默认值可能是更好的设计。
这都是未经测试的,抱歉,如果有一些功课留给你。
我有三个模型:
class Customer(models.Model):
pass
class IssueType(models.Model):
pass
class IssueTypeConfigPerCustomer(models.Model):
customer=models.ForeignKey(Customer)
issue_type=models.ForeignKey(IssueType)
class Meta:
unique_together=[('customer', 'issue_type')]
如何在没有 IssueTypeConfigPerCustomer 对象的情况下找到 (customer, issue_type) 的所有元组?
我想避免 Python 中的循环。首选在数据库中解决此问题的解决方案。
背景:对于每个客户和每个问题类型,数据库中都应该有一个配置。
如果您有能力为每种问题类型进行一次数据库访问,请尝试类似以下未经测试的代码段:
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())
这可能没问题,除非你有很多问题类型或者如果你每秒这样做几次,但你也可以考虑有一个合理的默认值,而不是为每个问题类型的组合强制配置对象和客户(恕我直言,这有点设计味道)。
[更新]
I updated the question: I want to avoid a loop in Python. A solution which solves this in the DB would be preferred.
在 Django 中,每个 Queryset
要么是一个模型实例列表,要么是一个 dict
(values
查询集),所以不可能 return 你的格式想要(模型的 tuples
的列表)而没有一些 Python(并且可能多次访问数据库)。
最接近叉积的是使用不带 where
参数的 "extra" 方法,但它涉及原始 SQL 并知道基础 table 名称对于其他型号:
missing = Customer.objects.extra(
select={"issue_type_id": 'appname_issuetype.id'},
tables=['appname_issuetype']
)
因此,每个 Customer 对象都会有一个额外的属性 "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
参数中即可。
为了 assemble (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
)
"""]
)
]
不仅这需要与基于 "generator" 的版本相同的数据库访问次数,而且恕我直言,它在 table 方面也更少,可读性更差并且违反了 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]
底线:在最好的情况下,您会以易读性和可移植性为代价进行两次查询。与 Customer 和 IssueType 的每个组合的强制配置相比,具有合理的默认值可能是更好的设计。
这都是未经测试的,抱歉,如果有一些功课留给你。