【问题标题】:INSERT new row if value does not exist and get id either way如果值不存在,则插入新行并以任一方式获取 id
【发布时间】:2012-11-09 14:12:25
【问题描述】:

我想向表中插入一条记录,如果该记录已经存在,则获取其 id,否则运行插入并获取新记录的 id。

我将插入数百万条记录,但不知道如何以有效的方式执行此操作。我现在正在做的是运行一个选择来检查记录是否已经存在,如果没有,插入它并获取插入记录的 id。随着桌子越来越大,我想SELECT 会杀了我。

我现在用 psycopg2 在 python 中做的事情看起来像这样:

select = ("SELECT id FROM ... WHERE ...", [...])
cur.execute(*select)
if not cur.rowcount:
    insert = ("INSERT INTO ... VALUES ... RETURNING id", [...])
    cur.execute(*insert)
rid = cur.fetchone()[0]

是否有可能在这样的存储过程中做一些事情:

BEGIN
    EXECUTE sql_insert;
    RETURN id;
    EXCEPTION WHEN unique_violation THEN
        -- return id of already existing record
        -- from the exception info ?
END;

对如何优化这样的案例有任何想法吗?

【问题讨论】:

  • 您似乎正在尝试实施 upsert。见depesz.com/2012/06/10/why-is-upsert-so-complicated
  • 我不相信这是一个 upsert。我没有做任何更新。我有数百万条重复记录,如果它已经在数据库中,我需要记录的 ID。
  • 对于一些背景:我有一个大约 40 条轧机记录值的笛卡尔积,我想将其分成 3 个表。分裂会导致很多重复。当我插入时,我将删除所有重复项。但我仍然想将 3 个表的原始连接保留在第 4 个表中。连接将在 3 个表中的记录 ID 上。
  • 我的错误,这不是一个 upsert。不过,它也有类似的问题。

标签: sql performance postgresql concurrency sql-insert


【解决方案1】:

首先,这显然不是UPSERT,因为UPDATE 从未被提及。不过,类似的并发问题也适用。

这种任务总会有竞争条件,但您可以将其最小化到一个极小的时间段,同时仅一次使用数据查询 ID-修改 CTE(PostgreSQL 9.1 引入):

给定一张桌子tbl

CREATE TABLE tbl(tbl_id serial PRIMARY KEY, some_col text UNIQUE);

使用这个查询:

WITH x AS (SELECT 'baz'::text AS some_col) -- enter value(s) once

   , y AS (
   SELECT x.some_col
        , (SELECT t.tbl_id FROM tbl t WHERE t.some_col = x.some_col) AS tbl_id
   FROM   x    
   )

   , z AS (
   INSERT INTO tbl(some_col)
   SELECT y.some_col
   FROM   y
   WHERE  y.tbl_id IS NULL
   RETURNING tbl_id
)

SELECT COALESCE(
         (SELECT tbl_id FROM z)
        ,(SELECT tbl_id FROM y)
       );
  • CTE x 只是为了方便:输入一次值。
  • CTE y 检索 tbl_id - 如果它已经存在。
  • CTE z 插入新行 - 如果没有。
  • 最终的SELECT 避免了在具有COALESCE 构造的表上运行另一个查询。

现在,如果并发事务在 CTE yz 之间提交了 some_col = 'foo' 的新行,这仍然会失败,但这极不可能。如果发生这种情况,您会遇到重复的密钥违规,并且必须重试。什么都没有丢失。如果你不面对并发写入,你可以忘记这一点。

您可以将其放入 plpgsql 函数中并自动重新运行重复键错误的查询。

不用说,在此设置中您需要两个索引(如上面我的 CREATE TABLE 语句中所示):

  • tbl_id 上的 UNIQUEPRIMARY KEY 约束(属于 serial 类型!)
  • some_col 上的另一个 UNIQUEPRIMARY KEY 约束

两者都自动实现索引。

【讨论】:

  • 我的错; insert-or-get-key 不是 upsert,只是在问题方面密切相关。
猜你喜欢
  • 2013-12-09
  • 2014-04-28
  • 2011-07-09
  • 1970-01-01
  • 2017-03-07
  • 2017-02-10
  • 2011-06-20
  • 2023-03-10
  • 2018-08-17
相关资源
最近更新 更多