Bulk Upsert с помощью SQLAlchemy Postgres - PullRequest
2 голосов
/ 27 марта 2019

Я слежу за документацией SQLAlchemy здесь , чтобы написать массовую инструкцию по внедрению с Postgres. Для демонстрации у меня есть простая таблица MyTable:

class MyTable(base):
    __tablename__ = 'mytable'
    id = Column(types.Integer, primary_key=True)
    test_value = Column(types.Text)

Создать общий оператор вставки достаточно просто:

from sqlalchemy.dialects import postgresql

values = [{'id': 0, 'test_value': 'a'}, {'id': 1, 'test_value': 'b'}]
insert_stmt = postgresql.insert(MyTable.__table__).values(values)

Проблема, с которой я сталкиваюсь, заключается в том, что я пытаюсь добавить часть upsert в конфликт.

update_stmt = insert_stmt.on_conflict_do_update(
    index_elements=[MyTable.id],
    set_=dict(data=values)
)

Попытка выполнить этот оператор приводит к ProgrammingError:

from sqlalchemy import create_engine
engine = create_engine('postgres://localhost/db_name')

engine.execute(update_stmt)

>>> ProgrammingError: (psycopg2.ProgrammingError) can't adapt type 'dict'

Я думаю, что мое недоразумение заключается в построении утверждения с помощью метода on_conflict_do_update. Кто-нибудь знает, как построить это утверждение? Я смотрел на другие вопросы о StackOverflow (например, здесь ), но я не могу найти способ исправить вышеуказанную ошибку.

1 Ответ

4 голосов
/ 27 марта 2019
update_stmt = insert_stmt.on_conflict_do_update(
    index_elements=[MyTable.id],
    set_=dict(data=values)
)

index_elements должен быть либо списком строк, либо списком объектов столбцов. Так что либо [MyTable.id], либо ['id'] (это правильно)

set_ должен быть словарем с именами столбцов в качестве ключей и действительными объектами обновления sql в качестве значений. Вы можете ссылаться на значения из блока вставки, используя атрибут excluded. Таким образом, чтобы получить результат, на который вы надеетесь, вы хотели бы set_={'test_value': insert_stmt.excluded.test_value} (ошибка, которую вы сделали, заключается в том, что data= в примере не является волшебным аргументом ... это было имя столбца в их таблице примеров)

Итак, все это будет

update_stmt = insert_stmt.on_conflict_do_update(
    index_elements=[MyTable.id],
    set_={'test_value': insert_stmt.excluded.test_value}
)

Конечно, в примере с реальным миром я обычно хочу изменить более одного столбца. В этом случае я бы сделал что-то вроде ...

update_columns = {col.name: col for col in insert_stmt.excluded if col.name not in ('id', 'datetime_created')}
update_statement = insert_stmt.on_conflict_do_update(index_elements=['id'], set_=update_columns)

(в этом примере будут перезаписаны все столбцы, кроме столбцов id и datetime_created)

...