syschel
@syschel
freelance/python/django/backend

Множестов объектов одним запросом (insert/update)?

Есть магазин. Есть товары приходящие в CSV файле.
Сейчас перебираю файл построчно. Строка = товар.
Если товара нет в БД, то добавляю. Если есть, то обновляю цену.
В итоге получаю кучу запросов на добавление, обновление. Хотя по сути, данные статичны и можно складировать в список ,а потом массово одним запросом скормить в БД. Как это сделать методами ОРМ джанги?

Цель:
Меньше обращений к БД, быстрее обрабатывается файл, меньше нагрузка на железо

Сейчас:
file_url = './../file/items.csv'
with open(file_url, 'rb') as csvfile: # перебираем строки
    spamreader = csv.reader(csvfile, delimiter='|', quoting=csv.QUOTE_MINIMAL)
    for row in spamreader: # перебираем ячейки в строке
        id_item = row['item'].replace("'", "")
        item = Item.objects.filter(id_item=id_item)
        if not item:
            item = Item(id_item=id_item)
            item.name = row['name'].replace("'", "")
            ....
        else:
            item = item[0]
            item.price = row['price'].replace("'", "")
        item.save()

Как видим, пробегая файл, каждый товар делает запрос в БД на получение товара, сохранение/обновление.

Часть логики моей, как вижу
К примеру проверку можно убрать, вытянув список всех товаров предварительно
all_item = Item.objects.all().values_list('id_item', flat=True)

И тогда уже завести два списка, куда сохранять товары предварительно, пробегая файл. Тупо проверяя есть ли значение в all_item
file_url = './../file/items.csv'
add_item = []
upd_item = []
with open(file_url, 'rb') as csvfile:
    spamreader = csv.reader(csvfile, delimiter='|', quoting=csv.QUOTE_MINIMAL)
    for row in spamreader:
        id_item = row['id_item'].replace("'", "")
        if id_item in all_item: # Та самая проверка
            upd_item.append({'id_item': id_item, 'price': row['price'].replace("'", "")})
        else:
            add_item.append({'id_item': id_item, 'name': row['name'].replace("'", "")})
# Вот тут уже то самое добавление / обновление
# Item(add_item).save() or Item(upd_item).save()


Интересует как именно "правильно" собрать эти списки и как потом одним/двумя запросами скормить в базу. Всё методами ОРМ джанги, а не кастылями с прямыми запросами в БД.

З.Ы. В магазин приходит порядка 10-40к товаров за день на добавление/изменение. Иногда, бывает и под 100к товаров, когда новый поставщик добавляется. В магазине за пару месяцев в БД может висеть под 1кк товаров.
  • Вопрос задан
  • 811 просмотров
Решения вопроса 2
sim3x
@sim3x
Цель:
Меньше обращений к БД, быстрее обрабатывается файл, меньше нагрузка на железо
напротив - тебе нужно максимум передать в бд пусть сама разруливает

В постгресе есть механизм транзакции. Грубо говоря, внутри транзакции коммит проходит только после твоей четкой команды.
Что дает: индекс не пересобирается, пока ты внутри транзакции твои данные не видны для запросов за пределами транзакции (при факапе делаешь роллбек и как ничего и не было)

Те делаешь https://docs.djangoproject.com/en/1.8/topics/db/tr...

@transaction.atomic
def do_stuff():
    # This code executes inside a transaction.

и в функции делаешь get_or_create
Делать список на 40к позиций не стоит - работай с каждой строчкой-товаром отдельно
40к селектов >>> список из 40k

Все проверки и чистку данных из цсв вынеси в отдельные функции, чтоб у тебя основная функция выглядела просто как набор вызовов

Очень-очень советую проводить добавление в модель через ModelForm с валидацией - мало ли какой цсв тебе дадут
Не используй евал и/или исполнение чего-либо из таких файлов
Чисти от js дескрипшены и тайтлы и вообще все что ты можешь в шаблоне случайно показать без фильтрации ака {{ foo|safe }}

Помести код в managed commands

используй профайлер и time ./manage.py do_stuff
Ответ написан
un1t
@un1t
Вставить много новых товаров не проблема, хоть милион.
Предварительно выбираем id уже существующих в базе товаров в set.
Затем используй bulk_create, кнечно не миллион сразу вставляй, а пачками по 1000-10000 за раз.
А вот обновить 40 тыс товаров в mysql/postgres удобных механизмов нет.
Если нам надо обновлять не всю информацию, а скажем только наличиие то можно сделать так.
Создаешь отдельную таблицу про наличие товара, туда вставляешь через bulk_create. После того как все вставил, старую таблицу грохаешь, а новую переимновываешь в старую.
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
@some1else
Про парсинг csv: смущает дергание вручную replace - может, вам нужно указать quotechar, или написать свой Dialect?
По поводу create и update - сейчас в джанге есть чудесный update_or_create.

Про ваше решение - обязательно заверните all_item в простейший set, чтобы вхождение искалось моментально, а не линейно от числа товаров! В вашем случае это просто ValuesQuerySet -> list!
Ответ написан
winordie
@winordie
Лучшая документация -- исходники
Как мне это видится:
1) Одним запросом получаешь товары из базы
2) Парсишь прайс
3) Сравниваешь полученные на шаге 1 и 2 списки
4) На их основе формируешь списки для добавления и обновления
5) С помощью bulk_create или create создаешь товары, c помощью update обновляешь
6) ...
7) Profit
То есть по сути как вы и написали (если я правильно понял)
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы