【问题标题】:What is the correct way to update an slqalchemy orm column from a pandas dataframe column从 pandas 数据框列更新 sqlalchemy orm 列的正确方法是什么
【发布时间】:2021-10-15 08:23:15
【问题描述】:

我已经加载了一些数据并修改了数据框中的一列,并希望更新数据库以反映更改。

我试过了:

db.session.query(sqlTableName).update({sqlTableName.sql_col_name: pdDataframe.pd_col_name})

但这只是清除了数据库中的列(将每个值设置为“0”,默认值)。我尝试了其他一些数据格式,但没有成功。我猜我混淆的数据类型有什么奇怪的地方,或者你不能直接用这样的变量更新列。

我可以用循环来做到这一点,但是……那真的很糟糕。很抱歉这个基本问题,在项目中断了很长时间之后,我对 sqlalchemy 的掌握肯定已经减弱了。

【问题讨论】:

  • 使用 to_sql() 将 DataFrame 上传到临时表,然后在主表上运行 UPDATE 查询,并在公共主键值上连接到临时表。
  • 我想过,但想避免无意义的 sql 使用。基本上,将同一列写入数据库两次。老实说,您不能像我原来的计划那样将 .update 用于列,这让我感到非常惊讶。我可能只是编写一些代码来创建一个更新列的 SQL 查询,而完全忽略 orm。 :/跛脚。 (谢谢你的回复)

标签: python pandas sqlalchemy


【解决方案1】:

为了将 DataFrame 上传到临时表,然后执行 UPDATE,您不需要自己编写 SQL,可以让 SQLAlchemy Core 为您完成:

import pandas as pd
import sqlalchemy as sa


def update_table_columns_from_df(engine, df, table_name, cols_to_update):
    metadata = sa.MetaData()
    main_table = sa.Table(table_name, metadata, autoload_with=engine)
    pk_columns = [x.name for x in main_table.primary_key.columns]

    df.to_sql("temp_table", engine, index=False, if_exists="replace")

    temp_table = sa.Table("temp_table", metadata, autoload_with=engine)
    with engine.begin() as conn:
        values_clause = {x: temp_table.columns[x] for x in cols_to_update}
        where_clause = sa.and_(
            main_table.columns[x] == temp_table.columns[x] for x in pk_columns
        )
        conn.execute(
            main_table.update().values(values_clause).where(where_clause)
        )
    temp_table.drop(engine)


if __name__ == "__main__":
    test_engine = sa.create_engine(
        "postgresql+psycopg2://scott:tiger@192.168.0.199/test",
        echo=True,  # (for demonstration purposes)
    )
    with test_engine.begin() as test_conn:
        test_conn.exec_driver_sql("DROP TABLE IF EXISTS main_table")
        test_conn.exec_driver_sql(
            """\
            CREATE TABLE main_table ( 
            id1 integer NOT NULL,
            id2 integer NOT NULL,
            txt1 varchar(50),
            txt2 varchar(50),
            CONSTRAINT main_table_pkey PRIMARY KEY (id1, id2)
            )
            """
        )
        test_conn.exec_driver_sql(
            """\
            INSERT INTO main_table (id1, id2, txt1, txt2)
            VALUES (1, 1, 'foo', 'x'), (1, 2, 'bar', 'y'), (1, 3, 'baz', 'z')
            """
        )

    df_updates = pd.DataFrame(
        [
            (1, 1, "new_foo", "new_x"),
            (1, 3, "new_baz", "new_z"),
        ],
        columns=["id1", "id2", "txt1", "txt2"],
    )
    update_table_columns_from_df(
        test_engine, df_updates, "main_table", ["txt1", "txt2"]
    )
    """SQL emitted:
    UPDATE main_table 
    SET txt1=temp_table.txt1, txt2=temp_table.txt2 
    FROM temp_table 
    WHERE main_table.id1 = temp_table.id1 AND main_table.id2 = temp_table.id2
    """

    df_result = pd.read_sql_query(
        "SELECT * FROM main_table ORDER BY id1, id2", test_engine
    )
    print(df_result)
    """
       id1  id2     txt1   txt2
    0    1    1  new_foo  new_x
    1    1    2      bar      y
    2    1    3  new_baz  new_z
    """

【讨论】:

  • 我最终尝试了 bulk_update_mappings 并且令人讨厌的是,尽管生成的 SQL 查询中只有一个“set”命令,但最终还是对更新的每一行都使用了一个“问题”。我的 sql server 根本不喜欢这样......所以看起来只是推一张表然后加入是最有意义的。我确信您的解决方案将完美运行,但在我的情况下,只需将其保留为具有公共索引的单独表是可以接受的,因此我不需要临时表。不过,我可以看到这在未来会派上用场。非常感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多