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

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}]

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

How to get flat aggregation of two calls to json_agg

I have the following tables:
products
id | name
----+-------------
1 | Shampoo
2 | Conditioner
productOptions
id | name | productId
----+-------------+-----------
1 | Hair Growth | 1
2 | Frizzy Hair | 1
images
id | fileName | productOptionId
----+-----------+-----------------
1 | bee.png | 1
2 | fancy.png | 2
3 | soap.png | 2
products have many productOptions, and productOptions have many images.
Following from this question, I have aggregated images.fileName twice to get an aggregated list of the fileNames for each product:
SELECT p.name, o.options, o.images
FROM products p
LEFT JOIN (
SELECT "productId", array_agg(name) AS options, json_agg(i.images) AS images
FROM "productOptions" o
LEFT JOIN (
SELECT "productOptionId", json_agg(i."fileName") AS images
FROM images i
GROUP BY 1
) i ON i."productOptionId" = o.id
GROUP BY 1
) o ON o."productId" = p.id;
name | options | images
-------------+-------------------------------+------------------------------------------
Shampoo | {"Hair Growth","Frizzy Hair"} | [["bee.png"], ["fancy.png", "soap.png"]]
Conditioner | |
I am wondering how to flatten the second json_agg so that the list of images is flat, and if my overall approach makes sense.
I didn't have to json_agg inside the inner-most JOIN, instead I can call array_agg(i.images) at the same point array_agg(name) AS options is called, to get a flat list of images:
SELECT p.name, o.options, o.images
FROM products p
LEFT JOIN (
SELECT "productId", array_agg(DISTINCT name) AS options, array_agg(i.images) AS images
FROM options o
LEFT JOIN (
SELECT "optionId", i."fileName" AS images
FROM images i
) i ON i."optionId" = o.id
GROUP BY 1
) o ON o."productId" = p.id;
name | options | images
-------------+-------------------------------+------------------------------
Shampoo | {"Frizzy Hair","Hair Growth"} | {bee.png,fancy.png,soap.png}
Conditioner |
A different approach:
I used DISTINCT in function json_agg() (you can use array_agg() instead), so as not to repeat the name of the product Options.
SELECT
p.name,
json_agg(DISTINCT po.name) AS options,
json_agg(i."fileName") AS images
FROM products p
LEFT JOIN "productOptions" po ON p.id = po."productId"
LEFT JOIN images AS i ON po.id = i."productOptionId"
GROUP BY p.name;
Or using subqueries:
SELECT
p.name,
po.options,
poi.images
FROM products p
LEFT JOIN (SELECT productId, json_agg(name) AS options
FROM productOptions
GROUP BY productId) AS po ON p.id = po.productId
LEFT JOIN (SELECT
productId,
json_agg(fileName) AS images
FROM productOptions po
INNER JOIN images i ON i.productOptionId = po.id
GROUP BY productId) AS poi ON p.id = poi.productId;

Select rows of multiple tables via joins

I have some tables which are related to each others.
A short demonstration:
Sites:
id | clip_id | article_id | unit_id
--------------+------------+--------
1 | 123 | 12 | 7
Clips:
id | title | desc |
------------+--------
1 | foo2 | abc1
Articles:
id | title | desc | slug
------------+---------------------
1 | foo2 | abc1 | article.html
Units:
id | vertical_id | title |
------------------+-------+
1 | 123 | abc |
Verticals:
id | name |
-----------+
1 | vfoo |
Now I want to do something like below:
SELECT ALL VERTICAL, UNIT, SITE, CLIP, ARTICLE attributes
from VERTICAL, UNIT, SITE, CLIP, ARTICLE TABLES
WHERE vertical_id = 2
Can some one help me how can I use joins for this?
Here is a running example of possibly what you want: http://sqlfiddle.com/#!15/af63b/2
select * from
sites
inner join units on sites.unit_id=units.id
inner join clips on clips.id=sites.clip_id
inner join articles on articles.id=sites.article_id
inner join verticals on verticals.id=units.vertical_id
where units.vertical_id=123
The problem is, that the description you gave us did not clearly specify which columns to join:
(answered) Why does units have a link to site via site_id and sites a link back to units via unit_id?
(answered) Why does units have a link to verticals via vertical_id and verticals a link back to units via unit_id?
I am guessing that your data does not giva a consistent example to get rows using the join. For vertical_id=123 there is no corresponding entry in verticals.
Edit:
I corrected the SQL due to corrections within the question. With this the two questions are answered.
select s.id, s.clip_id, s.article_id, u.title, u.vertical_id, c.title, v.unit_id, c.desc, a.slug
from sites s
join units u on s.id = u.id
join clips c on u.id = c.id
join verticals v on c.id = v.id
join articles a on v.id = a.id
where v.vertical_id = 'any id'

Selecting a row if it has all related options in a related table

Given a table definition:
Articles:
art_id | name
-------|--------------
1 | article1
2 | article2
3 | article3
Tags:
tag_id | description
-------|--------------
1 | Scientific
2 | Long
3 | Short
article_tags:
art_id | tag_id
-------|---------
1 | 1
1 | 2
2 | 1
2 | 3
3 | 1
3 | 2
3 | 3
The question is How to select all articles that are BOTH Scientific and Short?
Please note, it should be general for [2.N) tag combinations...
You can use the following query to get the result:
select a.art_id, a.name
from articles a
inner join article_tags at
on a.art_id = at.art_id
inner join tags t
on at.tag_id = t.tag_id
where t.description in ('Short', 'Scientific') -- tags here
group by a.art_id, a.name
having count(distinct t.tag_id) = 2 -- total count of tags here
See SQL Fiddle with Demo
Or this could be written:
select a.art_id, a.name
from articles a
inner join article_tags at
on a.art_id = at.art_id
inner join tags t
on at.tag_id = t.tag_id
group by a.art_id, a.name
having
sum(case when t.description = 'Short' then 1 else 0 end) >= 1 and
sum(case when t.description = 'Scientific' then 1 else 0 end) >= ;
See SQL Fiddle with Demo.
If you just want to return the article id, then you could just query the article_tag table:
select art_id
from article_tags
where tag_id in (1, 3)
group by art_id
having count(distinct tag_id) = 2
See SQL Fiddle with Demo
SELECT *
FROM articles
WHERE art_id IN
(
SELECT art_id
FROM article_tags
GROUP BY art_id
HAVING COUNT(art_id) > 1
)

sql GROUP BY but use the most recent record?

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