Django: доступ к правосторонней информации из левых соединений - PullRequest
1 голос
/ 04 июня 2019

Я делаю сложные запросы LEFT JOIN, но я не уверен, как извлечь информацию из объединенных таблиц. Я вижу множество примеров, которые используют LEFT JOIN для фильтрации результатов, но я не вижу никого, кто бы получал доступ к каким-либо значениям строки из LEFT JOIN. Во-первых, давайте начнем с нескольких упрощенных примеров моделей в качестве примера:

class Bar(models.Model):
    name = models.CharField(max_length=255)
    status = models.IntegerField(default=0)
    data = models.TextField(default=None)

    class Meta:
        db_table = 'bar'

class FooBar(models.Model):
    bar = models.ForeignKey(Bar, on_delete=models.CASCADE, related_name='foobars')
    status = models.IntegerField(default=0)
    data = models.TextField(default=None)

    class Meta:
        db_table = 'foobar'

class FuBar(models.Model):
    bar = models.ForeignKey(Bar, on_delete=models.CASCADE, related_name='fubars')
    status = models.IntegerField(default=0)
    data = models.TextField(default=None)

    class Meta:
        db_table = 'fubar'

Таблица Bar имеет отношение «один ко многим» с таблицами FooBar и FuBar. Это также означает, что FooBar и FuBar имеют неявное отношение «многие ко многим».

Вот RAW-эквивалент того, что я пытаюсь сделать:

SELECT
    foobar.id,
    foobar.status,
    foobar.data,
    bar.id,
    bar.status,
    bar.data,
    fubar.id,
    fubar.status,
    fubar.data
FROM foobar
JOIN bar ON bar.id = foobar.bar_id
LEFT JOIN fubar ON fubar.bar_id = bar.id
WHERE
    foobar.status = 1
    AND bar.status = 1
    AND fubar.status = 1;

Когда я смотрю на каждую запись в табличном наборе результатов, у меня есть вся информация для идентификации отдельных записей FooBar, Bar и Fubar, связанных с каждой строкой, и всех их значений столбцов. Давайте предположим, что мне нужно иметь доступ к каждому из этих столбцов в наборе результатов после завершения запроса.

Я могу воспроизвести те же самые соединения в Django:

q = (FooBar.objects
     .filter(status=1,
             bar__status=1,
             bar__fubars__status=1)
     .select_related('bar'))

Если я перебираю набор результатов, я могу получить доступ к объектам FooBar и Bar без необходимости выполнять какие-либо дополнительные запросы неявно или явно. Следующий фрагмент кода иллюстрирует, как я могу получить доступ к их информации.

for fb in q:
    foobar = fb
    foobar_id = fb.id
    foobar_status = fb.status
    foobar_data = fb.data
    bar = foobar.bar
    bar_id = fb.bar.id
    bar_status = fb.bar.status
    bar_data = fb.bar.data

    fubar = ???
    fubar_id = ???
    fubar_status = ???
    fubar_data = ???

Как получить доступ к отдельному объекту FuBar, связанному с каждой итерацией? В идеале я хотел бы иметь возможность получить его, не выполняя никаких дополнительных запросов в фоновом режиме, если это возможно.

Заранее спасибо.

--- редактировать ---

Я обновил описание проблемы выше, чтобы немного яснее понять точную природу проблемы. Я специально ищу отдельную конкретную запись FuBar, связанную с каждой строкой набора результатов. Обратите внимание, что следующее:

foobar.bar.fubars

Это не то, что я хочу. Короче говоря, это возвращает набор FuBar записей, относящихся к записи Bar в строке текущего набора результатов. Он не говорит мне, какая конкретная запись была LEFT JOIN'd для текущей строки в наборе результатов.

1 Ответ

1 голос
/ 04 июня 2019

Это не поможет без лишних запросов, но я думаю, что, по крайней мере, даст вам ожидаемые результаты.Поскольку я работаю над рабочими проектами, мой мозг может быть недостаточно сфокусирован.Но идея заключалась в том, чтобы аннотировать в идентификаторе FuBar из соответствующей строки строки FooBar, а затем выполнить его при итерации (что, конечно, является источником дополнительных запросов).

Я могу продолжать подключатьпрочь, чтобы увидеть, если что-нибудь еще приходит на ум.

foobars = FooBar.objects.filter(status=1, bar__status=1, bar__fubars__status=1)
fubar = FuBar.objects.filter(bar=OuterRef('bar')).values('id')
foobars = foobars.annotate(fubar_id=Subquery(fubar[:1]))
foobars = foobars.select_related('bar')

for foobar in foobars:
    bar = foobar.bar

    fubar = bar.fubars.filter(id=foobar.fubar_id).get()
...