Efficient way of writing subquery join statement in mysql - 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

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)

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

Multiple INNER JOIN subqueries sql

I have the below query which comes from this post count number of items in a row in mysql which counts how many times in a row a student has been present/absent from a class.
SELECT
classlist.studentid,
student.name,
classStatus.name status,
COUNT(*) presentcnt
FROM
classlist
INNER JOIN student ON classlist.studentid=student.id
INNER JOIN classstatus ON classlist.presentid=classstatus.id
INNER JOIN (
SELECT
studentid,
max(CASE WHEN presentid=0 THEN id END) max_0,
max(CASE WHEN presentid=1 THEN id END) max_1
FROM classlist
GROUP BY studentid
) s
ON coalesce(classlist.id>least(max_0,max_1) AND classlist.id<=greatest(max_0,max_1),1) AND s.studentid=classlist.studentid
GROUP BY classlist.studentid
This works as expected,
STUDENTID NAME STATUS PRESENTCNT
111 John Present 1
222 Kate Absent 2
333 Matt Present 5
I want to extend the query so that I have a column showing if the student particpated in the class.
If I run an independent query I get the results I want
SELECT
classlist.studentid,
student.name,
participatedStatus.name status,
COUNT(*) participatedcnt
FROM
classlist
INNER JOIN student ON classlist.studentid=student.id
INNER JOIN participatedStatus ON classlist.participatedid=participatedStatus.id
INNER JOIN (
SELECT
studentid,
max(CASE WHEN participatedid=0 THEN id END) max_0,
max(CASE WHEN participatedid=1 THEN id END) max_1
FROM classlist
group by studentid
) s
ON coalesce(classlist.id>least(max_0,max_1)
AND classlist.id<=greatest(max_0,max_1),1)
AND s.studentid=classlist.studentid
group by classlist.studentid
STUDENTID NAME STATUS PARTICIPATEDCNT
111 John Yes 1
222 Kate No 2
333 Matt Yes 2
However I want to merge them into the one query so I get
STUDENTID NAME STATUS PRESENTCNT STATUS2 PARTICIPATEDCNT
111 John Present 1 Yes 1
222 Kate Absent 2 No 2
333 Matt Present 5 Yes 2
I am confused about how this can be achieved as I am selecting count *, how can I acheive this?
A sample of the data I am using is in this fiddle and below
CREATE TABLE classlist
(`id` int, `studentid` int, `subjectid` int, `presentid` int, `participatedid` int);
CREATE TABLE student
(`id` int, `name` varchar(4));
CREATE TABLE subject
(`id` int, `name` varchar(4));
CREATE TABLE classStatus
(`id` int, `name` varchar(8));
CREATE TABLE participatedStatus
(`id` int, `name` varchar(8));
INSERT INTO classlist (`id`, `studentid`, `subjectid`, `presentid`, `participatedid`)
VALUES (1, 111, 1, 1, 0), (2, 222, 3, 0, 0), (3, 333, 2, 1, 0), (4, 111, 4, 0, 0), (5, 111, 1, 1, 0), (6, 222, 3, 0, 0), (7, 333, 2, 1, 1), (8, 111, 4, 0, 0), (9, 111, 4, 0, 0), (10, 111, 4, 0, 0), (11, 111, 1, 1, 1), (12, 333, 3, 1, 0), (13, 333, 2, 1, 1), (14, 333, 3, 1, 1);
INSERT INTO student (`id`, `name`)
VALUES (111, 'John'),(222, 'Kate'),(333, 'Matt');
INSERT INTO subject (`id`, `name`)
VALUES (1, 'MATH'),(2, 'ENG'),(3, 'SCI'),(4, 'GEO');
INSERT INTO classStatus (`id`, `name`)
VALUES (0, 'Absent'), (1, 'Present');
INSERT INTO participatedStatus (`id`, `name`)
VALUES (0, 'No'),(1, 'Yes');
SELECT
studid,
studname,
status,
presentcnt,
status1,
participatedcnt FROM
(SELECT
classlist.studentid studid,
student.name studname,
classStatus.name status,
COUNT(*) presentcnt
FROM
classlist
INNER JOIN student ON classlist.studentid=student.id
INNER JOIN classstatus ON classlist.presentid=classstatus.id
INNER JOIN (
SELECT
studentid,
max(CASE WHEN presentid=0 THEN id END) max_0,
max(CASE WHEN presentid=1 THEN id END) max_1
FROM classlist
GROUP BY studentid
) s
ON coalesce(classlist.id>least(max_0,max_1) AND classlist.id<=greatest(max_0,max_1),1) AND s.studentid=classlist.studentid
GROUP BY classlist.studentid)x
JOIN
(SELECT
classlist.studentid,
student.name,
participatedStatus.name status1,
COUNT(*) participatedcnt
FROM
classlist
INNER JOIN student ON classlist.studentid=student.id
INNER JOIN participatedStatus ON classlist.participatedid=participatedStatus.id
INNER JOIN (
SELECT
studentid,
max(CASE WHEN participatedid=0 THEN id END) max_0,
max(CASE WHEN participatedid=1 THEN id END) max_1
FROM classlist
group by studentid
) s
ON coalesce(classlist.id>least(max_0,max_1)
AND classlist.id<=greatest(max_0,max_1),1)
AND s.studentid=classlist.studentid
group by classlist.studentid)y
ON x.studid=y.studentid
Fiddle

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;

count number of items in a row in mysql

I have a list of students that shows whether they were present or absent from a particular class.
CREATE TABLE classlist
(`id` int, `studentid` int, `subjectid` int, `presentid` int)
;
CREATE TABLE student
(`id` int, `name` varchar(4))
;
CREATE TABLE subject
(`id` int, `name` varchar(4))
;
CREATE TABLE classStatus
(`id` int, `name` varchar(8))
;
INSERT INTO classlist
(`id`, `studentid`, `subjectid`, `presentid`)
VALUES
(1, 111, 1, 1),
(2, 222, 3, 0),
(3, 333, 2, 1),
(4, 111, 4, 0),
(5, 111, 1, 1),
(6, 222, 3, 0),
(7, 333, 2, 1),
(8, 111, 4, 0),
(9, 111, 4, 0),
(10, 111, 4, 0),
(11, 111, 1, 1),
(12, 333, 3, 1),
(13, 333, 2, 1),
(14, 333, 3, 1)
;
INSERT INTO student
(`id`, `name`)
VALUES
(111, 'John'),
(222, 'Kate'),
(333, 'Matt')
;
INSERT INTO subject
(`id`, `name`)
VALUES
(1, 'MATH'),
(2, 'ENG'),
(3, 'SCI'),
(4, 'GEO')
;
INSERT INTO classStatus
(`id`, `name`)
VALUES
(0, 'Absent'),
(1, 'Present')
;
See Fiddle http://sqlfiddle.com/#!2/a2d93/5
I can count who is absent and present overall with something like the below.
SELECT
studentid,
students.name AS NAME,
SUM(presentid = 1) AS present,
SUM(presentid = 0) AS absent
FROM classlist
INNER JOIN student as students ON classlist.studentid=students.id
GROUP BY studentid, NAME
But I want to know how many classes in a row a student has attended/missed, so that the teachers can easily see if someone has missed a lot of time or someone is rewarded for good attendance etc. I have seen some posts which talk about streaks but none of them seem to match the way the data has been presented to me so I am not sure how to acheive this?
Just to be clear based on my sample data below the out put for the streaks should be.
(1, 111, 1, 1), /* John Present 1 times in a row */
(2, 222, 3, 0), /* Kate Absent 1 times in a row */
(3, 333, 2, 1), /* Matt Present 1 times in a row */
(4, 111, 4, 0), /* John Absent 1 times in a row */
(5, 111, 1, 1), /* John Present 1 times in a row */
(6, 222, 3, 0), /* Kate Absent 2 times in a row */
(7, 333, 2, 1), /* Matt Present 2 times in a row */
(8, 111, 4, 0), /* John Absent 1 times in a row */
(9, 111, 4, 0), /* John Absent 2 times in a row */
(10, 111, 4, 0), /* John Absent 2 times in a row */
(11, 111, 1, 1), /* John Present 1 times in a row */
(12, 333, 3, 1), /* Matt Present 3 times in a row */
(13, 333, 2, 1), /* Matt Present 4 times in a row */
(14, 333, 3, 1) /* Matt Present 5 times in a row */
/*Showing the latest status for each user*/
/* John Present 1 times in a row */
/* Kate Absent 2 times in a row */
/* Matt Present 5 times in a row */
John present 1.
Kate absent 2.
Matt Present 5.
This should give the count of the consecutive rows with the same value as the last row:
select
classlist.studentid,
student.name,
classStatus.name status,
count(*) presentcnt
from
classlist inner join student
on classlist.studentid=student.id
inner join classstatus
on classlist.presentid=classstatus.id
inner join (
select
studentid,
max(case when presentid=0 then id end) max_0,
max(case when presentid=1 then id end) max_1
from classlist
group by studentid
) s
on coalesce(classlist.id>least(max_0,max_1) and
classlist.id<=greatest(max_0,max_1),1)
and s.studentid=classlist.studentid
group by classlist.studentid
On the subquery I'm extracting the max id where presentid = 0 and the max id where presentid = 1.
On the outer query I'm extracting and counting all rows where id is > than the least of max_0 and max_1, and <= than the greatest of those two. No matter what the last value is, these are all the rows with the same value as the last one.
If either max_0 or max_1 is null, it means that all rows have just one value, 1 or 0, and we have to get all of them. If one of max_0 or max_1 is null, the whole conditions will be null as well. Using Coalesce( condition, 1) I'm returning all rows in that case.
Notice that I am grouping by classlist.studentid and showing some nonaggregated columns, but this is a situation in which it is allowed since all nonaggregated columns have the same value.
Do you want this?:
SELECT
studentid,
name,
SUM(present = 1) AS present,
SUM(present = 0) AS absent,
count(subject) as classTimes,
subject
FROM your_table GROUP BY studentid, name,subject
If id from classlist can be used for ordering then you will get your expected result with
SELECT
s.name,
CASE t1.presentid
WHEN 0 THEN 'absent'
WHEN 1 THEN 'present'
END state,
t1.pc
FROM (
SELECT
c.id,
c.studentid,
c.presentid,
#pcount := IF( #pid = presentid AND #sid = studentid, #pcount + 1, 1 ) as pc,
#pid := presentid,
#sid := studentid
FROM
classlist c
ORDER BY
studentid, id ) t1
JOIN student s
ON t1.studentid = s.id
WHERE
( t1.studentid, t1.id ) IN ( SELECT
studentid,
MAX( id )
FROM classlist
GROUP BY studentid );
SQL Fiddle DEMO
Try this:
SELECT A.studentid, s.name, IF(presentid = 1, 'present', 'absent') STATUS,
ABS(SUM(IF(presentid = 1, 1, -1))) AS presentcnt
FROM classlist A
INNER JOIN student s ON A.studentid = s.id
LEFT JOIN (SELECT MAX(id) id, studentid
FROM classlist GROUP BY studentid
) B ON A.studentid = B.studentid AND A.id = B.id
GROUP BY A.studentid
Check this SQL FIDDLE DEMO
OUTPUT
| STUDENTID | NAME | STATUS | PRESENTCNT |
-------------------------------------------
| 111 | John | present | 1 |
| 222 | Kate | absent | 2 |
| 333 | Matt | present | 5 |
I'm not sure what point about your question.
If this you want
John present 1.
Kate absent 2.
Matt Present 5.
U may try this:
SELECT
studentid,
students.name AS NAME,
SUM(presentid = 1) AS present,
SUM(presentid = 0) AS absent,
IF( SUM(presentid = 1)-SUM(presentid = 0)>=0,SUM(presentid = 1)-SUM(presentid = 0),SUM(presentid = 0)-SUM(presentid = 1)) AS aliase
FROM classlist
INNER JOIN student as students ON classlist.studentid=students.id
GROUP BY studentid, NAME