لنفترض أن لديك جدول SQL موجودًا يسمى person_age
، حيث id
هو المفتاح الأساسي:
age
id
1 18
2 42
ولديك أيضًا بيانات جديدة DataFrame
يُسمى extra_data
age
id
2 44
3 95
ثم سيكون من المفيد أن يكون لديك خيار على extra_data.to_sql()
يسمح بتمرير DataFrame إلى SQL مع خيار INSERT
أو UPDATE
على الصفوف ، بناءً على primary key
.
في هذه الحالة ، سيتم تحديث الصف id=2
إلى age=44
id=3
وستتم إضافة الصف
age
id
1 18
2 44
3 95
merge
من SQLAlchemy ؟نظرت إلى الرمز المصدر pandas
sql.py
للتوصل إلى حل ، لكن لم أتمكن من المتابعة.
(نعتذر عن خلط sqlalchemy
و sqlite
import pandas as pd
from sqlalchemy import create_engine
import sqlite3
conn = sqlite3.connect('example.db')
c = conn.cursor()
c.execute('''DROP TABLE IF EXISTS person_age;''')
c.execute('''
CREATE TABLE person_age
(id INTEGER PRIMARY KEY ASC, age INTEGER NOT NULL)
''')
conn.commit()
conn.close()
##### Create original table
engine = create_engine("sqlite:///example.db")
sql_df = pd.DataFrame({'id' : [1, 2], 'age' : [18, 42]})
sql_df.to_sql('person_age', engine, if_exists='append', index=False)
#### Extra data to insert/update
extra_data = pd.DataFrame({'id' : [2, 3], 'age' : [44, 95]})
extra_data.set_index('id', inplace=True)
#### extra_data.to_sql() with row update or insert option
expected_df = pd.DataFrame({'id': [1, 2, 3], 'age': [18, 44, 95]})
expected_df.set_index('id', inplace=True)
ستكون هذه وظيفة رائعة ، لكن المشكلة الرئيسية هي أننا نريدها أن تكون مستقلة عن نكهة قاعدة البيانات وتعتمد على نواة sqlalchemy (لذلك ليس 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))
...
نقترح التنفيذ التالي ، مع توضيح الأسباب المنطقية بالتفصيل أدناه (جميع النقاط مفتوحة للمناقشة):
DELETE
و INSERT
upsert
، ويمكن أن تختلف عمليات التنفيذ باختلاف النكهاتupsert_ignore
الواضح أنه سيتم تخطي هذه العمليات في السجلات المطابقةUNIQUE
، فمن المعقول أن الصفوف المتعددة قد تتطابق مع شرط upert. في هذه الحالة ، لا يجب إجراء أي زيادة لأنه من الغموض أن يتم تحديث السجل. لفرض هذا الأمر من الباندا ، يجب تقييم كل صف على حدة للتحقق من تطابق صف واحد أو صف واحد فقط ، قبل إدخاله. في حين أن هذه الوظيفة سهلة التنفيذ بشكل معقول ، إلا أنها تؤدي إلى أن يتطلب كل سجل عملية قراءة وكتابة (بالإضافة إلى حذف إذا تم العثور على تعارض سجل واحد) ، مما يجعله غير فعال للغاية بالنسبة لمجموعات البيانات الأكبر.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) من وجهة نظر منطقية
اسمحوا لي أن أعرف إذا فهمت وجهة نظرك جيدًا ، ويرجى الصراخ إذا كنت تعتقد أن التنفيذ الحالي قيد المراجعة (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 تدعم الجداول المؤقتة ، فإن النهج الأكثر أمانًا والحيادي لللهجة للقيام بعملية صعود هو ، في معاملة واحدة:
إلى جانب كونها تقنية حيادية باللهجة ، فإنها تتمتع أيضًا بميزة توسيعها من خلال السماح للمستخدم النهائي باختيار كيفية إدراج أو كيفية تحديث البيانات وكذلك على أي مفتاح للانضمام إلى البيانات.
في حين أن بناء جملة الجداول المؤقتة ، وربط التحديث قد يختلف قليلاً بين اللهجات ، يجب دعمها في كل مكان.
يوجد أدناه دليل على المفهوم الذي كتبته لـ MySQL:
import uuid
import pandas as pd
from sqlalchemy import create_engine
# This proof of concept uses this sample database
# https://downloads.mysql.com/docs/world.sql.zip
# Arbitrary, unique temp table name to avoid possible collision
source = str(uuid.uuid4()).split('-')[-1]
# Table we're doing our upsert against
target = 'countrylanguage'
db_url = 'mysql://<{user: }>:<{passwd: }>.@<{host: }>/<{db: }>'
df = pd.read_sql(
f'SELECT * FROM `{target}`;',
db_url
)
# Change for UPDATE, 5.3->5.4
df.at[0,'Percentage'] = 5.4
# Change for INSERT
df = df.append(
{'CountryCode': 'ABW','Language': 'Arabic','IsOfficial': 'F','Percentage':0.0},
ignore_index=True
)
# List of PRIMARY or UNIQUE keys
key = ['CountryCode','Language']
# Do all of this in a single transaction
engine = create_engine(db_url)
with engine.begin() as con:
# Create temp table like target table to stage data for upsert
con.execute(f'CREATE TEMPORARY TABLE `{source}` LIKE `{target}`;')
# Insert dataframe into temp table
df.to_sql(source,con,if_exists='append',index=False,method='multi')
# INSERT where the key doesn't match (new rows)
con.execute(f'''
INSERT INTO `{target}`
SELECT
*
FROM
`{source}`
WHERE
(`{'`, `'.join(key)}`) NOT IN (SELECT `{'`, `'.join(key)}` FROM `{target}`);
''')
# Create a doubled list of tuples of non-key columns to template the update statement
non_key_columns = [(i,i) for i in df.columns if i not in key]
# Whitespace for aesthetics
whitespace = '\n\t\t\t'
# Do an UPDATE ... JOIN to set all non-key columns of target to equal source
con.execute(f'''
UPDATE
`{target}` `t`
JOIN
`{source}` `s` ON `t`.`{"` AND `t`.`".join(["`=`s`.`".join(i) for i in zip(key,key)])}`
SET
`t`.`{f"`,{whitespace}`t`.`".join(["`=`s`.`".join(i) for i in non_key_columns])}`;
''')
# Drop our temp table.
con.execute(f'DROP TABLE `{source}`;')
هنا ، أفترض الافتراضات التالية:
على الرغم من الافتراضات ، آمل أن تساعد تقنيتي المستوحاة من MERGE الجهود المبذولة لبناء خيار مرن وقوي.
أعتقد أن هذه وظيفة مفيدة ولكن خارج النطاق يبدو أنه من البديهي أن يكون لديك مثل هذه الميزة المشتركة أثناء إضافة صفوف إلى جدول.
يرجى التفكير مرة أخرى لإضافة هذه الوظيفة: من المفيد جدًا إضافة صفوف إلى جدول موجود.
للأسف ، 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
التعليق الأكثر فائدة
في حين أن
INSERT OR UPDATE
غير مدعوم من قبل جميع المحركات ، يمكن جعل محركINSERT OR REPLACE
محايدًا عن طريق حذف الصفوف من الجدول الهدف لمجموعة المفاتيح الأساسية في فهرس DataFrame متبوعًا بإدراج كل الصفوف في DataFrame. كنت تريد أن تفعل هذا في معاملة.