Subquery in FROM not working in Oracle SQL - sql

I'm working on converting a query from having a subquery in the WHERE section to the FROM section.
As I understand it, the FROM clause will return a table, which I'm calling "product_locations" and then my outer query can retrieve information from that table. I can't see if the outer query is working at this point, because I'm getting stuck on an error that I'm missing a right parenthesis.
At this point, I think my main problem is not understanding how to properly identify the two parts of an IN clause that constitutes the subquery.
Here is the original query:
SELECT size_option,
product.product_name
FROM sizes
JOIN available_in ON sizes.sized_id = available_in.sizes_id
JOIN product ON product.product_id = available_in.product_id
WHERE (SELECT COUNT (store_name)
FROM store_location IN (SELECT COUNT (store_location_id)
FROM sells
JOIN product ON sells.product_id = product.product_id
GROUP BY sells.store_location)
This is the query I'm attempting:
SELECT size_option,
product_location.product_name
FROM (SELECT COUNT(store_name)
FROM store_location) IN (SELECT COUNT (store_location_id)
FROM sells
JOIN product ON sells.product_id = product.product_id
GROUP BY sells.store_location_id) product_location
JOIN sizes ON sizes.size_option = product_location.size_option
JOIN available_in ON sizes.sizes_id = available_in.sizes_id
JOIN product ON product.product_id = available_in.product_id
and the error I'm getting:
SELECT size_option,
product_location.product_name
FROM (SELECT COUNT(store_name)
FROM store_location) IN (SELECT COUNT (store_location_id)
FROM sells
JOIN product ON sells.product_id = product.product_id
GROUP BY sells.store_location_id) product_location
JOIN sizes ON sizes.size_option = product_location.size_option
JOIN available_in ON sizes.sizes_id = available_in.sizes_id
JOIN product ON product.product_id = available_in.product_id
Error at Command Line : 4 Column : 31
Error report -
SQL Error: ORA-00933: SQL command not properly ended
00933. 00000 - "SQL command not properly ended"
*Cause:
*Action:
Here's a picture of the error in my editor
Here's the ERD

Using an IN condition in a FROM clause is likely to give you grief, as
"A condition specifies a combination of one or more expressions and logical (Boolean) operators and returns a value of TRUE, FALSE, or UNKNOWN."
(see documentation). Thus, a construct like (SELECT ...) IN (SELECT ...) - if syntactically correct - will return true, false, or unknown - which is useful in a WHERE clause (not in a FROM clause).
In one of your comments, you are explaining that ...
" The intention is to show all products that are available in all locations."
Using a small test data set (see dbfiddle), encompassing 5 of your tables, the following query may give you
a starting point for finding the solution:
-- Find the store count for each product (table SELLS)
-- and return all product_ids that are available everywhere (STORE_LOCATION count)
select product_id
from (
select
product_id
, count( store_location_id ) store_count
from sells
group by product_id
having count( store_location_id ) = ( select count(*) from store_location )
) ;
-- result
PRODUCT_ID
----------
10
Alternative
-- ---------------------------------------------------
-- use analytics -> we don't need GROUP BY and HAVING
-- ---------------------------------------------------
-- 1 count all store_locations (SL)
-- 2 count the amount of stores a product is located in (S)
-- 3 JOIN the 2 result sets (equijoin)
-- 4 return the product_id found
-- NOTE: we are working with Oracle -> don't use AS when defining table aliases (AS can be used for column aliases)
select product_id -- 4
from (
select count(*) store_count from store_location -- 1
) SL join (
select unique
product_id
, count( store_location_id ) over ( partition by product_id ) store_count -- 2
from sells
) S on SL.store_count = S.store_count -- 3
;
-- result
PRODUCT_ID
----------
10
Once you get the correct product_id(s), you can "bolt on" the remaining tables needed for your query (using JOINs) and write all required column names (including table aliases) into the SELECT.
E.g.
select
S.product_id
, P.product_name
, SZ.size_option
from (
select count(*) store_count from store_location
) SL join (
select unique
product_id
, count( store_location_id ) over ( partition by product_id ) store_count
from sells
) S on SL.store_count = S.store_count
join product P on S.product_id = P.product_id
join available_in AI on AI.product_id = P.product_id
join sizes SZ on AI.sizes_id = SZ.sizes_id
;
-- result
PRODUCT_ID PRODUCT_N SIZE_OP
---------- --------- -------
10 product10 option1
10 product10 option2

you query may like below
SELECT size_option,
product_location.product_name,
(
SELECT COUNT(store_name)
FROM store_location
) as store_name_cnt,
(
SELECT COUNT (store_location_id)
FROM sells
JOIN product ON sells.product_id = product.product_id
GROUP BY sells.store_location_id
) as location_count
from product_location
JOIN sizes ON sizes.size_option = product_location.size_option
JOIN available_in ON sizes.sizes_id = available_in.sizes_id
your query error
SELECT size_option,
product_location.product_name
FROM (SELECT COUNT(store_name) --invalid from
FROM store_location)
IN (SELECT COUNT (store_location_id) -- there is no where but you use in
FROM sells
JOIN product ON sells.product_id = product.product_id
GROUP BY sells.store_location_id) product_location -- inconsistent table alias
JOIN sizes ON sizes.size_option = product_location.size_option
JOIN available_in ON sizes.sizes_id = available_in.sizes_id
JOIN product ON product.product_id = available_in.product_id

Per the OP in comments: The intention is to show all products that are available in all locations.
Consider joining two aggregate query derived tables (or subqueries in FROM or JOIN clause):
one that counts stores at specific product
one that counts stores across all products
Then join the subqueries by corresponding product_id and store_count value. Be sure to use table aliases for readability.
SELECT s.size_option,
p.product_name
FROM sizes AS s
JOIN available_in AS a ON s.sized_id = a.sizes_id
JOIN product AS p ON p.product_id = a.product_id
JOIN
--- STORE COUNT BY PRODUCT
(
SELECT sub_p.product_id, COUNT(sl.store_location_id) AS store_count
FROM sells AS sl
JOIN product AS sub_p ON sl.product_id = sub_p.product_id
GROUP BY sub_p.product_id
) AS agg_p
ON agg_p.product_id = p.product_id
JOIN
--- STORE COUNT ACROSS ALL PRODUCTS
(
SELECT COUNT(sl.store_location_id) AS store_count
FROM sells AS sl
JOIN product AS sub_p ON sl.product_id = sub_p.product_id
) AS agg_s
ON agg_s.store_count = agg_p.store_count

Related

Get all records having count from one table is greater than sum of other table

I have three tables 1) CustomerOrders, 2) StockItems and 3) OrderContentsLine. StockItems have customerorderid (one to many relationship with CustomerOrders) and OrderContentsLine contains order items with item quantity (obviously one to many relationship with CustomerOrders).
Now I want to get All orders which have sum of quantity from OrderContentsLine table greater than count of StockItems
myquery looks like this
select co.OrderNumber,si.SalesOrderID, sum(ocl.Quantity) Ordered, count(si.SalesOrderID) Allocated from CustomerOrders co
inner join StockItems si on co.OrderID = si.SalesOrderID
inner join OrderContentsLine ocl on ocl.OrderID=co.OrderID
where co.CompanyId=531
group by si.SalesOrderID,co.OrderNumber
having count(si.SalesOrderID)>sum(ocl.Quantity)
but this query shows no results, and I am damn sure that many orders have greater order conterntline items than sum of quantity from StockItems table.
Can you please review my query and suggest the better way to get these orders!
My required output is
NOTE: this output is not generated by query !
I have just created a query that gives me the required output
select * from(
select co.OrderNumber, co.OrderID, co.OrderStatus,
(select sum(tbl.Quantity) from OrderContentsLine tbl where tbl.OrderID=co.OrderID) Ordered,
(select count(*) from StockItems tbl2 where tbl2.SalesOrderID=co.OrderID ) Allocated
from CustomerOrders co
)temp where temp.Allocated> temp.Ordered
Your problem is the multiple one-to-many joins. You are counting and summing duplicates. For example, if you have 1 order with 2 stock items, and 3 order lines, your join of the three tables will have 6 rows. You have no relationship between StockItem and OrderContentsLine, so you get a cartesian product.
You probably want something like
WITH ord AS
(
SELECT co.CompanyId, co.OrderID, co.OrderNumber, SUM(ocl.Quantity) AS Ordered
FROM CustomerOrders co
INNER JOIN OrderContentsLine ocl ON ocl.OrderID = co.OrderID
GROUP BY co.CompanyId, co.OrderNumber
), al AS
(
SELECT co.CompanyId, co.OrderID, co.OrderNumber, COUNT(si.SalesOrderID) AS Allocated
FROM CustomerOrders co
INNER JOIN StockItems si ON co.OrderID = si.SalesOrderID
GROUP BY co.CompanyId, co.OrderNumber
)
SELECT ord.CompanyId, ord.OrderNumber, ord.Ordered, al.Allocated
FROM ord
INNER JOIN al ON ord.OrderID = al.OrderID
WHERE companyId = 531
AND al.Allocated > ord.Ordered
Obviously hard to test with no data

Prevent duplicate rows when using LEFT JOIN in Postgres without DISTINCT

I have 4 tables:
Item
Purchase
Purchase Item
Purchase Discount
In these tables, the Purchase Discount has two entries, all the others have only one entry. But when I query them, due to the LEFT JOIN, I'm getting duplicate entries.
This query will be running in a large database, and I heard using DISTINCT will reduce the performance. Is there any other way I can remove duplicates without using DISTINCT?
Here is the SQL Fiddle.
The result shows:
[{"item_id":1,"purchase_items_ids":[1234,1234],"total_sold":2}]
But the result should come as:
[{"item_id":1,"purchase_items_ids":[1234],"total_sold":1}]
Using correlated subquery instead of LEFT JOIN:
SELECT array_to_json(array_agg(p_values)) FROM
(
SELECT t.item_id, t.purchase_items_ids, t.total_sold, t.discount_amount FROM
(
SELECT purchase_items.item_id AS item_id,
ARRAY_AGG(purchase_items.id) AS purchase_items_ids,
SUM(purchase_items.sold) as total_sold,
SUM((SELECT SUM(pd.discount_amount) FROM purchase_discounts pd
WHERE pd.purchase_id = purchase.id)) as discount_amount
FROM items
INNER JOIN purchase_items ON purchase_items.item_id = items.id
INNER JOIN purchase ON purchase.id = purchase_items.purchase_id
WHERE purchase.id = 200
GROUP by purchase_items.item_id
) as t
INNER JOIN items i ON i.id = t.item_id
) AS p_values;
db<>fiddle demo
Output:
[{"item_id":1,"purchase_items_ids":[1234],"total_sold":1,"discount_amount":12}]
First I would suggest to remove INNER JOIN items i ON i.id = t.item_id from the query which no reason to be there.
Then instead Left joining Purchase_Discounts table use subquery to get the Discount_amount (as mentioned in Lukasz Szozda's answer)
If there is no discount for any product then Discount_amount column will display NULL. If you want to avoid it then you can use COALESCE() as below instead:
COALESCE(SUM((select sum(discount_amount) from purchase_discounts
where purchase_discounts.purchase_id = purchase.id)),0) as discount_amount
Db-Fiddle:
SELECT array_to_json(array_agg(p_values)) FROM
(
SELECT t.item_id, t.purchase_items_ids, t.total_sold, t.discount_amount FROM
(
SELECT purchase_items.item_id AS item_id,
ARRAY_AGG(purchase_items.id) AS purchase_items_ids,
SUM(purchase_items.sold) as total_sold,
SUM((select sum(discount_amount) from purchase_discounts
where purchase_discounts.purchase_id = purchase.id)) as discount_amount
FROM items
INNER JOIN purchase_items ON purchase_items.item_id = items.id
INNER JOIN purchase ON purchase.id = purchase_items.purchase_id
WHERE
purchase.id = 200
GROUP by
purchase_items.item_id
) as t
) AS p_values;
Output:
array_to_json
[{"item_id":1,"purchase_items_ids":[1234],"total_sold":1,"discount_amount":12}]
db<>fiddle here
The core problem is that your LEFT JOIN multiplies rows. See:
Two SQL LEFT JOINS produce incorrect result
Aggregate discounts to a single row before the join. Or use a (uncorrelated) subquery expression:
SELECT json_agg(items)
FROM (
SELECT pi.item_id
, array_agg(pi.id) AS purchase_items_ids
, sum(pi.sold) AS total_sold
,(SELECT COALESCE(sum(pd.discount_amount), 0)
FROM purchase_discounts pd
WHERE pd.purchase_id = 200) AS discount_amount
FROM purchase_items pi
WHERE pi.purchase_id = 200
GROUP BY 1
) AS items;
Result:
[{"item_id":1,"purchase_items_ids":[1234],"total_sold":1,"discount_amount":12}]
db<>fiddle here
I added a couple of additional improvements:
Assuming referential integrity enforced by FK constraints, we don't need to involve the tables purchase and items at all.
Removed a subquery level doing nothing.
Using json_agg() instead of array_to_json(array_agg()).
Added COALESCE() to output 0 instead or NULL for no discounts.
Since discounts apply to the purchase in your model, not to individual items, it doesn't make sense to output discount_amount for every single item. Consider this query instead to return an array of items and a single, separate discount_amount:
SELECT json_build_object(
'items'
, json_agg(items)
, 'discount_amount'
, (SELECT COALESCE(sum(pd.discount_amount), 0)
FROM purchase_discounts pd
WHERE pd.purchase_id = 200)
)
FROM (
SELECT pi.item_id
, array_agg(pi.id) AS purchase_items_ids
, sum(pi.sold) AS total_sold
FROM purchase_items pi
WHERE pi.purchase_id = 200
GROUP BY 1
) AS items;
Result:
{"items" : [{"item_id":1,"purchase_items_ids":[1234],"total_sold":1}], "discount_amount" : 12}
db<>fiddle here
Using json_build_object() to assemble the JSON object.
Your example with a single item in the purchase isn't too revealing. I added a purchase with multiple items and no discount to my fiddle.
If you can have multiple values only in the purchase_discounts table then a subquery that aggregate multiple purchase_discounts rows into one before the join can solve the problem:
SELECT array_to_json(array_agg(p_values)) FROM
(
SELECT t.item_id, t.purchase_items_ids, t.total_sold, t.discount_amount FROM
(
SELECT purchase_items.item_id AS item_id,
ARRAY_AGG(purchase_items.id) AS purchase_items_ids,
SUM(purchase_items.sold) as total_sold,
X.discount_amount
FROM items
INNER JOIN purchase_items ON purchase_items.item_id = items.id
INNER JOIN purchase ON purchase.id = purchase_items.purchase_id
LEFT JOIN (SELECT purchase_id, sum(purchase_discounts.discount_amount) AS discount_amount FROM purchase_discounts GROUP BY purchase_id) X ON X.purchase_id = purchase.id
WHERE
purchase.id = 200
GROUP by
purchase_items.item_id,
X.discount_amount
) as t
INNER JOIN items i ON i.id = t.item_id
) AS p_values;
The LEFT JOIN is not causing your duplicates, I understand why you need it as there may not be any discounts, but for the data provided changing to an inner join produces the same result. You are getting duplicate entries because you use ARRAY_AGG(purchase_items.id). Further, with the data presented, the tables item and purchase are not necessary. You can use the window version of sum and distinct on to reduce the duplication of purchase_id, and eliminate the mentioned tables. Finally the middle select ... ) t can be completely removed. Resulting in: (see demo)
select array_to_json(array_agg(p_values))
from (select distinct on (pi.item_id, pi.id)
pi.item_id
, pi.id purchase_items_ids
, sum(pi.sold) over (partition by pi.item_id) total_sold
, sum(pd.discount_amount) over(partition by pi.item_id) discount_amount
from purchase_items pi
left join purchase_discounts pd
on pd.purchase_id = pi.purchase_id
order by pi.item_id, pi.id
) as p_values;
I think the left join does not cause, because with the Inner Join query result same as the left join, in discount with purchase_id=200 query has 2 results you can use from row_number with the partion_by same as:
ROW_NUMBER() OVER(PARTITION BY purchase_items.id order by purchase_items.id) rn
then select rn=1.
you change your query for the sum function, I think that you can use from partion_by.

Select most Occurred Value SQL with Inner Join

I am using this query to get the following data from different linked tables. But let's say the VENDORS for an item were three. Now here in result i want to show the Vendor which occurred most. I mean if Item ABC was supplied by 3 different vendors many times. Then here i want to get the Vendor who supplied most of the times item ABC.
My query is this.
use iBusinessFlex;
SELECT Items.Name,
Max(Items.ItemID) as ItemID ,
MAX(Items.Description)as Description,
MAX(ItemsStock.CurrentPrice) as UnitPrice,
MAX(ItemsStock.Quantity) as StockQuantiity,
MAX(Vendors.VendorName) as VendorName,
SUM(ItemReceived.Quantity) as TotalQuantity
From ItemReceived
INNER JOIN Items ON ItemReceived.ItemId=Items.ItemID
INNER JOIN ItemsStock ON ItemReceived.ItemId=ItemsStock.ItemID
INNER JOIN PurchaseInvoices ON PurchaseInvoices.PurchaseInvoiceId = ItemReceived.PurchaseInvoiceId
INNER JOIN Vendors ON Vendors.VendorId = PurchaseInvoices.VendorId
Group By Items.Name
EDIT : I have included this sub query but i am not sure if it is showing correct result. i mean Showing Vendor for each Item who provided that item most of the times
use iBusinessFlex;
SELECT Items.Name,
Max(Items.ItemID) as ItemID ,
MAX(Items.Description)as Description,MAX(ItemsStock.CurrentPrice) as UnitPrice,
MAX(ItemsStock.Quantity) as StockQuantiity,MAX(Vendors.VendorName) as VendorName,
SUM(ItemReceived.Quantity) as TotalQuantity
From ItemReceived
INNER JOIN Items ON ItemReceived.ItemId=Items.ItemID INNER JOIN ItemsStock
ON ItemReceived.ItemId=ItemsStock.ItemID INNER JOIN PurchaseInvoices
ON PurchaseInvoices.PurchaseInvoiceId = ItemReceived.PurchaseInvoiceId INNER JOIN Vendors
ON Vendors.VendorId IN (
SELECT Top 1 MAX(PurchaseInvoices.VendorId) as VendorOccur
FROM PurchaseInvoices INNER JOIN Vendors ON Vendors.VendorId=PurchaseInvoices.VendorId
GROUP BY PurchaseInvoices.VendorId
ORDER BY COUNT(*) DESC
And the Result Looks like this.
First, I would start with who ordered what thing the most. But the MOST is based on what... the most quantity? Price?, Number of Times? If you use one vendor and order 6 times qty of 10 you have 60 things. But order 1 time from another vendor for 100 qty, which one wins. You have to decide the basis of MOST, but I will go based on most times
per your original question.
So all things come from PurchasedInvoices which has a vendor ID. I dont care who the vendor is, just their ID, so no need to join. Also, don't need the item name if I am just looking for my counts. The query below will show per item, each vendor and their respective most times ordered and quantities ordered. I added the items and vendor table joins just to show the names.
select
IR.ItemID,
PI.VendorID,
max( I.Name ) Name,
max( V.VendorName ) VendorName,
count(*) as TimesOrderedFrom,
SUM( IR.Quantity ) as QuantityFromVendor
from
ItemsReceived IR
JOIN PurchaseInvoices PI
on IR.PurchaseInvoiceID = PI.PurchaseInvoiceID
JOIN Items I
on IR.ItemID = I.ItemID
JOIN Vendors V
on IR.VendorID = V.VendorID
group by
IR.ItemID,
PI.VendorID
order by
-- Per item
IR.ItemID,
-- Most count ordered
count(*),
-- If multiple vendors, same count, get total quantity
sum( IR.Quantity )
Now, to get only 1 per item, this would create a correlated subquery and you
can add 'TOP 1' to return only the first by this. Since the aggregate of count
is already done, you can then get the vendor contact info.
select
I.Name,
V.VendorName,
TopVendor.TimesOrderedFromVendor,
TopVendor.QuantityFromVendor
from
Items I
JOIN ( select TOP 1
IR.ItemID,
PI.VendorID,
count(*) as TimesOrderedFrom,
SUM( IR.Quantity ) as QuantityFromVendor
from
ItemsReceived IR
JOIN PurchaseInvoices PI
on IR.PurchaseInvoiceID = PI.PurchaseInvoiceID
where
-- correlated subquery based on the outer-most item
IR.ItemID = I.ItemID
group by
IR.ItemID,
PI.VendorID
order by
-- Per item
IR.ItemID,
-- Most count ordered
count(*),
-- If multiple vendors, same count, get total quantity
sum( IR.Quantity ) ) TopVendor
on I.ItemID = TopVendor.ItemID
JOIN Vendors V
on TopVendor.VendorID = V.VendorID
No sense in having the INNER Subquery joining on the vendor and items just for the names. Get those once and only at the end when the top vendor is selected.

WHERE Clause for One-To-Many Association

I have two tables Products and ProductProperties.
Products
name - string
description - text
etc etc
ProductProperties
product_id - integer
property_id - integer
There is also a table Properties which basically stores the list of property names and their attributes
How can I implement a SQL command that finds a product with the property_ids (A or B or C) AND (X or Y or Z)
I've got upto here:
SELECT DISTINCT "products".*
FROM "products"
INNER JOIN "product_properties" ON "product_properties"."product_id" = "products"."id" AND "product_properties"."deleted_at" IS NULL
WHERE "products"."deleted_at" IS NULL
AND (product_properties.property_id IN ('504, 506, 403'))
AND (product_properties.property_id IN ('520, 501, 502'))
But it doesn't really work since it's looking for a Product Property which has both values 504 and 520, which will never exist.
Would appreciate some help!
You need to define intermediate resultsets on a property group basis:
SELECT DISTINCT p.*
FROM products p
JOIN product_properties groupA ON groupA.product_id = p.id AND groupA.deleted_at IS NULL AND groupA.property_id IN ('504')
JOIN product_properties groupB ON groupB.product_id = p.id AND groupB.deleted_at IS NULL AND groupB.property_id IN ('520')
WHERE p.deleted_at IS NULL
You see, you detected the problem yourself very nicely: "But it doesn't really work since it's looking for a Product Property which has both values 504 and 520, which will never exist."
Indeed, recordsets are immutable within a query, all single criteria applied to them are applied all at once. You need to duplicate each table and apply individual criteria to them.
One method uses exists or in:
select p.*
from products p
where p.id in (select pp.product_id
from product_properties pp
where pp.propertyid in ('504', '520')
);
This saves you from having to use distinct in the outer query.
If, perchance, you really mean finding the products that have all the properties, then a join and group by work:
select p.*
from products p join
product_properties pp
on p.id = pp.product_id
where pp.propertyid in ('504', '520')
group by p.id -- yes, this is allowed in Postgres
having count(*) = 2;
Hi try this queries i just thinking about it so i didn't try any of them check i got the idea i want to do
SELECT DISTINCT "products".*
FROM products pr
WHERE id IN
(
SELECT product_id FROM ProductProperties WHERE property_id IN (504,520)
GROUP BY product_id
HAVING Count(*) = 2
) AND "products"."deleted_at" IS NULL
SELECT DISTINCT "products".*
FROM products pr, INNER JOIN (
SELECT product_id,count(*) as nbr FROM ProductProperties WHERE property_id IN (504,520)
GROUP BY product_id
) as temp ON temp.product_id = pr.id
WHERE "products"."deleted_at" IS NULL AND temp.nbr = 2
and also you can check this one as well ( you can use also the join in where clause instead of using INNER JOIN)
SELECT DISTINCT products.* FROM products as p
INNER JOIN product_properties as p1 ON p1.product_id = p.id
INNER JOIN product_properties as p2 ON p2.product_id = p.id
WHERE p.deleted_at IS NULL
AND p1.property_id = '504' AND p1.deleted_at IS NULL
AND p2.property_id = '520' AND p2.deleted_at IS NULL

Many to many query

I have two tables products and sections in a many to many relationship and a join table products_sections. A product can be in one or more sections (new, car, airplane, old).
Products
id name
-----------------
1 something
2 something_else
3 other_thing
Sections
id name
-----------------
1 new
2 car
Products_sections
product_id section_id
--------------------------
1 1
1 2
2 1
3 2
I want to extract all products that are both in the new and the car sections. In this example result returned should be product 1. What is the correct mysql query to obtain this?
SELECT Products.name
FROM Products
WHERE NOT EXISTS (
SELECT id
FROM Sections
WHERE name IN ('new','car')
AND NOT EXISTS (
SELECT *
FROM Products_sections
WHERE Products_sections.section_id = Sections.id
AND Products_sections.product_id = Products.id
)
)
In other words, select those products for which none of the desired Section.id values is missing from the Products_sections table for that product.
Answer andho's comment:
You can put
NOT EXISTS (<select query>)
into a WHERE clause like any other predicate. It will evaluate to TRUE if there are no rows in the result set described by <select query>.
Stepwise, here's how to get to this query as an answer:
Step 1. The requirement is to identify all products that are "in both the 'new' and 'car' sections".
Step 2. A product is in both the 'new' and 'car' sections if both the 'new' and 'car' sections contain the product. Equivalently, a product is in both the 'new' and 'car' sections if neither of those sections fails to contain the product. (Note the double negative: neither fails to contain.) Restated again, we want all the products for which there is no required section failing to contain the product.
The required sections are these:
SELECT id
FROM Sections
WHERE name IN ('new','car')
Therefore, the desired products are these:
SELECT Products.name
FROM Products
WHERE NOT EXISTS ( -- there does not exist
SELECT id -- a section
FROM Sections
WHERE name IN ('new','car') -- that is required
AND (the section identified by Sections.id fails to contain the product identified by Products.id)
)
Step 3. A given section (such as 'new' or 'car') does contain a particular product if there's a row in Products_sections for the given section and particular product. So a given section fails to contain a particular product if there is no such row in Products_sections.
Step 4. If the query below does contain a row, the section_id section does contain the product_id product:
SELECT *
FROM Products_sections
WHERE Products_sections.section_id = Sections.id
AND Products_sections.product_id = Products.id
So the section_id section fails to contain the product (and that's what we need to express) if the query above does not produce a row in its result, or if NOT EXISTS ().
Seems complicated, but once you get it in your head, it sticks: Are all required items present? Yes, so long as there does not exist a required item that is not present.
The way I always do these is this:
Start at what you're trying to get (products), and then go through your lookup table (products_sections) to what you're trying to filter by (sections). This way, you can have it in plain view what you're looking for, and you never have to memorize surrogate keys (which are a great thing to have, not to memorize).
select distinct
p.name
from
products p
inner join products_sections ps on
p.product_id = ps.product_id
inner join sections s1 on
ps.section_id = s1.section_id
inner join sections s2 on
ps.section_id = s2.section_id
where
s1.name = 'new'
and s2.name = 'car'
Voila. Three inner joins, and you have a nice, clear, concise query that is obvious what it's bringing back. Hope this helps!
SELECT product_id, count(*) AS TotalSection
FROM Products_sections
GROUP BY product_id
WHERE section_id IN (1,2)
HAVING TotalSection = 2;
See if this works in mysql.
The query below is a little unwieldy, but it should answer your question:
select products.id
from products
where products.id in
(
select products_sections.product_id
from products_sections
where products_sections.section_id=1
)
and products.id in
(
select products_sections.product_id
from products_sections
where products_sections.section_id=2
)
Self-join on two subsets of join table and then selecting unique product ids.
SELECT DISTINCT car.product_id
FROM ( SELECT product_id
FROM Product_sections
WHERE section_id = 2
) car JOIN
( SELECT product_id
FROM Product_sections
WHERE section_id = 1
) neww
ON (car.product_id = neww.product_id)
This query is a variation of more general solution:
SELECT DISTINCT car.product_id
FROM product_sections car join
product_sections neww ON (car.product_id = neww.product_id AND
car.section_id = 2 AND
neww.section_id = 2)
Less efficient but more straight forward solution is:
SELECT p.name FROM Products p WHERE
EXISTS (SELECT 'found car'
FROM Products_sections ps
WHERE ps.product_id = p.id AND ps.section_id = 2)
AND
EXISTS (SELECT 'found new'
FROM products_sections ps
WHERE ps.product_id = p.id AND ps.section_id = 1)
----------------
I manipulated with ids for clarity. If necessary replace expressions section_id = 2 and section_id = 1 with
section_id = (SELECT s.id FROM Sections s WHERE s.name = 'car')
section_id = (SELECT s.id FROM Sections s WHERE s.name = 'new')
Also, you can select product names by plugging in any of the queries above like this:
SELECT Products.name FROM Products
WHERE EXISTS (
SELECT 'found product'
FROM product_sections car join
product_sections neww ON (car.product_id = neww.product_id AND
car.section_id = 2 AND
neww.section_id = 2)
WHERE car.product_id = Products.id
)
SELECT p.*
FROM Products p
INNER JOIN (SELECT ps.product_id
FROM Products_sections ps
INNER JOIN Sections s
ON s.id = ps.section_id
WHERE s.name IN ("new","car")
GROUP BY ps.product_id
HAVING Count(ps.product_id) = 2) pp
ON p.id = pp.product_id
This query will get you the result without having to add more inner joins when you need to search more sections. What will change here are:
values inside the IN () paranthesis
The value in the where clause for count which should be replaced with the number of sections you are searching
SELECT id, name FROM
(
SELECT
products.id,
products.name,
sections.name AS section_name,
COUNT(*) AS count FROM products
INNER JOIN products_sections
ON products_sections.product_id=products.id
INNER JOIN sections
ON sections.id=products_sections.section_id
WHERE sections.name IN ('car', 'new')
GROUP BY products.id
) AS P
WHERE count = 2
select
`p`.`id`,
`p`.`name`
from `Sections` as `s`
join `Products_sections` as `ps` on `ps`.`section_id` = `s`.`id`
join `Products` as `p` on `p`.`id` = `ps`.`product_id`
where `s`.`id` in ( 1,2 )
having count( distinct `s`.`name` = 2 )
will return...
id name
-----------------
1 something
Is that what you were looking for?