Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding (Insert or update if key exists) option to .to_sql #14553 #29636

Closed
wants to merge 125 commits into from
Closed
Show file tree
Hide file tree
Changes from 74 commits
Commits
Show all changes
125 commits
Select commit Hold shift + click to select a range
848becc
updated .gitignore to ignore .python-version file
cvonsteg Oct 12, 2019
d9f7c23
skeleton framework
cvonsteg Oct 12, 2019
f0726b3
update docstring and add TODO
cvonsteg Oct 12, 2019
c742f47
scratch functions added
cvonsteg Oct 20, 2019
d2a0c2d
generator comment
cvonsteg Oct 20, 2019
686930c
Squashed 'vendor/github.com/V0RT3X4/python_utils/' content from commi…
cvonsteg Nov 1, 2019
1f47d45
Add "python_utils" from "git@github.com:V0RT3X4/python_utils.git@master"
cvonsteg Nov 1, 2019
881a934
Add "python_utils" from "git@github.com:V0RT3X4/python_utils.git@master"
cvonsteg Nov 1, 2019
c95de1d
update scratch
cvonsteg Nov 1, 2019
0082347
update scratch
cvonsteg Nov 1, 2019
2d85c70
starting to modify sql.py adding workflow for insert method, and scra…
cvonsteg Nov 4, 2019
0f32cd0
starting to modify sql.py adding workflow for insert method, and scra…
cvonsteg Nov 4, 2019
dc70c40
sql.py - methods to get pkey columns, pkey iterator, and almost finis…
cvonsteg Nov 5, 2019
a7b8e8e
sql.py - methods to get pkey columns, pkey iterator, and almost finis…
cvonsteg Nov 5, 2019
1d936ef
changed upsert to if_exists option, beginning implementation
cvonsteg Nov 7, 2019
9db2aaa
changed upsert to if_exists option, beginning implementation
cvonsteg Nov 7, 2019
57a246d
all workflow present - now to debugging
cvonsteg Nov 8, 2019
c290a78
all workflow present - now to debugging
cvonsteg Nov 8, 2019
35e0fc4
tidy up repo
cvonsteg Nov 9, 2019
6bcd6c2
tidy up repo
cvonsteg Nov 9, 2019
00e6319
Squashed 'vendor/github.com/V0RT3X4/python_utils/' content from commi…
cvonsteg Nov 9, 2019
8d654ca
Add "python_utils" from "git@github.com:V0RT3X4/python_utils.git@master"
cvonsteg Nov 9, 2019
966a95c
Add "python_utils" from "git@github.com:V0RT3X4/python_utils.git@master"
cvonsteg Nov 9, 2019
b214a68
added df argument to stop original df from getting deleted by upsert_…
cvonsteg Nov 9, 2019
41938cd
added df argument to stop original df from getting deleted by upsert_…
cvonsteg Nov 9, 2019
78f8e86
tidying up sql.py code and adding helpers to sql_scratch.py
cvonsteg Nov 10, 2019
025b0ef
tidying up sql.py code and adding helpers to sql_scratch.py
cvonsteg Nov 10, 2019
bbcf92b
made upsert_delete mask index agnostic
cvonsteg Nov 11, 2019
73fea73
made upsert_delete mask index agnostic
cvonsteg Nov 11, 2019
d8b7686
updated docuemntation for to_sql method
cvonsteg Nov 13, 2019
75e16ff
updated docuemntation for to_sql method
cvonsteg Nov 13, 2019
0dfe913
Added basic tests - need to figure out why postgres tests aren't working
cvonsteg Nov 13, 2019
779818a
Added basic tests - need to figure out why postgres tests aren't working
cvonsteg Nov 13, 2019
3fafc95
updated docstrings and added desription to
cvonsteg Nov 14, 2019
d79b970
updated docstrings and added desription to
cvonsteg Nov 14, 2019
c38f900
remove vendor
cvonsteg Nov 14, 2019
0636332
Merge branch 'sql-upsert' of github.com:V0RT3X4/pandas into sql-upsert
cvonsteg Nov 14, 2019
a15fc2f
clean up for PR
cvonsteg Nov 15, 2019
6c44506
wrapped whole insert workflow in transaction to avoid postgres freezing
cvonsteg Nov 15, 2019
e409bda
ENH: black file
cvonsteg Nov 15, 2019
d35e145
pep8 formatting
cvonsteg Nov 15, 2019
8a57126
black formatting sql.py
cvonsteg Nov 16, 2019
3c308d3
Merge branch 'master' into sql-upsert
cvonsteg Nov 18, 2019
d4764dc
Merge branch 'sql-upsert' of github.com:V0RT3X4/pandas into sql-upsert
cvonsteg Nov 21, 2019
4396aa3
Merge branch 'master' into sql-upsert
cvonsteg Nov 24, 2019
2b1c797
reformatted tests, added requested changes, and updated generic docst…
cvonsteg Nov 24, 2019
b23f528
reformatted tests, added requested changes, and updated generic docst…
cvonsteg Nov 24, 2019
899da90
line-separated docstring in generc and removed unwatned pattern from …
cvonsteg Nov 24, 2019
8ebc256
postgresql type-o and type-checking in sql tests
cvonsteg Nov 24, 2019
baad9e3
reformatting tests
cvonsteg Nov 24, 2019
79ef9c0
remove quotations from postgres queries
cvonsteg Nov 24, 2019
d0eb251
postgres formatting
cvonsteg Nov 24, 2019
b838ef5
Merge branch 'master' into sql-upsert
cvonsteg Nov 26, 2019
17a1d42
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Dec 1, 2019
a25c2f3
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Dec 18, 2019
f56b53f
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Dec 18, 2019
247bec0
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Dec 18, 2019
51a74a2
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Dec 19, 2019
91c750b
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Dec 20, 2019
f940b42
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Dec 21, 2019
6dedb71
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Dec 23, 2019
e3809a1
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Dec 25, 2019
6d692cd
merging master
cvonsteg Jan 8, 2020
efd2382
removed temp=self.frame in _get_index_formatted_dataframe
cvonsteg Jan 9, 2020
c3a6a95
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Jan 15, 2020
a0ce842
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Jan 15, 2020
07bc8ca
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Jan 16, 2020
4822ce0
Commenting out tests to confirm that stalled builds are caused by ups…
cvonsteg Jan 16, 2020
0fba1b6
re-enabling to_sql_upsert_keep test
cvonsteg Jan 16, 2020
c230d16
re-enabling to_upsert_ignore, whilst disabling to_upsert_keep
cvonsteg Jan 16, 2020
9a7ef9c
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Jan 31, 2020
2a078f6
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Jan 31, 2020
c3c6ed1
clean up flake8 version, re-activate test
cvonsteg Jan 31, 2020
79becdc
remove SQLAlchemyConn tests to confirm hypothesis
cvonsteg Feb 3, 2020
3b6ca76
relaunc TestMySQLAlchemyConn - remove TestMySQLAlchemy
cvonsteg Feb 3, 2020
61d998f
added all MySQL test classes back
cvonsteg Feb 3, 2020
e454c35
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Feb 10, 2020
b4c058c
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Feb 17, 2020
2ed4d32
Merge branch 'sql-upsert' of github.com:V0RT3X4/pandas into sql-upsert
cvonsteg May 16, 2020
c33d536
fix: merge master, resolve conflicts, move inserts out of nested tran…
cvonsteg May 18, 2020
b3bfbcc
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Jul 29, 2020
fafd646
Merge branch 'master' into sql-upsert
cvonsteg Aug 24, 2020
dbee26b
Merge branch 'sql-upsert' of github.com:V0RT3X4/pandas into sql-upsert
cvonsteg Aug 24, 2020
3a04edd
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Aug 24, 2020
0b03df7
Merge branch 'sql-upsert' of github.com:V0RT3X4/pandas into sql-upsert
cvonsteg Aug 24, 2020
f2d3596
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Aug 24, 2020
529e5fb
chore: re-order imports
cvonsteg Aug 24, 2020
6d718dc
remove unnecessary sort
cvonsteg Aug 25, 2020
53e3565
undo sort rmv
cvonsteg Aug 25, 2020
1bff71f
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Nov 8, 2020
26c0b0f
feat: use updates instead of deletes
cvonsteg Nov 9, 2020
59c76ac
chore: update tests to reflect new api
cvonsteg Nov 11, 2020
21a87e1
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Nov 15, 2020
286e8b8
feat: use on_conflict update methodology
cvonsteg Nov 16, 2020
780fcea
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Nov 16, 2020
70e0eb1
testing: add tests for single and composite primary keys
cvonsteg Nov 22, 2020
d4fc6d4
chore: add api tests for invalid if_exists, on_conflict args
cvonsteg Nov 23, 2020
c783496
chore: missing comma in tests
cvonsteg Nov 28, 2020
4f4e9d9
chore: use backticks
cvonsteg Nov 29, 2020
e466ff3
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Nov 30, 2020
f2bc121
chore: use add constraint syntax
cvonsteg Nov 30, 2020
b9bfedc
tidy up
cvonsteg Dec 18, 2020
43353f5
chore: merge upstream master
cvonsteg Dec 18, 2020
6196c29
chore: merge master
cvonsteg Dec 18, 2020
b27449e
fix: add not null constraints to mysql tables
cvonsteg Dec 18, 2020
c8c1826
fix: change mysql type from TEXT to VARCHAR for indexing
cvonsteg Dec 19, 2020
e28cd9e
chore: merge master
cvonsteg Dec 31, 2020
197e172
chore: use and_ inplace of tuple_
cvonsteg Mar 15, 2021
6ebe9e8
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Mar 15, 2021
b95f6c9
Merge remote-tracking branch 'upstream/master' into sql-upsert
cvonsteg Mar 16, 2021
8fa8e0e
chore: same change for update stmts
cvonsteg Mar 16, 2021
1dee409
fix: primary keys no longer require names getter
cvonsteg Mar 21, 2021
1f9fca7
chore: merge master
cvonsteg Jun 6, 2021
2b30a9e
chore: merge master
cvonsteg Jun 6, 2021
a05937e
chore: remove wrong kwarg
cvonsteg Jun 26, 2021
67143ff
chore: add multi-index lookup
cvonsteg Jun 28, 2021
7e1148e
merge master
cvonsteg Jul 18, 2021
11f201f
tests: fix on conflict with non append tests
cvonsteg Jul 18, 2021
7f0b5dd
fix: pass on_conflict into prep_table
cvonsteg Jul 19, 2021
e5d5ce7
merge master
cvonsteg Sep 6, 2021
8123cd7
fix: tests working
cvonsteg Sep 8, 2021
26faabe
chore: check metadata bind before reflect
cvonsteg Sep 8, 2021
9260eca
clean up docstrings
cvonsteg Sep 9, 2021
ad9f52f
merge: master
cvonsteg Oct 5, 2021
a63a77a
merge: master
cvonsteg Oct 6, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion pandas/core/generic.py
Expand Up @@ -2447,12 +2447,15 @@ def to_sql(
schema : str, optional
Specify the schema (if database flavor supports this). If None, use
default schema.
if_exists : {'fail', 'replace', 'append'}, default 'fail'
if_exists : {'fail', 'replace', 'append', 'upsert_overwrite', 'upsert_keep'},\
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may add ambiguity to the if_exists argument, as it is referring now to two concepts:

  • if table exists
  • if row exists

Would this be better as a separate if_row_exists parameter?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the documentation says:

How to behave if the table already exists.

So, the documentation should be updated at the least.

However, I think it would be conceptually cleaner to separate table-existence from row collision-checks.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're making a good point. I will look to incorporate this shortly

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On your original proposal, you mentioned using a method parameter. That seems like a reasonable name/approach :-)

Copy link

@rugg2 rugg2 Dec 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brylie I think it is a good point, but I am less sure about the action required.

I have actually sent a full reply here:
#14553 (comment)

Copy link

@fa-mc fa-mc Dec 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the "upsert" method in SQL checks on the primary key, how about name the parameter if_key_exists?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be explicit, the parameters should be something like:

  • if_table_exists
  • if_row_exists

Since renaming if_exists to if_table_exists is backwords incompatible, perhaps that rename could be avoided or the if_exists parameter could be marked as deprecated.

What is blocking us from adding a new argument for if_row_exists?

default 'fail'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this api is conflating two things. isn't upsert by-definition to overwrite? what is the usecase for 'upsert_keep'?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Over the course of this PR I have come to agree that using the term upsert, in this case is not clear. In reality this behaviour is more akin to an on_conflict argument (of which I suppose upsert is a subset). Do you think it'd be cleaner to add an on_conflict argument to the to_sql method signature, which takes parameters do nothing or overwrite?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that makes sense. overwrite could also be called do update.

How to behave if the table already exists.

* fail: Raise a ValueError.
* replace: Drop the table before inserting new values.
* append: Insert new values to the existing table.
* upsert_overwrite: Overwrite matches in database with incoming data.
* upsert_keep: Keep matches in database instead of incoming data.

index : bool, default True
Write DataFrame index as a column. Uses `index_label` as the column
Expand Down
193 changes: 169 additions & 24 deletions pandas/io/sql.py
Expand Up @@ -467,10 +467,15 @@ def to_sql(
schema : str, optional
Name of SQL schema in database to write to (if database flavor
supports this). If None, use default schema (default).
if_exists : {'fail', 'replace', 'append'}, default 'fail'
if_exists : {'fail', 'replace', 'append', 'upsert_overwrite', 'upsert_keep'},
default 'fail'.
- fail: If table exists, do nothing.
- replace: If table exists, drop it, recreate it, and insert data.
- append: If table exists, insert data. Create if does not exist.
- upsert_overwrite: If table exists, perform an UPSERT (based on primary keys),
prioritising incoming records over duplicates already in the database.
- upsert_keep: If table exists, perform an UPSERT (based on primary keys),
prioritising records already in the database over incoming duplicates.
index : boolean, default True
Write DataFrame index as a column.
index_label : str or sequence, optional
Expand All @@ -497,8 +502,14 @@ def to_sql(

.. versionadded:: 0.24.0
"""
if if_exists not in ("fail", "replace", "append"):
raise ValueError(f"'{if_exists}' is not valid for if_exists")
if if_exists not in (
"fail",
"replace",
"append",
"upsert_keep",
"upsert_overwrite",
):
raise ValueError("'{0}' is not valid for if_exists".format(if_exists))

pandas_sql = pandasSQL_builder(con, schema=schema)

Expand Down Expand Up @@ -647,13 +658,111 @@ def create(self):
elif self.if_exists == "replace":
self.pd_sql.drop_table(self.name, self.schema)
self._execute_create()
elif self.if_exists == "append":
elif self.if_exists in {"append", "upsert_overwrite", "upsert_keep"}:
pass
else:
raise ValueError(f"'{self.if_exists}' is not valid for if_exists")
else:
self._execute_create()

def _upsert_overwrite_processing(self):
"""
Generate delete statement for rows with clashing primary key from database.

`upsert_overwrite` prioritizes incoming data, over existing data in the DB.
This method generates the Delete statement for duplicate rows,
which is to be executed in the same transaction as the ensuing data insert.

Returns
----------
sqlalchemy.sql.dml.Delete
Delete statement to be executed against DB
"""
from sqlalchemy import tuple_

# Primary key data
primary_keys, primary_key_values = self._get_primary_key_data()
# Generate delete statement
delete_statement = self.table.delete().where(
tuple_(*(self.table.c[col] for col in primary_keys)).in_(primary_key_values)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be more clearly expressed as self.table.c[primary_keys].itertuples(index=False)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @WillAyd - thanks for the feedback.

self.table is a sqlalchemy Table object, and itertuples is a DataFrame method, so as I understand it, this wouldn't work. If I'm missing the point, please let me know and I'll adjust this and the select statement accordingly.

)
return delete_statement

def _upsert_keep_processing(self):
"""
Delete clashing values from a copy of the incoming dataframe.

`upsert_keep` prioritizes data in DB over incoming data.
This method creates a copy of the incoming dataframe,
fetches matching data from DB, deletes matching data from copied frame,
and returns that frame to be inserted.

Returns
----------
DataFrame
Filtered dataframe, with values that are already in DB removed.
"""
from sqlalchemy import tuple_, select

# Primary key data
primary_keys, primary_key_values = self._get_primary_key_data()
# Fetch matching pkey values from database
columns_to_fetch = [self.table.c[key] for key in primary_keys]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment - can you just select without looping?

select_statement = select(columns_to_fetch).where(
tuple_(*columns_to_fetch).in_(primary_key_values)
)
pkeys_from_database = _wrap_result(
data=self.pd_sql.execute(select_statement), columns=primary_keys
)
# Get temporary dataframe so as not to delete values from main df
temp = self._get_index_formatted_dataframe()
# Delete rows from dataframe where primary keys match
# Method requires tuples, to account for cases where indexes do not match
to_be_deleted_mask = (
temp[primary_keys]
.apply(tuple, axis=1)
.isin(pkeys_from_database[primary_keys].apply(tuple, axis=1))
)
temp.drop(temp[to_be_deleted_mask].index, inplace=True)

return temp

def _get_primary_key_data(self):
"""
Get primary keys from database, and yield dataframe columns with same names.

Upsert workflows require knowledge of what is already in the database.
This method reflects the meta object and gets a list of primary keys,
it then returns all columns from the incoming dataframe with names matching
these keys.

Returns
-------
primary_keys : list of str
Primary key names
primary_key_values : iterable
DataFrame rows, for columns corresponding to `primary_key` names
"""
# reflect MetaData object and assign contents of db to self.table attribute
self.pd_sql.meta.reflect(only=[self.name], views=True)
self.table = self.pd_sql.get_table(table_name=self.name, schema=self.schema)

primary_keys = [
str(primary_key.name)
for primary_key in self.table.primary_key.columns.values()
]

# For the time being, this method is defensive and will break if
# no pkeys are found. If desired this default behaviour could be
# changed so that in cases where no pkeys are found,
# it could default to a normal insert
if len(primary_keys) == 0:
raise ValueError(f"No primary keys found for table {self.name}")

temp = self._get_index_formatted_dataframe()
primary_key_values = zip(*[temp[key] for key in primary_keys])
return primary_keys, primary_key_values

def _execute_insert(self, conn, keys, data_iter):
"""Execute SQL statement inserting data

Expand All @@ -678,21 +787,36 @@ def _execute_insert_multi(self, conn, keys, data_iter):
data = [dict(zip(keys, row)) for row in data_iter]
conn.execute(self.table.insert(data))

def insert_data(self):
def _get_index_formatted_dataframe(self):
"""
Format index of incoming dataframe to be aligned with a database table.

Copy original dataframe, and check whether the dataframe index
is to be added to the database table.
If it is, reset the index so that it becomes a normal column, else return

Returns
-------
DataFrame
"""
# Originally this functionality formed the first step of the insert_data method.
# It will be useful to have in other places, so moved here to keep code DRY.
temp = self.frame.copy()
if self.index is not None:
temp = self.frame.copy()
temp.index.names = self.index
try:
temp.reset_index(inplace=True)
except ValueError as err:
raise ValueError(f"duplicate name in index/columns: {err}")
else:
temp = self.frame

column_names = list(map(str, temp.columns))
return temp

@staticmethod
def insert_data(data):
column_names = list(map(str, data.columns))
ncols = len(column_names)
data_list = [None] * ncols
blocks = temp._data.blocks
blocks = data._data.blocks

for b in blocks:
if b.is_datetime:
Expand All @@ -719,7 +843,21 @@ def insert_data(self):
return column_names, data_list

def insert(self, chunksize=None, method=None):
"""
Determines what data to pass to the underlying insert method.
"""
with self.pd_sql.run_transaction() as trans:
if self.if_exists == "upsert_keep":
data = self._upsert_keep_processing()
self._insert(data=data, chunksize=chunksize, method=method, conn=trans)
elif self.if_exists == "upsert_overwrite":
delete_statement = self._upsert_overwrite_processing()
trans.execute(delete_statement)
self._insert(chunksize=chunksize, method=method, conn=trans)
else:
self._insert(chunksize=chunksize, method=method, conn=trans)

def _insert(self, data=None, chunksize=None, method=None, conn=None):
# set insert method
if method is None:
exec_insert = self._execute_insert
Expand All @@ -730,9 +868,12 @@ def insert(self, chunksize=None, method=None):
else:
raise ValueError(f"Invalid parameter `method`: {method}")

keys, data_list = self.insert_data()
if data is None:
data = self._get_index_formatted_dataframe()

keys, data_list = self.insert_data(data=data)

nrows = len(self.frame)
nrows = len(data)

if nrows == 0:
return
Expand All @@ -744,15 +885,14 @@ def insert(self, chunksize=None, method=None):

chunks = int(nrows / chunksize) + 1

with self.pd_sql.run_transaction() as conn:
for i in range(chunks):
start_i = i * chunksize
end_i = min((i + 1) * chunksize, nrows)
if start_i >= end_i:
break
for i in range(chunks):
start_i = i * chunksize
end_i = min((i + 1) * chunksize, nrows)
if start_i >= end_i:
break

chunk_iter = zip(*[arr[start_i:end_i] for arr in data_list])
exec_insert(conn, keys, chunk_iter)
chunk_iter = zip(*[arr[start_i:end_i] for arr in data_list])
exec_insert(conn, keys, chunk_iter)

def _query_iterator(
self, result, chunksize, columns, coerce_float=True, parse_dates=None
Expand Down Expand Up @@ -1259,10 +1399,15 @@ def to_sql(
frame : DataFrame
name : string
Name of SQL table.
if_exists : {'fail', 'replace', 'append'}, default 'fail'
- fail: If table exists, do nothing.
- replace: If table exists, drop it, recreate it, and insert data.
- append: If table exists, insert data. Create if does not exist.
if_exists : {'fail', 'replace', 'append', 'upsert_overwrite', 'upsert_keep'},
default 'fail'.
- fail: If table exists, do nothing.
- replace: If table exRsts, drop it, recreate it, and insert data.
- append: If table exists, insert data. Create if does not exist.
- upsert_overwrite: If table exists, perform an UPSERT (based on primary keys),
prioritising incoming records over duplicates already in the database.
- upsert_keep: If table exists, perform an UPSERT (based on primary keys),
prioritising records already in the database over incoming duplicates.
index : boolean, default True
Write DataFrame index as a column.
index_label : string or sequence, default None
Expand Down