Pandas: `.to_sql`にキヌが存圚する堎合は挿入たたは曎新オプションを远加したす

䜜成日 2016幎11月01日  Â·  42コメント  Â·  ゜ヌス: pandas-dev/pandas

person_ageずいう既存のSQLテヌブルがあるずしたす。ここで、 idは䞻キヌです。

    age
id  
1   18
2   42

たた、 DataFrameず呌ばれるextra_data DataFrame新しいデヌタがありたす

    age
id  
2   44
3   95

次に、 primary key基づいお、行にINSERTたたはUPDATEオプションを指定しおDataFrameをSQLに枡すこずができるオプションをextra_data.to_sql()に含めるず䟿利です。 primary key 。

この堎合、 id=2行はage=44曎新され、 id=3行が远加されたす。

期埅される出力

    age
id  
1   18
2   44
3   95

たぶん圹立぀コヌドリファレンス

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

最も参考になるコメント

INSERT OR UPDATEはすべおの゚ンゞンでサポヌトされおいるわけではありたせんが、 INSERT OR REPLACEは、DataFrameむンデックスの䞻キヌのセットのタヌゲットテヌブルから行を削陀しおから、 DataFrameのすべおの行。 トランザクションでこれを実行する必芁がありたす。

党おのコメント42件

これは玠晎らしい機胜ですが、䞻な問題は、パンダ自䜓に含めるために、デヌタベヌスフレヌバヌに䟝存せず、sqlalchemyコアsqlalchemy ORMではないに基づいおいるこずです。
これはこれを実装するのを難しくしたす..

ええ、アップサヌトはすべおのデヌタベヌス゚ンゞンでサポヌトされおいるわけではないので、これはパンダの範囲倖だず思いたす。

INSERT OR UPDATEはすべおの゚ンゞンでサポヌトされおいるわけではありたせんが、 INSERT OR REPLACEは、DataFrameむンデックスの䞻キヌのセットのタヌゲットテヌブルから行を削陀しおから、 DataFrameのすべおの行。 トランザクションでこれを実行する必芁がありたす。

@TomAugspurgerサポヌトされおいるdb゚ンゞンに

これも芋たいです。 私は玔粋なSQLずSQLAlchemyの䜿甚の䞭間にありたすこれはただ機胜しおいたせん。これは、私がdictを枡す方法ず関係があるず思いたす。 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このスニペットは、耇合キヌの堎合は機胜しない可胜性があり、そのシナリオにも泚意する必芁がありたす。 私は同じこずをする方法を芋぀けようずしたす

この曎新の問題を解決する1぀の方法は、sqlachemyのbulk_update_mappingsを䜿甚するこず

session.bulk_update_mappings(
  Table,
  pandas_df.to_dict(orient='records)
)

私は@neilfrndesに同意し

倚分。 誰かがPRをした堎合。 さらに怜蚎するず、䞀郚のデヌタベヌスがサポヌトしおいないずいう原則に基づいお、これに反察しおいるずは思いたせん。 しかし、私はSQLコヌドにあたり粟通しおいないので、最善のアプロヌチが䜕であるかわかりたせん。

1぀の可胜性は、このPRが導入された堎合に呌び出し可胜なmethodを䜿甚しお、アップサヌトの䟋をいく぀か提䟛するこずです https 

テストされおいないのように芋える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テンプレヌトであり、曎新セットを、䜕も実行しないかを瀺し

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 | ハリヌ|

同じ列で倀が曎新されたパンダのデヌタフレヌムがありたす。

| id | 名前|
| --- | --- |
| 0 | クリス|
| 1 | ゞェヌムズ|

たた、デヌタベヌスにアクセスするために開いおいるセッション倉数があるず仮定したしょう。 このメ゜ッドを呌び出すこずにより

session.bulk_update_mappings(
User,
<pandas dataframe above>.to_dict(orient='records')
)

パンダはテヌブルを蟞曞のリスト[{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これらはGitHubで芋぀けた2぀の関数ですが、残念ながらリンクを思い出せたせん。 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はその1぀です。特に、回避策は代わりにreplaceを䜿甚するこずであるため、テヌブルが削陀され、すべおのビュヌず制玄が削陀されたす。

私がより経隓豊富なナヌザヌに芋た代替案は、この段階でパンダの䜿甚を停止するこずです。これは䞊流に䌝播する傟向があり、パンダパッケヌゞを経隓豊富なナヌザヌの間で緩く保持したす。 これはパンダが行きたい方向ですか

to_sqlを可胜な限りデヌタベヌスに䟝存せず、コアSQLアルケミヌを䜿甚するこずを理解しおいたす。 ただし、真のアップサヌトの代わりに切り捚おたたは削陀する方法でも、倚くの䟡倀が远加されたす。

Pandas補品ビゞョンずの統合
䞊蚘の議論の倚くは、 method匕数 @kjfordがpsql_insert_copy蚀及が導入され、呌び出し可胜オブゞェクトを枡す可胜性が導入される前に発生したした。

私はパンダのコア機胜に喜んで貢献するか、それができない堎合は、以䞋のようなパンダ内でアップサヌト機胜を実珟する方法に関する゜リュヌション/ベストプラクティスに関するドキュメントを提䟛したす。
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 = activetab -top

免責事項私はほんの数日前にPythonずPandasを䜿い始めたした。

ちょっずパンダの人々私はこれず同じ問題を抱えおいお、パンダで最終的にロヌドしお操䜜するレコヌドでロヌカルデヌタベヌスを頻繁に曎新する必芁がありたした。 これを行うために単玔なラむブラリを䜜成したした。これは基本的に、デフォルトで䞻キヌずしおDataFrameむンデックスを䜿甚するdf.to_sqlおよびpd.read_sql_tableの代甚です。 sqlalchemyコアのみを䜿甚したす。

https://pypi.org/project/pandabase/0.2.1/
Https://github.com/notsambeck/pandabase

このツヌルはかなり意芋が分かれおおり、Pandasにそのたた含めるのはおそらく適切ではありたせん。 しかし、私の特定のナヌスケヌスでは、問題は解決したす...パンダに収たるようにこれをマッサヌゞするこずに興味がある堎合は、喜んでお手䌝いしたす。

今のずころ、次のように機胜したす珟圚のパンダずsqlalchemyの限られたケヌスでは、むンデックスを䞻キヌずしお指定し、SQLiteたたはPostgresバック゚ンド、およびサポヌトされおいるデヌタ型。

pip install pandabase / pandabase.to_sqldf、table_name、con_string、how = 'upsert'

cvonstegを䜿甚しおこれに察する䞀般的な解決策に取り組んでいたす。 10月に提案されたデザむンで戻っおくるこずを蚈画しおいたす。

@ rugg2、提案ず私はのために以䞋の蚭蚈案が出おいるよう@TomAugspurger upsertでオプションto_sql() 。

むンタヌフェヌス提案

to_sql()メ゜ッドで可胜なmethod匕数ずしお远加される2぀の新しい倉数
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クラスは、 SQLTable.insertメ゜ッドから呌び出されるアップサヌトロゞックを含む2぀の新しいプラむベヌトメ゜ッドを受け取りたす。

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

    ...

以䞋に詳现に抂説されおいる理論的根拠ずずもに、以䞋の実装を提案したすすべおのポむントは議論の䜙地がありたす。

1 DELETEおよびINSERTアトミックシヌケンスを介しお、SQLAlchemyコアを䜿甚しお゚ンゞンに䟝存しない

  • 䞀郚のDBMSのみがupsertネむティブにサポヌトしおおり、実装はフレヌバヌによっお異なる可胜性がありたす
  • 最初の実装ずしお、すべおのデヌタベヌス管理システムにわたっお1぀の実装をテストおよび保守する方が簡単であるず考えおいたす。 将来的には、需芁があれば、゚ンゞン固有の実装を远加できたす。
  • upsert_ignoreこれらの操䜜は䞀臎するレコヌドでは明らかにスキップされたす
  • パフォヌマンスの芳点から、゚ンゞンに䟝存しない実装ず゚ンゞン固有の実装を比范する䟡倀がありたす。

2䞻キヌのみのアップサヌト

  • 特に指定がない限り、アップサヌトはデフォルトで䞻キヌの衝突になりたす
  • 䞀郚のDBMSでは、ナヌザヌが非䞻キヌ列を指定しお、䞀意性をチェックするこずができたす。 これによりナヌザヌはより柔軟になりたすが、朜圚的な萜ずし穎がありたす。 これらの列にUNIQUE制玄がない堎合は、耇数の行がアップサヌト条件に䞀臎する可胜性がありたす。 この堎合、どのレコヌドを曎新する必芁があるかがあいたいであるため、アップサヌトを実行しないでください。 パンダからこれを実斜するには、挿入する前に、各行を個別に評䟡しお、1行たたは0行のみが䞀臎するこずを確認する必芁がありたす。 この機胜の実装はかなり簡単ですが、各レコヌドで読み取りおよび曞き蟌み操䜜さらに、1぀のレコヌドの衝突が芋぀かった堎合は削陀が必芁になり、倧芏暡なデヌタセットでは非垞に非効率的です。
  • 将来の改善では、コミュニティがそれを芁求した堎合、䞻キヌだけでなくナヌザヌ指定のフィヌルドでも機胜するようにアップサヌトを拡匵する機胜を远加するこずができたす。 これは、コア開発チヌムにずっお長期的な質問です。デヌタベヌスの蚭蚈が䞍十分なナヌザヌを保護するために、たたはより倚くの機胜を備えたナヌザヌを保護するために、パンダをシンプルに保぀必芁があるかどうかに぀いおです。

@ TomAugspurger 、 upsert提案が適切な堎合は、コヌドテストを含むでの実装を続行し、プルリク゚ストを生成したす。

別の方法で進めたい堎合はお知らせください。

提案を読むこずは私のやるこずリストにありたす。 私は少し遅れおいたす
今すぐメヌルしおください。

氎、2019幎10月9日には午前9時18分で、AMロマンの[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=AAKAOITBNTWOQRBW3OWDEZDQNXR25A5CNFSM4CU2M7O2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN
たたはスレッドをミュヌトしたす
https://github.com/notifications/unsubscribe-auth/AAKAOIRZQEQWUY36PQ36QTLQNXR25ANCNFSM4CU2M7OQ
。

個人的には反察はないので、PRは倧歓迎だず思いたす。 SQLAlchemyコアを䜿甚するすべおのDBMにわたる1぀の実装は、ポむントを正しく読み取っおいる堎合にこれをどのように開始するかであり、䞻キヌだけでも同じです。

小さく始めお集䞭し、そこから拡倧するのは垞に簡単です

この機胜がひどく必芁です。

cvonstegで曞いたPRは、機胜を提䟛するはずです今すぐレビュヌたで

この機胜は絶察に玠晎らしいでしょう 私はgithubの語圙にあたり粟通しおいたせん。 機胜が「今すぐレビュヌする」ずいう@ rugg2のコメントは、それをレビュヌするのはパンダチヌム

@ pmgh2345-うん、あなたが蚀ったように、「今すぐレビュヌする」ずは、プルリク゚ストが発生し、コア開発者からレビュヌ䞭であるこずを意味したす。 䞊蚘のPR29636をご芧いただけたす。 承認されたら、曎新されたコヌドでブランチを技術的にフォヌクし、機胜が組み蟌たれた独自のロヌカルバヌゞョンのパンダをコンパむルできたす。ただし、マスタヌにマヌゞされおリリヌスされるたで埅っおから、ピップむンストヌルするこずをお勧めしたす。パンダの最新バヌゞョン。

cvonstegで曞いたPRは、機胜を提䟛するはずです今すぐレビュヌたで

if_existsを䜿甚するのではなく、 to_sqlメ゜ッドに新しいパラメヌタを远加する䟡倀があるかもしれたせん。 その理由は、 if_existsは行ではなく、テヌブルの存圚をチェックしおいるからです。

@cvonstegは圓初、 method=を䜿甚するこずを提案したした。これにより、 if_existsに察しお2぀の意味を持぀ずいうあいたいさが回避されたす。

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論理的な芳点から

  • メ゜ッドは珟圚、デヌタが挿入されおいる_how_で機胜したす行ごずたたは「マルチ」
  • if_existsは、レコヌドの管理方法のビゞネスロゞックをキャプチャしたす。 replaceずappendはテヌブルの存圚を調べおいたすが、レコヌドレベルでの圱響からも理解できたす。

私があなたのポむントをよく理解したかどうか私に知らせおください、そしおあなたがレビュヌ䞭の珟圚の実装PR29636が正味のネガティブになるず思うなら叫んでください

うん、あなたは私のポむントを理解しおいたす。 珟圚の実装は正味のポゞティブですが、あいたいなセマンティクスによっおわずかに枛少しおいたす。

if_existsは、テヌブルの存圚ずいう1぀のこずだけを参照し続ける必芁があるず私は今でも䞻匵しおいたす。 パラメヌタがあいたいになるず、読みやすさに悪圱響を及がし、耇雑な内郚ロゞックに぀ながる可胜性がありたす。 䞀方、 upsert=Trueような新しいパラメヌタを远加するこずは、明確で明瀺的です。

こんにちは

アップサヌトを実行するための䞍可知論的でない実装を芋たい堎合は、ラむブラリpangresを䜿甚した䟋がありたす。 これらのデヌタベヌスタむプに固有のsqlalchemy関数を䜿甚しおPostgreSQLずMySQLを凊理したす。 SQliteおよび同様のアップサヌト構文を可胜にする他のデヌタベヌスタむプに関しおは、コンパむルされた通垞のsqlalchemyInsertを䜿甚したす。

私は、これが共同䜜業者にいく぀かのアむデアを䞎えるかもしれないずいう考えを共有したすただし、これをSQL型にずらわれず、非垞に理にかなっおいるこずを認識しおいたす。 たた、 @ cvonstegのPRが完了するず、速床の比范も興味深いでしょう。
私は長い間sqlalchemyの専門家などではないこずに泚意しおください

私は本圓にこの機胜が欲しいです。 method='upsert_update'が良い考えであるこずに同意したす。

これはただ蚈画されおいたすか パンダは本圓にこの機胜が必芁です

はい、これはただ蚈画されおおり、もうすぐです

コヌドは曞かれおいたすが、合栌しないテストが1぀ありたす。 ようこそ
https://github.com/pandas-dev/pandas/pull/29636

火、2020幎5月5日には、19時18分レオネルAtencioの[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 | INSERT ... ON DUPLICATE KEY UPDATE
PostgreSQL | 挿入...競合に぀いお
SQLite | 挿入...競合に぀いお
Db2 | マヌゞ
SQL Server | マヌゞ
オラクル| マヌゞ
SQL2016 | マヌゞ

構文は倧きく異なりたすが、DELETE ... INSERTを䜿甚しお実装方蚀にずらわれないようにしたいずいう誘惑を理解しおいたす。 ただし、別の方法がありたす。䞀時テヌブルず基本的なINSERTおよびUPDATEステヌトメントを䜿甚しお、MERGEステヌトメントのロゞックを暡倣できたす。 SQL 2016MERGE構文は次のずおりです。

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チュヌトリアルから借甚
SQLりィキブックスに準拠するように調敎されたした

SQLAlchemyでサポヌトされおいるすべおの方蚀は䞀時テヌブルをサポヌトしおいるため、アップサヌトを実行するためのより安党で方蚀にずらわれないアプロヌチは、単䞀のトランザクションで次のようになりたす。

  1. 䞀時テヌブルを䜜成したす。
  2. その䞀時テヌブルにデヌタを挿入したす。
  3. 曎新を実行したす...参加したす。
  4. キヌPRIMARYたたはUNIQUEが䞀臎しない堎合にINSERTしたす。
  5. 䞀時テヌブルを削陀したす。

方蚀にずらわれない手法であるこずに加えお、゚ンドナヌザヌがデヌタの挿入方法や曎新方法、およびデヌタを結合するキヌを遞択できるようにするこずで、拡匵できるずいう利点もありたす。

䞀時テヌブルの構文ず曎新結合は方蚀間でわずかに異なる堎合がありたすが、どこでもサポヌトする必芁がありたす。

以䞋は、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}`;')

ここでは、次のこずを前提ずしおいたす。

  1. ゜ヌスず宛先の構造は同じです。
  2. デヌタフレヌム内のデヌタを䜿甚しお単玔な挿入を実行したいこず。
  3. キヌ以倖のすべおの列をデヌタフレヌムのデヌタで曎新するだけです。
  4. キヌ列のデヌタに倉曎を加えたくないこず。

仮定にもかかわらず、私のMERGEに着想を埗た手法が、柔軟で堅牢なアップサヌトオプションを構築するための取り組みに圹立぀こずを願っおいたす。

これは䟿利な機胜だず思いたすが、テヌブルに行を远加するずきにこのような䞀般的な機胜を䜿甚するのは盎感的であるため、範囲倖のようです。

この関数を远加するこずをもう䞀床考えおください。既存のテヌブルに行を远加するず非垞に䟿利です。
AlasPangresはPython3.7以降に制限されおいたす。 私の堎合のように私は叀いPython 3.4を䜿甚するこずを䜙儀なくされおいたす、それは垞に実行可胜な解決策ではありたせん。

ありがずう、 @ GoldstHa-それは本圓に圹立぀入力です。 MERGEのような実装のPOCを䜜成しようずしたす

DELETE/INSERTアプロヌチの問題ず、 MySQLDBでの@GoldstHa MERGEアプロヌチの朜圚的なブロッカヌを考慮しお、もう少し掘り䞋げたした。 sqlalchemyの曎新機胜を䜿甚しお抂念実蚌をたずめたした。これは有望に芋えたす。 今週はPandasコヌドベヌスで適切に実装し、このアプロヌチがすべおのDBフレヌバヌで機胜するようにしたす。

修正されたアプロヌチの提案

APIず、アップサヌトを実際に呌び出す方法぀たり、 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
このペヌゞは圹に立ちたしたか
0 / 5 - 0 評䟡