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
merge
๋ฅผ ์ฌ์ฉ ํ์๊ฒ ์ต๋๊น?ํด๊ฒฐ์ฑ
์ ์ฐพ๊ธฐ ์ํด 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)
์ด๊ฒ์ ์ข์ ๊ธฐ๋ฅ์ด ๋ ๊ฒ์ด์ง๋ง ์ฃผ์ ๋ฌธ์ ๋ pandas ์์ฒด์ ํฌํจํ๊ธฐ ์ํด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ทจํฅ์ ๋
๋ฆฝ์ ์ด๊ณ sqlalchemy ์ฝ์ด(sqlalchemy ORM์ด ์๋)๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๊ธฐ๋ฅผ ์ํ๋ค๋ ๊ฒ์
๋๋ค.
์ด๊ฒ์ ๊ตฌํํ๊ธฐ ์ด๋ ต๊ฒ ๋ง๋ค ๊ฒ์
๋๋ค ..
์, upsert๊ฐ ๋ชจ๋ db ์์ง์์ ์ง์๋์ง ์๊ธฐ ๋๋ฌธ์ ์ด๊ฒ์ด ํฌ๋์ ๋ฒ์๋ฅผ ๋ฒ์ด๋ฌ๋ค๊ณ ์๊ฐํฉ๋๋ค.
INSERT OR UPDATE
๊ฐ ๋ชจ๋ ์์ง์์ ์ง์๋๋ ๊ฒ์ ์๋์ง๋ง INSERT OR REPLACE
๋ DataFrame ์ธ๋ฑ์ค์ ๊ธฐ๋ณธ ํค ์งํฉ์ ๋ํ ๋์ ํ
์ด๋ธ์์ ํ์ ์ญ์ ํ ๋ค์ DataFrame์ ๋ชจ๋ ํ. ํธ๋์ญ์
์์ ์ด๊ฒ์ ํ๊ณ ์ถ์ ๊ฒ์
๋๋ค.
@TomAugspurger ์ง์๋๋ db ์์ง์ ๋ํด upsert ์ต์ ์ ์ถ๊ฐํ๊ณ ์ง์๋์ง ์๋ db ์์ง์ ๋ํด ์ค๋ฅ๋ฅผ ๋ฐ์์ํฌ ์ ์์ต๋๊น?
์ด๊ฒ๋ ๋ณด๊ณ ์ถ๋ค์. ๋๋ ์์ํ SQL๊ณผ SQL Alchemy๋ฅผ ์ฌ์ฉํ๋ ์ฌ์ด์ ๋ผ์ด ์์ต๋๋ค. ๋๋ ๋๋ ์ฝ์ ์ ์ํด 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_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์ bulk_update_mappings ๋ฅผ ์ฌ์ฉํ๋
session.bulk_update_mappings(
Table,
pandas_df.to_dict(orient='records)
)
@neilfrndes์ ๋์ํฉ๋๋ค. ์ผ๋ถ DB๊ฐ ์ง์ํ์ง ์๊ธฐ ๋๋ฌธ์ ์ด์ ๊ฐ์ ๋ฉ์ง ๊ธฐ๋ฅ์ด ๊ตฌํ๋์ง ์๋๋ก ํด์๋ ์ ๋ฉ๋๋ค. ์ด ๊ธฐ๋ฅ์ด ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ด ์์ต๋๊น?
์๋ง. ๋๊ตฐ๊ฐ๊ฐ PR์ ํ๋ค๋ฉด. ์ข ๋ ์๊ฐํด ๋ณด๋ฉด ์ผ๋ถ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์ง์ํ์ง ์๋๋ค๋ ์์น์ ๋ฐ๋ผ ์ด์ ๋ฐ๋ํ์ง ์๋๋ค๊ณ ์๊ฐํฉ๋๋ค. ๊ทธ๋ฌ๋ ๋๋ sql ์ฝ๋์ ๋๋ฌด ์ต์ํ์ง ์์ผ๋ฏ๋ก ์ต์ ์ ์ ๊ทผ ๋ฐฉ์์ด ๋ฌด์์ธ์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
ํ ๊ฐ์ง ๊ฐ๋ฅ์ฑ์ ์ด PR์ด ๋์
๋ ๊ฒฝ์ฐ method
ํธ์ถ ๊ฐ๋ฅ์ ์ฌ์ฉํ์ฌ upsert์ ๋ํ ๋ช ๊ฐ์ง ์๋ฅผ ์ ๊ณตํ๋ ๊ฒ์
๋๋ค: https://github.com/pandas-dev/pandas/pull/21401
๋ค์ด ํฌ์คํธ ๊ทธ๋ ์ค ๊ทธ ๊ฐ์ด ๋ณด์ผ ๊ฒ์ด๋ค (์๋) :
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์ ๋ํด์๋ ์ ์ฌํ ์์ ์ ์ํํ ์ ์์ต๋๋ค.
ํฌ์คํธ๊ทธ๋ ์ค์ ๊ฒฝ์ฐ 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 ์ด๊ฒ์ด ์ด๋ป๊ฒ ์๋ํ๋์ง ์๋ฅผ ๋ค์ด ์ฃผ์๊ฒ ์ต๋๊น?
๋๋ bulk_update_mappings๋ฅผ ์ดํด๋ณด๋ ค๊ณ ํ์ง๋ง ์ ๋ง๋ก ๊ธธ์ ์์ด ์๋ํ๊ฒ ๋ง๋ค์ง ๋ชปํ์ต๋๋ค.
@critianionescu92 ์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
id ๋ฐ name ํ๋๊ฐ ์๋ User๋ผ๋ ํ
์ด๋ธ์ด ์์ต๋๋ค.
| ์์ด๋ | ์ด๋ฆ |
| --- | --- |
| 0 | ์กด |
| 1 | ์กฐ |
| 2 | ํด๋ฆฌ |
์ด์ ๊ฐ์ง๋ง ๊ฐ์ด ์ ๋ฐ์ดํธ๋ ํฌ๋ ๋ฐ์ดํฐ ํ๋ ์์ด ์์ต๋๋ค.
| ์์ด๋ | ์ด๋ฆ |
| --- | --- |
| 0 | ํฌ๋ฆฌ์ค |
| 1 | ์ ์์ค |
๋ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ก์ธ์คํ๊ธฐ ์ํด ์ด๋ ค ์๋ ์ธ์ ๋ณ์๊ฐ ์๋ค๊ณ ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค. ์ด ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ:
session.bulk_update_mappings(
User,
<pandas dataframe above>.to_dict(orient='records')
)
Pandas๋ ํ ์ด๋ธ์ sql์ด ํ ์ด๋ธ์ ํ์ ์ ๋ฐ์ดํธํ๋ ๋ฐ ์ฌ์ฉํ ์ฌ์ ๋ชฉ๋ก [{id: 0, name: "chris"}, {id: 1, name:"james"}]์ผ๋ก ๋ณํํฉ๋๋ค. ๋ฐ๋ผ์ ์ต์ข ํ ์ด๋ธ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
| ์์ด๋ | ์ด๋ฆ |
| --- | --- |
| 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
๋ ๊ทธ ์ค ํ๋์
๋๋ค. ํนํ ๋ง์ ์ฌ๋๋ค์ด ํด๊ฒฐ ๋ฐฉ๋ฒ์ด replace
๋์ ์ฌ์ฉํ์ฌ ํ
์ด๋ธ๊ณผ ํจ๊ป ๋ชจ๋ ๋ณด๊ธฐ ๋ฐ ์ ์ฝ ์กฐ๊ฑด์ ์ญ์ ํ๋ค๋ ์ฌ์ค์ ์๊ณ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
๊ฒฝํ ๋ง์ ์ฌ์ฉ์์๊ฒ์ ๋ณธ ๋์์ ์ด ๋จ๊ณ์์ ํ๋ค ์ฌ์ฉ์ ์ค๋จํ๋ ๊ฒ์ ๋๋ค. ์ด๊ฒ์ ์ ์คํธ๋ฆผ์ผ๋ก ์ ํ๋๋ ๊ฒฝํฅ์ด ์๊ณ ๊ฒฝํ ๋ง์ ์ฌ์ฉ์ ์ฌ์ด์์ ํ๋ค ํจํค์ง๋ฅผ ๋์จํ๊ฒ ์ ์งํ๊ฒ ๋ง๋ญ๋๋ค. ์ด๊ฒ์ด ํ๋ค๊ฐ ๊ฐ๊ณ ์ ํ๋ ๋ฐฉํฅ์ธ๊ฐ?
๋๋ ์ฐ๋ฆฌ๊ฐ to_sql์ด ๊ฐ๋ฅํ ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ตฌ์ ๋ฐ์ง ์๊ณ ํต์ฌ SQL ์ฐ๊ธ์ ์ ์ฌ์ฉํ๊ธฐ๋ฅผ ์ํ๋ค๋ ๊ฒ์ ์ดํดํฉ๋๋ค. ์ง์ ํ upsert ๋์ ์๋ฅด๊ฑฐ๋ ์ญ์ ํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ ํ โโ๋ง์ ๊ฐ์น๋ฅผ ์ถ๊ฐํฉ๋๋ค.
Pandas ์ ํ ๋น์ ๊ณผ ํตํฉ
์์ ๋ง์ ๋
ผ์์ method
์ธ์( @kjford ๊ฐ psql_insert_copy
๋ก ์ธ๊ธํ ๋ฐ์ ๊ฐ์ด)๊ฐ ๋์
๋๊ณ
๋๋ Pandas์ ํต์ฌ ๊ธฐ๋ฅ ๋๋ ์คํจํ๋ ๊ฒฝ์ฐ ์๋์ ๊ฐ์ด Pandas ๋ด์์ upsert ๊ธฐ๋ฅ์ ๋ฌ์ฑํ๋ ๋ฐฉ๋ฒ์ ๋ํ ์๋ฃจ์
/๋ชจ๋ฒ ์ฌ๋ก์ ๋ํ ๋ฌธ์์ ๊ธฐ๊บผ์ด ๊ธฐ์ฌํ๊ณ ์ถ์ต๋๋ค.
https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html#io -sql-method
Pandas ํต์ฌ ๊ฐ๋ฐ/์ ํ ๊ด๋ฆฌ์๊ฐ ์ ํธํ๋ ๋ฐฉํฅ์ ๋ฌด์์ ๋๊น?
๋๋ ์ฐ๋ฆฌ๊ฐ ์์ง์ ํน์ ํ ๊ตฌํ์ ์ด๋ ค ์๋ค๊ณ ์๊ฐํฉ๋๋ค. method='upsert'
๋ฅผ ์ฌ์ฉํ์๋ ์ ์์ ํ๋นํด ๋ณด์ด์ง๋ง ํ์์ ์์ ๋ช
ํํ ๋์์ธ ์ ์์ ํด์ค ๋๊ตฐ๊ฐ๊ฐ ํ์ํ๋ค๊ณ ์๊ฐํฉ๋๋ค.
์๊ฐ์ด ์ง๋จ์ ๋ฐ๋ผ ์ฌ๋ฌ CSV์์ MySQL ํ ์ด๋ธ์ ๊ธฐ์กด ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธํ๋ ค๋ ์ ์ฌํ ์๊ตฌ ์ฌํญ์ด ์์ต๋๋ค.
๋๋ df.to_sql()์ด ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ์๋ก ์์ฑ๋ ์์ ํ ์ด๋ธ ์ ์ฝ์ ํ ๋ค์ MySQL ์ฟผ๋ฆฌ๋ฅผ ์คํํ์ฌ ๊ธฐ์กด ํ ์ด๋ธ์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐ/์ ๋ฐ์ดํธํ๋ ๋ฐฉ๋ฒ์ ์ ์ดํ ์ ์๋ค๊ณ ์๊ฐํ์ต๋๋ค.
MySQL ์ฐธ์กฐ: https://stackoverflow.com/questions/2472229/insert-into-select-from-on-duplicate-key-update?answertab=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์ ์๋ ๊ทธ๋๋ก ํฌํจํ๊ธฐ์ ์ ํฉํ์ง ์์ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๋ด ํน์ ์ฌ์ฉ ์ฌ๋ก์ ๊ฒฝ์ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค. Pandas์ ๋ง๊ฒ ๋ง์ฌ์งํ๋ ๋ฐ ๊ด์ฌ์ด ์๋ค๋ฉด ๊ธฐ๊บผ์ด ๋์๋๋ฆฌ๊ฒ ์ต๋๋ค.
ํ์ฌ๋ก์๋ ๋ค์์ด ์๋ํฉ๋๋ค(current-ish pandas ๋ฐ sqlalchemy์ ์ ํ๋ ๊ฒฝ์ฐ, ๊ธฐ๋ณธ ํค๋ก ๋ช ๋ช ๋ index, SQLite ๋๋ Postgres ๋ฐฑ์๋ ๋ฐ ์ง์๋๋ ๋ฐ์ดํฐ ์ ํ):
pip install pandabase / pandabase.to_sql(df, table_name, con_string, how='upsert')
cvonsteg๋ฅผ ์ฌ์ฉํ์ฌ ์ด์ ๋ํ ์ผ๋ฐ์ ์ธ ์๋ฃจ์ ์ ์์ ์ค์ ๋๋ค. 10์์ ์ ์๋ ๋์์ธ์ผ๋ก ๋์์ฌ ๊ณํ์ ๋๋ค.
@TomAugspurger ๊ฐ ์ ์ํ ๋๋ก @rugg2 ์ ์ ๋ to_sql()
์ upsert
์ต์
์ ๋ํด ๋ค์๊ณผ ๊ฐ์ ์ค๊ณ ์ ์์ ์ ์ํ์ต๋๋ค.
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() ๋ฉ์๋์์ ํธ์ถ๋๋ upsert ๋
ผ๋ฆฌ๋ฅผ ํฌํจํ๋ 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))
...
์ฐ๋ฆฌ๋ ์๋์ ์์ธํ ์ค๋ช ๋ ๊ทผ๊ฑฐ์ ํจ๊ป ๋ค์ ๊ตฌํ์ ์ ์ํฉ๋๋ค(๋ชจ๋ ์์ ์ ํ ๋ก ์ ์ํด ์ด๋ ค ์์ต๋๋ค).
DELETE
๋ฐ INSERT
์ ์์ ์ํ์ค๋ฅผ ํตํด SQLAlchemy ์ฝ์ด๋ฅผ ์ฌ์ฉํ๋ ์์ง ๋ถ๊ฐ์ง๋ก ์ upsert
์ง์ํ๋ฉฐ ๊ตฌํ์ ๋ฒ์ ์ ๋ฐ๋ผ ๋ค๋ฅผ ์ ์์ต๋๋ค.upsert_ignore
์ด๋ฌํ ์์
์ ์ผ์นํ๋ ๋ ์ฝ๋์์ ๋ถ๋ช
ํ ๊ฑด๋๋๋๋ค.UNIQUE
์ ์ฝ ์กฐ๊ฑด์ด ์์ผ๋ฉด ์ฌ๋ฌ ํ์ด upsert ์กฐ๊ฑด๊ณผ ์ผ์นํ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ ์
๋ฐ์ดํธํด์ผ ํ๋ ๋ ์ฝ๋๊ฐ ๋ชจํธํ๋ฏ๋ก upsert๋ฅผ ์ํํด์๋ ์ ๋ฉ๋๋ค. pandas์์ ์ด๋ฅผ ์ ์ฉํ๋ ค๋ฉด ์ฝ์
ํ๊ธฐ ์ ์ ๊ฐ ํ์ ๊ฐ๋ณ์ ์ผ๋ก ํ๊ฐํ์ฌ 1๊ฐ ๋๋ 0๊ฐ ํ๋ง ์ผ์นํ๋์ง ํ์ธํด์ผ ํฉ๋๋ค. ์ด ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ๊ฐ ์๋นํ ๊ฐ๋จํ์ง๋ง ๊ฐ ๋ ์ฝ๋์ ์ฝ๊ธฐ ๋ฐ ์ฐ๊ธฐ ์์
(1๊ฐ ๋ ์ฝ๋ ์ถฉ๋์ด ๋ฐ๊ฒฌ๋ ๊ฒฝ์ฐ ์ญ์ )์ด ํ์ํ๋ฏ๋ก ๋ ํฐ ๋ฐ์ดํฐ ์ธํธ์์๋ ๋งค์ฐ ๋นํจ์จ์ ์
๋๋ค.@TomAugspurger , upsert
์ ์์ด ๊ทํ์๊ฒ ์ ํฉ
๋ค๋ฅธ ๋ฐฉ์์ผ๋ก ์งํํ๋ ค๋ฉด ์๋ ค์ฃผ์ญ์์ค.
์ ์์๋ฅผ ์ฝ๋ ๊ฒ์ ๋์ ํ ์ผ ๋ชฉ๋ก์ ์์ต๋๋ค. ๋๋ ๋ด๋ณด๋ค ์ฝ๊ฐ ๋ค์ณ์ ธ์๋ค.
์ง๊ธ ์ด๋ฉ์ผ.
2019๋ 10์ 9์ผ ์์์ผ ์ค์ 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=AAKAOITBNTWOQRBW3OWDEZDQNXR25A5CNFSM4CU2M7O2YY3PNVWWK3TUL52HS4DFVEXG43VMXVBW
๋๋ ์ค๋ ๋ ์์๊ฑฐ
https://github.com/notifications/unsubscribe-auth/AAKAOIRZQEQWUY36PQ36QTLQNXR25ANCNFSM4CU2M7OQ
.
๋๋ ๊ฐ์ธ์ ์ผ๋ก ๊ทธ๊ฒ์ ๋ํด ๋ฐ๋ํ๋ ๊ฒ์ด ์์ผ๋ฏ๋ก PR์ ํ์ํ๋ค๊ณ ์๊ฐํฉ๋๋ค. SQLAlchemy ์ฝ์ด๋ฅผ ์ฌ์ฉํ๋ ๋ชจ๋ DBM์ ๋ํ ํ ๊ฐ์ง ๊ตฌํ์ ๋ด๊ฐ ๊ทํ์ ์์ ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ฝ๊ณ ๊ธฐ๋ณธ ํค์ ๋์ผํ ๊ฒฝ์ฐ ํ์คํ ์์ํด์ผ ํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
ํญ์ ์๊ฒ ์์ํ์ฌ ์ง์คํ๊ณ ํ์ฅํ๊ธฐ๊ฐ ๋ ์ฝ์ต๋๋ค.
์ด ๊ธฐ๋ฅ์ด ์ ์คํ ํ์ํฉ๋๋ค.
์ฐ๋ฆฌ๊ฐ cvonsteg๋ก ์์ฑํ PR์ ์ด์ ๊ธฐ๋ฅ์ ์ ๊ณตํด์ผ ํฉ๋๋ค: ์ง๊ธ ๋ฆฌ๋ทฐ๋ก!
์ด ๊ธฐ๋ฅ์ ์ ๋์ ์ผ๋ก ์๊ด์ค๋ฌ์ธ ๊ฒ์ ๋๋ค! ๋๋ github์ ์ดํ์ ์ ํตํ์ง ์์ต๋๋ค. ๊ธฐ๋ฅ์ด "์ง๊ธ ๋ฆฌ๋ทฐ๋ก ์งํ ์ค"์ด๋ผ๋ @rugg2 ์ ์๊ฒฌ์ ํด๋น ๊ธฐ๋ฅ์ ๊ฒํ ํ๋ ๊ฒ์ด ํฌ๋ ํ์ ๋ฌ๋ ค ์๋ค๋ ์๋ฏธ์ธ๊ฐ์? ์น์ธ๋๋ฉด ์ค์นํ ์ ์๋ ์ ๋ฒ์ ์ ํ๋ค๋ฅผ ํตํด ์ฌ์ฉํ ์ ์๊ฒ ๋ฉ๋๊น? ์๋๋ฉด git์ ํตํด ์ง์ ์ปค๋ฐ์ ์ ์ฉํด์ผ ํฉ๋๊น? (๋๋ conda๋ฅผ ํตํด ์ด๊ฒ์ ๋ฌธ์ ๊ฐ ์์์ผ๋ฏ๋ก ์ด ๊ฒฝ์ฐ ์ด ๊ธฐ๋ฅ์ด ์ค๋น๋ ๋๊น์ง ์๋๋ฅผ ๋์ด๊ณ ์ถ์ต๋๋ค.) ๊ฐ์ฌํฉ๋๋ค!!
@ pmgh2345 - ๋ค, "์ง๊ธ ๋ฆฌ๋ทฐ๊น์ง"๋ ํ ๋ฆฌํ์คํธ๊ฐ ์ ๊ธฐ๋์๊ณ ํต์ฌ ๊ฐ๋ฐ์๋ก๋ถํฐ ๊ฒํ
์ฐ๋ฆฌ๊ฐ cvonsteg๋ก ์์ฑํ PR์ ์ด์ ๊ธฐ๋ฅ์ ์ ๊ณตํด์ผ ํฉ๋๋ค: ์ง๊ธ ๋ฆฌ๋ทฐ๋ก!
if_exists
์ฌ์ฉํ๋ ๋์ to_sql
๋ฉ์๋์ ์ ๋งค๊ฐ๋ณ์๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ์ด์ ๋ if_exists
๊ฐ ํ์ด ์๋ ํ
์ด๋ธ์ ์กด์ฌ ์ฌ๋ถ๋ฅผ ํ์ธํ๊ธฐ ๋๋ฌธ์
๋๋ค.
@cvonsteg๋ ์๋ method=
์ฌ์ฉ์ ์ ์ ํ๋๋ฐ, ์ด๋ if_exists
๋ํด ๋ ๊ฐ์ง ์๋ฏธ๋ฅผ ๊ฐ๋ ๋ชจํธ์ฑ์ ๋ฐฉ์งํฉ๋๋ค.
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"๋ฅผ ์ ํํ๊ธฐ๋ฅผ ์ํ ๊ฒ์
๋๋ค. ๊ทธ๋ฌ๋ if_exists="append" ๋๋ "replace"(์๋ ๊ฒฝ์ฐ)์ ๋์์ "upsert" ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋์ผํ ๊ฐ๋ ฅํ ์ฌ์ฉ ์ฌ๋ก๋ ์์ต๋๋ค.
2) ๋ ผ๋ฆฌ์ ์ธ ๊ด์ ์์
๊ทํ์ ์์ ์ ์ ์ดํดํ๋ค๋ฉด ์๋ ค์ฃผ์๊ณ , ํ์ฌ ๊ฒํ ์ค์ธ ๊ตฌํ(PR #29636)์ด ๋ถ์ ์ ์ธ ์ํฅ์ ๋ฏธ์น ๊ฒ์ด๋ผ๊ณ ์๊ฐํ์๋ฉด ํฐ ์๋ฆฌ๋ก ๋ง์ํด ์ฃผ์ญ์์ค!
๋ค, ์ ์์ ์ ์ดํดํ๊ณ ๊ณ์ญ๋๋ค. ํ์ฌ ๊ตฌํ์ ์ ๊ธ์ ์ ์ด์ง๋ง ๋ชจํธํ ์๋ฏธ๋ก ์ธํด ์ฝ๊ฐ ๊ฐ์ํ์ต๋๋ค.
๋๋ ์ฌ์ ํ if_exists
๊ฐ ํ
์ด๋ธ ์กด์ฌ๋ผ๋ ํ ๊ฐ์ง๋ง ๊ณ์ ์ฐธ์กฐํด์ผ ํ๋ค๊ณ ์ฃผ์ฅํฉ๋๋ค. ๋งค๊ฐ๋ณ์์ ๋ชจํธ์ฑ์ด ์์ผ๋ฉด ๊ฐ๋
์ฑ์ ๋ถ์ ์ ์ธ ์ํฅ์ ๋ฏธ์น๊ณ ๋ด๋ถ ๋
ผ๋ฆฌ๊ฐ ๋ณต์กํด์ง ์ ์์ต๋๋ค. ๋ฐ๋ฉด upsert=True
์ ๊ฐ์ ์ ๋งค๊ฐ๋ณ์๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ๋ช
ํํ๊ณ ๋ช
์์ ์
๋๋ค.
์ฌ๋ณด์ธ์!
upserts ์ํ์ ๋ํ ๋ถ๊ฐ์ง๋ก ์ ๊ตฌํ์ โโ๋ณด๋ ค๋ฉด ๋ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ
๊ณต๋ ์์
์์๊ฒ ๋ช ๊ฐ์ง ์์ด๋์ด๋ฅผ ์ ๊ณตํ ์ ์๋ค๋ ์๊ฐ์ ๊ณต์ ํฉ๋๋ค. ๋ํ @cvonsteg ์ PR ์ด ์งํ๋ ๋ ์๋ ๋น๊ต๋ ํฅ๋ฏธ๋ก์ธ ๊ฒ์
๋๋ค.
์ ๋ ์ค๋ซ๋์ sqlalchemy ์ ๋ฌธ๊ฐ๊ฐ ์๋์์ต๋๋ค!
์ด ๊ธฐ๋ฅ์ ์ ๋ง ์ํฉ๋๋ค. method='upsert_update'
๊ฐ ์ข์ ์๊ฐ์ด๋ผ๋ ๋ฐ ๋์ํฉ๋๋ค.
์ด๊ฑฐ ์์ง ์์ ์ธ๊ฐ์? ํฌ๋๋ ์ด ๊ธฐ๋ฅ์ด ์ ๋ง ํ์ํฉ๋๋ค
์, ์ด๊ฒ์ ์์ง ๊ณํ๋์ด ์์ผ๋ฉฐ ๊ฑฐ์ โโ๋ค ์์ต๋๋ค!
์ฝ๋๊ฐ ์์ฑ๋์์ง๋ง ํต๊ณผํ์ง ๋ชปํ ํ
์คํธ๊ฐ ํ๋ ์์ต๋๋ค. ๋์์ ํ์ํฉ๋๋ค!
https://github.com/pandas-dev/pandas/pull/29636
2020๋ 5์ 5์ผ ํ์์ผ 19:18 Leonel Atencio [email protected]์์ ๋ค์๊ณผ ๊ฐ์ด ์ผ์ต๋๋ค.
์ด๊ฑฐ ์์ง ์์ ์ธ๊ฐ์? ํฌ๋๋ ์ด ๊ธฐ๋ฅ์ด ์ ๋ง ํ์ํฉ๋๋ค
โ
๋น์ ์ด ์ธ๊ธ๋์๊ธฐ ๋๋ฌธ์ ์ด๊ฒ์ ๋ฐ๋ ๊ฒ์ ๋๋ค.
์ด ์ด๋ฉ์ผ์ ์ง์ ๋ต์ฅํ๊ณ GitHub์์ ํ์ธํ์ธ์.
https://github.com/pandas-dev/pandas/issues/14553#issuecomment-624223231 ,
๋๋ ๊ตฌ๋ ์ทจ์
https://github.com/notifications/unsubscribe-auth/AI5X625A742YTYFZE7YW5A3RQBJ6NANCNFSM4CU2M7OQ
.
์ฌ๋ณด์ธ์! ๊ธฐ๋ฅ์ด ์ค๋น๋์๊ฑฐ๋ ์์ง ๋๋ฝ๋ ๊ฒ์ด ์์ต๋๊น? ๊ทธ๋๋ ๋๋ฝ๋ ๊ฒ์ด ์์ผ๋ฉด ๋์์ด ํ์ํ๋ฉด ์๋ ค์ฃผ์ธ์!
์์์ด ์๋์?))
Java ์ธ๊ณ์์ ์๊ธฐ ๋๋ฌธ์ ์ด ๊ฐ๋จํ ๊ธฐ๋ฅ์ด ๋ด ์ฝ๋๋ฒ ์ด์ค๋ฅผ ์์ ํ ๋ค์ง์ ๊ฒ์ด๋ผ๊ณ ๋ ์๊ฐํ์ง ๋ชปํ์ต๋๋ค.
์๋ ํ์ธ์ ์ฌ๋ฌ๋ถ,
๋๋ upsert๊ฐ ์ฌ๋ฌ ๋ฐฉ์ธ์ ๊ฑธ์ณ SQL์์ ๊ตฌํ๋๋ ๋ฐฉ๋ฒ์ ์ดํด๋ณด์๊ณ ์ฌ๊ธฐ์์ ๋์์ธ ๊ฒฐ์ ์ ์๋ฆด ์ ์๋ ์ฌ๋ฌ ๊ธฐ์ ์ ์ฐพ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๋จผ์ DELETE ... INSERT ๋ ผ๋ฆฌ ์ฌ์ฉ์ ๋ํด ๊ฒฝ๊ณ ํ๊ณ ์ถ์ต๋๋ค. ์ธ๋ ํค๋ ํธ๋ฆฌ๊ฑฐ๊ฐ ์๋ ๊ฒฝ์ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ์ฒด์ ๋ค๋ฅธ ๋ ์ฝ๋๊ฐ ์ญ์ ๋๊ฑฐ๋ ์๋ง์ด ๋ฉ๋๋ค. MySQL์์ REPLACE๋ ๋์ผํ ํผํด๋ฅผ ์ค๋๋ค. REPLACE๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ค์ ๋ก ๋ฐ์ดํฐ๋ฅผ ์์ ํ๋ ๋ฐ ๋ช ์๊ฐ์ด๋ ๊ฑธ๋ ธ์ต๋๋ค. ์ฆ, SQL์์ ๊ตฌํ๋ ๊ธฐ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๋ฐฉ์ธ | ๊ธฐ์
-- | --
MySQL | INSERT ... ์ค๋ณต ํค ์
๋ฐ์ดํธ ์
PostgreSQL | INSERT ... ์ถฉ๋ ์
SQLite | INSERT ... ์ถฉ๋ ์
DB2 | ๋ณํฉ
SQL ์๋ฒ | ๋ณํฉ
์ค๋ผํด | ๋ณํฉ
SQL:2016 | ๋ณํฉ
๋งค์ฐ ๋ค์ํ ๊ตฌ๋ฌธ์ ์ฌ์ฉํ์ฌ DELETE ... INSERT๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํ ๋ฐฉ์ธ์ ๋ถ๊ฐ์ง๋ก ์ ์ผ๋ก ๋ง๋ค๊ณ ์ถ์ ์ ํน์ ์ดํดํฉ๋๋ค. ๊ทธ๋ฌ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. ์์ ํ ์ด๋ธ๊ณผ ๊ธฐ๋ณธ INSERT ๋ฐ UPDATE ๋ฌธ์ ์ฌ์ฉํ์ฌ MERGE ๋ฌธ์ ๋ ผ๋ฆฌ๋ฅผ ๋ชจ๋ฐฉํ ์ ์์ต๋๋ค. 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,...);
์ค๋ผํด ํํ ๋ฆฌ์ผ ์์ ์ฐจ์ฉ
SQL Wikibook ์ ๋ง๊ฒ ์กฐ์ ๋จ
SQLAlchemy์์ ์ง์ํ๋ ๋ชจ๋ ๋ฐฉ์ธ์ ์์ ํ ์ด๋ธ์ ์ง์ํ๋ฏ๋ก upsert๋ฅผ ์ํํ๋ ๋ ์์ ํ๊ณ ๋ฐฉ์ธ์ ๊ตฌ์ ๋ฐ์ง ์๋ ์ ๊ทผ ๋ฐฉ์์ ๋จ์ผ ํธ๋์ญ์ ์์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๋ฐฉ์ธ์ ๊ตฌ์ ๋ฐ์ง ์๋ ๊ธฐ์ ์ธ ๊ฒ ์ธ์๋ ์ต์ข ์ฌ์ฉ์๊ฐ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ ํ๊ฑฐ๋ ์ ๋ฐ์ดํธํ๋ ๋ฐฉ๋ฒ๊ณผ ๋ฐ์ดํฐ๋ฅผ ๊ฒฐํฉํ ํค๋ฅผ ์ ํํ ์ ์๋๋ก ํจ์ผ๋ก์จ ํ์ฅ๋๋ ์ด์ ๋ ์์ต๋๋ค.
์์ ํ ์ด๋ธ์ ๊ตฌ๋ฌธ๊ณผ ์ ๋ฐ์ดํธ ์กฐ์ธ์ ๋ฐฉ์ธ๋ง๋ค ์ฝ๊ฐ ๋ค๋ฅผ ์ ์์ง๋ง ๋ชจ๋ ๊ณณ์์ ์ง์๋์ด์ผ ํฉ๋๋ค.
์๋๋ ๋ด๊ฐ 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์์ ์๊ฐ์ ์ป์ ๊ธฐ์ ์ด ์ ์ฐํ๊ณ ๊ฐ๋ ฅํ upsert ์ต์ ์ ๊ตฌ์ถํ๋ ค๋ ๋ ธ๋ ฅ์ ์ ๋ณด๊ฐ ๋๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.
๋๋ ์ด๊ฒ์ด ์ ์ฉํ ๊ธฐ๋ฅ์ด๋ผ๊ณ ์๊ฐํ์ง๋ง ๋ฒ์๋ฅผ ๋ฒ์ด๋ ํ ์ด๋ธ์ ํ์ ์ถ๊ฐํ๋ฉด์ ์ด๋ฌํ ๊ณตํต ๊ธฐ๋ฅ์ ๊ฐ๋ ๊ฒ์ด ์ง๊ด์ ์ธ ๊ฒ์ฒ๋ผ ๋ณด์ ๋๋ค.
์ด ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ ค๋ฉด ๋ค์ ์๊ฐํด ๋ณด์ญ์์ค. ๊ธฐ์กด ํ
์ด๋ธ์ ํ์ ์ถ๊ฐํ๋ ๊ฒ์ ๋งค์ฐ ์ ์ฉํฉ๋๋ค.
Alas Pangres๋ Python 3.7 ์ด์์ผ๋ก ์ ํ๋ฉ๋๋ค. ๋ด ๊ฒฝ์ฐ์ฒ๋ผ(๋๋ ์ค๋๋ Python 3.4๋ฅผ ์ฌ์ฉํด์ผ ํจ) ํญ์ ์คํ ๊ฐ๋ฅํ ์๋ฃจ์
์ ์๋๋๋ค.
๊ฐ์ฌํฉ๋๋ค, @GoldstHa - ์ ๋ง ์ ์ฉํ ์ ๋ณด์ ๋๋ค. MERGE์ ๊ฐ์ ๊ตฌํ์ ์ํด POC๋ฅผ ๋ง๋ค๋ ค๊ณ ํฉ๋๋ค.
DELETE/INSERT
์ ๊ทผ ๋ฐฉ์์ ๋ฌธ์ ์ MySQL DB์ @GoldstHa MERGE
์ ๊ทผ ๋ฐฉ์์ ๋ํ ์ ์ฌ์ ์ฐจ๋จ์ ๊ฐ์ํ ๋ ์กฐ๊ธ ๋ ํ๊ณ sqlalchemy ์
๋ฐ์ดํธ ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ๊ฐ๋
์ฆ๋ช
์ ํจ๊ป
API์ upsert๊ฐ ์ค์ ๋ก ์ด๋ป๊ฒ ํธ์ถ๋์ด์ผ ํ๋์ง์ ๋ํ ๋ช ๊ฐ์ง ์ข์ ๋
ผ์๊ฐ ์์์ต๋๋ค(์ฆ, 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์ ๋ชจ๋ ํ. ํธ๋์ญ์ ์์ ์ด๊ฒ์ ํ๊ณ ์ถ์ ๊ฒ์ ๋๋ค.