Complex SQL Query Syntax - mysql

I want make a query to mysql database that gets me the products that are available, have sale_id of anything1 or anything2 or both and belong to categories of Ids a or b or both from table category_product which has (category_product table) 2 columns product_id and category_id since my products can belong to multiple categories.
HERE IS MY QUERY:
SELECT *
FROM products
WHERE available = 1
AND sale_id = 3
OR sale_id = 4
AND id IN (
SELECT product_id
FROM category_product
WHERE category_id = 2
OR category_id = 3
)
But It didn't work!!!
The Result was that it got the first query correct the one with available = 1 and sale_id = 3 or 4 or both.. But the products it got didn't belong to categories 2 or 3 or both
Any Help Please!!!

I think you just need parentheses or IN:
SELECT p.*
FROM products p
WHERE p.available = 1 AND
p.sale_id IN (3, 4) AND
p.id IN (SELECT product_id
FROM category_product cp
WHERE category_id IN (2, 3)
);
You seem to be learning SQL -- or at least boolean expressions. AND and OR don't automatically do what you want. When you are mixing them, use parentheses until you get the hang of it.
Your condition parses as:
WHERE (p.available = 1 AND p.sale_id = 3) OR
(p.sale_id = 4 AND
p.id IN (SELECT product_id
FROM category_product cp
WHERE category_id IN (2, 3)
)
)

Related

How to exclude row in one-to-many relationship between tables (SQL query)

I have two tables, Products and ProductTags (I also have table Tags which is not part of the problem)
My query is
SELECT
product.id
FROM
Products JOIN ProductTags ON Products.id = ProductTags.product_id
WHERE ProductTags.tag_id = 10 and ProductTags.tag_id <> 20
Table ProductTags is one-to-many connection between product_id and tag_id, if it's called like that. Let's say that whole ProductTags table is:
product_id
tag_id
777
10
777
20
888
10
888
30
what I get as an output from my query is 777 and 888. But I want to exclude products which have tag #20.
In real query I also join with other tables (also I need to have access to other fields of Products table), so I CAN'T get proper result using only ProductTags table! I am aware that I could do it like
SELECT product_id ProductTags WHERE tag_id = 10
but this WON'T be proper solution!
One way is to use aggregation.
SELECT product_id id
FROM producttags
WHERE tag_id IN (10,
20)
GROUP BY product_id
HAVING max(tag_id) = 10;
Another one uses NOT EXISTS and a correlated subquery.
SELECT pt1.product_id
FROM producttag pt1
WHERE pt1.tag_id = 10
AND NOT EXISTS (SELECT *
FROM producttag pt2
WHERE pt2.product_id = pt1.product_id
AND pt2.tag_id = 20);
Note that the join of product isn't needed to only get the product IDs, unless there's no proper foreign key constraint on producttags.product_id.
SELECT product.id
FROM Products
JOIN ProductTags ON Products.id = ProductTags.product_id
GROUP BY product.id
HAVING SUM(ProductTags.tag_id = 10) > 0 -- at least one
AND SUM(ProductTags.tag_id = 20) = 0 -- none
This form allows any amount of simple or complex conditions.
For example:
-- strictly one row with tag_id = 30
AND SUM(ProductTags.tag_id = 30) = 1
-- at least one tag_id 40 or 50
AND SUM(ProductTags.tag_id IN (40, 50)) > 0
-- ... and so on

Many-To-Many select only rows with exactly same tags

I have 3 tables: tags, products and relation table between them.
Relation table looks for example like this:
tagId | ProductId
1 | 1
2 | 1
2 | 9
The user can pick two options "All of these" or "One of these".
So if user picks All of these, it's means that the product must have exactly all of tags which the user chose.
So if user pick tags with id 1 and 2, it should select only product with id 1, because this product has exactly the same tags the user chose. (Another way is if the user picks the tag with id 2, it should select only product with id 9.)
So, the product has to have all tags which the user chose (no more, no less).
SQL that I already have for Any/One of these:
SELECT DISTINCT s.SKU
FROM SKUToEAN as s
LEFT JOIN ProductDetails as p ON s.ProductDetailID=p.id
JOIN ProductTagRelation as ptr ON (ptr.productId=p.id and ptr.tagId IN(Ids of selected tags))
Example behavior:
TagId = 1 it should select => None
TagId = 2 it should select => 9
TagId = 1,2 it should select = 1,9
So probably I need two queries. One for any/one of these ( I already have this one ) and the second for all of these.
With PHP I decide which query to use.
You can GROUP BY on the ProductID and use conditional aggregation based filtering inside the Having clause. MySQL automatically casts boolean values to 0/1 when using in numeric context. So, in order to have a specific tagID value available against a ProductID, its SUM(tagId = ..) should be 1.
All of these:
SELECT ptr.productId, s.SKU
FROM SKUToEAN AS s
LEFT JOIN ProductDetails AS p
ON p.id = s.ProductDetailID
JOIN ProductTagRelation AS ptr
ON ptr.productId = p.id
GROUP BY ptr.productId, s.SKU
HAVING SUM(ptr.tagID = 1) AND -- 1 should be there
SUM(ptr.tagID = 2) AND -- 2 should be there
NOT SUM(ptr.tagID NOT IN (1,2)) -- other than 1,2 should not be there
Is this you are looking for (for all condition)?
select product.id
from products
inner join <table> on products.id = <table>.productId
group by product.id
having group_concat(<table>.tagId order by <table>.tagId separator ',') = '1,2';

MYSQL Query search in relationship

For the sake of clarity and this question i will rename the tables so it is a bit clearer for everybody and explain what i want to achieve:
There is an input form with options that return categories ID's. If a 'Product' has 'Category', i want to return/find the 'Product' which lets say has multiple categories(or just 1) and all of its categories are inside the array that is passed from the form.
Products table
ID Title
1 Pizza
2 Ice Cream
Categories table
ID Title
1 Baked food
2 Hot food
ProductsCategories table
ID ProductId CategoryId
1 1 1
2 1 2
So if i pass [1,2] the query should return Product with id 1 since all ProductsCategories are inside the requested array, but if i pass only 1 or 2, the query should return no results.
Currently i have the following query which works, but for some reason if i create a second Product and create a ProductCategory that has a CategoryId same as the first product, the query returns nulll...
SELECT products.*
FROM products
JOIN products_categories
ON products_categories.product_id= products.id
WHERE products_categories.category_id IN (1, 2)
HAVING COUNT(*) = (select count(*) from products_categories pc
WHERE pc .product_id = products.id)
All help is deeply appretiated! Cheers!
In order to match all values in IN clause, you just need to know in addition the number of passed categories which you must use it in HAVING clause:
SELECT
p.*,
GROUP_CONCAT(c.title) AS categories
FROM
Products p
INNER JOIN ProductsCategories pc ON pc.productId = p.ID
INNER JOIN Categories c ON c.ID = pc.categoryId
WHERE
pc.categoryId IN (1,2)
GROUP BY
p.id
HAVING
COUNT(DISTINCT pc.categoryId) = 2 -- this is # of unique categories in IN clause
So in case IN (1,2) result is:
+----+-------+---------------------+
| id | title | categories |
+----+-------+---------------------+
| 1 | Pizza | Baked Food,Hot Food |
+----+-------+---------------------+
1 row in set
In case IN (1,3) result is Empty set (no results).
#mitkosoft, thanks for your answer, but sadly the query is not producing the needed results. If the product's categories are partially in the passed categories the product is still returned. Additionally i might not know how many parameters are sent by the form.
Luckily I managed to create the query that does the trick and works perfectly fine (at least so far)
SELECT products.*,
COUNT(*) as resultsCount,
(SELECT COUNT(*) FROM products_categories pc WHERE pc.product_id = products.id) as categoriesCount
FROM products
JOIN products_categories AS productsCategories
ON productsCategories.product_id= products.id
WHERE productsCategories.category_id IN (7, 15, 8, 1, 50)
GROUP BY products.id
HAVING resultsCount = categoriesCount
ORDER BY amount DESC #optional
That way the query is flexible and gives me exactly what I needed! - Only those products that have all their categories inside the search parameters(not partially).
Cheers! :)

mysql products in category select

I have a table named product_cats, the relevant columns are
product_id,
cat_id
Say we have categories 1, 2, 3, 4, and 5.
I need all the product_id's in cat_id 1 and 2 and (3 or 4 or 5).
I have a few working methods at this point, but they're all terribly slow.
Can someone shed some mysql magic on me?
Edit:
So the product needs to be in both category 1 and 2, and in either 3, 4, or 5.
Here's one of my attempts to get the idea better
SELECT
p1.product_id
FROM
product_cats p1 JOIN product_cats p2 ON ( p2.product_id = p1.product_id )
JOIN product_cats p3 ON ( p3.product_id = p1.product_id )
JOIN product_cats p4 ON ( p4.product_id = p1.product_id )
WHERE
(p1.category_id=? AND p2.category_id=? AND p3.category_id=?)
OR
(p1.category_id=? AND p2.category_id=? AND p4.category_id=?)
To select the products that are both in category 1 and 2 you can use a query like this:
select
product_id
from
product_cats
where
cat_id in (1,2)
group by
product_id
having
count(distinct cat_id)=2;
an index on both product_id and cat_id should help improve performances:
alter table product_cats add index idx_product_cats (product_id, cat_id);
Using Mysql you can implement your logic by using group by and sum function with having clause
select product_id
from product_cats
group by product_id
having sum(cat_id = 1) > 0
and sum(cat_id = 2) > 0
and sum(cat_id in(3,4,5)) > 0
Above query will return product_ids which has cat_id 1 and cat_id 2 and cat_id can be 3,4,5

How to select records who match criteria defined in a join table?

I have this three tables:
products TABLE:
id:integer
name:string
features TABLE:
id:integer
name:string
features_products TABLE:
product_id:integer
feature_id:integer
The features_products TABLE tells me which features have each product. For example:
product_id feature_id
1 3
1 5
3 4
tells me the product 1 has features 3 and 5 and product 3 have feature 4, also, product 2 (if exists) doesn't have features.
My question is, how can I SELECT all the products from the products TABLE wich have determinated features? For example, SELECT products which have features 3 AND 5, or SELECT products which have feature 2
To select all the product ids for products which have both features 3 and 5:
SELECT product_id
FROM features_products
WHERE feature_id IN (3, 5)
GROUP BY product_id
HAVING COUNT(*) = 2
This assumes that there is a uniqueness contraint on (product_id, feature_id). If you want the entire row from the product table then use this in a subquery:
SELECT *
FROM products
WHERE product_id IN (
SELECT product_id
FROM features_products
WHERE feature_id IN (3, 5)
GROUP BY product_id
HAVING COUNT(*) = 2
)
Something similar to this:
select * from product
where id in ( select product_id from feature_products where feature id in ( 1,3 ) )
swap out the (1,3) for the features you want to include.
You could do the following the join those tables and get the data you need:
SELECT *
FROM products p JOIN features_products fp ON p.id = fp.product_id
JOIN features f ON f.id = fp.feature_id
WHERE f.id = 3 OR f.id = 5