从 plpgsql 中的 FOR 循环切换到基于集合的 SQL 命令

Posted

技术标签:

【中文标题】从 plpgsql 中的 FOR 循环切换到基于集合的 SQL 命令【英文标题】:Switching from FOR loops in plpgsql to set-based SQL commands 【发布时间】:2014-02-24 02:48:12 【问题描述】:

我有相当繁重的查询需要重写 FOR 循环,并且希望使用更多 SQL 而不是 plpgsql 结构来简化它。查询如下:

FOR big_xml IN SELECT unnest(xpath('//TAG1', my_xml)) LOOP
   str_xml = unnest(xpath('/TAG2/TYPE/text()', big_xml));

      FOR single_xml IN SELECT unnest(xpath('/TAG2/single', big_xml)) LOOP

         CASE str_xml::INT
           WHEN 1
           THEN
            INSERT INTO tab1(id, xml) VALUES (1, single_xml);
           WHEN 2
           THEN
            INSERT INTO tab2(id, xml) VALUES (1, single_xml);
           WHEN 3
            [...] 
           WHEN 11
            [...]
           ELSE 
              RAISE EXCEPTION 'something'
           END CASE;
      END LOOP;
    END LOOP;

RETURN xmlelement(NAME "out", xmlforest(1 AS out));

我已经开始重写它以获得更好的性能:

INSERT INTO tab1(id, xml)
  SELECT 1, unnest(xpath('/TAG2/single', (SELECT unnest(xpath('//TAG1', my_xml)))); 

但我不确定如何处理那些CASE ... INSERT 语句。 有任何想法吗? 或者也许我的方法完全错误?

25.02.14 编辑:PostgreSQL 9.3.1

【问题讨论】:

一如既往,示例值和您的 Postgres 版本会有所帮助。 unnest() 在你原来的这个声明中没有意义:str_xml = unnest(xpath('/TAG2/TYPE/text()', big_xml))。如果返回多行,则代码中断;如果这不能发生,unnest() 没有意义...... 【参考方案1】:

要根据您的数据插入到不同的表中,您需要一些程序代码。但是您可以使用单个循环和嵌套的unnest()

DEFINE
   int_xml    int;
   single_xml xml;
BEGIN
   FOR r IN                     -- or you can use two variables
      SELECT xpath('/TAG2/single', big_xml))[1]::text::int AS int_xml
           , unnest(xpath('/TAG2/TYPE/text()', big_xml)) AS single_xml
      FROM  (SELECT unnest(xpath('//TAG1', my_xml)) AS big_xml) sub
   LOOP
      CASE int_xml
      WHEN 1 THEN
         INSERT INTO tab1(id, xml) VALUES (1, single_xml);
      WHEN 2 THEN
         INSERT INTO tab2(id, xml) VALUES (1, single_xml);
      WHEN 3 THEN
          ...

      ELSE 
         RAISE EXCEPTION 'something'
      END CASE;
   END LOOP;
END

或者获取一个临时表或 CTE 并附加多个 INSERT 语句。对于很多行应该更快。

WITH cte AS (
   SELECT xpath('/TAG2/single', big_xml))[1]::text::int AS int_xml
        , unnest(xpath('/TAG2/TYPE/text()', big_xml)) AS single_xml
   FROM  (SELECT unnest(xpath('//TAG1', my_xml)) AS big_xml) sub
), i1 AS (
   INSERT INTO tab1(id, xml)
   SELECT 1, single_xml
   FROM   cte
   WHERE  int_xml = 1
), i2 AS (
   INSERT INTO tab2(id, xml)
   SELECT 1, single_xml
   FROM   cte
   WHERE  int_xml = 2
),
   ...
)
SELECT int_xml INTO my_var
FROM   cte
WHERE  int_xml <> ALL ('1','2','3','11'::int[])
LIMIT  1;

IF FOUND THEN
   RAISE EXCEPTION 'One of the int_xml did not fit: %!', my_var;
END IF;

最后一点弥补了原始文件中的异常。

如果您的big_xml 真的很大,请确保为 CTE 提供足够的临时缓冲区。如果常规设置太低,请仅为此会话增加 temp_buffers 设置。此相关答案中的详细信息:How can I insert common data into a temp table from disparate schemas?

【讨论】:

感谢您的回答,它一如既往地出色,但我对 INSERT 语句不够具体。当值等于 1 时,插入到 tab1,当 2 到 tab2 时等等。那么我应该为此使用动态 SQL 吗?或者有没有办法用标准的 SQL 来写? 所以我需要在 xml 中的 TYPE 和表名之间进行一些映射。也许通过未记录的表并加入? @Borys;我重写了答案以适应不同的目标表。考虑更新。

以上是关于从 plpgsql 中的 FOR 循环切换到基于集合的 SQL 命令的主要内容,如果未能解决你的问题,请参考以下文章

如何从plpgsql中的循环返回结果?

对于表中的行,将行保存在临时表中以在 plpgsql 的选择查询中使用其数据

PLpgSQL:将每条记录存储在 For Loop 中并返回 json

在plpgsql中循环数组维度

基于查询结果集的PHP跳过for循环迭代

在 plpgsql(PostgreSQL 的)中,可以将 CTE 保留到外循环吗?