postgres数组:在一个查询中更新多个不连续的数组索引

时间:2014-09-28 10:08:33

标签: arrays postgresql

考虑表temp1

create temporary table temp1 (
id integer,
days integer[]
);

insert into temp1 values (1, '{}');

另一个表temp2

create temporary table temp2(
id integer
);

insert into temp2 values (2);
insert into temp2 values (5);
insert into temp2 values (6);

我想使用temp2 id值作为temp1的days数组的索引。即我想更新

days [index] = 99其中index是temp2的id值。我想在单个查询中完成此操作,或者如果不可能,则是最佳方式。

这是我正在尝试的内容,它只更新一个索引,而不是全部。是否可以更新阵列的多个索引?我知道可以使用循环完成,但只是希望是否可以使用更优化的解决方案?

update temp1
set days[temp2.id] = 99
from temp2;

select * from temp1;

 id |    days    
----+------------
  1 | [2:2]={99}
(1 row)

1 个答案:

答案 0 :(得分:1)

TL; DR:不要为此使用数组。真。仅仅因为你不能意味着你应该这样做。


PostgreSQL的数组实际上并不是为就地修改而设计的;它们是数据值,而不是动态数据结构。我不认为你尝试做的事情有多大意义,并建议你在深入挖掘洞之前重新评估你的模式。

您不能只是从temp2构造一个空填充的数组值并执行切片更新,因为它会在days中用空值覆盖值。没有"只更新非空数组元素"操作

所以我们必须通过将数组分解为一个集合,修改它,重新组合成一个数组来实现这一点。

要解决我正在做的事情:

  • temp2获取所有行并添加相关值,以生成(索引,值)对
  • 在temp2上从1到最高索引的范围内执行generate_series并对其执行左连接,因此每个索引位置都有一行
  • 将未连接的原始数组上的所有内容连接起来并合并掉空值
  • ...然后按索引排序array_agg以重建数组。

使用更现实/有用的起始数组状态:

create temporary table temp1 (
id integer primary key,
days integer[]
);

insert into temp1 values (1, '{42,42,42}');

开发步骤1:索引/值对

首先将值与每个索引相关联:

select id, 99 from temp2;

开发步骤2:为缺失索引添加空值

然后加入generate_series以添加缺失索引的条目:

SELECT gs.i, temp2values.newval
FROM (
    SELECT id AS newvalindex, 99 as newval FROM temp2
  ) temp2values 
  RIGHT OUTER JOIN (
    SELECT i FROM generate_series(1, (select max(id) from temp2)) i
  ) gs 
  ON (temp2values.newvalindex = gs.i);

开发步骤3:合并

中的原始数组值

然后在unnested原始数组上加入 。你可以在PostgreSQL 9.4中使用UNNEST ... WITH ORDINALITY,但我猜你还没有运行,所以我会用row_number显示旧的方法。注意使用完全外连接和更改generate_series的外部边界来处理原始值数组比新值列表中的最高索引更长的情况:

SELECT gs.i, coalesce(temp2values.newval, originals.val) AS val
FROM (
    SELECT id AS newvalindex, 99 as newval FROM temp2
  ) temp2values
  RIGHT OUTER JOIN (
    SELECT i FROM generate_series(1, (select greatest(max(temp2.id), array_length(days,1)) from temp2, temp1 group by temp1.id)) i
  ) gs
  ON (temp2values.newvalindex = gs.i)
  FULL OUTER JOIN (
    SELECT row_number() OVER () AS index, val
    FROM temp1, LATERAL unnest(days) val
    WHERE temp1.id = 1
  ) originals
  ON (originals.index = gs.i)
ORDER BY gs.i;

这会产生类似:

regress=> \e
 i |    val 
---+----------
 1 |       42
 2 |       99
 3 |       42
 4 |         
 5 |       99
 6 |       99
(6 rows)

开发步骤4:生成所需的新数组值

现在我们只需要通过删除末尾的ORDER BY子句并使用array_agg将其重新转换为数组:

SELECT array_agg(coalesce(temp2values.newval, originals.val) ORDER BY gs.i)
FROM (
    SELECT id AS newvalindex, 99 as newval FROM temp2
  ) temp2values
  RIGHT OUTER JOIN (
    SELECT i FROM generate_series(1, (select greatest(max(temp2.id), array_length(days,1)) from temp2, temp1 group by temp1.id)) i
  ) gs
  ON (temp2values.newvalindex = gs.i)
  FULL OUTER JOIN (
    SELECT row_number() OVER () AS index, val
    FROM temp1, LATERAL unnest(days) val
    WHERE temp1.id = 1
  ) originals
  ON (originals.index = gs.i);

结果如:

       array_agg       
-----------------------
 {42,99,42,NULL,99,99}
(1 row)

最终查询:在UPDATE

中使用它
UPDATE temp1
SET days = newdays
FROM (
        SELECT array_agg(coalesce(temp2values.newval, originals.val) ORDER BY gs.i)
        FROM (
            SELECT id AS newvalindex, 99 as newval FROM temp2
          ) temp2values 
          RIGHT OUTER JOIN (
            SELECT i FROM generate_series(1, (select greatest(max(temp2.id), array_length(days,1)) from temp2, temp1 group by temp1.id)) i
          ) gs 
          ON (temp2values.newvalindex = gs.i)
          FULL OUTER JOIN (
            SELECT row_number() OVER () AS index, val
            FROM temp1, LATERAL unnest(days) val
            WHERE temp1.id = 1
          ) originals
          ON (originals.index = gs.i)
) calc_new_days(newdays)
WHERE temp1.id = 1;

但请注意,**这仅适用于temp1.id中的单个条目,并且我在查询中指定了temp1.id两次:一旦在查询中生成新数组值,一次在update谓词中。

为避免这种情况,您需要temp2中引用temp1.id的密钥,并且您需要进行一些更改以允许生成的填充行具有正确的ID值。

我希望这可以说服你,你可能不应该使用数组来做你正在做的事情,因为它可怕的