SQL subsets query - mysql

I am having trouble creating a query for an SQL table. The query I am trying to create shows the number of products within the category of "clothes" and does not show accessories for example a list of products that are entered as T-shirts or sweatshirts.
Here is the tables that have been created:
CREATE DATABASE IF NOT EXISTS product_list;
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS product_categories;
DROP TABLE IF EXISTS categories;
CREATE TABLE products (
product_id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(50) DEFAULT NULL,
active BOOL DEFAULT NULL
);
CREATE TABLE categories (
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50),
structure VARCHAR(50)
);
CREATE TABLE product_categories (
product_id INT,
category_id INT,
PRIMARY KEY(product_id, category_id)
);
INSERT INTO products VALUES
(NULL, "Blue Sweatshirt", false),
(NULL, "Short Sleeve T-Shirt", true),
(NULL, "White Vest", true),
(NULL, "Black Hairclip", true),
(NULL, "Knitted Hat", false),
(NULL, "Grey Sweatshirt", true),
(NULL, "Tartan Scarf", true);
INSERT INTO categories VALUES
(NULL, "Sweatshirts", "Clothes>Sweatshirts"),
(NULL, "T-Shirts", "Clothes>T-Shirts"),
(NULL, "Accessories", "Accessories"),
(NULL, "Winter", "Clothes>Winter"),
(NULL, "Vests", "Clothes>Vests");
INSERT INTO product_categories VALUES
(1, 1), (2, 2), (3, 5), (3, 4), (4, 3), (5, 3), (5, 4), (6, 1), (7, 3), (7, 4);

If I understand correctly, this is a set-within-sets query. You are looking for products that have at least one "clothes" category, and none of the categories are not clothes. I approach this using group by and having because it is quite flexible:
select pc.product_id
from Product_categories pc join
categories c
on pc.category_id = c.category_id
group by pc.product_id
having sum(case when c.structure like 'Clothes%' then 1 else 0 end) > 0 and
sum(case when c.structure not like 'Clothes%' then 1 else 0 end) = 0;
If you just want the count, then you can use this as a subquery and use count(*).
EDIT:
A small note. The question is now tagged with MySQL, which has convenient short-hand for the having clause:
having sum(c.structure like 'Clothes%') > 0 and
sum(c.structure not like 'Clothes%') = 0;

Try this query
select * from products a
join Product_categories b on a.product_id=b.product_id
join categories c on b.category_id=b.category_id
where c.name like '%Clothes%'

Related

php mysql query selection

I have 3 tables like these
news_table:
newsID int auto_increment,
title varchar(256),
records are like:
(1, 'some_title' ),
(2, 'some_title' ),
(3, 'some_title' ),
(4, 'some_title' ),
(5, 'some_title' ),
(6, 'some_title' ),
(7, 'some_title' ),
and so on...
news_exclude_table:
id int auto_increment
newsID int
records are like:
('', 2),
('', 3),
('', 7),
('', 10)
and so on...
I removed the auto_increment value. The number is the corresponding newsID
news_like_table:
id int auto_increment,
newsID int
memberID int
records are like:
('', '2', '110'),
('', '4', '110'),
('', '4', '115'),
and so on
I want to select all records from news_table, news_like_table that match a certain condition, but I want to exclude the ones those are also in news_exclude_table
So the selection result of query should have newsID "4" only
I can't figure out the mysql query to use with PHP in this case
select news_table.* from news_table, news_like_table, news_exclude_table where news_table.newsID = news_like_table.newsID and news_like_table.memberID='110' and news_table.newsID not in news_exclude_table.newsID
It should select newsID="4" and leave out newsID="2" because "2" is also in news_exclude_table
I can't get this working.
Kindly help.
Thanks
Switch to explicit joins and either leave the exclusion table out of the join and use a not in () with a subquery, or left join the exclusions table on the other 2, and use not null in the where clause. I'll show you the latter one:
select news_table.*
from news_table
inner join news_like_table on news_table.newsID = news_like_table.newsID
left join news_exclude_table on news_exclude_table.newsID=news_table.newsID
where news_like_table.memberID='110' and news_exclude_table.newsID is null

Returning records which only have one specific many to many relation

Given this structure
CREATE TABLE locations
(`id` int, `Name` varchar(128))
;
INSERT INTO locations
(`id`, `Name`)
VALUES
(1, 'Location 1'),
(2, 'Location 2'),
(3, 'Location 3')
;
CREATE TABLE locations_publications
(`id` int, `publication_id` int, `location_id` int)
;
INSERT INTO locations_publications
(`id`, `publication_id`, `location_id`)
VALUES
(1, 1, 1),
(2, 2, 1),
(3, 2, 2),
(4, 1, 3)
;
I would like to find only Location 2 based on the fact that it has only one relation with a publication_id = 2.
It should not return location one due to the fact that it has two relation rows.
This is sort of what I'm looking for but of course dosnt work because it limits the relationship to where publication_id = 2.
select * from locations
join locations_publications on locations_publications.location_id = locations.id
where locations_publications.publication_id = 2
group by (locations.location_id)
having count(*) = 1
You can do this with aggregation:
select location_id
from locations_publications
group by location_id
having count(*) = 1
If a location might have multiple records with the same publication, change the having criteria to count(distinct publication_id) = 1
Given your edits, you can use conditional aggregation for that:
select location_id
from locations_publications
group by location_id
having count(*) = sum(case when publication_id = 2 then 1 else 0 end)

Table Architecture Difficulty with Query

I'm working on a practice problem with DDL as follows:
CREATE TABLE people (
id SMALLINT NOT NULL AUTO_INCREMENT,
first_name VARCHAR(50),
last_name VARCHAR(50),
PRIMARY KEY (id)
)
;
CREATE TABLE cd (
id SMALLINT NOT NULL AUTO_INCREMENT,
artist VARCHAR(50),
title VARCHAR(50),
PRIMARY KEY(id),
owner SMALLINT,
FOREIGN KEY (owner) REFERENCES people(id)
)
;
CREATE TABLE lend (
id SMALLINT NOT NULL AUTO_INCREMENT,
cd_id SMALLINT,
lend_to SMALLINT,
FOREIGN KEY (lend_to) REFERENCES people(id),
FOREIGN KEY (cd_id) REFERENCES cd(id),
lend_date DATE DEFAULT '0000-00-00',
PRIMARY KEY(id)
)
;
INSERT INTO people (id, first_name, last_name) VALUES
(1, 'Brett', 'CEO'),
(2, 'Jeff', 'President'),
(3, 'Beta', 'Media'),
(4, 'Casey', 'Content')
;
INSERT INTO cd (id, artist, title, owner) VALUES
(1, 'The xx', 'Coexist', 2),
(2, 'ACDC', 'High Voltage', 1),
(3, 'Bjork', 'Cocoon', 3),
(4, 'Ella Fitzgerald', 'Ella Sings Gershwin', 4),
(5, 'Fever Ray', 'Live in Lulea', 2),
(6, 'Tom Waits', 'Rain Dogs', 4),
(7, 'Howlin Wolf', 'Smokestack Lightning', 1),
(8, 'Tupac', 'Poetic Justice', 4)
;
INSERT INTO lend (id, cd_id, lend_to, lend_date) VALUES
(1, 2, 3, '2014/01/03'),
(2, 3, 1, '2014/04/02'),
(3, 7, 4, '2013/12/22'),
(4, 4, 2, '2014/01/03')
;
I want my query to show who the CD is lent to. I can get the ID from the lend table, but want to display the full name of the individual lending it from the people table. Do I need to rework the design of how the lend table connects to the people table, or just use some sort of case function in the query? Below is my query so-far where I'm getting the l.lent_to and want to be showing the CONCAT(p.first_name, ' ', p.last_name) who the CD is lent to.
SELECT /*cd.id,*/
CONCAT(p.first_name, ' ', p.last_name) 'CD OWNER',
cd.title,
l.lend_to,
p.id ,
(
CASE
WHEN l.lend_to IS NULL
THEN 'Not Lent'
ELSE DATE_FORMAT(l.lend_date, '%m-%d-%Y')
END
) 'LEND DATE',
(
CASE
WHEN l.lend_to IS NULL
THEN 'Not Lent'
ELSE TIMESTAMPDIFF(day, l.lend_date, NOW())
END
) 'DAYS LENT'
FROM
people p
LEFT JOIN cd cd
ON p.id = cd.owner
LEFT JOIN lend l
ON cd.id = l.cd_id
LEFT JOIN lend l1
on p.id = l1.lend_to
;
See if this query gives you the basic information you are looking for
select c.title as 'Title', c.artist as 'Artist', o.first_name as 'Owner',
l.lend_date as 'Lend Date', p.first_name as 'Lender'
from cd c
left outer join people o on c.owner = o.id
left outer join lend l on c.id = l.cd_id
left outer join people p on l.lend_to = p.id
You can add additional switch logic to refine the result, if this is what you are looking for.
I've resolved the issue with a data architecture redesign. Take a look if interested.
http://sqlfiddle.com/#!2/b6158/3

Get reply numbers in mysql

I am working on a product review page where it will display several submitted reviews as well as the number of comments to each of them.
I thought I could use
SELECT title AS review_title,COUNT(id_group) AS Approved_reply_number
WHERE approved <> '0'
GROUP BY id_group`
but read somewhere that it isn't possible to copy the id values into another row on the insert process. So if someone submits a review, the id_group field for the reviews has to be left empty.
Here is the table example:
CREATE TABLE product_review
(`ID` int, `title` varchar(21), `id_group` int,`approved` int)
;
INSERT INTO product_review
(`ID`, `title`, `id_group`,`approved`)
VALUES
(1, 'AAA', Null,1),
(2, 'BBB', 1,1),
(3, 'CCC', Null,1),
(4, 'DDD', 3,0),
(5, 'EEE', 1,1),
(6, 'FFF', Null,1),
(7, 'GGG', 6,1),
(8, 'HHH',1,1),
(9, 'III', 6,1)
;
Those that are Null in id_group are the submitted reviews. The rest are replies and they contain the id of their corresponding reviews. I was wondering how can I get an output like this:
review_title approved_reply_number
AAA 3
CCC 0
FFF 2
You can use a self join and count query with group by and also a where clause to filter out reviews only
select t.title review_title ,count(*) approved_reply_number
from product_review t
left join product_review t1 on(t.id = t1.id_group)
where t.id_group is null
group by t.id
Demo

SQL "where IN" query in a many to many relation of 2 tables

I maybe ask a relatively simple question. But I cannot find a solution to this. It's a matter of two tables MANY TO MANY, so there's a third table between them. The schema below:
CREATE TABLE `options` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `options` (`id`, `name`) VALUES
(1, 'something'),
(2, 'thing'),
(3, 'some option'),
(4, 'other thing'),
(5, 'vacuity'),
(6, 'etc');
CREATE TABLE `person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `person` (`id`, `name`) VALUES
(1, 'ROBERT'),
(2, 'BOB'),
(3, 'FRANK'),
(4, 'JOHN'),
(5, 'PAULINE'),
(6, 'VERENA'),
(7, 'MARCEL'),
(8, 'PAULO'),
(9, 'SCHRODINGER');
CREATE TABLE `person_option_link` (
`person_id` int(11) NOT NULL,
`option_id` int(11) NOT NULL,
UNIQUE KEY `person_id` (`person_id`,`option_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `person_option_link` (`person_id`, `option_id`) VALUES
(1, 1),
(2, 1),
(2, 2),
(3, 2),
(3, 3),
(3, 4),
(3, 5),
(4, 1),
(4, 3),
(4, 6),
(5, 3),
(5, 4),
(5, 5),
(6, 1),
(7, 2),
(8, 3),
(9, 4)
(5, 6);
The idea is as follow: I would like to retrieve all people who have a link to option_id=1 AND option_id=3.
The expected result should be one person: John.
But I tried with something like that, which doesn't work because it returns also people who have 1 OR 3:
SELECT *
FROM person p
LEFT JOIN person_option_link l ON p.id = l.person_id
WHERE l.option_id IN ( 1, 3 )
What is the best practice in this case?
//////// POST EDITED: I need to focus on an other important point ////////
And what if we add a new condition with NOT IN? like:
SELECT *
FROM person p
LEFT JOIN person_option_link l ON p.id = l.person_id
WHERE l.option_id IN ( 3, 4 )
AND l.option_id NOT IN ( 6 )
In this case, the result should be FRANK, because PAULINE who has also 3 and 4, have the option 6 and we don't want that.
Thanks!
This is a Relational Division Problem.
SELECT p.id, p.name
FROM person p
INNER JOIN person_option_link l
ON p.id = l.person_id
WHERE l.option_id IN ( 1, 3 )
GROUP BY p.id, p.name
HAVING COUNT(*) = 2
SQLFiddle Demo
if a unique constraint was not enforce on option_id for every id, a DISTINCT keyword is required to filter unique option_ID
SELECT p.id, p.name
FROM person p
INNER JOIN person_option_link l
ON p.id = l.person_id
WHERE l.option_id IN ( 1, 3 )
GROUP BY p.id, p.name
HAVING COUNT(DISTINCT l.option_id) = 2
SQL of Relational Division
Use GROUP BY and COUNT:
SELECT p.id, p.name
FROM person p
LEFT JOIN person_option_link l ON p.id = l.person_id
WHERE l.option_id IN ( 1, 3 )
GROUP BY p.id, p.name
HAVING COUNT(Distinct l.option_id) = 2
I prefer using COUNT DISTINCT in case you could have the same option id multiple times.
Good luck.
It may not be the best option, but you could use a 'double join' to the person_option_link table:
SELECT *
FROM person AS p
JOIN person_option_link AS l1 ON p.id = l1.person_id AND l1.option_id = 1
JOIN person_option_link AS l2 ON p.id = l2.person_id AND l2.option_id = 3
This ensures that there is simultaneously a row with option ID of 1 and another with option ID of 3 for the given user.
The GROUP BY alternatives certainly work; they might well be quicker too (but you'd need to scrutinize query plans to be sure). The GROUP BY alternatives scale better to handle more values: for example, a list of the users with option IDs 2, 3, 5, 7, 11, 13, 17, 19 is fiddly with this variant but the GROUP BY variants work without structural changes to the query. You can also use the GROUP BY variants to select users with at least 4 of the 8 values which is substantially infeasible using this technique.
Using the GROUP BY does require a slight restatement (or rethinking) of the query, though, to:
How can I select people who have 2 of the option IDs in the set {1, 3}?
How can I select people who have 8 of the option IDs in the set {2, 3, 5, 7, 11, 13, 17, 19}?
How can I select people who have at least 4 of the option IDs in the set {2, 3, 5, 7, 11, 13, 17, 19}?
For the "has not these ids" part of the question, simply add a WHERE clause:
WHERE person_id NOT IN
(
SELECT person_id
FROM person_option_link
WHERE option_id = 4
)