从熊猫插入PostgreSQL表中,并进行“发生冲突”更新

时间:2019-03-15 17:26:25

标签: python pandas postgresql dataframe insert-update

我有一个熊猫DataFrame,我需要将其存储到数据库中。这是我当前要插入的代码行:

df.to_sql(table,con=engine,if_exists='append',index_label=index_col)

如果我的表中不存在df中的行,则此方法很好。如果已经存在一行,则会出现此错误:

sqlalchemy.exc.IntegrityError: (psycopg2.IntegrityError) duplicate key
value violates unique constraint "mypk"
DETAIL:  Key (id)=(42) already exists.
 [SQL: 'INSERT INTO mytable (id, owner,...) VALUES (%(id)s, %(owner)s,...']
 [parameters:...] (Background on this error at: http://sqlalche.me/e/gkpj)

什么也没插入。

PostgreSQL具有可选的ON CONFLICT子句,该子句可用于UPDATE现有的表行。我阅读了整个pandas.DataFrame.to_sql manual page,但在ON CONFLICT函数中找不到使用DataFrame.to_sql()的任何方法。

我已经考虑过根据db表中已有的内容将DataFrame分为两部分。因此,现在我有了两个insert_rowsupdate_rows数据框,并且可以安全执行

insert_rows.to_sql(table, con=engine, if_exists='append', index_label=index_col)

但是,似乎没有UPDATE等效于DataFrame.to_sql()。那么如何使用DataFrame update_rows更新表?

3 个答案:

答案 0 :(得分:0)

如果您在to_sql文档中注意到有一个提到method的参数,该参数采用可调用对象。创建此可调用对象应允许您使用所需的Postgres子句。这是他们在文档中提到的可赎回债券的示例:https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html#io-sql-method

它与您所需要的完全不同,但是请遵循传递给此可调用对象的参数。它们将允许您构造常规的SQL语句。

答案 1 :(得分:0)

为了以示例的形式跟进布伦丹的回答,这对我有用:

import os
import sqlalchemy as sa
import pandas as pd
from sqlalchemy.dialects.postgresql import insert


engine = sa.create_engine(os.getenv("DBURL"))
meta = sa.MetaData()
meta.bind = engine
meta.reflect(views=True)


def upsert(table, conn, keys, data_iter):
    upsert_args = {"constraint": "test_table_col_a_col_b_key"}
    for data in data_iter:
        data = {k: data[i] for i, k in enumerate(keys)}
        upsert_args["set_"] = data
        insert_stmt = insert(meta.tables[table.name]).values(**data)
        upsert_stmt = insert_stmt.on_conflict_do_update(**upsert_args)
        conn.execute(upsert_stmt)


if __name__ == "__main__":
    df = pd.read_csv("test_data.txt")
    with db.engine.connect() as conn:
        df.to_sql(
            "test_table",
            con=conn,
            if_exists="append",
            method=upsert,
            index=False,
        )

在此示例中,架构类似于:

CREATE TABLE test_table(
    col_a text NOT NULL,
    col_b text NOT NULL,
    col_c text,
    UNIQUE (col_a, col_b)
)

答案 2 :(得分:0)

如果有人想在 zdgriffith 的答案的基础上构建并动态生成表约束名称,您可以对 postgreSQL 使用以下查询:

select distinct tco.constraint_name
from information_schema.table_constraints tco
         join information_schema.key_column_usage kcu
              on kcu.constraint_name = tco.constraint_name
                  and kcu.constraint_schema = tco.constraint_schema
                  and kcu.constraint_name = tco.constraint_name
where kcu.table_name = '{table.name}'
  and constraint_type = 'PRIMARY KEY';

然后您可以格式化此字符串以填充 table.name 方法内的 upsert()

我也不需要 meta.bindmeta.reflect() 行。无论如何,后者很快就会被弃用。