Postgresql multiply and sum row using windows function? - postgresql

i need to somehow use the LAG along with the SUM after each returning line
table
id
valor
data
1
1,0182
2022-01-01
2
1,0183
2022-02-01
3
1,0174
2022-03-01
Expected result
id
valor
data
1
1,0182
2022-01-01
2
1,0368
2022-02-01
3
1,0548
2022-03-01
in the column "valor" I need to take the previous value, multiply it with the current value, and add this value
linha 1 1,0182
linha 2 (1,0182 x 1,0183)
linha 3 (1,0182 x 1,0183) x 1,0548
linha 4 ((1,0182 x 1,0183) x 1,0548) x ##,####
...
nd yes onwards
SELECT i.id,
valor,
COALESCE(LAG (valor) OVER ( PARTITION BY indice_correcao_id ORDER BY DATA ), 1) as valor_anteior,
SUM ( valor ) OVER ( PARTITION BY indice_correcao_id ORDER BY DATA ) AS cum_amt
FROM
indice_correcao_itens AS i
WHERE
i.indice_correcao_id = 1
AND i."data" BETWEEN '2022-01-01'
AND '2022-03-28'
ORDER BY i."data";

You can define your own aggregate function that returns the product of the input:
-- From https://stackoverflow.com/a/13156170/2650437
-- See this answer if your column is not a FLOAT but e.g. a NUMERIC
-- as you will need to adapt the aggregate a bit
CREATE AGGREGATE PRODUCT(DOUBLE PRECISION) (
SFUNC = float8mul,
STYPE = FLOAT8
);
You can use custom aggregates in window functions, so
CREATE TEMP TABLE t (
"id" INTEGER,
"valor" FLOAT,
"data" TIMESTAMP
);
INSERT INTO t ("id", "valor", "data")
VALUES ('1', 1.0182, '2022-01-01')
, ('2', 1.0183, '2022-02-01')
, ('3', 1.0174, '2022-03-01');
SELECT id, SUM(valor) OVER (ORDER BY data, id), PRODUCT(valor) OVER (ORDER BY data, id)
FROM t;
returns
+--+------------------+--------------+
|id|sum |product |
+--+------------------+--------------+
|1 |1.0182 |1.0182 |
|2 |2.0365 |1.03683306 |
|3 |3.0539000000000005|1.054873955244|
+--+------------------+--------------+

Related

Designate groups of consecutive equal values in an ordered dataset

I'm trying to get the output to look like below. The problem is that I can't do a first_value or RANK because when I partition by event and order by time, then it doesn't break them up in that order. I need them to order by time first and then partition each time.
One of the known solutions
Use lag() to mark rows when event changes and cumulative sum() to designate groups, e.g.:
with my_table(event, time) as (
values
('A', '12:01'),
('A', '12:02'),
('B', '12:03'),
('A', '12:04'),
('A', '12:05'),
('B', '12:06'),
('B', '12:07'),
('A', '12:08')
)
select
event,
time,
sum(change) over (order by time) as "desired row number"
from (
select
event,
time,
(event is distinct from lag(event) over (order by time))::int as change
from my_table
) s
event | time | desired row number
-------+-------+--------------------
A | 12:01 | 1
A | 12:02 | 1
B | 12:03 | 2
A | 12:04 | 3
A | 12:05 | 3
B | 12:06 | 4
B | 12:07 | 4
A | 12:08 | 5
(8 rows)
Custom aggregate
It would be nice to have the function:
select *, group_number(event) over (order by time)
from my_table;
This can be done with the custom aggregate:
create type group_number_internal as (number int, lag text);
create or replace function group_number_transition(group_number_internal, anyelement)
returns group_number_internal language sql strict as $$
select
case
when $2::text is distinct from $1.lag then $1.number+ 1
else $1.number
end,
$2::text
$$;
create or replace function group_number_final(group_number_internal)
returns int language sql as $$
select $1.number
$$;
create aggregate group_number(anyelement) (
sfunc = group_number_transition,
stype = group_number_internal,
finalfunc = group_number_final,
initcond = '(0, null)'
);
Test it in rextester.

Postgres query : using previous dynamically created colum value in next

I'm trying to implement what I have in code as a postgres query.
The following example isn't exactly what we're trying to do but I hope it shows how I'm trying to use the value from a previously calculated row in the next.
A sample table to help me demonstrate what I'm trying to do :
test=# select * from test ;
id | field1 | field2 | field3 | score
----+--------+--------+--------+-------
1 | 1 | 3 | 2 | 1.25
2 | 1 | -1 | 1 |
3 | 2 | 1 | 5 |
4 | 3 | -2 | 4 |
Here's the query in progress:
select id,
coalesce (
score,
case when lag_field3 = 2 then 0.25*(3*field1+field2) end
) as new_score
from (
select id, field1, field2, field3, score,
lag (field3) over (order by id) as lag_field3
from test
) inner1 ;
Which returns what I want so far ...
id | new_score
----+-----------
1 | 1.25
2 | 0.5
3 |
4 |
The next iteration of the query:
select id,
coalesce (
score,
case when lag_field3 = 2 then 0.25*(3*field1+field2) end,
case when field1 = 2 then 0.75 * lag (new_score) end
) as new_score
from (
select id, field1, field2, field3, score,
lag (field3) over (order by id) as lag_field3
from test
) inner1 ;
The difference is this :
case when field1 = 2 then 0.75 * lag (new_score) end
I know and understand why this won't work.
I've aliased the calculated field as new_score and when field1 = 2, I want 0.75 * the previous rows new_score value.
I understand that new_score is an alias and can't be used.
Is there some way I can accomplish this? I could try to copy that expression, wrap a lag around it, alias that as something else and try to work with that but that would get very messy.
Any ideas?
Many thanks.
Postgres lets you use windows in CASE statements. Probably you were missing the OVER (ORDER BY id) part. You can also define different windows but you can't use windows in conjunction with GROUP BY. Also, it won't let you use annidate windows, so you have to write down some subqueries or CTEs.
Here's the query:
SELECT id, COALESCE(tmp_score,
CASE
WHEN field1 = 2
THEN 0.75 * LAG(tmp_score) OVER (ORDER BY id)
-- missing ELSE statement here
END
) AS new_score
FROM (
SELECT id, field1,
COALESCE (
score,
CASE
WHEN LAG(field3) OVER (ORDER BY id) = 2
THEN 0.25*(3*field1+field2)
END
) AS tmp_score
FROM test
) inner1
The code to create and populate the table:
CREATE TABLE test(
id int,
field1 int,
field2 int,
field3 int,
score numeric
);
INSERT INTO test VALUES
(1, 1, 3, 2, 1.25),
(2, 1, -1, 1, NULL),
(3, 2, 1, 5, NULL),
(4, 3, -2, 4, NULL);
The query returns this output:
id | new_score
----+-----------
1 | 1.25
2 | 0.50
3 | 0.3750
4 |

Postgres - bind results of equal type by year - long to wide data

Please excuse my not very propper way of asking this as i am new to postgres...
Having the following two tables:
CREATE TABLE pub (
id int
, time timestamp
);
id time
1 1 2010-02-10 01:00:00
2 2 2011-02-10 01:00:00
3 3 2012-02-10 01:00:00
And
CREATE TABLE val (
id int
, type text
, val int
);
id type val
1 1 A 1
2 1 B 2
3 1 C 3
4 2 A 4
5 2 B 5
6 3 D 6
I would like to get the following output (for id <= 2 )
type 2010 2011
1 A 1 4
2 B 2 5
3 C 3 NULL
So type is the superset of all type's present in table val.
NULL meaning that there is no value for label C.
Ideally the column-headings are are years of the time. Alternatively the id itself...
Exists at least two ways to do this.
If your table have not many categories you can use CTE
WITH x AS (
SELECT type,
sum(val) FILTER (WHERE date_part('year', time) = 2010) AS "2010",
sum(val) FILTER (WHERE date_part('year', time) = 2011) AS "2011"
FROM pub AS p JOIN val AS v ON (v.id = p.id)
GROUP BY type
)
SELECT * FROM x
WHERE "2010" is NOT NULL OR "2011" IS NOT NULL
ORDER BY type
;
But if you have many or dynamic categories you must use crosstab:
CREATE EXTENSION tablefunc;
SELECT * FROM crosstab(
$$
SELECT type,
date_part('year', time)::text as time,
sum(val) AS val
FROM pub AS p JOIN val AS v ON (v.id = p.id)
GROUP BY type, 2
ORDER BY 1, 2
$$,
$$VALUES ('2010'::text), ('2011'), ('2012') $$
) AS ct (type text, "2010" int, "2011" int, "2012" int);
;

How to sum up all rows based on a enumeration column in PostgreSQL?

))Hi all, this is my table...
I would like to create a trigger function that whenever 'Total' is INSERTed on the timetype column it would SUM up all timeelapse FROM mytable WHERE fnname = 'ff' AND timetype = 'Lap' but only where timeindex has the highest decimal value (eg. in mytable = 1.1, 2.3, 3.3) (let's say the max possible decimal value its 1.9 or 2.9 or 3.9 and son on). So in the table above the trigger function would automatically sum up all blue highlighted squeares and place it in the last timeelpase row (where timetype = 'Total'). I How can I do that?
Thanks Advanced.
Group your data by the integer part of timeindex:
select distinct on (floor(timeindex)::int)
floor(timeindex)::int idx, timeindex, timeelapse
from mytable
where fnname = 'ff'
and timetype = 'Lap'
order by 1, 2 desc;
idx | timeindex | timeelapse
-----+-----------+------------
1 | 1.1 | 01:00:00
2 | 2.3 | 03:00:00
3 | 3.3 | 08:00:00
(3 rows)
In the trigger calculate the sum from the above query:
select sum(timeelapse) from (
select distinct on (floor(timeindex)::int)
floor(timeindex)::int idx, timeindex, timeelapse
from mytable
where fnname = 'ff'
and timetype = 'Lap'
order by 1, 2 desc
) alias;
sum
----------
12:00:00
(1 row)

Renumbering a column in postgresql based on sorted values in that column

Edit: I am using postgresql v8.3
I have a table that contains a column we can call column A.
Column A is populated, for our purposes, with arbitrary positive integers.
I want to renumber column A from 1 to N based on ordering the records of the table by column A ascending. (SELECT * FROM table ORDER BY A ASC;)
Is there a simple way to accomplish this without the need of building a postgresql function?
Example:
(Before:
A: 3,10,20,100,487,1,6)
(After:
A: 2,4,5,6,7,1,3)
Use the rank() (or dense_rank() ) WINDOW-functions (available since PG-8.4):
create table aaa
( id serial not null primary key
, num integer not null
, rnk integer not null default 0
);
insert into aaa(num) values( 3) , (10) , (20) , (100) , (487) , (1) , (6)
;
UPDATE aaa
SET rnk = w.rnk
FROM (
SELECT id
, rank() OVER (order by num ASC) AS rnk
FROM aaa
) w
WHERE w.id = aaa.id;
SELECT * FROM aaa
ORDER BY id
;
Results:
CREATE TABLE
INSERT 0 7
UPDATE 7
id | num | rnk
----+-----+-----
1 | 3 | 2
2 | 10 | 4
3 | 20 | 5
4 | 100 | 6
5 | 487 | 7
6 | 1 | 1
7 | 6 | 3
(7 rows)
IF window functions are not available, you could still count the number of rows before any row:
UPDATE aaa
SET rnk = w.rnk
FROM ( SELECT a0.id AS id
, COUNT(*) AS rnk
FROM aaa a0
JOIN aaa a1 ON a1.num <= a0.num
GROUP BY a0.id
) w
WHERE w.id = aaa.id;
SELECT * FROM aaa
ORDER BY id
;
Or the same with a scalar subquery:
UPDATE aaa a0
SET rnk =
( SELECT COUNT(*)
FROM aaa a1
WHERE a1.num <= a0.num
)
;