Misalkan Anda memiliki tabel SQL yang ada bernama person_age
, di mana id
adalah kunci utama:
age
id
1 18
2 42
dan Anda juga memiliki data baru di DataFrame
bernama extra_data
age
id
2 44
3 95
maka akan berguna untuk memiliki opsi pada extra_data.to_sql()
yang memungkinkan untuk meneruskan DataFrame ke SQL dengan opsi INSERT
atau UPDATE
pada baris, berdasarkan pada primary key
.
Dalam hal ini, baris id=2
akan diperbarui menjadi age=44
dan baris id=3
akan ditambahkan
age
id
1 18
2 44
3 95
merge
dari SQLAlchemy ?Saya melihat kode sumber pandas
sql.py
untuk menemukan solusi, tetapi saya tidak dapat mengikutinya.
(Maaf karena mencampur sqlalchemy
dan 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)
Ini akan menjadi fungsionalitas yang bagus, tetapi masalah utamanya adalah kami ingin itu menjadi database-flavor independen dan berdasarkan inti sqlalchemy (jadi bukan sqlalchemy ORM) untuk dimasukkan dalam panda itu sendiri.
Yang akan membuat ini sulit untuk diterapkan ..
Ya, saya pikir ini di luar cakupan panda karena upsert tidak didukung oleh semua mesin db.
Meskipun INSERT OR UPDATE
tidak didukung oleh semua mesin, INSERT OR REPLACE
dapat dibuat agnostik mesin dengan menghapus baris dari tabel target untuk kumpulan kunci utama dalam indeks DataFrame diikuti dengan menyisipkan semua baris dalam DataFrame. Anda ingin melakukan ini dalam sebuah transaksi.
@TomAugspurger Bisakah kita menambahkan opsi upsert untuk mesin db yang didukung dan membuat kesalahan untuk mesin db yang tidak didukung?
Saya ingin melihat ini juga. Saya terjebak di antara menggunakan SQL murni dan SQL Alchemy (belum membuatnya berfungsi, saya pikir itu ada hubungannya dengan bagaimana saya meneruskan dicts). Saya menggunakan psycopg2 COPY untuk menyisipkan secara massal, tetapi saya ingin menggunakan pd.to_sql untuk tabel di mana nilainya dapat berubah seiring waktu dan saya tidak keberatan memasukkannya sedikit lebih lambat.
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')
)
Dan SQL murni:
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 gaya ini bekerja untuk saya (insert_statement.excluded adalah alias untuk baris data yang melanggar batasan):
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 Cuplikan ini mungkin tidak berfungsi dalam kasus kunci komposit, skenario itu juga harus diperhatikan. Saya akan mencoba menemukan cara untuk melakukan hal yang sama
Salah satu cara untuk mengatasi masalah pembaruan ini adalah dengan menggunakan bulk_update_mappings sqlachemy . Fungsi ini mengambil daftar nilai kamus dan memperbarui setiap baris berdasarkan kunci utama tabel.
session.bulk_update_mappings(
Table,
pandas_df.to_dict(orient='records)
)
Saya setuju dengan @neilfrndes , seharusnya tidak mengizinkan fitur bagus seperti ini untuk tidak diimplementasikan karena beberapa DB tidak mendukung. Apakah ada kemungkinan fitur ini terjadi?
Mungkin. jika seseorang membuat PR. Pada pertimbangan lebih lanjut, saya tidak berpikir saya menentang ini dengan prinsip bahwa beberapa database tidak mendukungnya. Namun, saya tidak terlalu akrab dengan kode sql, jadi saya tidak yakin apa pendekatan terbaiknya.
Satu kemungkinan adalah memberikan beberapa contoh untuk upserts menggunakan method
callable jika PR ini diperkenalkan: https://github.com/pandas-dev/pandas/pull/21401
Untuk postgres yang akan terlihat seperti (belum diuji):
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)
Hal serupa dapat dilakukan untuk mysql .
Untuk postgres saya menggunakan execute_values. Dalam kasus saya, kueri saya adalah templat jinja2 untuk menandai apakah saya harus melakukan update set atau tidak melakukan apa-apa . Ini sudah cukup cepat dan fleksibel. Tidak secepat menggunakan COPY atau copy_expert tetapi berfungsi dengan baik.
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 bisakah Anda, tolong, berikan contoh bagaimana ini akan bekerja?
Saya mencoba melihat ke dalam bulk_update_mappings tetapi saya benar-benar tersesat dan tidak dapat membuatnya berfungsi.
@cristianionescu92 Contohnya adalah ini:
Saya memiliki tabel bernama Pengguna dengan bidang berikut: id dan nama.
| id | nama |
| --- | --- |
| 0 | John |
| 1 | Joe |
| 2 | Harry |
Saya memiliki bingkai data panda dengan kolom yang sama tetapi nilai yang diperbarui:
| id | nama |
| --- | --- |
| 0 | Kris |
| 1 | James |
Mari kita asumsikan juga bahwa kita memiliki variabel sesi yang terbuka untuk mengakses database. Dengan memanggil metode ini:
session.bulk_update_mappings(
User,
<pandas dataframe above>.to_dict(orient='records')
)
Pandas akan mengubah tabel menjadi daftar kamus [{id: 0, name: "chris"}, {id: 1, name:"james"}] yang akan digunakan sql untuk memperbarui baris tabel. Jadi tabel akhir akan terlihat seperti:
| id | nama |
| --- | --- |
| 0 | Kris |
| 1 | James |
| 2 | Harry |
Hai, @ danich1 dan terima kasih banyak atas tanggapan Anda. Saya menemukan sendiri mekanisme bagaimana pembaruan akan bekerja. Sayangnya saya tidak tahu cara bekerja dengan sesi, saya cukup pemula.
Biarkan saya menunjukkan kepada Anda apa yang saya lakukan:
`impor pypyodbc
from to_sql_newrows import clean_df_db_dups, to_sql_newrows #ini adalah 2 fungsi yang saya temukan di GitHub, sayangnya saya tidak dapat mengingat tautannya. Clean_df_db_dups mengecualikan dari kerangka data baris yang sudah ada dalam tabel SQL dengan memeriksa beberapa kolom kunci dan to_sql_newrows adalah fungsi yang menyisipkan ke sql baris baru.
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))`
Kode di atas pada dasarnya mengecualikan dari kerangka data baris yang sudah saya miliki di SQL dan hanya memasukkan baris baru. Yang saya butuhkan adalah memperbarui baris yang ada. Bisakah Anda, tolong, bantu saya memahami apa yang harus saya lakukan selanjutnya?
Motivasi untuk TO_SQL yang lebih baik
to_sql
mengintegrasikan lebih baik dengan praktik basis data semakin bernilai seiring dengan berkembangnya ilmu data dan bercampur dengan rekayasa data.
upsert
adalah salah satunya, khususnya karena banyak orang menemukan bahwa solusinya adalah menggunakan replace
sebagai gantinya, yang menjatuhkan tabel, dan dengan itu semua tampilan dan batasan.
Alternatif yang saya lihat pada pengguna yang lebih berpengalaman adalah berhenti menggunakan panda pada tahap ini, dan ini cenderung menyebar ke hulu dan membuat paket panda kehilangan retensi di antara pengguna berpengalaman. Apakah ini arah yang ingin dituju Panda?
Saya mengerti kami ingin to_sql tetap menjadi database agnostik sebanyak mungkin, dan menggunakan inti alkimia sql. Metode yang memotong atau menghapus alih-alih upsert yang sebenarnya masih akan menambah banyak nilai.
Integrasi dengan visi produk Pandas
Banyak perdebatan di atas terjadi sebelum pengenalan argumen method
(seperti yang disebutkan oleh @kjford dengan psql_insert_copy
) dan kemungkinan untuk meneruskan callable.
Saya akan dengan senang hati berkontribusi pada fungsionalitas inti panda, atau gagal, dokumentasi tentang solusi/praktik terbaik tentang cara mencapai fungsionalitas upsert dalam Pandas, seperti di bawah ini:
https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html#io -sql-method
Apa cara yang lebih disukai untuk pengembang inti / manajer produk Pandas?
Saya pikir kami terbuka untuk implementasi yang khusus untuk mesin. Proposal untuk menggunakan method='upsert'
tampaknya masuk akal, tetapi pada titik ini saya pikir kita membutuhkan seseorang untuk membuat proposal desain yang jelas.
Saya memiliki persyaratan serupa di mana saya ingin memperbarui data yang ada di tabel MySQL dari beberapa CSV dari waktu ke waktu.
Saya pikir saya bisa df.to_sql() untuk memasukkan data baru ke dalam tabel sementara yang baru dibuat dan kemudian menjalankan kueri MySQL untuk mengontrol cara menambahkan/memperbarui data di tabel yang ada .
Referensi MySQL: https://stackoverflow.com/questions/2472229/insert-into-select-from-on-duplicate-key-update?answertab=active#tab -top
Penafian: Saya mulai menggunakan Python dan Pandas hanya beberapa hari yang lalu.
Hai rakyat Panda: Saya memiliki masalah yang sama, perlu sering memperbarui basis data lokal saya dengan catatan yang akhirnya saya muat dan manipulasi di pandas. Saya membangun perpustakaan sederhana untuk melakukan ini - pada dasarnya ini adalah stand-in untuk df.to_sql dan pd.read_sql_table yang menggunakan indeks DataFrame sebagai kunci utama secara default. Hanya menggunakan inti sqlalchemy.
https://pypi.org/project/pandabase/0.2.1/
Https://github.com/notsambeck/pandabase
Alat ini cukup berpendirian, mungkin tidak sesuai untuk dimasukkan dalam Panda apa adanya. Tetapi untuk kasus penggunaan khusus saya, ini menyelesaikan masalah ... jika ada minat untuk memijat ini agar pas di Panda, saya senang membantu.
Untuk saat ini, berikut ini berfungsi (dalam kasus terbatas panda dan sqlalchemy saat ini, bernama indeks sebagai kunci utama, back end SQLite atau Postgres, dan tipe data yang didukung):
pip install pandabase / pandabase.to_sql(df, table_name, con_string, how='upsert')
Bekerja pada solusi umum untuk ini dengan cvonsteg. Berencana untuk kembali dengan desain yang diusulkan pada bulan Oktober.
@TomAugspurger seperti yang disarankan, @rugg2 dan saya telah membuat proposal desain berikut untuk opsi upsert
di to_sql()
.
2 variabel baru yang akan ditambahkan sebagai kemungkinan argumen method
dalam metode to_sql()
:
1) upsert_update
- pada pencocokan baris, perbarui baris dalam database (untuk memperbarui catatan secara sadar - mewakili sebagian besar kasus penggunaan)
2) upsert_ignore
- pada pencocokan baris, jangan perbarui baris dalam database (untuk kasus di mana kumpulan data tumpang tindih, dan Anda tidak ingin menimpa data dalam tabel)
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)
)
Untuk mengimplementasikan ini, kelas SQLTable
akan menerima 2 metode pribadi baru yang berisi logika upsert, yang akan dipanggil dari metode 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))
...
Kami mengusulkan implementasi berikut, dengan alasan yang diuraikan secara rinci di bawah ini (semua poin terbuka untuk diskusi):
DELETE
dan INSERT
upsert
, dan implementasinya dapat bervariasi antar rasaupsert_ignore
operasi ini jelas akan dilewati pada catatan yang cocokUNIQUE
, maka masuk akal bahwa beberapa baris mungkin cocok dengan kondisi upsert. Dalam hal ini, tidak ada upsert yang harus dilakukan karena tidak jelas catatan mana yang harus diperbarui. Untuk menerapkan ini dari panda, setiap baris perlu dinilai secara individual untuk memeriksa bahwa hanya 1 atau 0 baris yang cocok, sebelum dimasukkan. Meskipun fungsi ini cukup mudah untuk diterapkan, ini menghasilkan setiap catatan yang memerlukan operasi baca dan tulis (ditambah penghapusan jika ditemukan bentrokan 1 catatan), yang terasa sangat tidak efisien untuk kumpulan data yang lebih besar.@TomAugspurger , jika upsert
proposal yang dirancang dengan @cvonsteg cocok untuk Anda, kami akan melanjutkan implementasi dalam kode (termasuk tes) dan mengajukan permintaan tarik.
Beri tahu kami jika Anda ingin melanjutkan dengan cara yang berbeda.
Membaca proposal ada di daftar tugas saya. Saya sedikit tertinggal dari saya
email sekarang.
Pada hari Rabu, 9 Oktober 2019 pukul 09:18 Romain [email protected] menulis:
@TomAugspurger https://github.com/TomAugspurger , jika desain kami
dirancang dengan @cvonsteg https://github.com/cvonsteg cocok untuk Anda, kami akan
lanjutkan dengan implementasi dalam kode (termasuk tes) dan angkat tarik
meminta.Beri tahu kami jika Anda ingin melanjutkan dengan cara yang berbeda.
—
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/pandas-dev/pandas/issues/14553?email_source=notifications&email_token=AAKAOITBNTWOQRBW3OWDEZDQNXR25A5CNFSM4CU2M7O2YY3PNVWWK3TUL52HS4DFVREXG43VMVBWZ2YLODN5PWZK2YLODN5PWWZK2TULN5PWWZ22012
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AAKAOIRZQEQWUY36PQ36QTLQNXR25ANCNFSM4CU2M7OQ
.
Saya pribadi tidak menentangnya, jadi saya pikir PR diterima. Satu implementasi di semua DBM yang menggunakan inti SQLAlchemy tentu saja bagaimana ini harus dimulai jika saya membaca poin Anda dengan benar, dan sama hanya dengan kunci utama.
Selalu lebih mudah untuk memulai dari yang kecil dan fokus dan berkembang dari sana
sangat membutuhkan fitur ini.
PR yang kami tulis dengan cvonsteg sekarang harus memberikan fungsionalitas: hingga ulasan sekarang!
Fungsionalitas ini akan sangat luar biasa! Saya tidak terlalu mahir dalam kosakata github; apakah komentar oleh @rugg2 bahwa fungsinya "turun ke ulasan sekarang" berarti itu tergantung pada tim panda untuk meninjaunya? Dan jika disetujui, apakah itu berarti akan tersedia melalui panda versi baru yang dapat kita instal, atau apakah kita harus menerapkan sendiri komit secara manual melalui git? (Saya memiliki masalah dengan ini melalui conda jadi jika itu masalahnya saya ingin mempercepat pada saat fungsi ini siap). Terima kasih!!
@pmgh2345 - ya, seperti yang Anda katakan, "turun ke ulasan sekarang" berarti permintaan tarik telah diajukan dan sedang ditinjau dari pengembang inti. Anda dapat melihat PR yang disebutkan di atas (#29636). Setelah disetujui, Anda secara teknis dapat melakukan fork cabang dengan kode yang diperbarui dan mengkompilasi panda versi lokal Anda sendiri dengan fungsionalitas yang ada di dalamnya. Namun, saya pribadi akan merekomendasikan menunggu sampai digabungkan menjadi master dan dirilis, dan kemudian hanya menginstal pip panda versi terbaru.
PR yang kami tulis dengan cvonsteg sekarang harus memberikan fungsionalitas: hingga ulasan sekarang!
Mungkin ada baiknya menambahkan parameter baru ke metode to_sql
, daripada menggunakan if_exists
. Alasannya, if_exists
sedang memeriksa keberadaan tabel, bukan baris.
@cvonsteg awalnya diusulkan menggunakan method=
, yang akan menghindari ambiguitas memiliki dua arti untuk if_exists
.
df.to_sql(
name='table_name',
con=engine,
if_exists='append',
method='upsert_update' # (or upsert_ignore)
)
@brylie kita bisa menambahkan parameter baru yang benar, tapi seperti yang Anda tahu setiap parameter baru membuat API lebih kikuk. Ada pertukaran.
Jika kami harus memilih di antara parameter saat ini, seperti yang Anda katakan, kami awalnya berpikir untuk menggunakan argumen method
, tetapi setelah memikirkan lebih lanjut, kami menyadari bahwa (1) penggunaan dan (2) logika lebih cocok dengan if_exists
argumen.
1) dari sudut pandang penggunaan API
Pengguna akan ingin memilih metode = "multi" atau Tidak ada di satu sisi, dan "upsert" di sisi lain. Namun, tidak ada kasus penggunaan yang sama kuatnya dengan menggunakan fungsionalitas "upsert" pada saat yang sama dengan if_exists="append" atau "replace", jika ada.
2) dari sudut pandang logika
Beri tahu saya jika saya memahami maksud Anda dengan baik, dan mohon berteriak jika menurut Anda implementasi saat ini yang sedang ditinjau (PR #29636) akan menjadi negatif bersih!
Ya, Anda mengerti maksud saya. Implementasi saat ini adalah positif bersih tetapi sedikit berkurang oleh semantik yang ambigu.
Saya masih mempertahankan bahwa if_exists
harus terus merujuk hanya pada satu hal, keberadaan tabel. Memiliki ambiguitas dalam parameter berdampak negatif pada keterbacaan, dan dapat menyebabkan logika internal yang berbelit-belit. Padahal, menambahkan parameter baru, seperti upsert=True
jelas dan eksplisit.
Halo!
Jika Anda ingin melihat implementasi non agnostik untuk melakukan upsert, saya punya contoh dengan pangres perpustakaan saya. Ini menangani PostgreSQL dan MySQL menggunakan fungsi sqlalchemy khusus untuk tipe database tersebut. Adapun SQlite (dan tipe database lain yang memungkinkan sintaks upsert serupa) menggunakan Sisipan sqlalchemy reguler yang dikompilasi.
Saya berbagi pemikiran ini mungkin memberikan beberapa ide kepada kolaborator (saya sadar, bahwa kami ingin ini menjadi agnostik tipe SQL yang sangat masuk akal). Mungkin juga perbandingan kecepatan akan menarik juga ketika PR @cvonsteg berjalan.
Harap diingat saya bukan ahli sqlalchemy lama atau semacamnya!
Saya sangat menginginkan fitur ini. Saya setuju bahwa method='upsert_update'
adalah ide yang bagus.
Apakah ini masih direncanakan? Panda sangat membutuhkan fitur ini
Ya ini masih direncanakan, dan kita hampir sampai!
Kode ditulis, tetapi ada satu tes yang tidak lulus. Bantuan disambut!
https://github.com/pandas-dev/pandas/pull/29636
Pada Selasa, 5 Mei 2020, 19:18 Leonel Atencio [email protected] menulis:
Apakah ini masih direncanakan? Panda sangat membutuhkan fitur ini
—
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/pandas-dev/pandas/issues/14553#issuecomment-624223231 ,
atau berhenti berlangganan
https://github.com/notifications/unsubscribe-auth/AI5X625A742YTYFZE7YW5A3RQBJ6NANCNFSM4CU2M7OQ
.
Halo! Apakah fungsinya sudah siap atau ada yang masih kurang? Jika masih ada yang kurang, beri tahu saya jika ada yang bisa saya bantu!
Ada berita?))
Berasal dari dunia Java, tidak pernah menyangka fungsionalitas sederhana ini dapat membalikkan basis kode saya.
Halo semuanya,
Saya telah melihat bagaimana upsert diimplementasikan dalam SQL di seluruh dialek dan menemukan sejumlah teknik yang dapat menginformasikan keputusan desain di sini. Tapi pertama-tama, saya ingin memperingatkan agar tidak menggunakan logika DELETE ... INSERT. Jika ada kunci atau pemicu asing, catatan lain di seluruh basis data akan dihapus atau dikacaukan. Di MySQL, REPLACE melakukan kerusakan yang sama. Saya sebenarnya telah membuat jam kerja untuk diri saya sendiri memperbaiki data karena saya menggunakan REPLACE. Jadi, berikut adalah teknik yang diterapkan dalam SQL:
Dialek | Teknik
-- | --
MySQL | MASUKKAN ... PADA PEMBARUAN KUNCI DUPLICATE
PostgreSQL | MASUKKAN ... PADA KONFLIK
SQLite | MASUKKAN ... PADA KONFLIK
Db2 | MENGGABUNGKAN
SQL Server | MENGGABUNGKAN
Oracle | MENGGABUNGKAN
SQL:2016 | MENGGABUNGKAN
Dengan sintaks yang sangat bervariasi, saya memahami godaan untuk menggunakan DELETE ... INSERT untuk membuat dialek implementasi agnostik. Tapi ada cara lain: kita bisa meniru logika pernyataan MERGE menggunakan tabel temp dan pernyataan dasar INSERT dan UPDATE. Sintaks SQL:2016 MERGE adalah sebagai berikut:
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,...);
Dipinjam dari Tutorial Oracle
dan disesuaikan agar sesuai dengan SQL Wikibook
Karena setiap dialek yang didukung oleh SQLAlchemy mendukung tabel temp, pendekatan agnostik dialek yang lebih aman untuk melakukan upsert adalah, dalam satu transaksi:
Selain menjadi teknik dialek-agnostik, itu juga memiliki keuntungan yang diperluas dengan memungkinkan pengguna akhir untuk memilih cara memasukkan atau cara memperbarui data serta pada kunci apa untuk menggabungkan data.
Meskipun sintaks tabel temp, dan gabungan pembaruan mungkin sedikit berbeda antara dialek, mereka harus didukung di mana saja.
Di bawah ini adalah bukti konsep yang saya tulis untuk 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}`;')
Di sini, saya membuat asumsi berikut:
Terlepas dari asumsi tersebut, saya berharap teknik saya yang terinspirasi MERGE menginformasikan upaya untuk membangun opsi upsert yang fleksibel dan kuat.
Saya pikir ini adalah fungsi yang berguna namun di luar ruang lingkup tampaknya intuitif untuk memiliki fitur umum saat menambahkan baris ke tabel.
Harap pikirkan lagi untuk menambahkan fungsi ini: sangat berguna untuk menambahkan baris ke tabel yang ada.
Sayangnya Pangres terbatas pada Python 3.7+. Seperti dalam kasus saya (saya terpaksa menggunakan Python 3.4 lama), itu tidak selalu merupakan solusi yang layak.
Terima kasih, @GoldstHa -
Mengingat masalah dengan pendekatan DELETE/INSERT
, dan pemblokir potensial pada pendekatan @GoldstHa MERGE
pada DB MySQL, saya telah melakukan sedikit penggalian lagi. Saya telah mengumpulkan bukti konsep menggunakan fungsionalitas pembaruan sqlalchemy , yang terlihat menjanjikan. Saya akan mencoba untuk mengimplementasikannya dengan benar minggu ini di basis kode Pandas, memastikan bahwa pendekatan ini bekerja di semua rasa DB.
Ada beberapa diskusi bagus seputar API, dan bagaimana sebenarnya upsert harus dipanggil (yaitu melalui argumen if_exists
, atau melalui argumen upsert
eksplisit). Ini akan segera diklarifikasi. Untuk saat ini, ini adalah proposal pseudocode tentang bagaimana fungsionalitas akan bekerja menggunakan pernyataan 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
Komentar yang paling membantu
Meskipun
INSERT OR UPDATE
tidak didukung oleh semua mesin,INSERT OR REPLACE
dapat dibuat agnostik mesin dengan menghapus baris dari tabel target untuk kumpulan kunci utama dalam indeks DataFrame diikuti dengan menyisipkan semua baris dalam DataFrame. Anda ingin melakukan ini dalam sebuah transaksi.