Repository: pandas-dev/pandas

Issue:
BUG: to_sql errors with numeric index name - needs conversion to string
#### Code Sample, a copy-pastable example if possible

```python

import pandas as pd
import sqlalchemy as sa


d = {'2000': 1, '2001': 2, '2002': 3, '2003': 4}

df = pd.DataFrame(list(d.items()))
df = df.set_index([0])

db = sa.create_engine("sqlite:///test.db")
df.to_sql("test_tbl", db, if_exists="append")


```
#### Problem description

This errors as follows:

```
  File "sql.py", line 12, in <module>
    df.to_sql("test_tbl", db, if_exists="append")
  File "/home/zzz/.virtualenvs/sqlalchemy/lib/python3.5/site-packages/pandas/core/generic.py", line 1201, in to_sql
    chunksize=chunksize, dtype=dtype)
  File "/home/zzz/.virtualenvs/sqlalchemy/lib/python3.5/site-packages/pandas/io/sql.py", line 470, in to_sql
    chunksize=chunksize, dtype=dtype)
  File "/home/zzz/.virtualenvs/sqlalchemy/lib/python3.5/site-packages/pandas/io/sql.py", line 1146, in to_sql
    schema=schema, dtype=dtype)
  File "/home/zzz/.virtualenvs/sqlalchemy/lib/python3.5/site-packages/pandas/io/sql.py", line 563, in __init__
    self.table = self._create_table_setup()
  File "/home/zzz/.virtualenvs/sqlalchemy/lib/python3.5/site-packages/pandas/io/sql.py", line 770, in _create_table_setup
    for name, typ, is_index in column_names_and_types]
  File "/home/zzz/.virtualenvs/sqlalchemy/lib/python3.5/site-packages/pandas/io/sql.py", line 770, in <listcomp>
    for name, typ, is_index in column_names_and_types]
  File "/home/zzz/.virtualenvs/sqlalchemy/lib/python3.5/site-packages/sqlalchemy/sql/schema.py", line 1245, in __init__
    self._init_items(*args)
  File "/home/zzz/.virtualenvs/sqlalchemy/lib/python3.5/site-packages/sqlalchemy/sql/schema.py", line 79, in _init_items
    item._set_parent_with_dispatch(self)
AttributeError: 'int' object has no attribute '_set_parent_with_dispatch'
```

Commenting out the df.set_index line results in expected behaviour though with a simple integer index. Using sqlite3 as the library also results in errors.

#### Expected Output

#### Output of ``pd.show_versions()``

<details>
INSTALLED VERSIONS
------------------
commit: None
python: 3.5.2.final.0
python-bits: 64
OS: Linux
OS-release: 4.4.0-62-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_GB.UTF-8
LOCALE: en_GB.UTF-8

pandas: 0.19.2
nose: None
pip: 9.0.1
setuptools: 34.1.1
Cython: None
numpy: 1.12.0
scipy: None
statsmodels: None
xarray: None
IPython: None
sphinx: None
patsy: None
dateutil: 2.6.0
pytz: 2016.10
blosc: None
bottleneck: None
tables: None
numexpr: None
matplotlib: None
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml: None
bs4: None
html5lib: None
httplib2: None
apiclient: None
sqlalchemy: 1.1.5
pymysql: None
psycopg2: None
jinja2: None
boto: None
pandas_datareader: None

</details>



Discussion:
@redbullpeter Thanks for the report

The reason for the error is the integer index name. If you set this to a string, it will work

```
In [25]: df.to_sql("test_tbl", db, if_exists="append")
...
AttributeError: 'int' object has no attribute '_set_parent_with_dispatch'

In [26]: df.index.name = 'year'

In [27]: df.to_sql("test_tbl", db, if_exists="append")

In [28]: pd.read_sql("test_tbl", db)
Out[28]: 
   year  1
0  2002  3
1  2000  1
2  2003  4
3  2001  2
```

So integer column names are not allowed for writing to sql. For columns, this is checked for, and the integers are converted to a string. This check apparently does not happen for the index name (which also becomes a column label in the sql table), and by keeping it as an int, an error is raised in the underlying sqlalchemy code.

Fixes very welcome!
I can probably do the fix for that. Will it be sufficient to naively convert an int to a string for the index column?
Yes, I think it is just this line: https://github.com/pandas-dev/pandas/blob/93f5e3a0c11c82ad6b7365e83637d133c1a6e8a5/pandas/io/sql.py#L753 (note that a few lines below, the column name is wrapped in `text_type(..)`, this is not done for the index label. 
You should confirm with a test that this indeed solves the issue.

Resolving patch:
diff --git a/pandas/io/sql.py b/pandas/io/sql.py
--- a/pandas/io/sql.py
+++ b/pandas/io/sql.py
@@ -750,7 +750,8 @@ def _get_column_names_and_types(self, dtype_mapper):
             for i, idx_label in enumerate(self.index):
                 idx_type = dtype_mapper(
                     self.frame.index.get_level_values(i))
-                column_names_and_types.append((idx_label, idx_type, True))
+                column_names_and_types.append((text_type(idx_label),
+                                              idx_type, True))
 
         column_names_and_types += [
             (text_type(self.frame.columns[i]),
@@ -1220,7 +1221,7 @@ def _create_sql_schema(self, frame, table_name, keys=None, dtype=None):
 
 def _get_unicode_name(name):
     try:
-        uname = name.encode("utf-8", "strict").decode("utf-8")
+        uname = text_type(name).encode("utf-8", "strict").decode("utf-8")
     except UnicodeError:
         raise ValueError("Cannot convert identifier to UTF-8: '%s'" % name)
     return uname


Test patch:
diff --git a/pandas/tests/io/test_sql.py b/pandas/tests/io/test_sql.py
--- a/pandas/tests/io/test_sql.py
+++ b/pandas/tests/io/test_sql.py
@@ -709,6 +709,21 @@ def test_to_sql_index_label(self):
         self.assertEqual(frame.columns[0], 'other_label',
                          "Specified index_label not written to database")
 
+        # index name is integer
+        temp_frame.index.name = 0
+        sql.to_sql(temp_frame, 'test_index_label', self.conn,
+                   if_exists='replace')
+        frame = sql.read_sql_query('SELECT * FROM test_index_label', self.conn)
+        self.assertEqual(frame.columns[0], '0',
+                         "Integer index label not written to database")
+
+        temp_frame.index.name = None
+        sql.to_sql(temp_frame, 'test_index_label', self.conn,
+                   if_exists='replace', index_label=0)
+        frame = sql.read_sql_query('SELECT * FROM test_index_label', self.conn)
+        self.assertEqual(frame.columns[0], '0',
+                         "Integer index label not written to database")
+
     def test_to_sql_index_label_multiindex(self):
         temp_frame = DataFrame({'col1': range(4)},
                                index=MultiIndex.from_product(