【问题标题】:Postgres bulk INSERT function using JSON arguments使用 JSON 参数的 Postgres 批量 INSERT 函数
【发布时间】:2017-05-20 11:50:08
【问题描述】:

这是一个用于 postgres 9.6plpgsql 函数。它尝试INSERT 一行,如果插入没有失败(由于违反键约束),那么它会运行更多命令。

CREATE FUNCTION foo(int, text, text)
  RETURNS void AS
$$
BEGIN
  INSERT INTO table1 (id, val1, val2) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;
  IF FOUND THEN
    INSERT INTO table2 (table1_id, val1) VALUES ($1, $2);
    UPDATE table3 SET (val2, time) = ($3, now()) WHERE table1_id = $1;
  END IF;
END
$$

这个函数只处理一条记录,但是如何修改它来处理成批的数千条记录呢?

我找到了answer,它建议将 3 个函数参数中的每一个都设为一个数组。但是有没有一种方法可以让我传递更接近地表示记录在我的应用程序中的外观的参数?

例如,理想的解决方案是我的应用程序代码调用select foo($1),其中参数$1 是一个JSON 对象数组,其中每个内部对象都是要插入的记录。

[ 
  { "id": "1", "val1": "1-val1", "val2": "1-val2" },
  { "id": "2", "val1": "2-val1", "val2": "2-val2" },
  { "id": "3", "val1": "3-val1", "val2": "3-val2" },
  { "id": "4", "val1": "4-val1", "val2": "4-val2" }
]

第二好的选择是我的应用程序代码调用 select foo($1, $2, $3, $4),其中每个参数都是一个 JSON 对象,对应于要插入的记录。

{ "id": "1", "val1": "1-val1", "val2": "1-val2" }  // This would be $1
{ "id": "2", "val1": "2-val1", "val2": "2-val2" }  // This would be $2

我正在查看 Postgres here 提供的各种 JSON 函数,它们似乎与此相关,但我不知道究竟要使用哪个。我想要做的甚至可能吗?是否可以在任何地方使用 JSON 数组 而不是 JSON 对象 来实现这一点?

【问题讨论】:

  • 是的,现在试了一下,效果很好,谢谢!

标签: sql json postgresql plpgsql sql-insert


【解决方案1】:

数以千计的记录

1. 创建一个包含输入行的临时表,其中包含您的值$1$2$3。最快的上传方式是COPY - 如果数据不在同一台机器上,则使用\copy meta-command of psql。假设这张表:

CREATE TEMP TABLE tmp(id int PRIMARY KEY, val1 text, val2 text);

我添加了一个 PK 约束,这是完全可选的,但它确保我们正在处理唯一的非空 int 值。如果您可以保证输入数据,则不需要约束。

2. 将您的命令与数据修改 CTE 链接起来。正如我们在您的previous question 中确定的那样,在此特定操作中没有需要处理的竞争条件。

WITH ins1 AS (
   INSERT INTO table1 AS t1 (id, val1, val2)
   SELECT id, val1, val2 FROM tmp ON CONFLICT DO NOTHING
   RETURNING t1.id, t1.val1, t1.val2  -- only actually inserted rows returned
   )
, ins2 AS (
   INSERT INTO table2 (table1_id, val1)
   SELECT id, val1 FROM ins1
   )
UPDATE table3 t3
SET    val2 = i.val2
     , time = now()
FROM   ins1 i
WHERE  t3.table1_id = i.id;

第 1 步和第 2 步必须运行在同一个会话(不一定是同一个事务),因为临时表的范围绑定到同一个会话.

注意,UPDATE只依赖第一个INSERT,保证第二个INSERT的成功,因为没有ON CONFLICT DO NOTHING,如果有任何冲突,整个操作将被回滚第二个INSERT

相关:

只记录几条

有多种选择。您将 JSON 数组传递给函数的想法就是其中之一。如果对象与目标表匹配,您可以在单个 INSERT 查询中使用 json_populate_recordset()。或者只使用没有函数包装器的INSERT(作为准备好的语句)。

INSERT INTO target_tbl  -- it's ok to omit target columns here
SELECT *
FROM   json_populate_recordset(null::target_tbl,  -- use same table type
          json '[{ "id": "1", "val1": "1-val1", "val2": "1-val2" },
                 { "id": "2", "val1": "2-val1", "val2": "2-val2" },
                 { "id": "3", "val1": "3-val1", "val2": "3-val2" },
                 { "id": "4", "val1": "4-val1", "val2": "4-val2" }]');

对于少数几列,您还可以为每列传递一个数组并并行循环它们。您可以通过数组索引上的简单循环来做到这一点。从 Postgres 9.4 开始,还有方便的 unnest() 带有多个参数,可以在单个查询中完成所有操作:

最佳解决方案取决于您拥有的数据格式

【讨论】:

  • 谢谢。 (我会探索你的答案并尝试一下。)但是假设它不是数千行,我可以使用我在问题中描述的 JSON 参数吗?我要求的不仅仅是这个查询,而是因为我有几个 plpgsql 函数,我希望能够处理来自我的应用程序的多个记录(比如在函数内的循环中),但我必须预处理我的应用程序中的数据使其看起来像每个参数的单独数组(如stackoverflow.com/a/24350689/779159)。我的问题中的任何一个选项是否可能(一个对象数组或多个对象)?
  • 例如,我是否可以使用jsonb_to_recordset 函数传入单个JSON 对象数组,然后循环遍历该记录集并针对记录集中的每条记录运行INSERTs?
  • 我在stackoverflow.com/a/19147320/779159 中看到了您关于循环的答案。我怎么能循环通过json_populate_recordset返回的recordset(上面你的答案的仅用于几条记录部分),以便我可以执行INSERT INTOIF FOUND THEN,循环的每次迭代我的问题等等?
  • @user779159:你不需要循环。链式数据修改 CTE,如第一个查询中所示。这比为每一行循环更快、更安全。
  • @user779159:是的,我就是这个意思。您可以使用带有json_populate_recordset() 的第二个查询的结果来代替第一个查询中的临时表。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-02
  • 1970-01-01
  • 2021-06-10
  • 1970-01-01
  • 2021-07-05
  • 1970-01-01
相关资源
最近更新 更多