Get previous days' value in column in postgres - postgresql

I have a question.
I have a sql command that is getting the moving average for each day using window functions.
BEGIN;
DROP TABLE IF EXISTS vol_stats;
SELECT pk as fk,
avg(CAST(volume as FLOAT)) over (partition by account_id order by "endts") as average,
INTO vol_stats
from volume_temp
order by account_id, "endts";
COMMIT;
I would like to get one more value and that is the previous days' value.
The data structure looks like this.
acccount_id | value | timestamp
-------------------------------
a12 | 122 | jan 1
a13 | 133 | jan 1
a14 | 443 | jan 1
a12 | 251 | jan 2
a13 | 122 | jan 2
a14 | 331 | jan 2
a12 | 412 | jan 3
a13 | 323 | jan 3
a14 | 432 | jan 3
and we are computing this
acccount_id | value | timestamp | Average
-----------------------------------------
a12 | 122 | jan 1 | 122
a13 | 133 | jan 1 | 133
a14 | 443 | jan 1 | 443
a12 | 251 | jan 2 | 188.5
a13 | 122 | jan 2 | 222.5
a14 | 331 | jan 2 | 387
a12 | 412 | jan 3 | 261.6
a13 | 323 | jan 3 | 192.6
a14 | 432 | jan 3 | 402
What would be helpful would be to grab the previous days' value as well. Like this.
acccount_id | value | timestamp | Average | previous
-----------------------------------------
a12 | 122 | jan 1 | 122 | null
a13 | 133 | jan 1 | 133 | null
a14 | 443 | jan 1 | 443 | null
a12 | 251 | jan 2 | 188.5 | 122
a13 | 122 | jan 2 | 222.5 | 133
a14 | 331 | jan 2 | 387 | 443
a12 | 412 | jan 3 | 261.6 | 251
a13 | 323 | jan 3 | 192.6 | 122
a14 | 432 | jan 3 | 402 | 331

Just add another column to the SELECT list:
lag(volume) OVER (PARTITION BY account_id ORDER BY endts)

Related

ERROR: control reached end of trigger procedure without RETURN

I am learning how to make functions and triggers and I cannot get them to work. Here are all the relevant tables:
Table: "cust"
cust_id | email
------- | -----
1 | jack.daniels#google.com
2 | jose.quervo#yahoo.com
3 | jim.beam#protonmail.com
Table: "inv"
fm_id | inv_id
----- |--------
22 | 10
23 | 11
24 | 12
25 | 13
26 | 14
27 | 15
28 | 16
29 | 17
30 | 18
31 | 19
31 | 20
32 | 21
Table: "rent"
inv_id | cust_id | rent_dt
------ |-------- | --------
10 | 1 | 9/1/2022 10:29
11 | 1 | 9/2/2022 18:16
12 | 1 | 9/2/2022 18:17
13 | 1 | 9/17/2022 17:34
14 | 1 | 9/19/2022 6:32
15 | 1 | 9/19/2022 6:33
16 | 3 | 9/1/2022 18:45
17 | 3 | 9/1/2022 18:46
18 | 3 | 9/2/2022 18:45
19 | 3 | 9/2/2022 18:46
20 | 3 | 9/17/2022 18:32
21 | 3 | 9/19/2022 22:12
10 | 2 | 9/19/2022 11:43
11 | 2 | 9/19/2022 11:42
Table: "fm"
fm_id | titl
----- | -----
22 | Anaconda
23 | Exorcist
24 | Philadelphia
25 | Quest
26 | Sweden
27 | Speed
28 | Nemo
29 | Zoolander
30 | Truman
31 | Patient
32 | Racer
Table: "d_rpt"
cust_id | email | fm_id | inv_id | rent_dt
------- | ------------------------- | ----- | ------ | --------
1 | jack.daniels#google.com | 10 | 22 | 9/1/2022 10:29
1 | jack.daniels#google.com | 11 | 23 | 9/2/2022 18:16
1 | jack.daniels#google.com | 12 | 24 | 9/2/2022 18:17
1 | jack.daniels#google.com | 13 | 25 | 9/17/2022 17:34
1 | jack.daniels#google.com | 14 | 26 | 9/19/2022 6:32
1 | jack.daniels#google.com | 15 | 27 | 9/19/2022 6:33
2 | jose.quervo#yahoo.com | 10 | 22 | 9/19/2022 11:43
2 | jose.quervo#yahoo.com | 11 | 23 | 9/19/2022 11:42
3 | jim.beam#protonmail.com | 16 | 28 | 9/1/2022 18:45
3 | jim.beam#protonmail.com | 17 | 29 | 9/1/2022 18:46
3 | jim.beam#protonmail.com | 18 | 30 | 9/2/2022 18:45
3 | jim.beam#protonmail.com | 19 | 31 | 9/2/2022 18:46
3 | jim.beam#protonmail.com | 20 | 31 | 9/17/2022 18:32
3 | jim.beam#protonmail.com | 21 | 32 | 9/19/2022 22:12
Table: "sm_rpt"
email | rd_cnt
------------------------ | ------
jack.daniels#google.com | 6
jim.beam#protonmail.com | 6
When a insert statement is run on the 'd_rpt' table the trigger should fire and run the truncate/insert/select statement for the the 'sm_rpt' table. Here are the trigger function and trigger statement:
Trigger Function:
CREATE FUNCTION ins_trig_func()
RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
BEGIN
TRUNCATE FROM sm_rpt;
INSERT INTO sm_rpt
SELECT
cust.email,
count (DISTINCT inv.fm_id) AS "rd_cnt"
FROM cust
INNER JOIN rent ON rent.cust_id = cust.cust_id
INNER JOIN inv ON inv.inv_id = rent.inv_id
INNER JOIN fm ON fm.fm_id = inv.fm_id
WHERE rent.rent_dt BETWEEN '2005-08-01' AND '2005-08-31'
GROUP BY cust.email
ORDER BY "rd_cnt" DESC;
END;
$$
Trigger statement:
CREATE TRIGGER nw_wn
AFTER INSERT
ON d_rpt
FOR EACH STATEMENT
EXECUTE PROCEDURE ins_trig_func();
I get success when creating both the trigger function and trigger statement from that. However when I go to test it out by truncating and then inserting new data, on the 'd_rpt' table, I get an error for the trigger and that is what I cannot solve. Here is that error:
ERROR: control reached end of trigger procedure without RETURN
CONTEXT: PL/pgSQL function insert_trigger_function()
SQL state: 2F005
Any help you can offer would be greatly appreciated.
PS.
I did not include the truncate and insert commands that I use because I have already verified they work fine outside of trying to implement this trigger. The issue I think has to lie in the trigger function but the search feature and google are not my friend in trying to identify specifically what.
**
EDITS To the above are found below here
**
OK, I have solved it now with both of your help. Thank you so much Belayer
and Adrian Klaver !!!!
CREATE FUNCTION ins_trig_func()
RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
BEGIN
TRUNCATE FROM sm_rpt;
INSERT INTO sm_rpt
SELECT
cust.email,
count (DISTINCT inv.fm_id) AS "rd_cnt"
FROM cust
INNER JOIN rent ON rent.cust_id = cust.cust_id
INNER JOIN inv ON inv.inv_id = rent.inv_id
INNER JOIN fm ON fm.fm_id = inv.fm_id
WHERE rent.rent_dt BETWEEN '2005-08-01' AND '2005-08-31'
GROUP BY cust.email
RETURN NULL;
END;
$$

postgrest retreive ranked results

I made a game, with level and scores saved into an sql table like this :
create table if not exists api.scores (
id serial primary key,
pseudo varchar(50),
level int,
score int,
created_at timestamptz default CURRENT_TIMESTAMP
);
I want to display the scores in the ui with the rank of each score, based on the score column, ordered by desc.
Here is a sample data :
id | pseudo | level | score | created_at
----+----------+-------+-------+-------------------------------
1 | test | 1 | 1 | 2020-05-01 11:25:20.446402+02
2 | test | 1 | 1 | 2020-05-01 11:28:11.04001+02
3 | szef | 1 | 115 | 2020-05-01 15:45:06.201135+02
4 | erg | 1 | 115 | 2020-05-01 15:55:19.621372+02
5 | zef | 1 | 115 | 2020-05-01 16:14:09.718861+02
6 | aa | 1 | 115 | 2020-05-01 16:16:49.369718+02
7 | zesf | 1 | 115 | 2020-05-01 16:17:42.504354+02
8 | zesf | 2 | 236 | 2020-05-01 16:18:07.070728+02
9 | zef | 1 | 115 | 2020-05-01 16:22:23.406013+02
10 | zefzef | 1 | 115 | 2020-05-01 16:23:49.720094+02
Here is what I want :
id | pseudo | level | score | created_at | rank
----+----------+-------+-------+-------------------------------+------
31 | zef | 7 | 730 | 2020-05-01 18:40:42.586224+02 | 1
50 | Cyprien | 5 | 588 | 2020-05-02 14:08:39.034112+02 | 2
49 | cyprien | 4 | 438 | 2020-05-01 23:35:13.440595+02 | 3
51 | Cyprien | 3 | 374 | 2020-05-02 14:13:41.071752+02 | 4
47 | cyprien | 3 | 337 | 2020-05-01 23:27:53.025475+02 | 5
45 | balek | 3 | 337 | 2020-05-01 19:57:39.888233+02 | 5
46 | cyprien | 3 | 337 | 2020-05-01 23:25:56.047495+02 | 5
48 | cyprien | 3 | 337 | 2020-05-01 23:28:54.190989+02 | 5
54 | Cyzekfj | 2 | 245 | 2020-05-02 14:14:34.830314+02 | 9
8 | zesf | 2 | 236 | 2020-05-01 16:18:07.070728+02 | 10
13 | zef | 1 | 197 | 2020-05-01 16:28:59.95383+02 | 11
14 | azd | 1 | 155 | 2020-05-01 17:53:30.372793+02 | 12
38 | balek | 1 | 155 | 2020-05-01 19:08:57.622195+02 | 12
I want to retreive the rank based on the full table whatever the result set.
I'm using the postgrest webserver.
How do I do that ?
You are describing window function rank():
select t.*, rank() over(order by score desc) rnk
from mytable t
order by score desc

How to do sum of different values without duplicate

How to do a sum of different values but same ID without duplicate different values on a column?
My Input in SQL Command.
SELECT
students.id AS student_id,
students.name,
COUNT(*) AS enrolled,
c2.price AS course_price,
(COUNT(*) * price) AS paid
FROM students
LEFT JOIN enrolls e on students.id = e.student_id
LEFT JOIN courses c2 on e.course_id = c2.id
WHERE student_id NOTNULL
GROUP BY students.id, students.name, c2.price
ORDER BY student_id ASC;
My result.
student_id | name | enrolled | paid
------------+---------------------+----------+------
1001 | Gulbadan Bálint | 1 | 90
1002 | Hanna Adair | 5 | 450
1003 | Taddeo Bhattacharya | 1 | 90
1004 | Persis Havlíček | 1 | 75
1004 | Persis Havlíček | 5 | 450
1005 | Tory Bateson | 1 | 90
1007 | Dávid Fèvre | 1 | 90
1008 | Masuyo Stoddard | 1 | 90
1009 | Iiris Levitt | 1 | 75
1009 | Iiris Levitt | 2 | 180
1013 | Artair Kovač | 1 | 30
1013 | Artair Kovač | 1 | 90
1015 | Matilda Guinness | 2 | 180
1017 | Margarita Ek | 1 | 90
1018 | Misti Zima | 3 | 270
1019 | Conall Ventura | 1 | 90
1020 | Vivian Monday | 2 | 180
My expected result.
student_id | name | enrolled | paid
------------+---------------------+----------+------
1001 | Gulbadan Bálint | 1 | 90
1002 | Hanna Adair | 5 | 450
1003 | Taddeo Bhattacharya | 1 | 90
1004 | Persis Havlíček | 6 | 525
1005 | Tory Bateson | 1 | 90
1007 | Dávid Fèvre | 1 | 90
1008 | Masuyo Stoddard | 1 | 90
1009 | Iiris Levitt | 3 | 255
1013 | Artair Kovač | 2 | 120
1015 | Matilda Guinness | 2 | 180
1017 | Margarita Ek | 1 | 90
1018 | Misti Zima | 3 | 270
1019 | Conall Ventura | 1 | 90
1020 | Vivian Monday | 2 | 180
I think that the cause come from a GROUP BY command but it will throw an error if I do not write a GROUP BY price.
Perhaps you can use SUM() function.
Please see link below, maybe it's same case with you:
how to group by and return sum row in Postgres
You have excluded course_price column both in your current and expected result. It seems you had wrongly included that in group by.
SELECT
students.id AS student_id,
students.name,
COUNT(*) AS enrolled,
--c2.price AS course_price, --exclude this in o/p?
(COUNT(*) * price) AS paid
FROM students
LEFT JOIN enrolls e on students.id = e.student_id
LEFT JOIN courses c2 on e.course_id = c2.id
WHERE student_id NOTNULL
GROUP BY students.id, students.name --,c2.price --and remove it from here
ORDER BY student_id ASC;

If a users record on column x is not null how do I count how many records that user has after the first time it is not null?

I would like to create a count per user of number of records after the first time that x is not null for that user.
I have a table that is similar to the following:
id | user_id | completed_at | x
----+---------+--------------+---
1 | 1001 | 2017-06-01 | 1
20 | 1001 | 2017-06-01 | 2
21 | 1001 | 2017-06-02 | 4
22 | 1001 | 2017-06-03 |
24 | 1001 | 2017-06-03 |
25 | 1001 | 2017-06-04 |
23 | 1001 | 2017-06-04 |
12 | 1001 | 2017-06-06 |
13 | 1001 | 2017-06-07 |
14 | 1001 | 2017-06-08 |
2 | 1002 | 2017-06-02 | 3
27 | 1002 | 2017-06-02 | 7
15 | 1002 | 2017-06-09 |
3 | 1003 | 2017-06-03 |
4 | 1004 | 2017-06-04 |
5 | 1005 | 2017-06-05 |
33 | 1005 | 2017-06-20 | 8
34 | 1006 | 2017-07-10 | 9
6 | 1006 | 2017-10-06 |
7 | 1007 | 2017-10-07 |
8 | 1008 | 2017-10-08 |
9 | 1009 | 2017-10-09 |
10 | 1010 | 2017-10-10 |
16 | 1011 | 2017-06-01 |
11 | 1011 | 2017-07-01 | 5
17 | 1012 | 2017-06-02 |
26 | 1012 | 2017-07-02 | 6
18 | 1013 | 2017-06-03 |
19 | 1014 | 2017-06-04 |
31 | 1014 | 2017-06-24 |
32 | 1014 | 2017-06-24 |
30 | 1014 | 2017-06-24 |
29 | 1014 | 2017-06-24 |
28 | 1014 | 2017-06-24 |
The expected output would look like this:
+------+------------+---------------+
| user | first_x | records_after |
+------+------------+---------------+
| 1001 | 2017-06-01 | 9 |
| 1002 | 2017-06-02 | 2 |
| 1005 | 2017-06-20 | 0 |
| 1011 | 2017-07-01 | 0 |
| 1012 | 2017-07-02 | 0 |
+------+------------+---------------+
Using running count, and then conditional count for running count > 0
Sample
WITH flags AS (
SELECT
user_id,
completed_at,
sum(CASE WHEN x IS NULL THEN 0 ELSE 1 END) OVER (PARTITION BY user_id ORDER BY completed_at ROWS BETWEEN UNBOUNDED PRECEDING AND 0 FOLLOWING) AS flag
FROM users
),
completed AS (
SELECT DISTINCT ON (user_id)
user_id,
completed_at AS first_x
FROM flags
WHERE flag > 0
ORDER BY user_id, completed_at
)
SELECT DISTINCT
user_id AS user,
first_x,
count(flag) FILTER (WHERE flag>0) - 1 AS records_after
FROM flags
NATURAL JOIN completed
GROUP BY 1, 2
ORDER BY 1

Select rows by one column value should only be repeat N times

My table is:
id sub_id datetime resource
---|-----|------------|-------
1 | 10 | 04/03/2009 | 399
2 | 11 | 04/03/2009 | 244
3 | 10 | 04/03/2009 | 555
4 | 10 | 03/03/2009 | 300
5 | 11 | 03/03/2009 | 200
6 | 11 | 03/03/2009 | 500
7 | 11 | 24/12/2008 | 600
8 | 13 | 01/01/2009 | 750
9 | 10 | 01/01/2009 | 760
10 | 13 | 01/01/2009 | 570
11 | 11 | 01/01/2009 | 870
12 | 13 | 01/01/2009 | 670
13 | 13 | 01/01/2009 | 703
14 | 13 | 01/01/2009 | 705
I need to select for each sub_id only 2 times
Result would be:
id sub_id datetime resource
---|-----|------------|-------
1 | 10 | 04/03/2009 | 399
3 | 10 | 04/03/2009 | 555
5 | 11 | 03/03/2009 | 200
6 | 11 | 03/03/2009 | 500
8 | 13 | 01/01/2009 | 750
10 | 13 | 01/01/2009 | 570
How can I achieve this result in postgres ?
Use the window function row_number():
select id, sub_id, datetime, resource
from (
select *, row_number() over (partition by sub_id order by id)
from my_table
) s
where row_number < 3;
look at the order column (I use id to match your sample):
t=# with data as (select *,count(1) over (partition by sub_id order by id) from t)
select id,sub_id,datetime,resource from data where count <3;
id | sub_id | datetime | resource
----+--------+------------+----------
1 | 10 | 2009-03-04 | 399
3 | 10 | 2009-03-04 | 555
2 | 11 | 2009-03-04 | 244
5 | 11 | 2009-03-03 | 200
8 | 13 | 2009-01-01 | 750
10 | 13 | 2009-01-01 | 570
(6 rows)