PostgreSQL - JOIN on string_agg - postgresql

I have three tables.
Students
student_id | name
1 Rhon
Subjects
subject_id | subject_name | student_id
1 Physics 1
2 Math 1
Grades
grade_id | student_id | subject_id | grade
1 1 1 90
2 1 2 89
3 1 2 88
I would like to result to be like this:
student_id | student_name | subject_name | grades
1 Rhon Physics 90
1 Rhon Math 88,89
My current query is:
SELECT students.student_id, subjects.subject_id, string_agg(grades.grade, ',')
FROM students
JOIN subjects ON students.student_id = subjects.student_id
JOIN grades ON subjects.subject_id = grades.subject_id;
Is there something wrong with my query? Am I missing something? The error says that student_id needs to be in a GROUP BY clause but I don't want that.

You can do this with a sub-query:
SELECT s.student_id, s.student_name, j.subject_name, g.grades
FROM students s
JOIN subjects j
JOIN (
SELECT student_id, subject_id, string_agg(grade, ',') AS grades
FROM grades
GROUP BY student_id, subject_id) g USING (student_id, subject_id);
Why exactly do you not want to GROUP BY student_id?

Related

Typeorm order by after distinct on with postgresql

I have a table below:
id
product_id
priceĀ 
1
1
100
2
1
150
3
2
120
4
2
190
5
3
100
6
3
80
I want to select cheapest price for product and sort them by price
Expected output:
id
product_id
price
6
3
80
1
1
100
3
2
120
What I try so far:
`
repository.createQueryBuilder('products')
.orderBy('products.id')
.distinctOn(['products.id'])
.addOrderBy('price')
`
This query returns, cheapest products but not sort them. So, addOrderBy doesn't effect to products. Is there a way to sort products after distinctOn ?
SELECT id,
product_id,
price
FROM (SELECT id,
product_id,
price,
Dense_rank()
OVER (
partition BY product_id
ORDER BY price ASC) dr
FROM product) inline_view
WHERE dr = 1
ORDER BY price ASC;
Setup:
postgres=# create table product(id int, product_id int, price int);
CREATE TABLE
postgres=# insert into product values (1,1,100),(2,1,150),(3,2,120),(4,2,190),(5,3,100),(6,3,80);
INSERT 0 6
Output
id | product_id | price
----+------------+-------
6 | 3 | 80
1 | 1 | 100
3 | 2 | 120
(3 rows)

Difference of top two values while GROUP BY

Suppose I have the following SQL Table:
id | score
------------
1 | 4433
1 | 678
1 | 1230
1 | 414
5 | 8899
5 | 123
6 | 2345
6 | 567
6 | 2323
Now I wanted to do a GROUP BY id operation wherein the score column would be modified as follows: take the absolute difference between the top two highest scores for each id.
For example, the response for the above query should be:
id | score
------------
1 | 3203
5 | 8776
6 | 22
How can I perform this query in PostgreSQL?
Using ROW_NUMBER along with pivoting logic we can try:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY score DESC) rn
FROM yourTable
)
SELECT id,
ABS(MAX(score) FILTER (WHERE rn = 1) -
MAX(score) FILTER (WHERE rn = 2)) AS score
FROM cte
GROUP BY id;
Demo

do I need a new field for counting scores form another table?

I have a Users Table
it has id, username, A, and B as my columns.
I also have a Votes Table.
a user can vote on A, and B.
it has id, user_id foreign key, A_id foreign key, and B_id foreign key.
I would like to do a query to Users and have the tally of votes for A and votes for B included.
so lets say I find a User with the id of 1,
How would I get something like
{
"id": 1,
"display_name": "test",
"A" : 32,
"B" : 132
}
Assuming there are 32 rows in the Votes table and 132 rows for B in the Votes table.
simplified table:
t=# select* from users;
i | t
---+---
1 | A
1 | B
1 | B
2 | A
2 | A
2 | B
(6 rows)
query:
t=# select distinct
i
, sum(case when t='A' then count(1) filter (where t='A') else 0 end) over (partition by i) a
, sum(case when t='B' then count(1) filter (where t='B') else 0 end ) over (partition by i)b
from users
group by i,t
order by i;
i | a | b
---+---+---
1 | 1 | 2
2 | 2 | 1
(2 rows)

Count the number of consecutive entries fulfilling a condition within a GROUP BY

I've got a list of users who are behind on their bills, and I want to generate an entry for each of them that says how many consecutive bills they've been behind on. So here's the table:
user | bill_date | outstanding_balance
---------------------------------------
a | 2017-03-01 | 90
a | 2016-12-01 | 60
a | 2016-09-01 | 30
b | 2017-03-01 | 50
b | 2016-12-01 | 0
b | 2016-09-01 | 40
c | 2017-03-01 | 0
c | 2016-12-01 | 0
c | 2016-09-01 | 1
And I want a query that would generate the following table:
user | consecutive_billing_periods_behind
-----------------------------------------
a | 3
b | 1
a | 0
In other words, if you've paid up at any point, I want to ignore all of the earlier entries, and only count how many billing periods you've been behind since you've been last paid up. How do I do this most simply?
If I understood the question correctly, first you need to find the last date that any given customer paid their bill so the last date their outstanding balance was 0. You can do this by this subquery:
(SELECT
user1,
bill_date AS no_outstanding_bill_date
FROM table1
WHERE outstanding_balance = 0)
Then you need get the last bill date and create field for each row if they are outstanding bill. Then filter the rows between the last clear day to last bill date of each customer by this where clause:
WHERE bill_date >= last_clear_day AND bill_date <= last_bill_date
Then if you put the pieces together you can have the results by this query:
SELECT
DISTINCT
user1,
sum(is_outstanding_bill)
OVER (
PARTITION BY user1 ) AS consecutive_billing_periods_behind
FROM (
SELECT
user1,
last_value(bill_date)
OVER (
PARTITION BY user1
ORDER BY bill_date
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS last_bill_date,
CASE WHEN outstanding_balance > 0
THEN 1
ELSE 0 END AS is_outstanding_bill,
bill_date,
outstanding_balance,
nvl(max(t2.no_outstanding_bill_date)
OVER (
PARTITION BY user1 ), min(bill_date)
OVER (
PARTITION BY user1 )) AS last_clear_day
FROM table1 t1
LEFT JOIN (SELECT
user1,
bill_date AS no_outstanding_bill_date
FROM table1
WHERE outstanding_balance = 0) t2 USING (user1)
) table2
WHERE bill_date >= last_clear_day AND bill_date <= last_bill_date
Since we are using distinct you will not need the group by clause.
select
user,
count(case when min_balance > 0 then 1 end)
as consecutive_billing_periods_behind
from
(
select
user,
min(outstanding_balance)
over (partition by user order by bill_date) as min_balance
from tbl
)
group by user
Or:
select
user,
count(*)
as consecutive_billing_periods_behind
from
(
select
user,
bill_date,
max(case when outstanding_balance = 0 then bill_date) over
(partition by user)
as max_bill_date_with_zero_balance
from tbl
)
where
-- If user has no outstanding_balance = 0, then
max_bill_date_with_zero_balance is null
-- Count all rows in this case.
-- Otherwise
or
-- count rows with
bill_date > max_bill_date_with_zero_balance
group by user

One table with two different tasks

I got a test from my lecturer, I have to make one table with 3 columns inside: prodName, Qty, and totSalesToDate. Column Qty shows how many products have been sold in the input date, and totSalesToDate indicates products have been sold during the beginning of a month until the input date. Here is the example result table:
prodName | Qty | totSalesToDate
Car | 2 | 10
Bicycle | 8 | 22
Truck | 1 | 7
Motor-cycle | 3 | 12
I have to make this table using stored procedure (TSQL) with no subqueries. So far, the queries I made is:
create procedure SalesReport #date varchar(10)
as
select p.prodName, sum(s.Qty) as Qty
from PeriodTime pt full join Sales s on pt.Time = s.Time full join Product p on s.prodID = p.prodID
where #date = pt.Date
group by p.prodName
union
select p.prodName, sum(s.Qty) as totSalesToDate
from PeriodTime pt full join Sales s on pt.Time = s.Time full join Product p on s.prodID = p.prodID
where pt.Date between '2010060' and #date and p.prodName is not null
group by p.prodName
go
But the result I get is like this:
prodName | Qty
Car | 2
Car | 10
Bicycle | 8
Bicycle | 22
Truck | 1
Truck | 7
Motor-cycle | 3
Motor-cycle | 12
Anybody can help? I've been googling around but still cannot find the answer. Thanks.
How about
create procedure SalesReport #date varchar(10)
as
select p.prodName,
SUM(CASE WHEN #date = pt.Date THEN s.Qty ELSE 0 END) as Qty,
SUM(CASE WHEN pt.Date between '2010060' and #date THEN s.Qty ELSE 0.0 END) AS totSalesToDate
from PeriodTime pt full join Sales s on pt.Time = s.Time full join Product p on s.prodID = p.prodID
group by p.prodName
go