Джанго ОРМ Кросс Продукт

У меня есть три модели:

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.

Это все не проверено, извините, если вам оставили домашнее задание.

Другие вопросы по тегам