Предположим, у вас есть существующая таблица SQL с именем person_age
, где id
- это первичный ключ:
age
id
1 18
2 42
и у вас также есть новые данные в DataFrame
под названием extra_data
age
id
2 44
3 95
тогда было бы полезно иметь параметр в extra_data.to_sql()
который позволяет передавать DataFrame в SQL с параметром INSERT
или UPDATE
для строк на основе primary key
.
В этом случае строка id=2
будет обновлена до age=44
а строка id=3
будет добавлена
age
id
1 18
2 44
3 95
merge
из SQLAlchemy ?Я посмотрел на pandas
sql.py
исходный код, чтобы придумать решение, но не смог уследить.
(Приносим извинения за смешивание sqlalchemy
и sqlite
import pandas as pd
from sqlalchemy import create_engine
import sqlite3
conn = sqlite3.connect('example.db')
c = conn.cursor()
c.execute('''DROP TABLE IF EXISTS person_age;''')
c.execute('''
CREATE TABLE person_age
(id INTEGER PRIMARY KEY ASC, age INTEGER NOT NULL)
''')
conn.commit()
conn.close()
##### Create original table
engine = create_engine("sqlite:///example.db")
sql_df = pd.DataFrame({'id' : [1, 2], 'age' : [18, 42]})
sql_df.to_sql('person_age', engine, if_exists='append', index=False)
#### Extra data to insert/update
extra_data = pd.DataFrame({'id' : [2, 3], 'age' : [44, 95]})
extra_data.set_index('id', inplace=True)
#### extra_data.to_sql() with row update or insert option
expected_df = pd.DataFrame({'id': [1, 2, 3], 'age': [18, 44, 95]})
expected_df.set_index('id', inplace=True)
Это была бы хорошая функциональность, но основная проблема в том, что мы хотим, чтобы она была независимой от базы данных и основывалась на ядре sqlalchemy (а не на ORM sqlalchemy) для включения в сам pandas.
Что сделает это трудным для реализации.
Да, я думаю, что это выходит за рамки возможностей pandas, поскольку upserts не поддерживаются всеми механизмами db.
Хотя INSERT OR UPDATE
поддерживается не всеми движками, INSERT OR REPLACE
можно сделать независимым от движка, удалив строки из целевой таблицы для набора первичных ключей в индексе DataFrame с последующей вставкой все строки в DataFrame. Вы хотите сделать это в транзакции.
@TomAugspurger Можно ли добавить опцию upsert для поддерживаемых механизмов БД и
Я тоже хочу это увидеть. Я застрял между использованием чистого SQL и SQL Alchemy (еще не получил, чтобы это работало, я думаю, что это как-то связано с тем, как я передаю dicts). Я использую psycopg2 COPY для массовой вставки, но мне бы хотелось использовать pd.to_sql для таблиц, где значения могут изменяться со временем, и я не возражаю, чтобы вставка была немного медленнее.
insert_values = df.to_dict(orient='records')
insert_statement = sqlalchemy.dialects.postgresql.insert(table).values(insert_values)
upsert_statement = insert_statement.on_conflict_do_update(
constraint='fact_case_pkey',
set_= df.to_dict(orient='dict')
)
И чистый SQL:
def create_update_query(df, table=FACT_TABLE):
"""This function takes the Airflow execution date passes it to other functions"""
columns = ', '.join([f'{col}' for col in DATABASE_COLUMNS])
constraint = ', '.join([f'{col}' for col in PRIMARY_KEY])
placeholder = ', '.join([f'%({col})s' for col in DATABASE_COLUMNS])
values = placeholder
updates = ', '.join([f'{col} = EXCLUDED.{col}' for col in DATABASE_COLUMNS])
query = f"""INSERT INTO {table} ({columns})
VALUES ({placeholder})
ON CONFLICT ({constraint})
DO UPDATE SET {updates};"""
query.split()
query = ' '.join(query.split())
return query
def load_updates(df, connection=DATABASE):
"""Uses COPY from STDIN to load to Postgres
:param df: The dataframe which is writing to StringIO, then loaded to the the database
:param connection: Refers to a PostgresHook
"""
conn = connection.get_conn()
cursor = conn.cursor()
df1 = df.where((pd.notnull(df)), None)
insert_values = df1.to_dict(orient='records')
for row in insert_values:
cursor.execute(create_update_query(df), row)
conn.commit()
cursor.close()
del cursor
conn.close()
@ldacey этот стиль у меня сработал (insert_statement.excluded - это псевдоним для строки данных, которая нарушила ограничение):
insert_values = merged_transactions_channels.to_dict(orient='records')
insert_statement = sqlalchemy.dialects.postgresql.insert(orders_to_channels).values(insert_values)
upsert_statement = insert_statement.on_conflict_do_update(
constraint='orders_to_channels_pkey',
set_={'channel_owner': insert_statement.excluded.channel_owner}
)
@cdagnino Этот фрагмент может не работать в случае составных ключей, и об этом сценарии также следует позаботиться. Я постараюсь найти способ сделать то же самое
Один из способов решить эту проблему с обновлением - использовать sqlachemy bulk_update_mappings . Эта функция принимает список значений словаря и обновляет каждую строку на основе первичного ключа таблицы.
session.bulk_update_mappings(
Table,
pandas_df.to_dict(orient='records)
)
Я согласен с @neilfrndes , не следует допускать, чтобы такая приятная функция не была реализована, потому что некоторые БД не поддерживают. Есть ли шанс, что эта функция может появиться?
Наверное. если кто-то делает пиар. При дальнейшем рассмотрении, я не думаю, что я против этого по принципу, что некоторые базы данных не поддерживают его. Однако я не слишком хорошо знаком с кодом sql, поэтому не уверен, что лучше всего подходит.
Одна из возможностей - предоставить несколько примеров для апсертов с использованием вызываемого method
если введен этот PR: https://github.com/pandas-dev/pandas/pull/21401
Для postgres это будет выглядеть примерно так (непроверено):
from sqlalchemy.dialects import postgresql
def pg_upsert(table, conn, keys, data_iter):
for row in data:
row_dict = dict(zip(keys, row))
stmt = postgresql.insert(table).values(**row_dict)
upsert_stmt = stmt.on_conflict_do_update(
index_elements=table.index,
set_=row_dict)
conn.execute(upsert_stmt)
Что-то подобное можно было бы сделать для mysql .
Для postgres я использую execute_values. В моем случае мой запрос представляет собой шаблон jinja2, чтобы указать, следует ли мне выполнять набор обновлений или ничего не делать . Это было довольно быстро и гибко. Не так быстро, как при использовании COPY или copy_expert, но работает хорошо.
from psycopg2.extras import execute_values
df = df.where((pd.notnull(df)), None)
tuples = [tuple(x) for x in df.values]
``
with pg_conn:
with pg_conn.cursor() as cur:
execute_values(cur=cur,
sql=insert_query,
argslist=tuples,
template=None,
)
@ danich1 , пожалуйста,
Я попытался заглянуть в bulk_update_mappings, но действительно потерялся и не смог заставить его работать.
@ cristianionescu92 Вот пример:
У меня есть таблица под названием User со следующими полями: id и name.
| id | имя |
| --- | --- |
| 0 | Джон |
| 1 | Джо |
| 2 | Гарри |
У меня есть фрейм данных pandas с теми же столбцами, но обновленными значениями:
| id | имя |
| --- | --- |
| 0 | Крис |
| 1 | Джеймс |
Также предположим, что у нас есть переменная сеанса, открытая для доступа к базе данных. Вызывая этот метод:
session.bulk_update_mappings(
User,
<pandas dataframe above>.to_dict(orient='records')
)
Pandas преобразует таблицу в список словарей [{id: 0, name: "chris"}, {id: 1, name: "james"}], которые sql будет использовать для обновления строк таблицы. Итоговая таблица будет выглядеть так:
| id | имя |
| --- | --- |
| 0 | Крис |
| 1 | Джеймс |
| 2 | Гарри |
Привет, @ danich1 ,
Позвольте мне показать вам, что я делаю:
`импорт pypyodbc
from to_sql_newrows import clean_df_db_dups, to_sql_newrows # это 2 функции, которые я нашел на GitHub, к сожалению, я не могу вспомнить ссылку. Clean_df_db_dups исключает из фрейма данных строки, которые уже существуют в таблице SQL, путем проверки нескольких ключевых столбцов, а to_sql_newrows - это функция, которая вставляет в sql новые строки.
from sqlalchemy import create_engine
engine = create_engine("engine_connection_string")
#Write data to SQL
Tablename = 'Dummy_Table_Name'
Tablekeys = Tablekeys_string
dftoupdateorinsertinSQL= random_dummy_dataframe
#Connect to sql server db using pypyodbc
cnxn = pypyodbc.connect("Driver={SQL Server};"
"Server=ServerName;"
"Database=DatabaseName;"
"uid=userid;pwd=password")
newrowsdf= clean_df_db_dups(dftoupdateorinsertinSQL, Tablename, engine, dup_cols=Tablekeys)
newrowsdf.to_sql(Tablename, engine, if_exists='append', index=False, chunksize = 140)
end=timer()
tablesize = (len(newrowsdf.index))
print('inserted %r rows '%(tablesize))`
Приведенный выше код в основном исключает из фрейма данных строки, которые у меня уже есть в SQL, и вставляет только новые строки. Мне нужно обновить существующие строки. Не могли бы вы помочь мне понять, что мне делать дальше?
Мотивация к лучшему TO_SQL
to_sql
Лучшая интеграция с методами работы с базами данных приобретает все большую ценность по мере того, как наука о данных растет и смешивается с инженерией данных.
upsert
- один из них, в частности потому, что многие люди считают, что вместо этого нужно использовать replace
, который удаляет таблицу, а вместе с ней и все представления и ограничения.
Альтернатива, которую я видел у более опытных пользователей, - это прекратить использовать pandas на этом этапе, и это имеет тенденцию распространяться вверх по течению и ослабляет удержание пакета pandas среди опытных пользователей. Это то направление, в котором хочет идти Панда?
Я понимаю, что мы хотим, чтобы to_sql оставался максимально независимым от базы данных и использовал базовую алхимию sql. Однако метод, который усекает или удаляет вместо истинного upsert, все равно добавляет большую ценность.
Интеграция с видением продукта Pandas
Многие из вышеперечисленных дебатов происходили до введения аргумента method
(как упоминалось @kjford с psql_insert_copy
) и возможности передать вызываемый объект.
Я с радостью внесу свой вклад либо в основную функциональность pandas, либо, в противном случае, документацию по решению / передовой практике о том, как достичь функциональности upsert в Pandas, например, ниже:
https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html#io -sql-method
Какой путь вперед предпочитают основные разработчики / менеджеры по продукту Pandas?
Я думаю, что мы открыты для реализации, специфичной для движка. Предложение использовать method='upsert'
кажется разумным, но на данный момент я думаю, что нам нужен кто-то, кто придет с четким предложением по дизайну.
У меня есть аналогичное требование, когда я хочу обновлять существующие данные в таблице MySQL из нескольких CSV с течением времени.
Я думал, что могу df.to_sql () вставить новые данные во вновь созданную временную таблицу, а затем запустить запрос MySQL, чтобы контролировать, как добавлять / обновлять данные в существующей таблице .
Ссылка на MySQL: https://stackoverflow.com/questions/2472229/insert-into-select-from-on-duplicate-key-update?answertab=active#tab -top
Отказ от ответственности: я начал использовать Python и Pandas всего несколько дней назад.
Привет, панда: у меня была такая же проблема, когда мне приходилось часто обновлять мою локальную базу данных записями, которые я в конечном итоге загружаю и обрабатываю в пандах. Для этого я создал простую библиотеку - по сути, она заменяет df.to_sql и pd.read_sql_table, которая по умолчанию использует индекс DataFrame в качестве первичного ключа. Использует только ядро sqlalchemy.
https://pypi.org/project/pandabase/0.2.1/
Https://github.com/notsambeck/pandabase
Этот инструмент довольно самоуверенный, вероятно, не подходит для включения в Pandas как есть. Но для моего конкретного случая использования он решает проблему ... если есть интерес в том, чтобы массировать это, чтобы оно соответствовало Pandas, я рад помочь.
На данный момент работает следующее (в ограниченном случае текущих pandas и sqlalchemy, именованный индекс в качестве первичного ключа, серверная часть SQLite или Postgres и поддерживаемые типы данных):
pip install pandabase / pandabase.to_sql (df, имя_таблицы, con_string, how = 'upsert')
Работая над общим решением этой проблемы с помощью cvonsteg. Планирую вернуться с предложенным дизайном в октябре.
@TomAugspurger, как было предложено, мы с @rugg2 сделали следующее предложение по дизайну для опции upsert
в to_sql()
.
2 новые переменные, которые будут добавлены в качестве возможного аргумента method
в метод to_sql()
:
1) upsert_update
- при совпадении строки, обновить строку в базе данных (для сознательного обновления записей - представляет большинство случаев использования)
2) upsert_ignore
- при совпадении строки, не обновлять строку в базе данных (для случаев, когда наборы данных перекрываются, и вы не хотите переопределять данные в таблицах)
import pandas as pd
from sqlalchemy import create_engine
engine = create_engine("connection string")
df = pd.DataFrame(...)
df.to_sql(
name='table_name',
con=engine,
if_exists='append',
method='upsert_update' # (or upsert_ignore)
)
Чтобы реализовать это, класс SQLTable
получит 2 новых закрытых метода, содержащих логику обновления, которые будут вызываться из метода SQLTable.insert () :
def insert(self, chunksize=None, method=None):
#set insert method
if method is None:
exec_insert = self._execute_insert
elif method == "multi":
exec_insert = self.execute_insert_multi
#new upsert methods <<<
elif method == "upsert_update":
exec_insert = self.execute_upsert_update
elif method == "upsert_ignore":
exec_insert = self.execute_upsert_ignore
# >>>
elif callable(method):
exec_inset = partial(method, self)
else:
raise ValueError("Invalid parameter 'method': {}".format(method))
...
Мы предлагаем следующую реализацию, с обоснованием, подробно изложенным ниже (все вопросы открыты для обсуждения):
DELETE
и INSERT
upsert
, и реализации могут различаться в зависимости от вкусов.upsert_ignore
эти операции, очевидно, будут пропущены для совпадающих записей.UNIQUE
, то вполне вероятно, что несколько строк могут соответствовать условию upsert. В этом случае не следует выполнять обновление, так как неясно, какая запись должна быть обновлена. Чтобы добиться этого от pandas, каждая строка должна быть индивидуально оценена, чтобы проверить, совпадают ли только 1 или 0 строк, прежде чем она будет вставлена. Хотя эту функциональность довольно просто реализовать, она приводит к тому, что каждая запись требует операции чтения и записи (плюс удаление, если обнаружено одно противоречие), что кажется крайне неэффективным для больших наборов данных.@TomAugspurger , если вам подходит предложение upsert
разработанное с помощью @cvonsteg , мы продолжим реализацию в коде (включая тесты) и
Сообщите нам, если вы хотите действовать иначе.
Прочитать предложение в моем списке дел. Я немного отстал от своего
электронная почта прямо сейчас.
В среду, 9 октября 2019 г., в 9:18 Ромен [email protected] написал:
@TomAugspurger https://github.com/TomAugspurger , если дизайн мы
разработан с использованием @cvonsteg https://github.com/cvonsteg подходит вам, мы будем
продолжите реализацию в коде (включая тесты) и поднимите запрос
запрос.Сообщите нам, если вы хотите действовать иначе.
-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/pandas-dev/pandas/issues/14553?email_source=notifications&email_token=AAKAOITBNTWOQRBW3OWDEZDQNXR25A5CNFSM4CU2M7O2YY3PNVWWK3TUL52HS4DFVREWWK3TUL52HS4DFMVWWK3TUL52HS4DFMVRWWWK3TUL52HS4DFMVRE
или отключить поток
https://github.com/notifications/unsubscribe-auth/AAKAOIRZQEQWUY36PQ36QTLQNXR25ANCNFSM4CU2M7OQ
.
Лично я ничего не имею против, поэтому считаю, что пиар приветствуется. Одна реализация для всех DBM, использующих ядро SQLAlchemy, безусловно, должна начинаться, если я правильно читаю ваши аргументы, и то же самое только с первичными ключами.
Всегда легче начать с малого и сфокусироваться, а потом расширяться
эта функция очень нужна.
PR, который мы писали с помощью cvonsteg, теперь должен дать функциональность: теперь перейдем к обзорам!
Эта функциональность была бы просто великолепной! Я не слишком разбираюсь в лексике гитхаба; означает ли комментарий @ rugg2 о том, что функциональность «сейчас
@ pmgh2345 -
PR, который мы писали с помощью cvonsteg, теперь должен дать функциональность: теперь перейдем к обзорам!
Возможно, стоит добавить новый параметр в метод to_sql
, а не использовать if_exists
. Причина в том, что if_exists
проверяет наличие таблицы, а не строки.
@cvonsteg изначально предлагал использовать method=
, что позволило бы избежать двусмысленности двух значений для if_exists
.
df.to_sql(
name='table_name',
con=engine,
if_exists='append',
method='upsert_update' # (or upsert_ignore)
)
@brylie, мы могли бы добавить новый параметр, который является истинным, но, как вы знаете, каждый новый параметр делает API более неуклюжим. Есть компромисс.
Если нам нужно выбрать один из текущих параметров, как вы сказали, мы изначально думали об использовании аргумента method
, но после дополнительных размышлений мы поняли, что и (1) использование, и (2) логика лучше соответствует if_exists
аргумент.
1) с точки зрения использования API
Пользователь захочет выбрать как method = "multi" или None, с одной стороны, так и "upsert", с другой. Однако не существует равнозначно сильных вариантов использования функции «upsert» одновременно с if_exists = «append» или «replace», если таковые имеются.
2) с логической точки зрения
Сообщите мне, хорошо ли я понял вашу точку зрения, и, пожалуйста, кричите, если вы считаете, что текущая реализация, находящаяся на рассмотрении (PR # 29636), будет отрицательной!
Да, вы понимаете мою точку зрения. Текущая реализация является чистой положительной, но несколько ослаблена неоднозначной семантикой.
Я по-прежнему утверждаю, что if_exists
должно по-прежнему относиться только к одному - существованию таблицы. Неоднозначность параметров отрицательно влияет на удобочитаемость и может привести к запутанной внутренней логике. Принимая во внимание, что добавление нового параметра, например upsert=True
является ясным и явным.
Привет!
Если вы хотите увидеть неагностическую реализацию для выполнения апсертов, у меня есть пример с моей библиотекой pangres . Он обрабатывает PostgreSQL и MySQL, используя функции sqlalchemy, специфичные для этих типов баз данных. Что касается SQlite (и других типов баз данных, допускающих аналогичный синтаксис upsert), он использует скомпилированную обычную вставку sqlalchemy.
Я разделяю это мнение, что это может дать несколько идей для соавторов (хотя я знаю, что мы хотим, чтобы это не зависело от типа SQL, что имеет большой смысл). Также, возможно, будет интересно сравнить скорость, когда пройдет PR @cvonsteg .
Пожалуйста, учтите, что я не являюсь экспертом по sqlalchemy или тому подобное!
Мне очень нужна эта функция. Я согласен с тем, что method='upsert_update'
- хорошая идея.
Это все еще планируется? Пандам действительно нужна эта функция
Да, это все еще планируется, и мы почти закончили!
Код написан, но есть один тест, который не проходит. Помощь приветствуется!
https://github.com/pandas-dev/pandas/pull/29636
Во вторник, 5 мая 2020 г., 19:18 Леонель Атенсио [email protected] написал:
Это все еще планируется? Пандам действительно нужна эта функция
-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/pandas-dev/pandas/issues/14553#issuecomment-624223231 ,
или отказаться от подписки
https://github.com/notifications/unsubscribe-auth/AI5X625A742YTYFZE7YW5A3RQBJ6NANCNFSM4CU2M7OQ
.
Привет! Функционал готов или чего-то еще не хватает? Если чего-то по-прежнему не хватает, дайте мне знать, могу ли я чем-нибудь помочь!
Любые новости?))
Исходя из мира Java, никогда не думал, что эта простая функциональность может перевернуть мою кодовую базу с ног на голову.
Всем привет,
Я изучил, как апсерты реализованы в SQL на разных диалектах, и нашел здесь ряд методов, которые могут помочь в принятии дизайнерских решений. Но сначала я хочу предостеречь от использования логики DELETE ... INSERT. Если есть внешние ключи или триггеры, другие записи в базе данных будут удалены или иным образом испорчены. В MySQL REPLACE наносит такой же ущерб. На самом деле я потратил часы на исправление данных, потому что использовал REPLACE. Итак, вот методы, реализованные в SQL:
Диалект | Техника
- | -
MySQL | ВСТАВИТЬ ... ПРИ ОБНОВЛЕНИИ ДВОЙНОГО КЛЮЧА
PostgreSQL | ВСТАВИТЬ ... ПРИ КОНФЛИКТЕ
SQLite | ВСТАВИТЬ ... ПРИ КОНФЛИКТЕ
Db2 | ОБЪЕДИНЕНИЕ
SQL Server | ОБЪЕДИНЕНИЕ
Oracle | ОБЪЕДИНЕНИЕ
SQL: 2016 | ОБЪЕДИНЕНИЕ
Я понимаю, что из-за сильно различающегося синтаксиса возникает соблазн использовать DELETE ... INSERT, чтобы сделать диалект реализации независимым. Но есть другой способ: мы можем имитировать логику оператора MERGE, используя временную таблицу и базовые операторы INSERT и UPDATE. Синтаксис SQL: 2016 MERGE следующий:
MERGE INTO target_table
USING source_table
ON search_condition
WHEN MATCHED THEN
UPDATE SET col1 = value1, col2 = value2,...
WHEN NOT MATCHED THEN
INSERT (col1,col2,...)
VALUES (value1,value2,...);
Заимствовано из Oracle Tutorial
и настроен в соответствии с SQL Wikibook
Поскольку каждый диалект, поддерживаемый SQLAlchemy, поддерживает временные таблицы, более безопасный, не зависящий от диалекта подход к выполнению upsert будет заключаться в одной транзакции:
Помимо того, что это метод, не зависящий от диалекта, он также имеет то преимущество, что он расширяется, позволяя конечному пользователю выбирать, как вставлять или как обновлять данные, а также какой ключ для соединения данных.
Хотя синтаксис временных таблиц и обновлений может немного отличаться в зависимости от диалекта, они должны поддерживаться повсюду.
Ниже приведено доказательство концепции, которую я написал для MySQL:
import uuid
import pandas as pd
from sqlalchemy import create_engine
# This proof of concept uses this sample database
# https://downloads.mysql.com/docs/world.sql.zip
# Arbitrary, unique temp table name to avoid possible collision
source = str(uuid.uuid4()).split('-')[-1]
# Table we're doing our upsert against
target = 'countrylanguage'
db_url = 'mysql://<{user: }>:<{passwd: }>.@<{host: }>/<{db: }>'
df = pd.read_sql(
f'SELECT * FROM `{target}`;',
db_url
)
# Change for UPDATE, 5.3->5.4
df.at[0,'Percentage'] = 5.4
# Change for INSERT
df = df.append(
{'CountryCode': 'ABW','Language': 'Arabic','IsOfficial': 'F','Percentage':0.0},
ignore_index=True
)
# List of PRIMARY or UNIQUE keys
key = ['CountryCode','Language']
# Do all of this in a single transaction
engine = create_engine(db_url)
with engine.begin() as con:
# Create temp table like target table to stage data for upsert
con.execute(f'CREATE TEMPORARY TABLE `{source}` LIKE `{target}`;')
# Insert dataframe into temp table
df.to_sql(source,con,if_exists='append',index=False,method='multi')
# INSERT where the key doesn't match (new rows)
con.execute(f'''
INSERT INTO `{target}`
SELECT
*
FROM
`{source}`
WHERE
(`{'`, `'.join(key)}`) NOT IN (SELECT `{'`, `'.join(key)}` FROM `{target}`);
''')
# Create a doubled list of tuples of non-key columns to template the update statement
non_key_columns = [(i,i) for i in df.columns if i not in key]
# Whitespace for aesthetics
whitespace = '\n\t\t\t'
# Do an UPDATE ... JOIN to set all non-key columns of target to equal source
con.execute(f'''
UPDATE
`{target}` `t`
JOIN
`{source}` `s` ON `t`.`{"` AND `t`.`".join(["`=`s`.`".join(i) for i in zip(key,key)])}`
SET
`t`.`{f"`,{whitespace}`t`.`".join(["`=`s`.`".join(i) for i in non_key_columns])}`;
''')
# Drop our temp table.
con.execute(f'DROP TABLE `{source}`;')
Здесь я делаю следующие предположения:
Несмотря на предположения, я надеюсь, что моя техника, основанная на MERGE, внесет вклад в усилия по созданию гибкого и надежного варианта upsert.
Я думаю, что это полезная функция, однако кажется, что она выходит за рамки, поскольку интуитивно понятно иметь такую общую функцию при добавлении строк в таблицу.
Пожалуйста, подумайте еще раз, чтобы добавить эту функцию: очень полезно добавлять строки в существующую таблицу.
Увы, Пангрес ограничен Python 3.7+. Как и в моем случае (я вынужден использовать старый Python 3.4), это не всегда жизнеспособное решение.
Спасибо, @GoldstHa - это действительно полезный вклад. Я попытаюсь создать POC для реализации, подобной MERGE.
Учитывая проблемы с подходом DELETE/INSERT
и потенциальным блокировщиком подхода @GoldstHa MERGE
в базах данных MySQL, я немного покопался. Я собрал доказательство концепции с использованием функции обновления sqlalchemy , которая выглядит многообещающей. На этой неделе я попытаюсь реализовать его должным образом в кодовой базе Pandas, убедившись, что этот подход работает во всех вариантах БД.
Было несколько хороших дискуссий вокруг API и того, как на самом деле следует вызывать upsert (т.е. через аргумент if_exists
или через явный аргумент upsert
). Это будет уточнено в ближайшее время. На данный момент это предложение псевдокода о том, как эта функциональность будет работать с использованием оператора SqlAlchemy
upsert
:
Identify primary key(s) and existing pkey values from DB table (if no primary key constraints identified, but upsert is called, return an error)
Make a temp copy of the incoming DataFrame
Identify records in incoming DataFrame with matching primary keys
Split temp DataFrame into records which have a primary key match, and records which don't
if upsert:
Update the DB table using `update` for only the rows which match
else:
Ignore rows from DataFrame with matching primary key values
finally:
Append remaining DataFrame rows with non-matching values in the primary key column to the DB table
Самый полезный комментарий
Хотя
INSERT OR UPDATE
поддерживается не всеми движками,INSERT OR REPLACE
можно сделать независимым от движка, удалив строки из целевой таблицы для набора первичных ключей в индексе DataFrame с последующей вставкой все строки в DataFrame. Вы хотите сделать это в транзакции.