Получить идентификаторы таблицы «многие ко многим»

#sql #django #many-to-many

Вопрос:

в джанго, если есть таблица со многими-многими полями, как это :

 class Category(models.Model):
    name = models.CharField(unique=True, max_length=255)
    icon = models.ImageField(upload_to='images')
    sport = models.ManyToManyField(Sport) 
 

когда вы пытаетесь получить все объекты из этой таблицы, вы делаете это :

 Category.objects.all()
 

и результат будет таким же после сериализации :

     "data": [
     {
       "id": 1,
       "name": '..'
       "icon": '...'
       "sport": [
          1,
          2
      ]
    ]
   }
 

итак, мой вопрос здесь, как достичь того же результата с помощью чистого sql
Я попробовал это :

 sql = '''select a.*,b.sport_id,array_agg(b.sport_id) as sport from category a join category_sport b on b.category_id=a.id group by a.id,b.sport_id ;'''
 

но результат был таким :

 "data": [
  {
   "id": 1,
    "name": '..'
    "icon": '...'
    "sport": [
      1,
      ]
  },
  {
    "id": 1,
    "name": '..'
    "icon": '...'
    "sport": [
          2
      ]
    ]
  }
 

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

1. я не понял вашего вопроса не будете ли вы, пожалуйста, более конкретны

2. я предполагаю, что вы хотите отобразить данные из базы данных в своем шаблоне

3. речь идет вовсе не о шаблоне django, а о sql, поэтому я хочу просто получить результат objects.all() в django в sql с множеством полей

Ответ №1:

Насколько я понимаю, если вам нужно добиться того , что вам просто нужно удалить b.sport_id , вы хотите объединить sport_id в массив для каждой категории, поэтому нет причин группироваться по этому полю, ваш SQL должен быть:

 sql = '''select a.id, array_agg(b.sport_id) as sport from category a join category_sport b on b.category_id=a.id group by a.id;'''
 

Помните, что все неагрегированные выбранные поля должны отображаться в разделе «Группировать по».

Другой альтернативой, если вы используете PostgreSQL, может быть использование ArrayAgg , что-то вроде:

 from django.contrib.postgres.aggregates import ArrayAgg

Category.objects.aggregate(sport=ArrayAgg('sport__id'))
 

Кроме того, вы можете увидеть этот очень простой пример PostgreSQL 13:

Схема:

 CREATE TABLE a (
  id INT,
  title VARCHAR(128)
);

CREATE TABLE b (
  id INT
);

CREATE TABLE ab (
  id INT,
  aid INT,
  bid INT
);

INSERT INTO a (id, title) VALUES (1, 'title_a');
INSERT INTO a (id, title) VALUES (2, 'title_b');

INSERT INTO b (id) VALUES (1);
INSERT INTO b (id) VALUES (2);
INSERT INTO b (id) VALUES (3);
INSERT INTO b (id) VALUES (4);
INSERT INTO b (id) VALUES (5);
INSERT INTO b (id) VALUES (6);

INSERT INTO ab (id, aid, bid) VALUES(1, 1, 1);
INSERT INTO ab (id, aid, bid) VALUES(2, 1, 3);
INSERT INTO ab (id, aid, bid) VALUES(3, 1, 5);
INSERT INTO ab (id, aid, bid) VALUES(4, 2, 2);
INSERT INTO ab (id, aid, bid) VALUES(5, 2, 4);
INSERT INTO ab (id, aid, bid) VALUES(6, 2, 6);

 

Запрос:

 SELECT a.id, a.title, array_agg(ab.bid) AS bs FROM a JOIN ab ON a.id=ab.aid GROUP BY a.id, a.title;
 

Результат:

 | id  | title   | bs      |
| --- | ------- | ------- |
| 2   | title_b | [2,4,6] |
| 1   | title_a | [1,3,5] |
 

Вы можете протестировать его на https://www.db-fiddle.com/

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

1. Вы не можете этого сделать, и вы получите ошибку с «b.sport_id» должен появиться в ГРУППЕ BY. Я думаю, что этого невозможно достичь всего за один sql-запрос

2. Вы правы! но это только потому, что вы включаете b.sport_id также в свой выбор, поэтому просто удалите его из выбора и group_by.Я обновил свой ответ, потому что я не понимал, что вы включаете это поле в первую очередь, я обновил свой ответ.

3. но есть проблема, если мы не вставляли элементы в таблицу ab, что происходит, когда мы создаем новую запись в таблице a с нулевым значением в поле manytomany, поэтому в этом случае, когда мы выполняем запрос, мы не получим записи с нулевым значением в поле manytomany

4. а также я обнаружил, что выполнение запроса занимает больше времени, чем должно, поэтому, как вы думаете, есть другой способ повысить производительность, например, использовать массив вместо array_agg

5. Не уверен, хорошо ли я понял, но нет причин устанавливать нулевое значение в поле ManyToManyField, помните, что это не реальное поле в базе данных, это менеджер , аналогичный .objects для моделей, вы можете использовать его для выполнения операций, таких как запросы или обновления в промежуточной таблице, вы можете увидеть здесь очень хорошие примеры