web-dev-qa-db-fra.com

Python Pandas to_sql, comment créer une table avec une clé primaire?

Je voudrais créer une table MySQL avec la fonction to_sql de Pandas qui a une clé primaire (il est généralement bon d'avoir une clé primaire dans une table mysql) comme suit:

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

mais cela crée une table sans clé primaire (ou même sans index).

La documentation mentionne le paramètre 'index_label' qui combiné avec le paramètre 'index' pourrait être utilisé pour créer un index mais ne mentionne aucune option pour les clés primaires.

Documentation

22
patapouf_ai

Avertissement: cette réponse est plus expérimentale que pratique, mais mérite peut-être d'être mentionnée.

J'ai trouvé que la classe pandas.io.sql.SQLTable A nommé l'argument key et si vous lui affectez le nom du champ, ce champ devient la clé primaire:

Malheureusement, vous ne pouvez pas simplement transférer cet argument depuis la fonction DataFrame.to_sql(). Pour l'utiliser, vous devez:

  1. créer une instance de pandas.io.SQLDatabase

    engine = sa.create_engine('postgresql:///somedb')
    pandas_sql = pd.io.sql.pandasSQL_builder(engine, schema=None, flavor=None)
    
  2. définir une fonction analogue à pandas.io.SQLDatabase.to_sql() mais avec un argument *kwargs supplémentaire qui est passé à l'objet pandas.io.SQLTable créé à l'intérieur (je viens de copier la méthode to_sql() d'origine et d'ajouter *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. appelez cette fonction avec votre instance SQLDatabase et la trame de données que vous souhaitez enregistrer

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

Et nous obtenons quelque chose comme

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

dans la base de données.

PS Vous pouvez bien sûr utiliser les fonctions monkey-patch DataFrame, io.SQLDatabase Et io.to_sql() pour utiliser cette solution de contournement avec commodité.

14
krvkir

Ajoutez simplement la clé primaire après avoir téléchargé la table avec des 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`);')
32
tomp

automap_base de sqlalchemy.ext.automap (tableNamesDict est un dict avec seulement les tables Pandas):

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

Ce qui aurait parfaitement fonctionné, sauf pour un problème, la carte automatique nécessite que les tables aient une clé primaire. Ok, pas de problème, je suis sûr Pandas to_sql a un moyen d'indiquer la clé primaire ... non. C'est là que ça devient un peu 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')

J'itère le dict de DataFrames, j'obtiens une liste des colonnes à utiliser pour la clé primaire (c'est-à-dire celles contenant id), j'utilise get_schema pour créer les tables vides, puis ajoutez le DataFrame à la table.

Maintenant que vous avez les modèles, vous pouvez les nommer et les utiliser explicitement (c'est-à-dire User = Base.classes.user) avec session.query ou créez un dict de toutes les classes avec quelque chose comme ceci:

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

Et interrogez avec:

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