sql GROUP BY but use the most recent record? - group-by

I am trying to do something like this, and I'm not sure if it's possible using a simple GROUP BY:
SELECT GD.GradebookDetailId, G.SubjectCode, G.Description, G.UnitsAcademic, G.UnitsNonAcademic,
GD.Grade, GD.Remarks, G.FacultyName, STR_TO_DATE(G.DateApproved, '%m/%d/%Y %h:%i:%s') AS 'DateAproved'
FROM gradebookdetail GD
INNER JOIN gradebook G ON GD.GradebookId=G.GradebookId
WHERE G.DateApproved IS NOT NULL AND G.GradebookType='final' AND StudentIdNumber='2012-12345'
GROUP BY ???????
ORDER BY G.SubjectCode ASC
So when I have a table like this:
Student: 2012-12345
SubjectCode | Grade | DateApproved
SUBJ123 | 2.00 | 1/4/2012
SUBJ123 | 1.75 | 1/5/2012
SUBJ987 | 1.50 | 1/5/2012
It will should look like this:
Student: 2012-12345
SubjectCode | Grade | DateApproved
SUBJ123 | 1.75 | 1/5/2012
SUBJ987 | 1.50 | 1/5/2012
EDIT: Here's the solution:
SELECT
GD.GradebookDetailId,
G.SubjectCode,
G.Description,
G.UnitsAcademic,
G.UnitsNonAcademic,
GD.Grade,
GD.Remarks,
G.FacultyName,
STR_TO_DATE(G.DateApproved, '%m/%d/%Y %h:%i:%s') AS 'DateAproved'
FROM
gradebookdetail GD INNER JOIN
gradebook G ON GD.GradebookId=G.GradebookId
WHERE
G.DateApproved IS NOT NULL AND
G.GradebookType='final' AND
StudentIdNumber='2011-10172' AND
NOT EXISTS (
SELECT 1 FROM gradebook G2 INNER JOIN gradebookdetail GD2 ON G2.GradebookId=GD2.GradebookId
WHERE
G2.GradebookType = 'final' AND
G2.DateApproved IS NOT NULL AND
GD2.StudentIdNumber = GD.StudentIdNumber AND
G2.SubjectCode = G.SubjectCode AND
G2.DateApproved > G.DateApproved
)
ORDER BY G.SubjectCode ASC

I think this query will do what you want, but I haven't tested it. So let me know if something is wrong with it.
SELECT
GD.GradebookDetailId,
G.SubjectCode,
G.Description,
G.UnitsAcademic,
G.UnitsNonAcademic,
GD.Grade,
GD.Remarks,
G.FacultyName,
STR_TO_DATE(G.DateApproved, '%m/%d/%Y %h:%i:%s') AS 'DateAproved'
FROM
gradebookdetail GD INNER JOIN
gradebook G ON GD.GradebookId=G.GradebookId
WHERE
G.DateApproved IS NOT NULL AND
G.GradebookType='final' AND
StudentIdNumber='2012-12345' AND
NOT EXISTS (
SELECT 1 FROM gradebook G2
WHERE
G2.GradebookType = 'final' AND
G2.DateApproved IS NOT NULL AND
G2.StudentIdNumber = G.StudentIdNumber AND
G2.StudentCode = G.SubjectCode AND
G2.DateApproved > G.DateApproved
)
ORDER BY G.SubjectCode ASC

Related

Inner join and update the table in one execution DB2

I have two tables, where I would like to update table_2 if the column's value is same and then applying inner join with table1. I would like to do in one execution.
Here I habe table1 and table2, where item_2 of table2 has same value with status = 0. Here I would like to update one of the status with 9.
table1
#|ID| ITEM_1 |Application
-+--+----------+------
1|1| item1 | read
2|2| item1 | write
3|3| item1 | learn
table2
#|ID| ITEM_2 |Description |STATUS
-+--+---------+---------------------
1|10| item1 | des1 | 0
2|11| item1 | des2 | 0
3|12| item1 | des3 | 2
For updating table2, I used lag() function and then inner join with table1.
But here I need to execute two times, first for update and then second for inner join. But I am looking to execute in one time.
update
UPDATE
(
SELECT
T2.*
, lag(ITEM_2, 1, 0) over (order by ITEM_2 ASC) as C2
FROM TABLE_2 T2 where T2.STATUS = 0
)
SET STATUS = 9
WHERE C2 = ITEM_2;
#|ID| ITEM_2 |Description |STATUS
-+--+---------+---------------------
1|10| item1 | des1 | 0
2|11| item1 | des2 | 9
3|12| item1 | des3 | 2
inner join
select T1.ID, T1.ITEM_1, T1.Appliction, T2.ID, T2.ITEM_2, T2.Description, T2.STATUS
from TABLE_1 T1
INNER JOIN TABLE_2 T2 ON T1.ITEM_1 = T2.ITEM_2
where T2.STATUS = 0
ID | ITEM_1 | APPLICTION | ID | ITEM_2 | DESCRIPTION | STATUS
1 | item1 | read | 10 | item1 | des1 | 0
WITH U AS
(SELECT COUNT (1) AS DUMMY FROM NEW TABLE
(UPDATE TABLE_2 A SET STATUS = 9 WHERE EXISTS
(SELECT 1 FROM TABLE_2 B WHERE A.ITEM_2 = B.ITEM_2 AND A.ID > B.ID AND B.STATUS = 0
)))
select T1.ID, T1.ITEM_1, T1.Appliction, T2.ID, T2.ITEM_2, T2.Description, T2.STATUS
from TABLE_1 T1
Inner join TABLE_2 T2 ON T1.ITEM_1 = T2.ITEM_2
where T2.STATUS = 0`
fiddle

postgres one to many flattened return

I am trying to write a postgres query that uses 3 tables: people, attribute, and a people_attribute join
people table:
id, name
attribute table:
id, name, attr_group
people_attribute join:
people_id, attribute_id
desired output:
name | fav_colors | fav_music | fav_foods
-----------------------------------------------------------------
michael | red,blue,green | pop,hip-hop,jazz | pizza,burgers,tacos
bob | orange,green | null | tacos,steak,fish
...etc
The tags can vary from none to ~12 for each attr_group
Here is the query I am working with:
select
p.id,
p.name,
(case when a.attr_group like 'fav_colors' then string_agg(a.name, ',') else null end) as fav_colors,
(case when a.attr_group like 'fav_music' then string_agg(a.name, ',') else null end) as fav_music,
(case when a.attr_group like 'fav_foods' then string_agg(a.name, ',') else null end) as fav_foods,
from people as p
join people_attribute as pa on pa.people_id = p.id
join "attribute" as a on a.id = pa.attribute_id
group by 1,2,a.attr_group
order by 1 asc;
which returns:
name | fav_colors | fav_music | fav_foods
-----------------------------------------------------------------
michael | red,blue,green | null | null
michael | null | pop,hip-hop,jazz | null
michael | null | null | pizza,burgers,tacos
bob | null | null | null
bob | orange,green | null | null
bob | null | null | tacos,steak,fish
I feel like I'm getting close, but am unsure how to flatten this out to achieve the desired output as shown above. Any help would be greatly appreciated!
You want to use filter for this:
select p.id,
p.name,
string_agg(a.name, ',') filter (where a.attr_group = 'fav_color') as fav_colors,
string_agg(a.name, ',') filter (where a.attr_group = 'fav_music') as fav_music,
string_agg(a.name, ',') filter (where a.attr_group = 'fav_foods') as fav_foods,
from people as p
join people_attribute as pa
on pa.people_id = p.id
join "attribute" as a
on a.id = pa.attribute_id
group by p.id, p.name
order by 1 asc;
Using filter passes only values that match the filter where condition into the aggregation.
The reason yours was showing three rows per people record is because you added attribute.attr_group to your group by. You had no choice since you were using attribute.attr_group in your case conditionals.
Using filter makes attribute.attr_group part of the aggregation, so you do not have to include it in your group by list.

Postgresql select based on another select with order, limit and agregate

I have a tables like:
books:
id | title | rating_avr | visible
1 | 'Overlord' | 5 | true
2 | 'Avengers' | 10 | false
tags_books:
tag_id | book_id | likes
1 | 1 | 5
2 | 1 | 25
1 | 2 | 11
tags:
id | name
1 | 'Adventure'
2 | 'Drama'
Now i need to load books that have tag 'Drama' with LIMIT, ORDER and agregate tags for each book.
I managed to achive this using query:
SELECT b.id, b.title, b.rating_avr, json_agg(json_build_object('id', tb2.tag_id)) as tags
FROM books b
LEFT JOIN tags_books tb ON tb.book_id = b.id
LEFT JOIN tags_books tb2 ON tb2.book_id = b.id
WHERE tb.tag_id = 1 AND b.visible=true
GROUP BY b.id ORDER BY b.rating_avr DESC LIMIT 5
What i'm curious about:
1) Is it ok to join same table 2 times? First is for where clause and second to agregate tags.
2) How can i order agregated tags based on likes?
3) Is it a right approach, or maybe there is better way to do it?
It is strange that in your query, you don't use the table tags, although you want to fetch books with tag 'Drama' which is a column in the table tags.
What I would do is first get the ids of all the books with tag 'Drama' with a query like this:
SELECT b.id FROM books b
INNER JOIN tags_books tb ON tb.book_id = b.id
INNER JOIN tags t ON t.id = tb.tag_id
WHERE t.name = 'Drama' AND b.visible=true
and then use it to get the result:
SELECT
b.id, b.title, b.rating_avr,
json_agg(json_build_object('id', tb.tag_id) order by tb.likes desc) as tags
FROM books b INNER JOIN tags_books tb
ON tb.book_id = b.id
WHERE b.id IN (
SELECT b.id FROM books b
INNER JOIN tags_books tb ON tb.book_id = b.id
INNER JOIN tags t ON t.id = tb.tag_id
WHERE t.name = 'Drama' AND b.visible=true
)
GROUP BY b.id, b.title, b.rating_avr
ORDER BY b.rating_avr DESC LIMIT 5
See the demo.
Results:
> id | title | rating_avr | tags
> -: | :------- | ---------: | :-----------------------
> 1 | Overlord | 5 | [{"id" : 2}, {"id" : 1}]

How to data show Data Row Column in Postgresql?

Here is my data and query. I want row data show in columns... Can someone help me to modify the query? I am using PostgreSQL queries.
select
ss.name, ip.product_name, ssr.quantity
from
services_servicerecipe ssr
inner join
services_service ss on ssr.service_id = ss.id
inner join
inventory_product ip on ssr.product_id = ip.id
order by
ss.name
Output:
Service_name | Product_name | Quantity
-------------+------------------+-----------
Balayage | 7.3-revlon | 2
Balayage | 701-revlon | 1
I want it to look like this
Service_name | Product_name | Quantity | Product_name | Quantity
-------------+-------------------+--------------+------------------+----------
Balayage | 7.3-revlon | 2 | 701-revlon | 1
Here is a pivot option, using ROW_NUMBER:
with cte as (
select ss.name, ip.product_name, ssr.quantity,
row_number() over (partition by ss.name order by ip.product_name) rn
from services_servicerecipe ssr
inner join services_service ss on ssr.service_id = ss.id
inner join inventory_product ip on ssr.product_id = ip.id
)
select
name,
max(case when rn = 1 then product_name end) as product1,
max(case when rn = 1 then quantity end) as quantity1,
max(case when rn = 2 then product_name end) as product2,
max(case when rn = 2 then quantity end) as quantity2
from cte
group by name;

EAV data in SQL Server

I have no control over the data or the database structure. I have this EAV type of data where a consultant can speak one or many languages and he can travel to 1 or many countries in Europe and he has many skills indeed.
FYI there are 10 different main categories in my data.
Some consultants speak 10 languages while other speak only one.
The data looks a bit like this
____________________________________________
| ConsultantID | Category | Value |
--------------------------------------------
| 1 | Language | English |
| 1 | Language | French (fluent) |
| 1 | Language | Spanish (working)|
| 1 | Country | Ireland |
| 1 | Country | Italy |
| 1 | Country | Germany |
| 1 | Country | Belgium |
| 456 | Language | French (working) |
| 456 | Country | Belgium |
| 847 | Language | English |
| 847 | Country | Belgium |
--------------------------------------------
I want to list all consultants willing to travel to Belgium and who speak French (working or fluent). Based on my current example that would be #1 and #456
I wrote the query below which list all values matching a category for a consultant (note this is not dynamic as the number of value in my example is set to 5 max - so already a poor design).
SELECT
ID, category,
MAX(CASE seq WHEN 1 THEN value ELSE '' END ) +
MAX(CASE seq WHEN 2 THEN ',' + value ELSE '' END ) +
MAX(CASE seq WHEN 3 THEN ',' + value ELSE '' END ) +
MAX(CASE seq WHEN 4 THEN ',' + value ELSE '' END ) +
MAX(CASE seq WHEN 5 THEN ',' + value ELSE '' END )
FROM
(SELECT
p1.ID, p1.category, p1.value,
(SELECT COUNT(*)
FROM tblWebPracticeInfo p2
WHERE p2.category = p1.category
AND p2.ID = P1.ID
AND p2.value <= p1.value)
FROM
tblWebPracticeInfo p1) D (ID, category, value, seq )
GROUP BY
ID, category
ORDER BY
ID;
I would then need to query this table...
But without even a where clause it takes already 2 seconds to execute
I have something else more basic (but similarly not efficient)
select *
from tblWebMemberInfo m
where
m.ID in (select p.id from tblWebPracticeInfo p
where p.category = 'Language' and p.value like 'French%')
and m.ID in (select p.id from tblWebPracticeInfo p
where p.category = 'Country' and p.value = 'Belgium')
order by m.ID
That's basically where I am. As you can see nothing genius and nothing which is really working.
Can you point me to the right track.
I'm using SQL Server 2005 - v9.00.1
Many thanks in advance for your time & help
If you just need to list the consultants then you can use exists():
select p.Id ...
from Person p /* Assuming you have a regular table for people,
if not, use distinct or group by */
where exists (
select 1
from tblWebPracticeInfo l
where l.Id = p.Id
and l.Category = 'Language'
and l.Value = 'French'
)
and exists (
select 1
from tblWebPracticeInfo c
where c.Id = p.Id
and c.Category = 'Country'
and c.Value = 'Belgium'
)
You could also use aggregation and having like so:
select ConsultantID
from tblWebMemberInfo m
where (p.category = 'Language' and p.value like 'French%')
or (p.category = 'Country' and p.value = 'Belgium')
group by ConsultantID
having count(*) = 2 /* number of conditions to match is 2 */