Джанго ОРМ Кросс Продукт
У меня есть три модели:
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')]
Как я могу найти все кортежи (custmer, issue_type), где нет объекта IssueTypeConfigPerCustomer?
Я хочу избежать цикла в Python. Решение, которое решает это в БД, было бы предпочтительным.
Предыстория: для каждого клиента и для каждого типа проблемы должна быть конфигурация в БД.
1 ответ
Если вы можете позволить себе одну поездку в базу данных для каждого типа проблемы, попробуйте что-то вроде этого непроверенного фрагмента:
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. Решение, которое решает это в БД, было бы предпочтительным.
В Джанго каждый Queryset
это либо список экземпляров модели, либо dict
(values
наборы запросов), поэтому невозможно вернуть нужный формат (список tuples
модели) без некоторого Python (и, возможно, несколько поездок в базу данных).
Наиболее близким к перекрестному продукту будет использование "дополнительного" метода без where
параметр, но он включает в себя сырой SQL и знание имени базовой таблицы для другой модели:
missing = Customer.objects.extra(
select={"issue_type_id": 'appname_issuetype.id'},
tables=['appname_issuetype']
)
В результате каждый объект Customer будет иметь дополнительный атрибут "Issue_type_id", содержащий идентификатор одного IssueType. Вы можете использовать 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]
Итог: в лучшем случае вы делаете два запроса за счет разборчивости и переносимости. Наличие разумных значений по умолчанию, вероятно, является лучшим дизайном по сравнению с обязательными настройками для каждой комбинации Customer и IssueType.
Это все не проверено, извините, если вам оставили домашнее задание.