Reputation: 165
NOTE: This is very similar to, but not the same as my other recent question at Looping inside a Postgres UPDATE query. The answer provided on that question works perfectly, but the client wants each value calculated using a specific formula rather than a randomized selection to enforce an even distribution.
(Postgres 10.10) I have the following fields in my_table:
loval INTEGER
hival INTEGER
valcount INTEGER
vals INTEGER[]
I need to set vals to an array containing valcount - 2 integers (loval and hival are included in the final array) each calculated as follows using n as a loop counter running from 2 to valcount-1:
loval + (n - 1) * ((hival - loval) / (valcount- 1))
So for:
loval: 3
hival: 22
valcount: 6
I'm looking to set vals to:
{3, 7, 11, 14, 18, 22}
I know how to do this with an inefficient "loop through the cursor" solution, but I'm wondering if Postgres has a way to do a looping computation inline or a built-in distribution function that can do this.
Upvotes: 1
Views: 121
Reputation: 377
You can edit the answer from the post you linked in this way:
update my_table
set vals = array_append(array_prepend(loval,array(select (loval + (n-1) * ((hival - loval)::decimal / (valcount- 1)))::int
from generate_series(2, valcount-1) as n)),hival);
Demo:
| loval | hival | valcount | vals | | ----- | ----- | -------- | --------------- | | 3 | 22 | 6 | 3,7,11,14,18,22 |
::decimal
will make sure that the division is done with floating numbers so that your second value is 7 instead of 6 with only integers.
array_prepend
and array_append
will add an element at the beginning and at the end of the array
Upvotes: 1
Reputation: 37487
Use generate_series()
to create the running n for your formula. Use the WITH ORDINALITY
option of generate_series()
to have an ordinal number you can order the results by. Make sure you cast (at least) one operand of the division in your formula to decimal
as otherwise integer division is used. Use round()
to round the result. Then use array_agg()
to build the array. Use ORDER BY
for array_agg()
to order the array elements by the ordinal number generate by generate_series()
. To update each row you can use a subquery.
UPDATE my_table t
SET vals = (SELECT array_agg(round(t.loval + (gs.n - 1) * ((t.hival - t.loval)::numeric / (t.valcount - 1))) ORDER BY gs.o)
FROM generate_series(1, t.valcount) WITH ORDINALITY gs (n, o));
Upvotes: 1
Reputation: 222622
You could make use of generate_series()
and array_agg()
like so:
select
t.*,
(
select array_agg(loval + (n - 1) * (hival - loval) / (valcount - 1) order by n)
from generate_series(1, valcount) as d(n)
) vals
from t
| loval | hival | valcount | vals | | ----- | ----- | -------- | --------------- | | 3 | 22 | 6 | 3,6,10,14,18,22 |
Upvotes: 0