Pandas: Menambahkan opsi (Sisipkan atau perbarui jika ada kunci) ke `.to_sql`

Dibuat pada 1 Nov 2016  ·  42Komentar  ·  Sumber: pandas-dev/pandas

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

Keluaran yang Diharapkan

    age
id  
1   18
2   44
3   95

(Mungkin) referensi kode yang bermanfaat

Saya melihat kode sumber pandas sql.py untuk menemukan solusi, tetapi saya tidak dapat mengikutinya.

Kode untuk meniru contoh di atas

(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)
Enhancement IO SQL

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.

Semua 42 komentar

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() .

Proposal Antarmuka

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)
)

Usulan Implementasi

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):

(1) Mesin agnostik menggunakan inti SQLAlchemy, melalui urutan atom DELETE dan INSERT

  • Hanya beberapa dbms yang secara native mendukung upsert , dan implementasinya dapat bervariasi antar rasa
  • Sebagai implementasi pertama, kami yakin akan lebih mudah untuk menguji dan memelihara satu implementasi di semua dbm. Di masa depan, jika ada permintaan, implementasi khusus mesin dapat ditambahkan.
  • Untuk upsert_ignore operasi ini jelas akan dilewati pada catatan yang cocok
  • Perlu membandingkan implementasi engine-agnostik vs implementasi spesifik engine dalam hal kinerja.

(2) Upser hanya pada Kunci Utama

  • Upsert default ke bentrokan kunci utama kecuali ditentukan lain
  • Beberapa DBMS memungkinkan pengguna untuk menentukan kolom non-kunci utama, yang digunakan untuk memeriksa keunikan. Sementara ini memberi pengguna lebih banyak fleksibilitas, ia datang dengan potensi jebakan. Jika kolom ini tidak memiliki batasan UNIQUE , 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.
  • Dalam peningkatan di masa mendatang, jika komunitas memintanya, kami dapat menambahkan fungsionalitas untuk memperluas upsert agar tidak hanya berfungsi pada kunci utama, tetapi juga pada bidang yang ditentukan pengguna. Ini adalah pertanyaan jangka panjang untuk tim pengembang inti, apakah Panda harus tetap sederhana untuk melindungi pengguna yang memiliki basis data yang dirancang dengan buruk, atau memiliki lebih banyak fungsi.

@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

  • metode saat ini berfungsi pada _bagaimana_ data dimasukkan: baris demi baris atau "multi"
  • if_exists menangkap logika bisnis tentang bagaimana kami mengelola catatan kami: "ganti", "tambahkan", "upsert_update" (upsert saat kunci ada, tambahkan saat baru), "upsert_ignore" (abaikan saat kunci ada, tambahkan saat baru). Meskipun mengganti dan menambahkan melihat keberadaan tabel, itu juga dapat dipahami dampaknya pada tingkat rekor.

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:

  1. Buat tabel suhu.
  2. Masukkan data ke tabel temp itu.
  3. Lakukan UPDATE ... BERGABUNG.
  4. INSERT di mana kuncinya (PRIMARY atau UNIQUE) tidak cocok.
  5. Jatuhkan tabel suhu.

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:

  1. Struktur sumber dan tujuan Anda sama.
  2. Bahwa Anda ingin melakukan penyisipan sederhana menggunakan data dalam kerangka data Anda.
  3. Bahwa Anda hanya ingin memperbarui semua kolom non-kunci dengan data dari kerangka data Anda.
  4. Bahwa Anda tidak ingin membuat perubahan apa pun pada data di kolom kunci.

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.

Proposal Pendekatan yang Dimodifikasi

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
Apakah halaman ini membantu?
0 / 5 - 0 peringkat