Почему django queryset.extra() выдает OperationalError: (1242, ‘Подзапрос возвращает больше 1 строки’)?

#python #mysql #django #django-models

#python #mysql #django #django-модели

Вопрос:

Я использую Django ORM с MySQL и трачу много времени на запросы. Для некоторых «продвинутых» случаев я решил использовать необработанные запросы, поскольку для них я не мог сделать это с помощью аннотаций. Проблема с необработанными запросами заключается в том, что они не добавляют «поле» в набор запросов, например аннотации или агрегации. Итак, я использую extra. Но теперь я столкнулся с проблемой:

 qs_products = Productos.objects.all()
qs_productos.extra({
    "stock":
    """
        SELECT SUM(items.cantidad)
        FROM 
            `encargosProveedor_listado_articulos` AS encargos, 
            `itemArticulosProveedor`AS items, `articulos` as articulos
        WHERE 
            encargos.itemarticulosproveedor_id=items.id and 
            articulos.id=items.articulos_id
        GROUP BY articulos.producto_id
    """
    })
 

Это результат для этого запроса непосредственно от моего администратора БД:

  --------------------- 
| SUM(items.cantidad) |
 --------------------- 
|          14         |
 --------------------- 
|          4          |
 --------------------- 
 

Но при запуске этого кода в django с использованием extra()

MySQLdb._exceptions.OperationalError: (1242, ‘Подзапрос возвращает более 1 строки’)

В чем проблема возврата более одной строки?Запрос возвращает две строки, потому что у меня есть два продукта, это разумно. Я хочу назначить запас для каждого из продуктов.

Альтернативы? Предложения? Подсказки?

Ответ №1:

Вы пытаетесь выполнить соединение между внешним запросом и extra подзапросом, и для этого базе данных требуется явное предложение join . Я полагаю, вы можете добавить предложение WHERE, чтобы extra оно работало:

 qs_productos.extra({
    "stock":
    """
        SELECT SUM(items.cantidad)
        FROM 
            `encargosProveedor_listado_articulos` AS encargos, 
            `itemArticulosProveedor`AS items, `articulos` as articulos
        WHERE 
            articulos.producto_id = productos.id and
            encargos.itemarticulosproveedor_id=items.id and 
            articulos.id=items.articulos_id       
        GROUP BY articulos.producto_id
    """
})
 

productos in articulos.producto_id = productos.id нужно было бы заменить фактическим именем таблицы из основного запроса.

Поскольку extra он устарел (еще не совсем устарел, хотя начиная с Django 2.2), вот эквивалентный RawSQL запрос:

 qs_productos.annotate(stock=RawSQL(
    """
        SELECT SUM(items.cantidad)
        FROM 
            `encargosProveedor_listado_articulos` AS encargos, 
            `itemArticulosProveedor`AS items, `articulos` as articulos
        WHERE 
            articulos.producto_id = productos.id and
            encargos.itemarticulosproveedor_id=items.id and 
            articulos.id=items.articulos_id       
        GROUP BY articulos.producto_id
    """,
    ()
))
 

Пустой кортеж в RawSQL требуется, поскольку функция принимает params аргумент кортежа, который не является необязательным, даже если у вас нет параметров для передачи.

В качестве бонуса RawSQL менее чувствителен к предыдущим values вызовам и output_field при необходимости предлагает необязательный параметр.

Комментарии:

1. Спасибо! Это сработало. Но все же я не получаю поле ‘stock’ в своем наборе запросов. Вы знаете, почему?

2. Единственная причина, о которой я могу думать, — это использовать values() before extra() , и в этом случае extra поле автоматически отбрасывается .

3. Нет, я использую values() после запроса, чтобы показать / посмотреть, есть ли запасное поле.

4. Затем вам нужно будет отладить ошибку, которая скрывается где-то в вашем коде. Или вы можете попробовать RawSQL альтернативу, предложенную выше, возможно, это устранит проблему.