88
99from contextlib import contextmanager
1010from datetime import date , datetime , time
11+ from functools import partial
1112import re
1213import warnings
1314
@@ -395,7 +396,7 @@ def read_sql(sql, con, index_col=None, coerce_float=True, params=None,
395396
396397
397398def to_sql (frame , name , con , schema = None , if_exists = 'fail' , index = True ,
398- index_label = None , chunksize = None , dtype = None ):
399+ index_label = None , chunksize = None , dtype = None , method = None ):
399400 """
400401 Write records stored in a DataFrame to a SQL database.
401402
@@ -429,6 +430,17 @@ def to_sql(frame, name, con, schema=None, if_exists='fail', index=True,
429430 Optional specifying the datatype for columns. The SQL type should
430431 be a SQLAlchemy type, or a string for sqlite3 fallback connection.
431432 If all columns are of the same type, one single value can be used.
433+ method : {None, 'multi', callable}, default None
434+ Controls the SQL insertion clause used:
435+
436+ - None : Uses standard SQL ``INSERT`` clause (one per row).
437+ - 'multi': Pass multiple values in a single ``INSERT`` clause.
438+ - callable with signature ``(pd_table, conn, keys, data_iter)``.
439+
440+ Details and a sample callable implementation can be found in the
441+ section :ref:`insert method <io.sql.method>`.
442+
443+ .. versionadded:: 0.24.0
432444 """
433445 if if_exists not in ('fail' , 'replace' , 'append' ):
434446 raise ValueError ("'{0}' is not valid for if_exists" .format (if_exists ))
@@ -443,7 +455,7 @@ def to_sql(frame, name, con, schema=None, if_exists='fail', index=True,
443455
444456 pandas_sql .to_sql (frame , name , if_exists = if_exists , index = index ,
445457 index_label = index_label , schema = schema ,
446- chunksize = chunksize , dtype = dtype )
458+ chunksize = chunksize , dtype = dtype , method = method )
447459
448460
449461def has_table (table_name , con , schema = None ):
@@ -568,8 +580,29 @@ def create(self):
568580 else :
569581 self ._execute_create ()
570582
571- def insert_statement (self ):
572- return self .table .insert ()
583+ def _execute_insert (self , conn , keys , data_iter ):
584+ """Execute SQL statement inserting data
585+
586+ Parameters
587+ ----------
588+ conn : sqlalchemy.engine.Engine or sqlalchemy.engine.Connection
589+ keys : list of str
590+ Column names
591+ data_iter : generator of list
592+ Each item contains a list of values to be inserted
593+ """
594+ data = [dict (zip (keys , row )) for row in data_iter ]
595+ conn .execute (self .table .insert (), data )
596+
597+ def _execute_insert_multi (self , conn , keys , data_iter ):
598+ """Alternative to _execute_insert for DBs support multivalue INSERT.
599+
600+ Note: multi-value insert is usually faster for analytics DBs
601+ and tables containing a few columns
602+ but performance degrades quickly with increase of columns.
603+ """
604+ data = [dict (zip (keys , row )) for row in data_iter ]
605+ conn .execute (self .table .insert (data ))
573606
574607 def insert_data (self ):
575608 if self .index is not None :
@@ -612,11 +645,18 @@ def insert_data(self):
612645
613646 return column_names , data_list
614647
615- def _execute_insert (self , conn , keys , data_iter ):
616- data = [dict (zip (keys , row )) for row in data_iter ]
617- conn .execute (self .insert_statement (), data )
648+ def insert (self , chunksize = None , method = None ):
649+
650+ # set insert method
651+ if method is None :
652+ exec_insert = self ._execute_insert
653+ elif method == 'multi' :
654+ exec_insert = self ._execute_insert_multi
655+ elif callable (method ):
656+ exec_insert = partial (method , self )
657+ else :
658+ raise ValueError ('Invalid parameter `method`: {}' .format (method ))
618659
619- def insert (self , chunksize = None ):
620660 keys , data_list = self .insert_data ()
621661
622662 nrows = len (self .frame )
@@ -639,7 +679,7 @@ def insert(self, chunksize=None):
639679 break
640680
641681 chunk_iter = zip (* [arr [start_i :end_i ] for arr in data_list ])
642- self . _execute_insert (conn , keys , chunk_iter )
682+ exec_insert (conn , keys , chunk_iter )
643683
644684 def _query_iterator (self , result , chunksize , columns , coerce_float = True ,
645685 parse_dates = None ):
@@ -1085,7 +1125,8 @@ def read_query(self, sql, index_col=None, coerce_float=True,
10851125 read_sql = read_query
10861126
10871127 def to_sql (self , frame , name , if_exists = 'fail' , index = True ,
1088- index_label = None , schema = None , chunksize = None , dtype = None ):
1128+ index_label = None , schema = None , chunksize = None , dtype = None ,
1129+ method = None ):
10891130 """
10901131 Write records stored in a DataFrame to a SQL database.
10911132
@@ -1115,7 +1156,17 @@ def to_sql(self, frame, name, if_exists='fail', index=True,
11151156 Optional specifying the datatype for columns. The SQL type should
11161157 be a SQLAlchemy type. If all columns are of the same type, one
11171158 single value can be used.
1159+ method : {None', 'multi', callable}, default None
1160+ Controls the SQL insertion clause used:
1161+
1162+ * None : Uses standard SQL ``INSERT`` clause (one per row).
1163+ * 'multi': Pass multiple values in a single ``INSERT`` clause.
1164+ * callable with signature ``(pd_table, conn, keys, data_iter)``.
1165+
1166+ Details and a sample callable implementation can be found in the
1167+ section :ref:`insert method <io.sql.method>`.
11181168
1169+ .. versionadded:: 0.24.0
11191170 """
11201171 if dtype and not is_dict_like (dtype ):
11211172 dtype = {col_name : dtype for col_name in frame }
@@ -1131,7 +1182,7 @@ def to_sql(self, frame, name, if_exists='fail', index=True,
11311182 if_exists = if_exists , index_label = index_label ,
11321183 schema = schema , dtype = dtype )
11331184 table .create ()
1134- table .insert (chunksize )
1185+ table .insert (chunksize , method = method )
11351186 if (not name .isdigit () and not name .islower ()):
11361187 # check for potentially case sensitivity issues (GH7815)
11371188 # Only check when name is not a number and name is not lower case
@@ -1442,7 +1493,8 @@ def _fetchall_as_list(self, cur):
14421493 return result
14431494
14441495 def to_sql (self , frame , name , if_exists = 'fail' , index = True ,
1445- index_label = None , schema = None , chunksize = None , dtype = None ):
1496+ index_label = None , schema = None , chunksize = None , dtype = None ,
1497+ method = None ):
14461498 """
14471499 Write records stored in a DataFrame to a SQL database.
14481500
@@ -1471,7 +1523,17 @@ def to_sql(self, frame, name, if_exists='fail', index=True,
14711523 Optional specifying the datatype for columns. The SQL type should
14721524 be a string. If all columns are of the same type, one single value
14731525 can be used.
1526+ method : {None, 'multi', callable}, default None
1527+ Controls the SQL insertion clause used:
1528+
1529+ * None : Uses standard SQL ``INSERT`` clause (one per row).
1530+ * 'multi': Pass multiple values in a single ``INSERT`` clause.
1531+ * callable with signature ``(pd_table, conn, keys, data_iter)``.
1532+
1533+ Details and a sample callable implementation can be found in the
1534+ section :ref:`insert method <io.sql.method>`.
14741535
1536+ .. versionadded:: 0.24.0
14751537 """
14761538 if dtype and not is_dict_like (dtype ):
14771539 dtype = {col_name : dtype for col_name in frame }
@@ -1486,7 +1548,7 @@ def to_sql(self, frame, name, if_exists='fail', index=True,
14861548 if_exists = if_exists , index_label = index_label ,
14871549 dtype = dtype )
14881550 table .create ()
1489- table .insert (chunksize )
1551+ table .insert (chunksize , method )
14901552
14911553 def has_table (self , name , schema = None ):
14921554 # TODO(wesm): unused?
0 commit comments