MySQL Update rows with double left join, limiting first match - mysql

I have three tables (SQLFiddle with tables created)
Orange text is what I need to get by comparing Products.name with Filters.filter.
I figured out that substring match can be done like this:
on Products.name LIKE CONCAT('%',Filters.filter,'%');
I only need the first filter match to be used. So "Mushroom soup" will match "Soup" and not "Mushroom".
What is best approach for this task?
A tested SQLFiddle link, if possible, please.
What I tried: (feel free to skip everything below)
Tried double join:
update Products
left join Filters on Products.name LIKE CONCAT('%',Filters.filter,'%')
join Categories on Filters.category_name = Categories.name
set Products.category_id = Categories.id, Products.filter = Filters.filter;
But this doesn't result in first match being used, because first join returned all filter matches (multiple lines per 1 product), and after second join categories are from last match.
For example: Mushroom soup got into "Mushroom-Ingredients" filter\category, instead of "Soup\Meals".
Also tried join + order by:
select Products.name, Filters.*
from Products
left join Filters on Products.name LIKE CONCAT('%',Filters.filter,'%')
group by Products.name;
This returns the first match per product as I needed, but I can't do a second left-join in the same query, neither can I get "group by" to work after "update" function instead of "select".
For when sqlfiddle collapses:
CREATE TABLE Products
(
`name` varchar(30),
`category_name` varchar (30),
`category_id` int
);
INSERT INTO Products (`name`)
VALUES ('Mushrooms'), ('Can of mushrooms'),
('Can of soup'), ('Mushroom soup'),
('Tomato soup'), ('Tomato');
CREATE TABLE Filters
(
`filter` varchar(30),
`category_name` varchar(30)
);
INSERT INTO Filters (`filter`, `category_name`)
VALUES ('Can of', 'Canned food'), ('Soup', 'Meals'),
('Mushroom', 'Ingredients'), ('Tomato', 'Ingredients');
CREATE TABLE Categories
(
`id` int,
`name` varchar(30)
);
INSERT INTO Categories (`id`, `name`)
VALUES (1, "Canned food"), (2, 'Ingredients'), (3, 'Meals');

You made the task quite easy with your sqlfiddle as well as your attempt to solve the problem with Select query.
It works the way you want I guess and all I need to do is to add one more left join with category table (IDK why you were unable to join Category as it's working properly).
So. I've edited your select query as follows:
select Products.name, Filters.*,Categories.id from Products
left join Filters
on Products.name LIKE CONCAT('%',Filters.filter,'%')
left join Categories
on Categories.name = Filters.category_name
GROUP BY Products.name;
You will get the results you want with this query.
Now, in order to update Products table with the result of this query, you can do following:
update Products
left join Filters
on Products.name LIKE CONCAT('%',Filters.filter,'%')
left join Categories
on Categories.name = Filters.category_name
set Products.category_name = Filters.category_name,
Products.category_id = Categories.id;
Click here for Working Demo
Hope it helps!

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....

Mysql select query NOT IN in same table

I have a table called products with 3 columns. One of the column is product_id. This column contains duplicate product ids. From another source I was getting some other product ids. I want to display the not existing product ids count . For example I am getting product ids 12,13,14,15,16 from one source. In my products table I am having 12,13,14. So now I want to display the count of not existing product ids . In this case that should be 2. To achieve that I tried with below query.
SELECT COUNT(DISTINCT product_id)
FROM products p1
WHERE NOT EXISTS
(SELECT COUNT(DISTINCT product_id)
FROM products p2
WHERE p2.product_id IN (12,13,14,15,16) AND p1.product_id = p2.product_id)
But I am getting empty result. Any help would be greatly appreciated.
If the list of products comes from an application, and you know the length of the list as well (in this case 5) you can find the number of non-existent products in the table like this:
SELECT 5 - COUNT(DISTINCT product_id) AS non_existent_products
FROM products
WHERE product_id IN (12, 13, 14, 15, 16)
Output
non_existent_products
2
If you don't know the length of the list, you can figure it out using e.g.
LENGTH('(12, 13, 14, 15, 16)') - LENGTH(REPLACE('(12, 13, 14, 15, 16)', ',', '')) + 1
Demo on dbfiddle
Update
If the other product_id values are coming from another query (e.g. SELECT product_id FROM othertable) you can find the count of products in that result that are not in the products table using a LEFT JOIN of the results from that query with the products table and taking the difference in COUNTs of product_id from the two. Something like this:
SELECT COUNT(DISTINCT q.product_id) - COUNT(DISTINCT p.product_id) AS non_existent_products
FROM (SELECT product_id FROM products2) q
LEFT JOIN products p ON p.product_id = q.product_id
Demo on dbfiddle
Let's assume, we have 2 tables having the following values:
INSERT INTO `products` (`product_id`)
VALUES (1),(2),(3),(4),(1),(3),(1),(2),(5)
;
INSERT INTO `other_products` (`product_id`)
VALUES (2),(4)
;
Then this query gives you the count of each id 1, 3 and 5 (not existing in other_products)
SELECT `p`.`product_id`, COUNT(`p`.`product_id`) `count`
FROM `products` `p`
LEFT JOIN `other_products` `o`
USING (`product_id`)
WHERE `o`.`product_id` IS NULL
GROUP BY `p`.`product_id`
;
while this one gives you the total distinct ids not existing in the other table
SELECT count(DISTINCT `p`.`product_id`)
FROM `products` `p`
LEFT JOIN `other_products` `o`
USING (`product_id`)
WHERE `o`.`product_id` IS NULL
;
See Live Demo
you can use something like this
select count(A.product_id) from (select distinct product_id from product where product_id not in (1,2)) AS A;
here you are selecting distinct product_id from table product where product id's are not equals to 1 and 2 then creating an alias for the result as A.
A acts as temporary table, so you need to use A.product_id to select.
Sorry for the bad english, but it i hope it meets requirement of question.
The problem you are having it's that your sub query always returns a value (some times returns 0) so when you do NOT EXISTS () it's allways FALSE, because you allways get an answer.
You should change NOT EXISTS by 0> like this
SELECT COUNT(DISTINCT product_id)
FROM products p1
WHERE 0>
(SELECT COUNT(DISTINCT product_id)
FROM products p2
WHERE p2.product_id IN (12,13,14,15,16) AND p1.product_id = p2.product_id)

Can't get rid of repeats in mysql query

I'm completing some queries for my databases class and I've ran into one that I can't seem to get.
It says: "For the student with ID "20084" (or any other value) show the total number of credits for the courses taken. Don't display the tot_creds value from the student table, you should use SQL aggregation on courses taken by the student."
I have looked for some answers online but none of them truly convinced me and basically all of them gave me different results.
I've got this on my own:
select sum(credits)
from (course join section using(course_id)) join (takes join student using(ID))
using (course_id, sec_id, semester, year)
where student.ID = 20084;
The problem I have is that the student has repeated a couple courses and the query returns the credits of those repeated courses as well. I've tried putting distinct in front of sum(credits) but the answer is the same.
Is there anything I'm missing?
Thanks.
This should return what you are looking for. In the sub-select, I used MAX() even though the values of repeated items would be the same just so it would return one result from repeated rows. This sub select will return one row for each instance of a course, so the sum of that would not include the repeated courses.
SELECT
SUM(`a`.`credits`) as `credits`
FROM (
SELECT
MAX(`course`.`credits`) AS `credits`
FROM `student`
JOIN `takes`
ON `takes`.`ID` = `student`.`ID`
JOIN `section`
USING (`course_id`,`sec_id`,`semester`,`year`)
JOIN `course`
ON `course`.`course_id` = `section`.`course_id`
WHERE `student`.`ID` = 20084
GROUP BY `section`.`course_id`
ORDER BY `takes`.`grade` DESC
) AS `a`
To test the sub-query, try running this query. It should return a list of all courses taken, with just one row per course:
SELECT
`section`.`course_id`,
`section`.`sec_id`,
`section`.`semester`,
`section`.`year`,
MAX(`course`.`credits`) AS `credits`
FROM `student`
JOIN `takes`
ON `takes`.`ID` = `student`.`ID`
JOIN `section`
USING (`course_id`,`sec_id`,`semester`,`year`)
JOIN `course`
ON `course`.`course_id` = `section`.`course_id`
WHERE `student`.`ID` = 20084
GROUP BY `section`.`course_id`
ORDER BY `takes`.course_id`,`takes`.`grade` DESC

MYSQL select query with count condition and join

I want to know the difference between below queries. Both returns the same value. I have idea about INNER JOIN and but i can't understand how first query returns the same value.
SELECT *
FROM
`products`
WHERE
(
SELECT count(*)
FROM `categories`
WHERE `products`.`category_id` = `categories`.`id` AND `slug` = 'aprons'
) >= 1
AND `slug` <> 'products-1'
SELECT products.*
FROM
products
INNER JOIN categories ON categories.id = products.category_id
WHERE
products.slug <> 'products-1'
AND categories.slug = 'aprons'
the second query is more efficient. and have use inner join . first one is used a sub query. sub query is not necessary to verify to categories existence. same result are displayed but on point of optimisation second query is more efficient.
when thinking the first query
(
SELECT count(*)
FROM categories
WHERE products.category_id = categories.id AND slug = 'aprons'
) >= 1
this part is execute for every single products record . simply categories is iterated for every single record in products table.
The first query will be more efficient since the tables will not need to be joined. You may not have noticed a big difference in execution time, but if the tables were much larger, you would have noticed that the first query returned much faster.
An Inner Join will combine the two tables based on the rows that have values in both tables. In other words, there will not be any null values in the resulting table.

how to query unrelated tables without UNION, JOIN or multiple selection

My issue is that I would like to query several different columns from 2 different tables, it is for a search feature where I want the results to show a mix of categories and products in the array.
Correct me if Im wrong, but I cant use JOIN because the tables are not related in any way.
I cant use UNION because there is no consistency in the amount of columns I want choose from per table.
And I tried simply selecting the combination of columns from the combination of tables but that multiplies everything. However Ill show you the code for the last option as perhaps there is a way of tweaking it to get it to work...
SELECT pd.product_id, pd.name, pd.meta_keyword, pd.description, cd.category_id,
cd.name AS 'category_name'
FROM oc_product_description AS pd, oc_category_description AS cd
WHERE pd.name LIKE '%$word%'
OR cd.name LIKE '%$word%';
This returns the entire list of oc_category_description category_name values for every successful hit of pd.name whereas I ONLY want successful results of either the product name or the category name.
If you want UNION, you have to add all columns from the various tables and NULL values in the columns that are not supplied in other tables:
SELECT pd.product_id, -- pd columns
pd.name ,
pd.meta_keyword,
pd.description,
NULL AS category_id, -- cd columns
NULL AS category_name
FROM oc_product_description as pd
WHERE pd.name LIKE '%$word%'
UNION ALL
SELECT NULL,
NULL,
NULL,
NULL,
cd.category_id ,
cd.name AS category_name
FROM oc_category_description as cd
WHERE cd.name LIKE '%$word%' ;