Pandas: إضافة خيار (إدراج أو تحديث إذا كان المفتاح موجودًا) إلى ".to_sql"

تم إنشاؤها على ١ نوفمبر ٢٠١٦  ·  42تعليقات  ·  مصدر: pandas-dev/pandas

لنفترض أن لديك جدول 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

(ربما) مراجع تعليمات برمجية مفيدة

نظرت إلى الرمز المصدر 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) لتضمينها في الباندا نفسها.
الأمر الذي سيجعل هذا الأمر صعب التنفيذ ..

نعم ، أعتقد أن هذا خارج نطاق الباندا نظرًا لأن الارتفاعات غير مدعومة من قبل جميع محركات db.

في حين أن INSERT OR UPDATE غير مدعوم من قبل جميع المحركات ، يمكن جعل محرك INSERT OR REPLACE محايدًا عن طريق حذف الصفوف من الجدول الهدف لمجموعة المفاتيح الأساسية في فهرس DataFrame متبوعًا بإدراج كل الصفوف في DataFrame. كنت تريد أن تفعل هذا في معاملة.

TomAugspurger هل يمكننا إضافة خيار upert لمحركات db المدعومة وإلقاء خطأ لمحركات db غير المدعومة؟

أود أن أرى هذا أيضًا. أنا عالق بين استخدام SQL الخالصة و SQL Alchemy (لم أجعل هذا يعمل بعد ، أعتقد أن له علاقة بكيفية تمرير الإملاء). أستخدم نسخة psycopg2 للإدراج بالجملة ، لكني أرغب في استخدام 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 . تأخذ هذه الوظيفة قائمة بقيم القاموس وتقوم بتحديث كل صف بناءً على المفتاح الأساسي للجداول.

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 ، هل يمكنك ، من فضلك ، أن

حاولت إلقاء نظرة على مخططات التحديث السائبة لكنني فقدت حقًا ولم أتمكن من العمل.

@ cristianionescu92 مثال على ذلك:
لدي جدول يسمى المستخدم مع الحقول التالية: المعرف والاسم.

| معرف | الاسم |
| --- | --- |
| 0 | جون |
| 1 | جو |
| 2 | هاري |

لدي إطار بيانات الباندا بنفس الأعمدة ولكن القيم المحدثة:

| معرف | الاسم |
| --- | --- |
| 0 | كريس |
| 1 | جيمس |

لنفترض أيضًا أن لدينا متغير جلسة مفتوح للوصول إلى قاعدة البيانات. من خلال استدعاء هذه الطريقة:

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

ستحول Pandas الجدول إلى قائمة قواميس [{id: 0، name: "chris"}، {id: 1، name: "james"}] التي سيستخدمها sql لتحديث صفوف الجدول. لذلك سيبدو الجدول النهائي كما يلي:

| معرف | الاسم |
| --- | --- |
| 0 | كريس |
| 1 | جيمس |
| 2 | هاري |

مرحبًا ، @ danich1 وشكرًا جزيلاً على ردك. لقد اكتشفت بنفسي آليات كيفية عمل التحديث. لسوء الحظ ، لا أعرف كيفية العمل مع الجلسة ، فأنا مبتدئ تمامًا.

دعني أريكم ما أفعله:

`استيراد pypyodbc
من to_sql_newrows import clean_df_db_dups، to_sql_newrows # هذه وظيفتان وجدتهما على 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 بدلاً من ذلك ، مما يؤدي إلى إسقاط الجدول ، ومعه كل وجهات النظر والقيود.

البديل الذي رأيته لدى المستخدمين الأكثر خبرة هو التوقف عن استخدام الباندا في هذه المرحلة ، وهذا يميل إلى الانتشار في المنبع ويجعل حزمة الباندا فضفاضة بين المستخدمين ذوي الخبرة. هل هذا هو الاتجاه الذي يريد الباندا أن يسلكه؟

أتفهم أننا نريد أن نبقى حياديًا في قاعدة البيانات قدر الإمكان ، وأن نستخدم نواة SQL Alchemy. الطريقة التي تقطع أو تحذف بدلاً من زيادة حقيقية ستظل تضيف الكثير من القيمة.

التكامل مع رؤية منتج Pandas
حدث الكثير من النقاش أعلاه قبل إدخال الوسيطة method (كما ذكر من قبل kjford مع psql_insert_copy ) وإمكانية تمرير قابل للاستدعاء.

يسعدني أن أساهم إما في وظيفة الباندا الأساسية ، أو إذا أخفقت في ذلك ، في التوثيق حول الحل / أفضل الممارسات حول كيفية تحقيق وظيفة أعلى في 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 قبل أيام قليلة فقط.

مرحبًا Pandas folk: لقد واجهت نفس المشكلة ، فأنا بحاجة إلى تحديث قاعدة البيانات المحلية الخاصة بي بشكل متكرر بالسجلات التي أقوم في النهاية بتحميلها والتلاعب بها في حيوانات الباندا. لقد أنشأت مكتبة بسيطة للقيام بذلك - إنها أساسًا بديل لـ df.to_sql و pd.read_sql_table الذي يستخدم فهرس DataFrame كمفتاح أساسي افتراضيًا. يستخدم نواة 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 = 'upert')

العمل على حل عام لهذا الأمر مع cvonsteg. تخطط للعودة بتصميم مقترح في أكتوبر.

TomAugspurger كما هو مقترح ، توصلت أنا و upsert في to_sql() .

اقتراح الواجهة

يمكن إضافة متغيرين جديدين كوسيطة 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 طريقتين خاصتين جديدتين تحتويان على منطق upert ، والذي سيتم استدعاؤه من أسلوب 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))

    ...

نقترح التنفيذ التالي ، مع توضيح الأسباب المنطقية بالتفصيل أدناه (جميع النقاط مفتوحة للمناقشة):

(1) حيادي المحرك باستخدام نواة SQLAlchemy ، عبر تسلسل ذري DELETE و INSERT

  • فقط بعض dbms تدعم أصلاً upsert ، ويمكن أن تختلف عمليات التنفيذ باختلاف النكهات
  • كأول تطبيق ، نعتقد أنه سيكون من الأسهل اختبار تنفيذ واحد والحفاظ عليه عبر جميع dbms. في المستقبل ، إذا كان هناك طلب ، يمكن إضافة تطبيقات خاصة بالمحرك.
  • بالنسبة إلى upsert_ignore الواضح أنه سيتم تخطي هذه العمليات في السجلات المطابقة
  • سيكون من المفيد مقارنة التنفيذ الحيادي بالمحرك والتطبيقات الخاصة بالمحرك من حيث الأداء.

(2) Upsert على المفتاح الأساسي فقط

  • يزيد من التقصير إلى تعارضات المفتاح الأساسي ما لم يتم تحديد خلاف ذلك
  • تسمح بعض نظم إدارة قواعد البيانات (DBMS) للمستخدمين بتحديد أعمدة المفتاح غير الأساسي ، والتي يتم التحقق من التفرد مقابلها. في حين أن هذا يمنح المستخدم مزيدًا من المرونة ، إلا أنه يأتي مع عيوب محتملة. إذا كانت هذه الأعمدة لا تحتوي على قيد UNIQUE ، فمن المعقول أن الصفوف المتعددة قد تتطابق مع شرط upert. في هذه الحالة ، لا يجب إجراء أي زيادة لأنه من الغموض أن يتم تحديث السجل. لفرض هذا الأمر من الباندا ، يجب تقييم كل صف على حدة للتحقق من تطابق صف واحد أو صف واحد فقط ، قبل إدخاله. في حين أن هذه الوظيفة سهلة التنفيذ بشكل معقول ، إلا أنها تؤدي إلى أن يتطلب كل سجل عملية قراءة وكتابة (بالإضافة إلى حذف إذا تم العثور على تعارض سجل واحد) ، مما يجعله غير فعال للغاية بالنسبة لمجموعات البيانات الأكبر.
  • في التحسين المستقبلي ، إذا طلب المجتمع ذلك ، فيمكننا إضافة الوظيفة لتوسيع نطاق العمل ليس فقط على المفتاح الأساسي ، ولكن أيضًا على الحقول المحددة للمستخدم. هذا سؤال طويل المدى لفريق التطوير الأساسي ، حول ما إذا كان يجب أن تظل Pandas بسيطة لحماية المستخدمين الذين لديهم قاعدة بيانات سيئة التصميم ، أو لديهم وظائف أكثر.

TomAugspurger ، إذا كان upsert المصمم باستخدام cvonsteg يناسبك ، ونرفع طلب السحب.

أخبرنا إذا كنت ترغب في المضي قدمًا بشكل مختلف.

قراءة الاقتراح مدرجة في قائمة المهام الخاصة بي. أنا متأخر قليلا عن بلدي
البريد الإلكتروني الآن.

يوم الأربعاء 9 أكتوبر 2019 الساعة 9:18 صباحًا كتب Romain [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=AAKAOITBNTWOQRBW3OWDEZDQNXR25A5CNFSM4CU2M7O2YY3PNVWWK3TUL52HS4DFVREXG43
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AAKAOIRZQEQWUY36PQ36QTLQNXR25ANCNFSM4CU2M7OQ
.

أنا شخصياً ليس لدي أي شيء ضد ذلك ، لذا أعتقد أن العلاقات العامة موضع ترحيب. من المؤكد أن تطبيقًا واحدًا عبر جميع DBMs باستخدام نواة SQLAlchemy هو كيف يجب أن يبدأ هذا إذا كنت أقرأ نقاطك بشكل صحيح ، ونفس الشيء مع المفاتيح الأساسية فقط.

من الأسهل دائمًا البدء بشكل صغير ومركّز والتوسع من هناك

هذه الميزة بشدة.

العلاقات العامة التي كتبناها مع cvonsteg يجب أن تعطي الآن الوظيفة: وصولاً إلى المراجعات الآن!

هذه الوظيفة ستكون مجيدة للغاية! أنا لست ضليع جدا في مفردات جيثب. هل تعليق @ rugg2 بأن الوظيفة "

@ pmgh2345 - نعم ، كما قلت ، "وصولاً إلى المراجعات الآن" يعني أنه تم رفع طلب سحب وهو قيد المراجعة من المطورين الأساسيين. يمكنك مشاهدة العلاقات العامة المذكورة أعلاه (# 29636). بمجرد الموافقة عليه ، يمكنك تقنيًا تقسيم الفرع بالكود المحدث وتجميع نسختك المحلية من الباندا مع الوظيفة المضمنة. ومع ذلك ، أوصي شخصيًا بالانتظار حتى يتم دمجه في Master وإصداره ، ثم تثبيت Pip فقط أحدث نسخة من الباندا.

العلاقات العامة التي كتبناها مع 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 يمكننا إضافة

إذا كان علينا الاختيار من بين المعلمات الحالية ، كما قلت ، فكرنا في البداية في استخدام الوسيطة method ، ولكن بعد المزيد من الأفكار ، أدركنا أن كلاً من (1) الاستخدام و (2) المنطق يناسب بشكل أفضل if_exists وسيطة.

1) من وجهة نظر استخدام API
سيرغب المستخدم في اختيار كلا الأسلوبين = "متعدد" أو لا شيء من جهة ، و "upert" من جهة أخرى. ومع ذلك ، لا توجد حالات استخدام قوية مكافئة مع استخدام وظيفة "upert" في نفس الوقت مثل if_exists = "إلحاق" أو "استبدال" ، إن وجدت.

2) من وجهة نظر منطقية

  • تعمل الطريقة حاليًا على _how_ يتم إدخال البيانات: صف بصف أو "متعدد"
  • يلتقط if_exists منطق الأعمال لكيفية إدارتنا لسجلاتنا: "استبدال" ، "إلحاق" ، "upert_update" (upert عند وجود المفتاح ، وإلحاقه عند الجديد) ، و "upert_ignore" (تجاهل عند وجود المفتاح ، وإلحاقه عندما يكون جديدًا). على الرغم من أن الاستبدال والإلحاق ينظران إلى وجود الجدول ، إلا أنه يمكن فهمه أيضًا من خلال تأثيره على مستوى السجل.

اسمحوا لي أن أعرف إذا فهمت وجهة نظرك جيدًا ، ويرجى الصراخ إذا كنت تعتقد أن التنفيذ الحالي قيد المراجعة (PR # 29636) سيكون صافيًا سلبيًا!

نعم ، أنت تفهم وجهة نظري. التطبيق الحالي هو نتيجة إيجابية صافية ولكن يتضاءل قليلاً بسبب دلالات غامضة.

ما زلت أصر على أن if_exists يجب أن يستمر في الإشارة إلى شيء واحد فقط ، وهو وجود الجدول. يؤثر الغموض في المعلمات سلبًا على قابلية القراءة ، وقد يؤدي إلى منطق داخلي معقد. حيث إن إضافة معامل جديد ، مثل upsert=True هو أمر واضح وصريح.

أهلا!

إذا كنت تريد أن ترى تطبيقًا غير حيادي للقيام بعمليات شكا ، فلدي مثال مع pangres مكتبتي. يتعامل مع PostgreSQL و MySQL باستخدام وظائف sqlalchemy المخصصة لأنواع قواعد البيانات هذه. أما بالنسبة لـ SQlite (وأنواع قواعد البيانات الأخرى التي تسمح ببناء جملة مماثل) ، فإنه يستخدم إدراج sqlalchemy العادي المترجم.

أشارك في هذا التفكير أنه قد يعطي بعض الأفكار للمتعاونين (على الرغم من أنني أدرك أننا نريد أن يكون هذا من نوع SQL الحيادي وهو أمر منطقي للغاية). قد تكون أيضًا مقارنة السرعة مثيرة للاهتمام أيضًا عند مرور العلاقات العامة لـ cvonsteg .
يرجى مراعاة أنني لست خبيرًا في الكيمياء sqlalchemy لفترة طويلة أو هكذا!

أنا حقا أريد هذه الميزة. أوافق على أن method='upsert_update' فكرة جيدة.

هل هذا ما زال مخططًا له؟ تحتاج الباندا حقًا إلى هذه الميزة

نعم ، لا يزال هذا مخططًا له ، ونحن على وشك الانتهاء!

تمت كتابة الكود ، ولكن هناك اختبار واحد لم ينجح. رحب بالمساعدة!
https://github.com/pandas-dev/pandas/pull/29636

في الثلاثاء ، 5 مايو ، 2020 ، الساعة 19:18 ، كتب ليونيل أتينسيو إخطارات github.com:

هل هذا ما زال مخططًا له؟ تحتاج الباندا حقًا إلى هذه الميزة

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/pandas-dev/pandas/issues/14553#issuecomment-624223231 ،
أو إلغاء الاشتراك
https://github.com/notifications/unsubscribe-auth/AI5X625A742YTYFZE7YW5A3RQBJ6NANCNFSM4CU2M7OQ
.

أهلا! هل الوظيفة جاهزة أم لا يزال هناك شيء مفقود؟ إذا كان هناك شيء لا يزال مفقودًا ، فيرجى إبلاغي إذا كان بإمكاني المساعدة في أي شيء!

أى اخبار؟))

قادمة من عالم Java ، لم أعتقد أبدًا أن هذه الوظيفة البسيطة قد تقلب قاعدة التعليمات البرمجية الخاصة بي رأسًا على عقب.

مرحبا جميعا،

لقد بحثت في كيفية تنفيذ الارتفاعات في SQL عبر اللهجات ووجدت عددًا من التقنيات التي يمكن أن تحدد قرارات التصميم هنا. لكن أولاً ، أريد التحذير من استخدام DELETE ... INSERT logic. إذا كانت هناك مفاتيح خارجية أو مشغلات ، فسوف ينتهي الأمر بحذف السجلات الأخرى عبر قاعدة البيانات أو إفسادها. في MySQL ، يؤدي REPLACE نفس الضرر. لقد أنشأت بالفعل ساعات عمل لنفسي لإصلاح البيانات لأنني استخدمت REPLACE. ومع ذلك ، فإليك التقنيات المطبقة في SQL:

اللهجة | تقنية
- | -
MySQL | أدخل ... عند التحديث الرئيسي المكرر
PostgreSQL | أدخل ... في النزاع
سكليتي | أدخل ... في النزاع
Db2 | دمج
خادم SQL | دمج
أوراكل | دمج
SQL: 2016 | دمج

مع اختلاف التركيب الجامح ، أفهم إغراء استخدام الحذف ... إدراج لجعل لغة التنفيذ محايدة. ولكن هناك طريقة أخرى: يمكننا تقليد منطق عبارة 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 تدعم الجداول المؤقتة ، فإن النهج الأكثر أمانًا والحيادي لللهجة للقيام بعملية صعود هو ، في معاملة واحدة:

  1. قم بإنشاء جدول مؤقت.
  2. أدخل البيانات في هذا الجدول المؤقت.
  3. قم بتحديث ... انضم.
  4. أدخل حيث لا يتطابق المفتاح (أساسي أو فريد).
  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 الجهود المبذولة لبناء خيار مرن وقوي.

أعتقد أن هذه وظيفة مفيدة ولكن خارج النطاق يبدو أنه من البديهي أن يكون لديك مثل هذه الميزة المشتركة أثناء إضافة صفوف إلى جدول.

يرجى التفكير مرة أخرى لإضافة هذه الوظيفة: من المفيد جدًا إضافة صفوف إلى جدول موجود.
للأسف ، Pangres يقتصر على Python 3.7+. كما في حالتي (أجد نفسي مضطرًا لاستخدام Python 3.4 القديم) فهو ليس دائمًا حلاً قابلاً للتطبيق.

شكرًا ، GoldstHa - هذه مساهمة مفيدة حقًا. سأحاول إنشاء نقطة حماية لتنفيذ يشبه الدمج

نظرًا للمشكلات المتعلقة بالنهج DELETE/INSERT ، والمانع المحتمل على MERGE على قواعد بيانات MySQL ، فقد قمت بإجراء المزيد من البحث. لقد خدشت معًا دليلًا على المفهوم باستخدام وظيفة

اقتراح نهج معدل

كانت هناك بعض المناقشات الجيدة حول واجهة برمجة التطبيقات ، وكيف يجب أن يُطلق عليها اسم "upert" فعليًا (أي عبر الوسيطة 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 التقييمات