【问题标题】:getting ids of multiple rows inserted in psycopg2获取在 psycopg2 中插入的多行的 id
【发布时间】:2014-02-07 10:11:24
【问题描述】:

我想使用 psycopg2 到 INSERT 多行,然后使用单个查询返回所有 ids(按顺序)。这就是 PostgreSQL 的 RETURNING 扩展的设计目的,使用 cursor.execute 似乎可以正常工作:

cursor.execute(
    "INSERT INTO my_table (field_1, field_2) "
    "VALUES (0, 0), (0, 0) RETURNING id;"
)
print cursor.fetchall()

[(1,), (2,)]

现在,为了传递动态生成的数据,cursor.executemany 似乎是要走的路:

data = [(0, 0), (0, 0)]

cursor.executemany(
    "INSERT INTO my_table (field_1, field_2) "
    "VALUES (%s, %s) RETURNING id;",
    data
)

但是,在这种情况下,cursor.fetchall() 会产生以下结果:

[(4,), (None,)]

我如何让它正确返回所有ids 而不仅仅是一个?

【问题讨论】:

  • 有趣的问题。 psycopg2 版本,以及底层 PostgreSQL 版本客户端?
  • psycopg2 2.4.5 (dt dec pq3 ext) 和 PostgreSQL 9.2.4

标签: python postgresql psycopg2


【解决方案1】:

您不应该能够从executemany 获得结果:

该函数主要用于更新数据库的命令:查询返回的任何结果集都将被丢弃。

每个the psycopg2 docs

您最好在事务中循环单个insert,或使用多值insert... returning,但在后一种情况下,您必须小心使用另一个输入值匹配返回的ID,您可以'不只是假设返回 ID 的顺序与输入 VALUES 列表的顺序相同。

当我在本地运行您的测试时,它完全失败了:

>>> import psycopg2
>>> conn = psycopg2.connect("dbname=regress")
>>> curs = conn.cursor()
>>> curs.execute("create table my_table(id serial primary key, field_1 integer, field_2 integer);")
>>> data = [(0, 0), (0, 0)]
>>> curs.executemany(
...     "INSERT INTO my_table (field_1, field_2) "
...     "VALUES (%s, %s) RETURNING id;",
...     data
... )
>>> 
>>> curs.fetchall()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
psycopg2.ProgrammingError: no results to fetch

用 psycopg2 2.5.1 测试。

【讨论】:

  • 谢谢!我完全不知道RETURNING 的订单没有保证。不幸的是,我无法唯一匹配返回的ids。关于在这种情况下如何进行的任何想法(不仅仅是插入一个虚拟的唯一字段)?
  • @Jian 如果您需要将返回的生成 ID 与插入的元组相关联,您只需将它们一一插入,或者使用 returning * 并匹配整个插入的元组。 (Pg 目前确实按照输入出现在 values 子句中的顺序返回 ID,但它可能会在将来停止这样做,并且 SQL 规范并不要求这样做)。
  • @CraigRinger,您能否指出 PostgreSQL 文档,其中说明 RETURNING 返回的行是否按照您提供的值的顺序排列?如果不能保证这将是一个真正的痛苦。
  • 这就是重点——它在任何地方都没有明确保证,并且在 SQL 中,除非特别订购,否则一切都是无序集。 RETURNING order 可能发生变化的原因可能是引入了对索引排序表的支持。不过,在 pgsql-general 上提出问题可能是一个好主意 - SQL 规范本身可能提供这样的保证,在这种情况下 Pg 必须遵守它。现在,它似乎可以正常工作,因为实现将按照它们作为输入呈现的顺序使用和发出行。
【解决方案2】:

诀窍是使用 mogrify。它使用单个执行和 ID,因此无论如何都比 executemany 快:

def insert_many(self, table: str, id_column: str, values: list):
    if not values:
        return []

    keys = values[0].keys()
    query = cursor.mogrify("INSERT INTO {} ({}) VALUES {} RETURNING {}".format(
            table,
            ', '.join(keys),
            ', '.join(['%s'] * len(values)),
            id_column
        ), [tuple(v.values()) for v in values])

    conn = psycopg2.connect("host=localhost4 port=5432 dbname=cpn")
    cursor = conn.cursor()
    cursor.execute(query)
    return [t[0] for t in (cursor.fetchall()]

【讨论】:

    【解决方案3】:

    将动态生成的数据作为元组数组传递并取消嵌套

    import psycopg2
    
    insert = """
        insert into my_table (field_1, field_2)
        select field_1, field_2
        from unnest(%s) s(field_1 int, field_2 int)
        returning id
    ;"""
    
    data = [(0,0),(1,1),(2,2)]
    
    conn = psycopg2.connect("host=localhost4 port=5432 dbname=cpn")
    cursor = conn.cursor()
    cursor.execute(insert, (data,))
    print cursor.fetchall()
    conn.commit()
    conn.close()
    

    打印

    [(1,), (2,), (3,)]
    

    【讨论】:

    • 不错!我想知道如何在不使用 Python 字符串格式的情况下接受动态生成的数据,而这正是这样做的。但是如果你事先不知道字段名称,因为它们也是动态生成的呢?是否有一个干净的 psycopg2 API 来格式化这些?
    猜你喜欢
    • 2011-07-11
    • 2020-07-15
    • 2017-08-31
    • 2012-07-26
    • 2018-05-26
    • 2012-01-23
    • 1970-01-01
    • 2011-11-22
    相关资源
    最近更新 更多