Get reply numbers in mysql - 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

Related

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)

SQL subsets query

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%'

Efficient way of writing subquery join statement in mysql

I have a reviews table like the one below:
A user can up vote or down vote these reviews. For which, I am maintaining another table named review_counts. It looks like the one below:
Here, 1 means up vote and -1 is down vote.
Now, I am joining these two tables such that I will get reviews with total number of up vote counts and down vote counts all together. To achieve this, I have written the below query which is working fine.
SELECT * FROM `reviews` as x
LEFT JOIN
(SELECT count(votes) as vote_up, review_id FROM `review_counts` WHERE votes = 1) as y ON x.review_id = y.review_id
LEFT JOIN
(SELECT count(votes) as vote_down, review_id FROM `review_counts` WHERE votes = -1) as z ON x.review_id = z.review_id
For which, I get the result like this:
Now, the question is that I am using two JOINS on same table to get the vote up and vote down, Is there any other way through which I can achieve similar results using single join statement?
You could do this with a single LEFT JOIN and SUM(CASE WHEN...END):
CREATE TABLE reviews(
id INT,
review_id VARCHAR(10),
review VARCHAR(10)
)
CREATE TABLE review_counts(
id INT,
review_id VARCHAR(10),
votes INT
)
INSERT INTO reviews VALUES
(1, 'review1', 'Review 1'),
(2, 'review2', 'Review 2');
INSERT INTO review_counts VALUES
(1, 'Review1', 1),
(2, 'Review1', 1),
(3, 'Review1', 1),
(4, 'Review1', 1),
(5, 'Review1', 1),
(6, 'Review2', -1),
(7, 'Review2', -1),
(8, 'Review2', -1),
(9, 'Review2', -1),
(10, 'Review2', -1);
SELECT
r.*,
SUM(CASE WHEN c.votes = 1 THEN 1 ELSE 0 END) AS Vote_Up,
SUM(CASE WHEN c.votes = -1 THEN 1 ELSE 0 END) AS Vote_Down
FROM reviews r
LEFT JOIN review_counts c
ON c.review_id = r.review_id
GROUP BY r.id, r.review_id, r.review
DROP TABLE reviews
DROP TABLE review_counts
RESULT
id review_id review Vote_Up Vote_Down
----------- ---------- ---------- ----------- -----------
1 review1 Review 1 5 0
2 review2 Review 2 0 5

top contibuting users for particular category

I would like to find top contributors of particular state:
The candidates below have gathered particular votes for that state.
Find Top candidates for that states.
create table uservotes(id int, name varchar(50), vote int,state int);
INSERT INTO uservotes VALUES
(1, 'A', 34,1),
(2, 'B', 80,1),
(3, 'bA', 30,1),
(4, 'C', 8,1),
(5, 'D', 4,1),
(6, 'E', 14,2),
(7, 'F', 304,2),
(8, 'AA', 42,3),
(9, 'Ab', 6,3),
(10, 'Aa', 10,3);
States
create table states(state_id int, name_state varchar(50));
INSERT INTO states VALUES
(1, 'CA'),
(2, 'AL'),
(3, 'AZ'),
I am looking for:
for
CAL
2
1
3
4
5
based on the ranks of contribution.
How do I get that.
I really appreciate any help.
Thanks in Advance.
Code tried :
select uv.*, (#rank := #rank + 1) as rank
from uservotes uv,states s cross join
(select #rank := 0) const on uv.statesid = s.state_id
where name_state = 'CAL'
order by vote desc;
This is easy. You can use join and a group_concat():
select name_state, substring_index(group_concat(id order by votes desc), ',', 5)
from uservotes uv join
states s
on uv.state = s.state
group by name_state;
group_concat() will put all the id's in order with the highest votes first. substring_index() will extract the first five.
EDIT:
To get the top ranked users in one row, just add a where name_state = 'CA' to the above query.
To get them in different rows:
select uv.*
from uservotes uv join
states s
on uv.state = s.state
where state = 'CA'
order by votes desc
limit 5;

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
)