【问题标题】:Python Pandas to_sql, how to create a table with a primary key?Python Pandas to_sql,如何创建带有主键的表?
【发布时间】:2015-09-01 06:14:57
【问题描述】:

我想用 Pandas 的 to_sql 函数创建一个 MySQL 表,该函数有一个主键(在 mysql 表中有一个主键通常很好),如下所示:

group_export.to_sql(con = db, name = config.table_group_export, if_exists = 'replace', flavor = 'mysql', index = False)

但这会创建一个没有任何主键(甚至没有任何索引)的表。

文档提到参数“index_label”与“index”参数结合可用于创建索引,但没有提及任何主键选项。

Documentation

【问题讨论】:

  • @unutbu 我认为index=True 只是确保将索引写入表并且它是sql中的索引,而不是主键
  • 是的,索引只是使用行号作为索引,这不是我想要的。
  • 目前还不支持指定主键(它在功能愿望清单上)。可能的解决方法是首先创建表,然后使用to_sql 中的“附加”选项。要创建表,pd.io.sql.get_schema 可能有助于创建模式(然后可以调整/执行以创建表)
  • 谢谢@joris,你是对的index=True 创建索引,但不是主键。
  • @joris,尝试添加到现有表中会出现错误:“ID INT PRIMARY KEY NOT NULL”的“NOT NULL 约束失败”。 PRIMARY KEY怎么填写?

标签: python mysql pandas primary-key pandasql


【解决方案1】:

免责声明:这个答案更多的是实验性而不是实用性,但也许值得一提。

我发现pandas.io.sql.SQLTable 类有命名参数key,如果你给它分配字段的名称,那么这个字段就成为主键:

不幸的是,您不能只从DataFrame.to_sql() 函数中传递这个参数。要使用它,您应该:

  1. 创建pandas.io.SQLDatabase 实例

    engine = sa.create_engine('postgresql:///somedb')
    pandas_sql = pd.io.sql.pandasSQL_builder(engine, schema=None, flavor=None)
    
  2. 定义类似于pandas.io.SQLDatabase.to_sql() 的函数,但附加了*kwargs 参数,该参数传递给在其中创建的pandas.io.SQLTable 对象(我刚刚复制了原始to_sql() 方法并添加了*kwargs):

    def to_sql_k(self, frame, name, if_exists='fail', index=True,
               index_label=None, schema=None, chunksize=None, dtype=None, **kwargs):
        if dtype is not None:
            from sqlalchemy.types import to_instance, TypeEngine
            for col, my_type in dtype.items():
                if not isinstance(to_instance(my_type), TypeEngine):
                    raise ValueError('The type of %s is not a SQLAlchemy '
                                     'type ' % col)
    
        table = pd.io.sql.SQLTable(name, self, frame=frame, index=index,
                         if_exists=if_exists, index_label=index_label,
                         schema=schema, dtype=dtype, **kwargs)
        table.create()
        table.insert(chunksize)
    
  3. 使用您的 SQLDatabase 实例和您要保存的数据框调用此函数

    to_sql_k(pandas_sql, df2save, 'tmp',
            index=True, index_label='id', keys='id', if_exists='replace')
    

我们得到类似的东西

CREATE TABLE public.tmp
(
  id bigint NOT NULL DEFAULT nextval('tmp_id_seq'::regclass),
...
)

在数据库中。

PS 当然,您可以使用猴子补丁 DataFrameio.SQLDatabaseio.to_sql() 函数来方便地使用此解决方法。

【讨论】:

  • 不错。谢谢你。但最后我发现只在之前制作表格并附加到它更简单。
  • 我也希望 to_sql 的 index_label 选项会有所帮助。
  • 很好的答案,不幸的是,如果键列是文本类型,它不适用于 MySQL,因为 pandas 似乎没有办法指定键长度。它给出了错误 1170,“密钥规范中使用的 BLOB/TEXT 列没有密钥长度”
  • @krvkir 嗨,我想在to_sql函数中添加primary key,但是当我在该函数中写keys='id'时,它显示TypeError: to_sql() got an unexpected keyword argument 'keys',你知道原因吗? ?我使用 python 3.6.6、postgres 10.3、SQLAlchemy 1.2.15 和 pandas 0.23.4,非常感谢您的任何建议。
【解决方案2】:

automap_base from sqlalchemy.ext.automap(tableNamesDict 是一个只有 Pandas 表的字典):

metadata = MetaData()
metadata.reflect(db.engine, only=tableNamesDict.values())
Base = automap_base(metadata=metadata)
Base.prepare()

这本来可以完美运行,除了一个问题,automap 要求表具有主键。好的,没问题,我确信 Pandas to_sql 有办法指示主键......不。这是它变得有点hacky的地方:

for df in dfs.keys():
    cols = dfs[df].columns
    cols = [str(col) for col in cols if 'id' in col.lower()]
    schema = pd.io.sql.get_schema(dfs[df],df, con=db.engine, keys=cols)
    db.engine.execute('DROP TABLE ' + df + ';')
    db.engine.execute(schema)
    dfs[df].to_sql(df,con=db.engine, index=False, if_exists='append')

我遍历DataFramesdict,获取用于主键的列列表(即包含id 的列),使用get_schema 创建空表,然后附加@987654331 @ 到桌子上。

现在您已经有了模型,您可以显式命名并使用它们(即User = Base.classes.user)和session.query 或使用以下内容创建所有类的字典:

alchemyClassDict = {}
for t in Base.classes.keys():
    alchemyClassDict[t] = Base.classes[t]

并查询:

res = db.session.query(alchemyClassDict['user']).first()

【讨论】:

  • pd.io.sql.get_schema is not in the public interface 所以不太好依赖它。此外,该代码仅在数据框没有索引时才有效。否则必须使用schema = pd.io.sql.get_schema(df.reset_index(), table_name, con=db.engine, keys=cols)之类的东西
【解决方案3】:

用pandas上传表格后只需添加主键即可。

group_export.to_sql(con=engine, name=example_table, if_exists='replace', 
                    flavor='mysql', index=False)

with engine.connect() as con:
    con.execute('ALTER TABLE `example_table` ADD PRIMARY KEY (`ID_column`);')

【讨论】:

  • SQLite 不支持这个。
  • @tomp - 非常感谢,如果需要,请添加 FIRST 以将索引放在前面 ALTER TABLE {table_name}` 更改 index id int(4) NOT NULL auto_increment FIRST;`
【解决方案4】:
with engine.connect() as con:
    con.execute('ALTER TABLE for_import_ml ADD PRIMARY KEY ("ID");')

for_import_ml是数据库中的表名。

对 tomp 的回答稍加改动(我会发表评论,但没有足够的声望点)。

我正在使用 PGAdmin 和 Postgres(在 Heroku 上)进行检查,它可以工作。

【讨论】:

    【解决方案5】:

    从 pandas 0.15 开始,至少对于某些风格,您可以使用参数 dtype 来定义主键列。您甚至可以通过这种方式激活AUTOINCREMENT。对于 sqlite3,这看起来像这样:

    import sqlite3
    import pandas as pd
    
    df = pd.DataFrame({'MyID': [1, 2, 3], 'Data': [3, 2, 6]})
    with sqlite3.connect('foo.db') as con:
        df.to_sql('df', con=con, dtype={'MyID': 'INTEGER PRIMARY KEY AUTOINCREMENT'})
    

    【讨论】:

    • OP 应该将此作为选择的答案进行宣传,因为它运作良好。
    • @DanSteingart OP 询问了 MySQL,但仅 SQLite 支持 dtype 的字符串值。一般来说,dtype值需要是SQLAlchemy类型(如Integer()),所以我认为对于SQLite来说,字符串通过并且可以用于设置主键只是一个意外。在其他 SQL 后端中,这通常不起作用。
    猜你喜欢
    • 2015-10-18
    • 2015-07-31
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 2018-04-09
    • 1970-01-01
    • 2021-12-14
    • 2017-10-12
    相关资源
    最近更新 更多