Использование django bulk_create с отношениями M2M

У меня есть куча объектов в объекте ответа, которые я сохраняю в базе данных. Выполнение этого объекта за объектом очень медленно, поскольку это, по сути, означает, что если его 30 000 объектов, он сделает 30 000 коммитов в базу данных, на самом деле.

Пример 1:

for obj in response['RESULTS']:

    _city = City.objects.create(
        id=obj['id'],
        name=obj['name'],
        shortname=obj['shortname'],
        location=obj['location'],
        region=region_fk
    )

    _events = Event.objects.get(pk=obj['Event'])
    _events.city_set.add(_city)

Мой новый подход к реализации bulk_create() выглядит примерно так:

Пример 2:

bulk_list = []

for obj in response['RESULTS']:

    # get the foreignkey instead of duplicating data

    if obj.get('Region'):
        region_fk = Region.objects.get(pk=obj['Region'])      

    bulk_list.append(
        City(
            id=obj['id'],
            name=obj['name'],
            shortname=obj['shortname'],
            location=obj['location'],
            region=region_fk
        )
    )

bulk_save = City.objects.bulk_create(bulk_list)

Хотя это намного быстрее, чем моя предыдущая попытка, у нее есть проблема, теперь я не знаю, как добавить свои отношения M2M.

модели.py

class City(models.Model):

    id = models.CharField(primary_key=True, max_length=64)
    name = models.CharField(max_length=32)
    shortname = models.CharField(max_length=32)
    location = models.CharField(max_length=32)
    region = models.ForeignKey(max_length=32)
    events = models.ManyToManyField(Event)


class Event(models.Model):

    id = models.CharField(primary_key=True, max_length=64)
    description = models.TextField()
    date = models.DateTimeField()

class Region(models.Model):

    id = models.IntegerField(primary_key=True)

Вопросы

Я просмотрел stackoverflow и нашел несколько примеров, но я не понимаю их полностью. Кажется, что в большинстве ответов говорится о массовом_создании отношений M2M также с помощью модели through, и я не уверен, что это то, что я ищу.

  1. Как я могу добавить эти отношения M2M?
  2. Пожалуйста, разбейте это, чтобы я мог понять, я хочу учиться :-)

Любая помощь или указатели высоко ценятся. Спасибо.

Другая информация

Я бегу:

  • postgresql
  • Джанго == 1.11

Похожие сообщения

Документы Django по этой теме

Пример ответа:

"RESULT": [
  {
    "City": [
      {
        "id": "349bc6ab-1c82-46b9-889e-2cc534d5717e",
        "name": "Stockholm",
        "shortname": "Sthlm",
        "location": "Sweden",
        "region": [
          2
        ],
        "events": [
          {
            "id": "989b6563-97d2-4b7d-83a2-03c9cc774c21",
            "description": "some text",
            "date": "2017-06-19T00:00:00"
          },
          {
            "id": "70613514-e569-4af4-b770-a7bc9037ddc2",
            "description": "some text",
            "date": "2017-06-20T00:00:00"
          },
            {
            "id": "7533c16b-3b3a-4b81-9d1b-af528ec6e52b",
            "description": "some text",
            "date": "2017-06-22T00:00:00"
          },
      }
  }
]

person sphrak    schedule 18.06.2017    source источник
comment
Пожалуйста, добавьте соответствующие модели к вашему вопросу.   -  person Klaus D.    schedule 19.06.2017
comment
Я добавил models.py для завершения. Спасибо   -  person sphrak    schedule 19.06.2017


Ответы (1)


Это зависит.

Если у вас есть отношения M2M без явной модели through, то возможным решением с Django ORM будет:

from itertools import groupby

# Create all ``City`` objects (like you did in your second example):
cities = City.objects.bulk_create(
    [
        City(
            id=obj['id'],
            name=obj['name'],
            shortname=['shortname'],
            location=['location'],
            region=['region']
        ) for obj in response['RESULTS']
    ]
)

# Select all related ``Event`` objects.
events = Event.objects.in_bulk([obj['Event'] for obj in response['RESULTS']])

# Add all related cities to corresponding events:
for event_id, event_cities_raw in groupby(response['RESULTS'], lambda x: x['Event']):
    event = events[event_id]
    # To avoid DB queries we can gather all cities ids from response
    city_ids = [city['id'] for city in event_cities_raw]
    # And get saved objects from bulk_create result, which are required for ``add`` method.
    event_cities = [city for city in cities if city.pk in city_ids]
    event.city_set.add(*event_cities)

1 запрос bulk_create, 1 запрос in_bulk + 1 запрос для каждого уникального события в ответе (event.city_set.add по умолчанию выполняет одиночный запрос UPDATE).

С явной моделью through должна быть возможность использовать другой bulk_create для этой модели, другими словами, заменить все запросы event.city_set.add одним ExplicitThrough.objects.bulk_create.

Возможно, вам нужно будет обрабатывать ситуацию, когда Событие от response['RESULTS'] не существует, тогда вам придется создавать эти объекты с другим bulk_create.

Ответ на ваш комментарий:

Если в response['RESULTS'] будут какие-то события, которых нет в БД. В этом случае вы можете сделать еще один bulk_create прямо под Event.objects.in_bulk запросом:

new_events = Event.objects.create_bulk([obj['Event'] for obj in response['RESULTS'] if obj['Event']['id'] not in events])

Но здесь это зависит от структуры объекта в response['RESULTS']. Но вообще здесь нужно создавать недостающие события. Это должно быть быстрее, чем использование вызовов Event.objects.get_or_create.

person Stranger6667    schedule 18.06.2017
comment
Спасибо за интересный и пояснительный ответ, это вроде работает, но вы правы в том, что мне нужно обработать ситуацию, когда в ответе и в базе данных нет события. Поскольку я новичок в python и django, где должна происходить эта проверка? редактировать: отправил ответ до того, как я закончил писать первый, исправлено - person sphrak; 19.06.2017
comment
Я обновил ответ. Уточните, пожалуйста, как выглядит отдельный элемент из response['RESULTS']? - person Stranger6667; 19.06.2017
comment
Здравствуйте, я обновил пример ответа в json в исходном сообщении. В предоставленном вами коде вы говорите event = events[event_id], а затем event.city_set.add(*event_cities) - разве event не должно быть экземпляром события? как event = Event.objects.get(event_id=obj['id'])? а потом event.city_set.add(*event_cities)? Извините, если смущаю вас своим собственным замешательством :) - person sphrak; 19.06.2017
comment
[..] еще раз подумав об этом, что, если бы событие также имело поле внешнего ключа для какой-либо другой модели? Что тогда произойдет? В настоящее время я немного озадачен этим, кажется, мне нужно что-то, что связывает все вместе, чтобы полностью понять, что здесь происходит. - person sphrak; 19.06.2017
comment
in_bulk возвращает такой словарь {‹id›: ‹Event instance›}, так что этот код должен работать :) - person Stranger6667; 19.06.2017
comment
Для внешних ключей в Event все может быть так — создать/выбрать все связанные объекты (так же, как существующие/не существующие события), добавить их в словари объектов Event и создать события, как указано выше - person Stranger6667; 19.06.2017
comment
Хорошо, я думаю, я понял, я собираюсь еще немного поэкспериментировать с этим и отвечу, как только у меня будет несколько работающее решение. А пока спасибо, что нашли время объяснить :) - person sphrak; 19.06.2017
comment
Привет еще раз, я экспериментировал с некоторыми сейчас, и, похоже, это работает, но мне нужно провести больше испытаний. Еще один, может быть, глупый вопрос, но в вашем примере вы используете понимание списка [...] for obj in response['RESULT'] - но где мне поставить проверку перед добавлением объекта в список? У меня есть некоторые значения по умолчанию, которые нужно поместить в список, если в ответе есть None. - person sphrak; 19.06.2017
comment
Если вы хотите добавить какую-то проверку, то вы можете использовать if: [i for i in response['RESULTS'] if i['description'] is not None] - в этом коде результирующий список будет содержать только объекты, которые не имеют значения описания None. Если вам нужно указать некоторые значения по умолчанию, вы можете использовать метод .get() dict - [{'id': obj['id'], 'description': obj.get('description', 'DEFAULT_DESCRIPTION')} for obj in response['RESULT']] - person Stranger6667; 19.06.2017
comment
Я обновил Example 2 и models.py в исходном сообщении - возможно, это отражает то, что я пытаюсь сделать более эффективно. Я предполагаю, что буду делать много этих проверок в будущем, поскольку данные, как правило, противоречивы. Итак, в основном я хочу выполнить этот тип проверки в Example 2, но с пониманием списка, как вы показали в своем примере, поскольку это должно быть даже быстрее, чем добавление в список. - person sphrak; 19.06.2017