Select row dependent on other rows in the table - mysql

Apologies if this has been asked before but I really didn't know what to search and thus not sure how to word the question title so fingers crossed.
I'm building quite a common system where one product can have multiple categories and on the front-end, the user can search for products specifying multiple categories.
Say we had the following schema and data ( I cant post images yet, apologies, please see link )-
table name: product_categories
Data:
The products table is very standard - id, name, amount etc.
So product 1 has 2 categories.
If the user checks 2 tickboxes on the search page which represent category 2 and 3, how would I query that so only products which have both of those categories come back?
Something such as follows doesn't work, as there is no one row in that which has a category id of 2 AND a category id of 3:
SELECT * from product_categories where `category_id` = 2 AND `category_id` = 3;
I've tried using WHERE IN aswell, however, this would return product 1 even if i was looking for products which had both category id 2 and 6 ( for example ) which I don't want.
This is part of a bigger query however, I will be able to apply the solution if I manage to find one.

Something along this way should work
SELECT * FROM products WHERE
EXISTS (SELECT * FROM product_categories
WHERE product_id = products.id AND category_id = 2)
AND
EXISTS (SELECT * FROM product_categories
WHERE product_id = products.id AND category_id = 3)
...
Other solution is to generate JOIN for each category.
SELECT * FROM products
JOIN product_categories AS cs1 ON cs1.product_id = products.id
AND cs1.category_id = 2
JOIN product_categories AS cs2 ON cs2.product_id = products.id
AND cs2.category_id = 3
...
Unless you expect product_categories to be bigger than tens of millions of rows, both solutions should work reasonably, if correct indexes are created.

I think you need to use a sub-query here:
SELECT * from product_categories where id IN (SELECT id from product_categories where `category_id` = 2) AND IN (SELECT id from product_categories where `category_id` = 3)
I am not 100% sure this works (long time since I worked with SQL)

Many thanks for the suggestions guys, much appreciated. I think found a solution, this seems to produce the expected results but I'll be writing some more Unit Tests around my method to ensure it works.
The solution I found is as follows:
select * from products_categories where category_id in (2,3) HAVING count(distinct(id)) = 2
So this uses WHERE IN, however, using HAVING, it then checks the count of unique records returned and ensures it's equal to the amount of categories provided.
Cheers,
Darren

Related

How to select a distinct column value matching multiple criterea

I have a table containing attributes with the following structure:
id: bigint unsigned autoincrement
product_id: bigint foreign key
attribute_id: bigint foreign key
value: varchar(100)
I can query one criteria in the following fashion:
SELECT DISTINCT product_id FROM product_attributes WHERE attribute_id = ? AND value = ?
However I need to be able to find products that match multiple such criteria and would like to avoid multiple database queries for performance reasons. Simply adding more criteria with AND won't work since they will involve the same columns so for example:
SELECT DISTINCT product_id FROM product_attributes WHERE attribute_id = 1 AND value = 'Blue'
INTERSECT
SELECT DISTINCT product_id FROM product_attributes WHERE attribute_id = 2 AND value = '36'
INTERSECT
SELECT DISTINCT product_id FROM product_attributes WHERE attribute_id = 3 AND value = 'slim'
I have read about the INTERSECT statement which seems like it might work but I've read that MySQL doesn't support it, a search through MySQL 8 documentation produced no relevant result and the query above which I assume is correct produces an error on MySQL.
I've also read that something similar could be achieved with an inner join, but all the examples I've found involve multiple tables. There might also be an even better or simpler way to write the query that hasn't occurred to me. Or perhaps it's actually better to just send multiple queries and calculate the intersection outside of MySQL (though I would be very surprised) I appreciate greatly any help from anyone who has done something similar in the past.
You need to use aggregation to count the number of matching rows to the set of conditions and assert that it is equal to the number of conditions:
SELECT product_id
FROM product_attributes
WHERE (attribute_id, value) IN ((1, 'Blue'), (2, '36'), (3, 'slim'))
GROUP BY product_id
HAVING COUNT(*) = 3
This is the key / value store problem.
It's a slight pain in the neck to do what you want. Use JOIN operations to pivot the values into a row. Like this.
SELECT p.product_id,
color.value AS color,
size.value AS size,
cut.value AS cut
FROM ( SELECT DISTINCT product_id FROM product_attributes ) p
LEFT JOIN product_attributes color ON color.product_id = p.product_id
AND color.attribute_id = 1
LEFT JOIN product_attributes size ON size.product_id = p.product_id
AND size.attribute_id = 2
LEFT JOIN product_attributes cut ON cut.product_id = p.product_id
AND cut.attribute_id = 3
This generates a resultset with one row per product/color/size/cut combination
Then you can filter that resultset like this
SELECT *
FROM (
SELECT p.product_id,
color.value AS color,
size.value AS size,
cut.value AS cut
FROM ( SELECT DISTINCT product_id FROM product_attributes ) p
LEFT JOIN product_attributes color ON color.product_id = p.product_id
AND color.attribute_id = 1
LEFT JOIN product_attributes size ON size.product_id = p.product_id
AND size.attribute_id = 2
LEFT JOIN product_attributes cut ON cut.product_id = p.product_id
AND cut.attribute_id = 3
) combinations
WHERE color='Blue' AND size='36' AND cut='slim'
MySQL's query planner is smart enough that this doesn't run as slowly as you might guess, given the proper indexes.
The FROM clause generates a comprehensive list of product ids, from your product_attributes table to join to the specific attributes. If you have some other table for products, use that instead of the SELECT DISTINCT....

struggling with select with join and where clause

I'm running some SQL through phpmyadmin. I'm trying to do a query with 2 tables. The first xlsws_product has all the product info the second xlsws_category_assn contains two columns one with category id and the other with product id. The category names are in a third table but I don't need them for this.
What I am looking to do is select the rows from the product table that are in the categories with id 210, 218 and 370. This is what I tried so far:
SELECT *
FROM xlsws_product
JOIN xlsws_product_category_assn ON xlsws_product.id = xlsws_product_category_assn.product_id
WHERE
xlsws_product_category_assn.category_id = '210' OR '218' OR '370'`
The result for this gave me 24090 rows from a bunch of categories and there should only by a handful of rows in those categories. What's bizarre here is that there are 56474 rows in the product table so I'm not sure how the results are being filtered.
Just for the hell of it I tried limiting my query to just one category id with the following query:
SELECT *
FROM xlsws_product
JOIN xlsws_product_category_assn ON xlsws_product.id = xlsws_product_category_assn.product_id
WHERE xlsws_product_category_assn.category_id = '210'
This yielded zero rows...
I'm sure there is something simple I am missing but after spending a while searching for a solution I just can't figure it out. Thanks for the help.
If you need to find all the data which are in the category '210' OR '218' OR '370'
You can do as
SELECT * FROM
xlsws_product xp
JOIN xlsws_product_category_assn xpc ON xp.id = xpc.product_id
WHERE
xpc.category_id in (210,218,370)
If you need to find the products which have all the 3 given category you can do as
SELECT * FROM
xlsws_product xp
JOIN xlsws_product_category_assn xpc ON xp.id = xpc.product_id
WHERE
xpc.category_id in (210,218,370)
group by xp.id having count(*) = 3

MYSQL select results from 3 tables with an array of ids

Ok, so I have 3 mysql tables where I need to extract data from. Anything to do with joins really gets me stuck!
Table 1 = products (productid, name)
Table 2 = category (categoryid, name)
Table 3 = categoryproduct (categoryid, productid) - my join table
I have an array of product ids which I need to get a random selection of products that fall into the same categories as these products.
The idea is that the results of the query will display a section in my cart of similar/related products that the customer may like
So something like
SELECT name etc FROM table1
WHERE table2.categoryid of results of the query = table3.categoryid of current products
ORDER BY RAND()
LIMIT 3
How do I write that??
Assuming you're using PHP, following method will fetch 10 related products from database.
$productids = array(1002,789,999,203,321);
$sql = '
SELECT * FROM
products p JOIN categoryproduct pc
ON p.productid = pc.productid
WHERE pc.categoryid IN(
SELECT DISTINCT(categoryid) FROM
products inner_p JOIN categoryproduct inner_pc
ON inner_p.productid = inner_pc.productid
WHERE inner_p.productid IN('.implode(',',$productids).')
)
ORDER BY RAND()
LIMIT 10';
If i have understood your problem correctly then this query may help. Here instead of subquery you can give comma separated string which contains categoryid of different products selected by the user.
select p.name
from products p,categoryproduct cp
where p.productid=cp.productid
and cp.categorid in(
select categoryid
from cartitems)
order by RAND()

MySQL join with 3 tables trick

I can't obtain the result I want, let me explain:
I have two tables :
CATEGORIES
PROJECTS
and between them another table which makes the link:
ASSIGNED_CAT with 2 columns: PROJECT_ID and CATEGORY_ID
In ASSIGNED_CAT I get the IDs of CATEGORIES and PROJECTS linked together.
Now I need to get the CATEGORIES which contains PROJECTS and if not don't show them.
Example:
If CATEGORY 1 have projects display the CATEGORY NAME and if not don't show.
Any idea to trick this? I've tried many SQL JOIN without success.
I guess you are missing the EXISTS clause.
The following query selects all categories, for which at least one row exists in the intersection table.
SELECT category_name
FROM categories c
WHERE EXISTS (
SELECT 1
FROM assigned_cat ac
WHERE ac.category_id = c.category_id
)

SQL query finding best categories match

I have categories and multiple categorization for my Items. How to find, for specific Item, other Items that have same categories, ordered by most categories matching (aka best match)?
My table structure is roughly:
Item Table
ID
Name
...
Category Table
ID
Name
...
Categorization Table
ID
Item_ID
Category_ID
...
To find all Items having similar categories, for example, I use
SELECT `items`.*
FROM `items`
INNER JOIN `categorizations` c1
ON c1.`item_id` = `items`.`id`
INNER JOIN `categorizations` c2
ON c2.`item_id` = <Item_ID>
WHERE `c1.`category_id` = c2.`category_id`
This should produce a table of counts of category matches between each pair of items that share at least one category.
select i1.item_id,i2.item_id,count(1)
from items i1
join categorizations c1 on c1.item_id=i1.item_id
join categorizations c2 on c2.category_id=c1.category_id
join items i2 on c2.item_id=i2.item_id
where i1.item_id <> i2.item_id
group by i1.item_id,i2.item_id
order by count(1)
I suspect that it may be a bit slow, though. I don't have an instance of MySQL at the moment to try it out.
Something like:
select item_id, count(id)
from item_category ic
where exists(
select category_id
from item_category ic2
where ic2.item_id = #item_id
and ic2.category_id = ic.category_id )
where item_id <> #item_id
group by item_id
order by count(item_id) desc
An alternative method which I have just implemented to solve this problem is using bitwise operators to speed things up. In MySQL this method only works if you have 64 or less categories as the bit functions are 64 bit.
1) Assign each category a unique integer value which is a power of 2.
2) For each item sum the category values that the item is in to create a 64 bit int representing all of the categories that the item is in.
3) To compare an item to another do something like:
SELECT id, BIT_COUNT(item1categories & item2categories) AS numMatchedCats FROM tablename HAVING numMatchedCats > 0 ORDER BY numMatchedCats DESC
The BIT_COUNT() function might be MySQL specific so an alternative may well be required for any other DB.
MySQL bit functions used are explained here:
http://dev.mysql.com/doc/refman/5.0/en/bit-functions.html