Skip to content

Commit 1d83aa2

Browse files
Artemy Kolchinskyartemyk
Artemy Kolchinsky
authored andcommitted
Fixed incorrect datatype conversion on multi-indexes
Fixed test to use assertTrue Simplifying tests to write and read back Simplified multiindex test to use a roundtrip Release notes update
1 parent 85ad2b7 commit 1d83aa2

File tree

3 files changed

+35
-24
lines changed

3 files changed

+35
-24
lines changed

doc/source/v0.15.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ There are no experimental changes in 0.15.0
446446

447447
Bug Fixes
448448
~~~~~~~~~
449+
- Bug in multiindexes dtypes getting mixed up when DataFrame is saved to SQL table (:issue:`8021`)
449450
- Bug in Series 0-division with a float and integer operand dtypes (:issue:`7785`)
450451
- Bug in ``Series.astype("unicode")`` not calling ``unicode`` on the values correctly (:issue:`7758`)
451452
- Bug in ``DataFrame.as_matrix()`` with mixed ``datetime64[ns]`` and ``timedelta64[ns]`` dtypes (:issue:`7778`)

pandas/io/sql.py

+23-18
Original file line numberDiff line numberDiff line change
@@ -664,20 +664,28 @@ def _index_name(self, index, index_label):
664664
else:
665665
return None
666666

667+
def _get_column_names_and_types(self, dtype_mapper):
668+
column_names_and_types = []
669+
if self.index is not None:
670+
for i, idx_label in enumerate(self.index):
671+
idx_type = dtype_mapper(
672+
self.frame.index.get_level_values(i).dtype)
673+
column_names_and_types.append((idx_label, idx_type))
674+
675+
column_names_and_types += zip(
676+
list(map(str, self.frame.columns)),
677+
map(dtype_mapper, self.frame.dtypes)
678+
)
679+
return column_names_and_types
680+
667681
def _create_table_statement(self):
668682
from sqlalchemy import Table, Column
669683

670-
columns = list(map(str, self.frame.columns))
671-
column_types = map(self._sqlalchemy_type, self.frame.dtypes)
684+
column_names_and_types = \
685+
self._get_column_names_and_types(self._sqlalchemy_type)
672686

673687
columns = [Column(name, typ)
674-
for name, typ in zip(columns, column_types)]
675-
676-
if self.index is not None:
677-
for i, idx_label in enumerate(self.index[::-1]):
678-
idx_type = self._sqlalchemy_type(
679-
self.frame.index.get_level_values(i))
680-
columns.insert(0, Column(idx_label, idx_type, index=True))
688+
for name, typ in column_names_and_types]
681689

682690
return Table(self.name, self.pd_sql.meta, *columns)
683691

@@ -957,16 +965,13 @@ def insert(self):
957965
def _create_table_statement(self):
958966
"Return a CREATE TABLE statement to suit the contents of a DataFrame."
959967

960-
columns = list(map(str, self.frame.columns))
968+
column_names_and_types = \
969+
self._get_column_names_and_types(self._sql_type_name)
970+
961971
pat = re.compile('\s+')
962-
if any(map(pat.search, columns)):
972+
column_names = [col_name for col_name, _ in column_names_and_types]
973+
if any(map(pat.search, column_names)):
963974
warnings.warn(_SAFE_NAMES_WARNING)
964-
column_types = [self._sql_type_name(typ) for typ in self.frame.dtypes]
965-
966-
if self.index is not None:
967-
for i, idx_label in enumerate(self.index[::-1]):
968-
columns.insert(0, idx_label)
969-
column_types.insert(0, self._sql_type_name(self.frame.index.get_level_values(i).dtype))
970975

971976
flv = self.pd_sql.flavor
972977

@@ -976,7 +981,7 @@ def _create_table_statement(self):
976981
col_template = br_l + '%s' + br_r + ' %s'
977982

978983
columns = ',\n '.join(col_template %
979-
x for x in zip(columns, column_types))
984+
x for x in column_names_and_types)
980985
template = """CREATE TABLE %(name)s (
981986
%(columns)s
982987
)"""

pandas/io/tests/test_sql.py

+11-6
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,15 @@ def test_to_sql_index_label_multiindex(self):
581581
'test_index_label', self.conn, if_exists='replace',
582582
index_label='C')
583583

584+
def test_multiindex_roundtrip(self):
585+
df = DataFrame.from_records([(1,2.1,'line1'), (2,1.5,'line2')],
586+
columns=['A','B','C'], index=['A','B'])
587+
588+
df.to_sql('test_multiindex_roundtrip', self.conn)
589+
result = sql.read_sql_query('SELECT * FROM test_multiindex_roundtrip',
590+
self.conn, index_col=['A','B'])
591+
tm.assert_frame_equal(df, result, check_index_type=True)
592+
584593
def test_integer_col_names(self):
585594
df = DataFrame([[1, 2], [3, 4]], columns=[0, 1])
586595
sql.to_sql(df, "test_frame_integer_col_names", self.conn,
@@ -641,9 +650,7 @@ def test_read_sql_delegate(self):
641650
"SELECT * FROM iris", self.conn)
642651
iris_frame2 = sql.read_sql(
643652
"SELECT * FROM iris", self.conn)
644-
tm.assert_frame_equal(iris_frame1, iris_frame2,
645-
"read_sql and read_sql_query have not the same"
646-
" result with a query")
653+
tm.assert_frame_equal(iris_frame1, iris_frame2)
647654

648655
iris_frame1 = sql.read_sql_table('iris', self.conn)
649656
iris_frame2 = sql.read_sql('iris', self.conn)
@@ -697,9 +704,7 @@ def test_sql_open_close(self):
697704
def test_read_sql_delegate(self):
698705
iris_frame1 = sql.read_sql_query("SELECT * FROM iris", self.conn)
699706
iris_frame2 = sql.read_sql("SELECT * FROM iris", self.conn)
700-
tm.assert_frame_equal(iris_frame1, iris_frame2,
701-
"read_sql and read_sql_query have not the same"
702-
" result with a query")
707+
tm.assert_frame_equal(iris_frame1, iris_frame2)
703708

704709
self.assertRaises(sql.DatabaseError, sql.read_sql, 'iris', self.conn)
705710

0 commit comments

Comments
 (0)