Order by and limit on a multiple left join - PostgresSQL and python - sql

I have the following relations:
A Product have multiple Images
A Product can have multiple Categories
A Category can have multiple Products
I want to get:
only the 'short_name' from the first category
only the first image url order_by another parameter
I have the following SQL, in PostgreSql:
SELECT DISTINCT ON(I.product_id) P.id, P.name, P.short_description,
CAT.short_name AS category, I.url
FROM products_product AS P
LEFT JOIN products_product_categories AS RPC ON P.id = RPC.product_id
LEFT JOIN categories_category AS CAT ON RPC.category_id = CAT.id
LEFT JOIN products_productimage AS I ON I.product_id = P.id
WHERE (P.is_active = TRUE)
My issue is that I don't know to limit left join and order by, I try to add LIMIT 1
LEFT JOIN categories_category AS CAT ON RPC.category_id = CAT.id LIMIT 1
but it is not working, I receive a code error 'syntax error at or near "LEFT"'
Category table
id | category_name | category_short_name
1 catA A
2 catB B
3 catC C
Product table
id | product_name | product_desc
1 P1 lorem1
2 P2 lorem2
3 P3 lorem3
ManytoMany: product_category
id product_id category_id
1 1 1
2 2 1
3 1 2
4 3 3
5 3 3
Image table
id url product_id order
1 lo1 1 4
2 lo2 1 0
3 lo3 1 1
4 lo4 2 0
For Product with id1 I expect to get:
name: P1, desc 'lorem1', category short_name : cat A, image url lo2

DISTINCT ON makes no sense without ORDER BY. As you want two different orders (on i.order for images and on cat.id for categories), you must do this in separate subqueries.
select p.id, p.name, p.short_description, c.short_name, i.url
from products_product p
left join
(
select distinct on (pcat.product_id) pcat.product_id, cat.short_name
from products_product_categories pcat
join categories_category cat on cat.id = pcat.category_id
order by pcat.product_id, cat.id
) c on c.product_id = p.id
left join
(
select distinct on (product_id) product_id, url
from products_productimage
order by product_id, order
) i on i.product_id = p.id
where p.is_active
order by p.id;
Two alternatives to write this query are:
subqueries with fetch first row only in the select clause
lateral left joins on subqueries with fetch first row only

Related

JOIN Products that are IN another table

I tried to join some Products based on if they're in a table or not. I'm using MSSQL and I'm trying to do this to see if the category has some products.
simplified Query:
SELECT c.CategoryID, c.Name, p.ProductID
FROM Category AS c
JOIN Product AS p ON p.ProductID IN (
SELECT PrductID FROM exampleTable
)
ProductTable:
ProductID
CategoryID
1
1
2
1
3
2
4
4
The output I receive:
CategoryID
Name
ProductID
1
Cat1
1
1
Cat1
2
2
Cat2
3
4
Cat4
4
The expected output:
CategoryID
Name
ProductID
1
Cat1
1
2
Cat2
3
4
Cat4
4
I'm trying to only join a product if it's in the select statement and not join all products which have the same category id.
In pseudo code I'm trying to achive this:
JOIN Product AS p IF p.ProductID IN (Subquery)
Is this somehow possible?
Ed banga's answer is IMHO more elegant and perfoment but to be closer to what you proposed in your question, you can simply use a where clause.
SELECT c.CategoryID, c.Name, p.ProductID
FROM Category AS c
JOIN Product AS p ON p.CategoryID = c.CategoryID
WHERE p.ProductID IN (
SELECT PrductID FROM exampleTable
)

How to do a nested COALESCE subquery in a join for 3 different tables?

I have these 3 tables, And using query, have table sorted on distinct on group/product.
Now I would like to add a child table each of these 3 table with say just 1 column default_something
Query:
SELECT DISTINCT ON (pg.id, p.prod_id)
pg.group_name, p.name AS prod_name, v.version
FROM product_group pg
LEFT JOIN product p ON pg.id = p.group_id
LEFT JOIN version v ON v.prod_id = p.prod_id
ORDER BY pg.id, p.prod_id, v.version DESC;
Product Group Table
id group_name
---------------------------
1 Nice
2 Very Nice
Product table
prod_id name group_id
---------------------------
1 something 2
2 psp3 1
3. other one 2
Version Table
version_id prod_id version
---------------------------
1 2 1.0
2 2 1.1
3 3 2.3
4 1 0.1
5. 1 0.2
Product Group Child Table
pgt_child_id group_id default_something
---------------------------------
1 2 root2
2 1 root1
Product Child table
pt_child_id prod_id default_something
-------------------------------------------
1 3 override2
Version Child Table
v_child_id version_id default_something
-------------------------------------------
1 2 winner
Running the query DBFiddle I get this now...
Group_name prod_name version
---------------------------------
Nice psp3 1.1
Very Nice something 0.2
Very Nice other one 2.3
What I want is like this
Group_name prod_name version default_something
-----------------------------------------------------
Nice psp3 1.1 winner
Very Nice something 0.2. root2
Very Nice other one 2.3. override2
Basically if Version table has the field default_something.. that always wins.. If Version Table does not and if Product Table has the field default_something, that would win.. And Group Product Table basically has the lowest priority so if Version Table and Product Table does not have the field then value from Group Product Table wins.
I would assume SELECT COALESCE() would work to get the value which wins from 3 child tables.. I just have not figured out how to put that in a subquery with a join or something.
You can make just a COALESCE with with three subqueries:
SELECT DISTINCT ON (pg.id, p.prod_id)
pg.group_name, p.name AS prod_name, v.version,
COALESCE((select default_something from version_child where version_id = v.id),
(select default_something from product_child where prod_id = p.prod_id),
(select default_something from product_group_child where group_id = pg.id)
) as something
FROM product_group pg
LEFT JOIN product p ON pg.id = p.group_id
LEFT JOIN version v ON v.prod_id = p.prod_id
ORDER BY pg.id, p.prod_id, v.version DESC;
see the amended DBFiddle
or left join the three tables and make the coalesce on the default_somethings
SELECT DISTINCT ON (pg.id, p.prod_id)
pg.group_name, p.name AS prod_name, v.version,
COALESCE(v2.default_something,
p2.default_something,
pg2.default_something
) as something
FROM product_group pg
LEFT JOIN product p ON pg.id = p.group_id
LEFT JOIN version v ON v.prod_id = p.prod_id
LEFT JOIN version_child v2 ON v2.version_id = v.id
LEFT JOIN product_child p2 ON p2.prod_id = p.prod_id
LEFT JOIN product_group_child pg2 ON pg2.group_id = pg.id
ORDER BY pg.id, p.prod_id, v.version DESC;
DBFiddle

SQL split one column into two columns based on values and use columns

Table: ProductionOrder
Id Ordernumber Lotsize
1 Order1 50
2 Order 2 75
3 WO-order1 1
4 WO-order2 1
Table: history
Id ProductionOrderID Completed
1 3 1
2 3 1
3 4 1
4 4 1
Table: ProductionOrderDetail
ID ProductionOrderID ProductionOrderDetailDefID Content
1 1 16 50
2 1 17 7-1-2018
3 2 16 75
4 2 17 7-6-2018
Start of my code:
Select p.ID, p.OrderNumber,
Case productionOrderDetailDefID
Where(Select pd1.productionOrderDetailDefID where ProductionOrderDetialDefID = 16) then min(pd1.content)
from ProductionOrder p
Left join History h1 on p.id = h1.productionOrderID
Left Join ProductionOrderDetail pd1 on p.ID = ProductionOrderID
The result in trying to get is
Id Ordernumber Lotsize Productionorder Completed
1 Order1 50 WO-order1 2
2 Order 2 75 WO-order2 2
Any help would be appreciated.
Try this
SELECT ordernumber,lotsize,Ordernumber,count(Ordernumberid)
FROM productionorder inner join history on productionorder.id = history.Ordernumberid
GROUP BY Ordernumber;
A bit of weird joins going on here. You should add this to a SQL fiddle so that we can see our work easier.
A link to SQL fiddle: http://sqlfiddle.com/
Here is my first attempt
SELECT
po.id
, po.ordernumber
, po.lotsize
, po2.productionorder
, SUM(h.completed)
FROM productionorder as po
INNER JOIN history as h
ON h.id = po.id
INNER JOIN prodcuctionorder as po2
ON po2.ordernumberid = h.ordernumberid
WHERE po.id NOT EXISTS IN ( SELECT ordernumberid FROM history )
GROUP BY
po.id
, po.ordernumber
, po.lotzise
, po2.productionorder
How far does that get you?

Select all categories with COUNT of sub-categories

I need to select all categories with count of its sub-categories.
Assume here are my tables:
categories
id | title
----------
1 | colors
2 | animals
3 | plants
sub_categories
id | category_id | title | confirmed
------------------------------------
1 1 red 1
2 1 blue 1
3 1 pink 1
4 2 cat 1
5 2 tiger 0
6 2 lion 0
What I want is :
id | title | count
------------------
1 colors 3
2 animals 1
3 plants 0
What I have tried so far:
SELECT c.id, c.title, count(s.category_id) as count from categories c
LEFT JOIN sub_categories s on c.id = s.category_id
WHERE c.confirmed = 't' AND s.confirmed='t'
GROUP BY c.id, c.title
ORDER BY count DESC
The only problem with this query is that this query does not show categories with 0 sub categories!
You also can check that on SqlFiddle
Any help would be great appreciated.
The reason you don't get rows with zero counts is that WHERE clause checks s.confirmed to be t, thus eliminating rows with NULLs from the outer join result.
Move s.confirmed check into join expression to fix this problem:
SELECT c.id, c.title, count(s.category_id) as count from categories c
LEFT JOIN sub_categories s on c.id = s.category_id AND s.confirmed='t'
WHERE c.confirmed = 't'
GROUP BY c.id, c.title
ORDER BY count DESC
Adding Sql Fiddle: http://sqlfiddle.com/#!17/83add/13
I think you can try this too (it evidence what column(s) you are really grouping by):
SELECT c.id, c.title, RC
from categories c
LEFT JOIN (SELECT category_id, COUNT(*) AS RC
FROM sub_categories
WHERE confirmed= 't'
GROUP BY category_id) s on c.id = s.category_id
WHERE c.confirmed = 't'
ORDER BY RC DESC

group twice in one query

I use below code but doesn't return what I expect,
the table relationship,
each gallery is include multiple media and each media is include multiple media_user_action.
I want to count each gallery how many media_user_action and order by this count
rows: [
{
"id": 1
},
{
"id": 2
}
]
and this query will return duplicate gallery rows something like
rows: [
{
"id": 1
},
{
"id": 1
},
{
"id": 2
}
...
]
I think because in the LEFT JOIN subquery select media_user_action rows only group by media_id,
need to group by gallery_id also ?
SELECT
g.*
FROM gallery g
LEFT JOIN gallery_media gm ON gm.gallery_id = g.id
LEFT JOIN (
SELECT
media_id,
COUNT(*) as mua_count
FROM media_user_action
WHERE type = 0
GROUP BY media_id
) mua ON mua.media_id = gm.media_id
ORDER BY g.id desc NULLS LAST OFFSET $1 LIMIT $2
table
gallery
id |
1 |
2 |
gallery_media
id | gallery_id fk gallery.id | media_id fk media.id
1 | 1 | 1
2 | 1 | 2
3 | 2 | 3
....
media_user_action
id | media_id fk media.id | user_id | type
1 | 1 | 1 | 0
2 | 1 | 2 | 0
3 | 3 | 1 | 0
...
media
id |
1 |
2 |
3 |
UPDATE
There's more other table I need to select, this is a part in a function like this https://jsfiddle.net/g8wtqqqa/1/ when user input option then build query.
So I correct my question I need to find a way if user want to count media_user_action order by it, I wanna know how to put these in a subquery possible not change any other code
Base on below #trincot answer I update code, only add media_count on top change a little bit and put those in sub query. is what I want,
now they are group by gallery.id, but sort media_count desc and asc are same result not working I can't find why?
SELECT
g.*,
row_to_json(gi.*) as gallery_information,
row_to_json(gl.*) as gallery_limit,
media_count
FROM gallery g
LEFT JOIN gallery_information gi ON gi.gallery_id = g.id
LEFT JOIN gallery_limit gl ON gl.gallery_id = g.id
LEFT JOIN "user" u ON u.id = g.create_by_user_id
LEFT JOIN category_gallery cg ON cg.gallery_id = g.id
LEFT JOIN category c ON c.id = cg.category_id
LEFT JOIN (
SELECT
gm.gallery_id,
COUNT(DISTINCT mua.media_id) media_count
FROM gallery_media gm
INNER JOIN media_user_action mua
ON mua.media_id = gm.media_id AND mua.type = 0
GROUP BY gm.gallery_id
) gm ON gm.gallery_id = g.id
ORDER BY gm.media_count asc NULLS LAST OFFSET $1 LIMIT $2
The join with gallery_media table is multiplying your results. The count and grouping should happen after you have made that join.
You could achieve that like this:
SELECT g.id,
COUNT(DISTINCT mua.media_id)
FROM gallery g
LEFT JOIN gallery_media gm
ON gm.gallery_id = g.id
LEFT JOIN media_user_action mua
ON mua.media_id = gm.id AND type = 0
GROUP BY g.id
ORDER BY 2 DESC
If you need the other informations as well, you could use the above (in simplified form) as a sub-query, which you join with anything else that you need, but will not multiply the number of rows:
SELECT g.*
row_to_json(gi.*) as gallery_information,
row_to_json(gl.*) as gallery_limit,
media_count
FROM gallery g
LEFT JOIN (
SELECT gm.gallery_id,
COUNT(DISTINCT mua.media_id) media_count
FROM gallery_media gm
INNER JOIN media_user_action mua
ON mua.media_id = gm.id AND type = 0
GROUP BY gm.gallery_id
) gm
ON gm.gallery_id = g.id
LEFT JOIN gallery_information gi ON gi.gallery_id = g.id
LEFT JOIN gallery_limit gl ON gl.gallery_id = g.id
ORDER BY media_count DESC NULLS LAST
OFFSET $1
LIMIT $2
The above assumes that gallery_id is unique in the tables gallery_information and gallery_limit.
You're grouping by media_id to get a count, but since one gallery can have many gallery_media, you still end up with multiple rows for one gallery. You can either sum the mua_count from your subselect:
SELECT g.*, sum(mua_count)
FROM gallery g
LEFT JOIN gallery_media gm ON gm.gallery_id = g.id
LEFT JOIN (
SELECT media_id,
COUNT(*) as mua_count
FROM media_user_action
WHERE type = 0
GROUP BY media_id
) mua ON mua.media_id = gm.media_id
GROUP BY g.id
ORDER BY g.id desc NULLS LAST;
id | sum
----+-----
2 | 1
1 | 2
Or you can just JOIN all the way through and group once on g.id:
SELECT g.id, count(*)
FROM gallery g
JOIN gallery_media gm ON gm.gallery_id = g.id
JOIN media_user_action mua ON mua.media_id = gm.id
GROUP BY g.id
ORDER BY count DESC;
id | count
----+-------
1 | 2
2 | 1
If you only want to show data from table gallery (with select g.*) then why do you join the other tables? Outer joins either join one ore more records to each main record (depending on how many matches are found in the outer-joined table), so no surprise you get duplicates (in your case because gallery ID 1 has two matches in gallery_media).