考虑表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)
答案 0 :(得分:1)
TL; DR:不要为此使用数组。真。仅仅因为你不能意味着你应该这样做。
PostgreSQL的数组实际上并不是为就地修改而设计的;它们是数据值,而不是动态数据结构。我不认为你尝试做的事情有多大意义,并建议你在深入挖掘洞之前重新评估你的模式。
您不能只是从temp2构造一个空填充的数组值并执行切片更新,因为它会在days
中用空值覆盖值。没有"只更新非空数组元素"操作
所以我们必须通过将数组分解为一个集合,修改它,重新组合成一个数组来实现这一点。
要解决我正在做的事情:
temp2
获取所有行并添加相关值,以生成(索引,值)对generate_series
并对其执行左连接,因此每个索引位置都有一行array_agg
以重建数组。使用更现实/有用的起始数组状态:
create temporary table temp1 (
id integer primary key,
days integer[]
);
insert into temp1 values (1, '{42,42,42}');
首先将值与每个索引相关联:
select id, 99 from temp2;
然后加入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);
然后在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)
现在我们只需要通过删除末尾的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值。
我希望这可以说服你,你可能不应该使用数组来做你正在做的事情,因为它可怕的。