Skip to content

ENH: Allow to_sql to recognize single sql type #11886 #13252

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

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion doc/source/whatsnew/v0.18.2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ Other enhancements

pd.Timestamp(year=2012, month=1, day=1, hour=8, minute=30)

- ``DataFrame.to_sql `` now allows a single value as the SQL type for all columns (:issue:`11886`).

- The ``pd.read_csv()`` with ``engine='python'`` has gained support for the ``decimal`` option (:issue:`12933`)

- ``Index.astype()`` now accepts an optional boolean argument ``copy``, which allows optional copying if the requirements on dtype are satisfied (:issue:`13209`)
Expand All @@ -89,7 +91,7 @@ Other enhancements

- ``pd.read_html()`` has gained support for the ``decimal`` option (:issue:`12907`)


- ``DataFrame.to_sql `` now allows a single value as the SQL type for all columns (:issue:`11886`).

.. _whatsnew_0182.api:

Expand Down
21 changes: 16 additions & 5 deletions pandas/io/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
string_types, text_type)
from pandas.core.api import DataFrame, Series
from pandas.core.common import isnull
from pandas.core.generic import is_dictlike
from pandas.core.base import PandasObject
from pandas.types.api import DatetimeTZDtype
from pandas.tseries.tools import to_datetime
Expand Down Expand Up @@ -550,9 +551,10 @@ def to_sql(frame, name, con, flavor='sqlite', schema=None, if_exists='fail',
chunksize : int, default None
If not None, then rows will be written in batches of this size at a
time. If None, all rows will be written at once.
dtype : dict of column name to SQL type, default None
dtype : single SQLtype or dict of column name to SQL type, default None
Optional specifying the datatype for columns. The SQL type should
be a SQLAlchemy type, or a string for sqlite3 fallback connection.
If all columns are of the same type, one single value can be used.

"""
if if_exists not in ('fail', 'replace', 'append'):
Expand Down Expand Up @@ -1231,11 +1233,16 @@ def to_sql(self, frame, name, if_exists='fail', index=True,
chunksize : int, default None
If not None, then rows will be written in batches of this size at a
time. If None, all rows will be written at once.
dtype : dict of column name to SQL type, default None
dtype : single SQL type or dict of column name to SQL type, default
None
Optional specifying the datatype for columns. The SQL type should
be a SQLAlchemy type.
be a SQLAlchemy type. If all columns are of the same type, one
single value can be used.

"""
if dtype and not is_dictlike(dtype):
dtype = {col_name: dtype for col_name in frame}

if dtype is not None:
from sqlalchemy.types import to_instance, TypeEngine
for col, my_type in dtype.items():
Expand Down Expand Up @@ -1644,11 +1651,15 @@ def to_sql(self, frame, name, if_exists='fail', index=True,
chunksize : int, default None
If not None, then rows will be written in batches of this
size at a time. If None, all rows will be written at once.
dtype : dict of column name to SQL type, default None
dtype : single SQL type or dict of column name to SQL type, default
None
Optional specifying the datatype for columns. The SQL type should
be a string.
be a string. If all columns are of the same type, one single value
can be used.

"""
if dtype and not is_dictlike(dtype):
dtype = {col_name: dtype for col_name in frame}
if dtype is not None:
for col, my_type in dtype.items():
if not isinstance(my_type, str):
Expand Down
22 changes: 11 additions & 11 deletions pandas/io/tests/parser/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1302,17 +1302,17 @@ def test_read_duplicate_names(self):

def test_inf_parsing(self):
data = """\
,A
a,inf
b,-inf
c,+Inf
d,-Inf
e,INF
f,-INF
g,+INf
h,-INf
i,inF
j,-inF"""
,A
a,inf
b,-inf
c,+Inf
d,-Inf
e,INF
f,-INF
g,+INf
h,-INf
i,inF
j,-inF"""
inf = float('inf')
expected = Series([inf, -inf] * 5)

Expand Down
28 changes: 28 additions & 0 deletions pandas/io/tests/test_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -1552,6 +1552,19 @@ def test_dtype(self):
self.assertTrue(isinstance(sqltype, sqlalchemy.String))
self.assertEqual(sqltype.length, 10)

def test_to_sql_single_dtype(self):
cols = ['A', 'B']
data = [('a', 'b'),
('c', 'd')]
df = DataFrame(data, columns=cols)
df.to_sql('single_dtype_test', self.conn, dtype=sqlalchemy.TEXT)
meta = sqlalchemy.schema.MetaData(bind=self.conn)
meta.reflect()
sqltypea = meta.tables['single_dtype_test'].columns['A'].type
sqltypeb = meta.tables['single_dtype_test'].columns['B'].type
self.assertTrue(isinstance(sqltypea, sqlalchemy.TEXT))
self.assertTrue(isinstance(sqltypeb, sqlalchemy.TEXT))

def test_notnull_dtype(self):
cols = {'Bool': Series([True, None]),
'Date': Series([datetime(2012, 5, 1), None]),
Expand Down Expand Up @@ -2025,6 +2038,21 @@ def test_dtype(self):
self.assertRaises(ValueError, df.to_sql,
'error', self.conn, dtype={'B': bool})

def test_to_sql_single_dtype(self):
if self.flavor == 'mysql':
raise nose.SkipTest('Not applicable to MySQL legacy')
self.drop_table('single_dtype_test')
cols = ['A', 'B']
data = [('a', 'b'),
('c', 'd')]
df = DataFrame(data, columns=cols)
df.to_sql('single_dtype_test', self.conn, dtype='STRING')
self.assertEqual(
self._get_sqlite_column_type('single_dtype_test', 'A'), 'STRING')
self.assertEqual(
self._get_sqlite_column_type('single_dtype_test', 'B'), 'STRING')
self.drop_table('single_dtype_test')

def test_notnull_dtype(self):
if self.flavor == 'mysql':
raise nose.SkipTest('Not applicable to MySQL legacy')
Expand Down