MySQL Aggregration? - mysql

I have the following two tables
Table: items
ID | TITLE
249 | One
250 | Two
251 | Three
And I have voting for these:
Table: votes
VID | IID | userid | votes
01 | 249 | 6 | 5
02 | 249 | 7 | -5
03 | 249 | 8 | 5
04 | 249 | 9 | 5
05 | 250 | 6 | -5
06 | 250 | 7 | -5
07 | 250 | 8 | 5
-5 means a DOWNVOTE, and +5 means an upvote. Assuming I am logged in as user 6, what SQL query will give me:
Table: result
ID | TITLE | TOTALVOTES | UPVOTES | DOWNVOTES | CURRENTUSERVOTED
249 | One | 4 | 3 | 1 | 1
250 | Two | 3 | 1 | 2 | 1
251 | Three | 0 | 0 | 0 | 0

Use CASE expressions within your aggregate functions:
SELECT a.ID,
a.TITLE,
COUNT(b.IID) AS TOTALVOTES,
COUNT(CASE WHEN b.votes = 5 THEN 1 END) AS UPVOTES,
COUNT(CASE WHEN b.votes = -5 THEN 1 END) AS DOWNVOTES,
COUNT(CASE WHEN b.userid = 6 THEN 1 END) AS CURRENTUSERVOTED
FROM items a
LEFT JOIN votes b ON a.ID = b.IID
GROUP BY a.ID,
a.TITLE

this should work
select I.Id, I.Title, count(*) as TotalVotes,
sum(case when votes > 0 then 1 else 0 end) as UPVOTES,
sum(case when votes < 0 then 1 else 0 end) as DOWNVOTES,
case when sum(Case when userid = #userloged then 1 else 0 end) = 1 then 1 else 0 end as CURRENTUSERVOTED
from items I
LEFT JOIN Votes V
on I.Id = V.IID
group by I.ID, I.Title
probably are better ways to get CURRENTUSERVOTED

Related

MySQL: SUM(CASE WHEN(...)) + GROUP BY. Why details of dataset cannot show even with GROUP BY?

Credit: Leetcode 1308. Running Total for Different Genders
Sample Score table:
+-------------+--------+------------+--------------+
| player_name | gender | day | score_points |
+-------------+--------+------------+--------------+
| Aron | F | 2020-01-01 | 17 |
| Alice | F | 2020-01-07 | 23 |
| Bajrang | M | 2020-01-07 | 7 |
| Khali | M | 2019-12-25 | 11 |
| Slaman | M | 2019-12-30 | 13 |
| Joe | M | 2019-12-31 | 3 |
| Jose | M | 2019-12-18 | 2 |
| Priya | F | 2019-12-31 | 23 |
| Priyanka | F | 2019-12-30 | 17 |
+-------------+--------+------------+--------------+
Requirement: Write an SQL query to find the (cumulative) total score for each gender at each day.
Order the result table by gender and day. The sample Result table is as follows:
+--------+------------+-------+
| gender | day | total |
+--------+------------+-------+
| F | 2019-12-30 | 17 |
| F | 2019-12-31 | 40 |
| F | 2020-01-01 | 57 |
| F | 2020-01-07 | 80 |
| M | 2019-12-18 | 2 |
| M | 2019-12-25 | 13 |
| M | 2019-12-30 | 26 |
| M | 2019-12-31 | 29 |
| M | 2020-01-07 | 36 |
+--------+------------+-------+
My code is as follows. I cannot figure out why it only returns one row[{"headers": ["gender", "day", "total"], "values": [["F", "2019-12-30", 184]]}], even with GROUP BY.
SELECT s1.gender, s1.day,
SUM(CASE WHEN s1.day < s2.day AND s1.gender = s2.gender THEN s1.score_points ELSE 0 END) AS total
FROM Scores s1, Scores s2
GROUP BY s1.gender AND s1.day
ORDER BY s1.gender AND s1.day
Thank you so much if anyone can help me out!!
You should do a proper join with an ON clause and use valid syntax for GROUP BY and ORDER BY:
select s1.gender, s1.day, sum(s2.score_points) total
from scores s1 inner join scores s2
on s2.gender = s1.gender and s2.day <= s1.day
group by s1.gender, s1.day
order by s1.gender, s1.day
See the demo.
Results:
| gender | day | total |
| ------ | -----------| ----- |
| F | 2019-12-30 | 17 |
| F | 2019-12-31 | 40 |
| F | 2020-01-01 | 57 |
| F | 2020-01-07 | 80 |
| M | 2019-12-18 | 2 |
| M | 2019-12-25 | 13 |
| M | 2019-12-30 | 26 |
| M | 2019-12-31 | 29 |
| M | 2020-01-07 | 36 |
I'm not sure if this would be efficient, but it'll pass through:
SELECT T1.gender, T1.day, SUM(T2.score_points) AS total
FROM Scores T1
JOIN Scores T2
WHERE T1.gender = T2.gender AND T2.day <= T1.day
GROUP BY T1.gender, t1.day
ORDER BY t1.gender, t1.day;
References
For additional details, you can see the Discussion Board. There are plenty of accepted solutions with a variety of languages and explanations, efficient algorithms, as well as asymptotic time/space complexity analysis1, 2 in there.
AND is a boolean operation that returns true/false (1/0). You seem to want conditional aggregation:
SELECT s.day,
SUM(CASE WHEN s.gender = 'M' THEN s.score_points ELSE 0 END) AS total_male,
SUM(CASE WHEN s.gender = 'F' THEN s.score_points ELSE 0 END) AS total_female
FROM Scores s
GROUP BY s.day
ORDER BY s.day;
The GROUP BY keys determine what the result set looks like. Each set of unique values has one row in the result set, with the rest of the columns summarized to fit on the row.
In this case, you want one row per day, so that is what should be in the GROUP BY.
Then for the cumulative amount use window funtions:
SELECT s.day,
SUM(CASE WHEN s.gender = 'M' THEN s.score_points ELSE 0 END) AS total_male,
SUM(CASE WHEN s.gender = 'F' THEN s.score_points ELSE 0 END) AS total_female,
SUM(SUM(CASE WHEN s.gender = 'M' THEN s.score_points ELSE 0 END)) OVER (ORDER BY s.day) AS running_male,
SUM(CASE WHEN s.gender = 'F' THEN s.score_points ELSE 0 END) OVER (ORDER BY s.day) AS running_female
FROM Scores s
GROUP BY s.day
ORDER BY s.day;

Create 2 columns based on value in existing column

I have the following table. I would like to add 2 new columns with a select query that will show the total based on the flag type.
Table:
tt | company | count | flag
--------------------------------------------
123 | adeco | 5 | 1
123 | mic | 4 | 2
333 | manpower | 88 | 2
444 | linar | 2 | 2
555 | dlank | 3 | 1
Desired:
tt | company | total | flag | total_flag1 | total_flag2
-------------------------------------------------------------------
123 | adeco | 5 | 1 | 5 | 0
123 | mic | 4 | 2 | 0 | 4
333 | manpower | 88 | 2 | 0 | 88
444 | linar | 2 | 2 | 0 | 2
555 | dlank | 3 | 1 | 3 | 0
By your desired result, you should use case when or if syntax to to this:
select
yourtable.*,
case when flag = 1 then `count` else 0 end as total_flag1,
case when flag = 2 then `count` else 0 end as total_flag2
from yourtable
Or
select
yourtable.*,
if(flag = 1, `count`, 0) as total_flag1,
if(flag = 2, `count`, 0) as total_flag2
from yourtable
I think you can do what you want using correlated subqueries or join:
select t.*, tsum.total_flag1, tsum.total_flag2
from t join
(select t.tt,
sum(case when flag = 1 then total else 0 end) as total_flag1,
sum(case when flag = 2 then total else 0 end) as total_flag2
from t
group by t.tt
) tsum
on t.tt = tsum.tt;

MySQL Cumulative Totals For Each Day of the Month

I need some help with this SQL running total. I've tried some of the suggestions but haven't got it to work 100% and my totals are not in order.
The query shows number of stones and number of carats produced each day, first for stones over 5 carats, then for stones under 5 carats, so after first column which is the date, those 4 columns appear.
Then the next two columns simply add the stones together and the carats together for daily totals.
After that I would like to add two columns which create running totals of stones and carats.
Any help would be appreciated. Thank you!
select
`dbo_List_Dates`.`Full_Date` AS `Full_Date`,
if(isnull(`qry_Register_Over5ct`.`StonesO5`),
0,`qry_Register_Over5ct`.`StonesO5`) AS `StonesO5`,
if(isnull(`qry_Register_Over5ct`.`CaratsO5`),
0,format(`qry_Register_Over5ct`.`CaratsO5`,3)) AS `CaratsO5`,
if(isnull(`qry_Register_Under5ct`.`StonesU5`),
0,`qry_Register_Under5ct`.`StonesU5`) AS `StonesU5`,
if(isnull(`qry_Register_Under5ct`.`CaratsU5`),
0,format(`qry_Register_Under5ct`.`CaratsU5`,3)) AS `CaratsU5`,
(if(isnull(`qry_Register_Over5ct`.`StonesO5`),
0,`qry_Register_Over5ct`.`StonesO5`) + if(isnull(`qry_Register_Under5ct`.`StonesU5`),
0,`qry_Register_Under5ct`.`StonesU5`)) AS `Stones`,
format((if(isnull(`qry_Register_Over5ct`.`CaratsO5`),
0,`qry_Register_Over5ct`.`CaratsO5`) + if(isnull(`qry_Register_Under5ct`.`CaratsU5`),
0,`qry_Register_Under5ct`.`CaratsU5`)),3) AS `Carats`,
date_format(`dbo_List_Dates`.`Full_Date`,'%Y-%m') AS `Date_Filter`
from
(
(`dbo_List_Dates`
left join `qry_Register_Over5ct`
on((`dbo_List_Dates`.`Full_Date`=qry_Register_Over5ct`.`Shift_Date`))
)
left join `qry_Register_Under5ct`
on((`dbo_List_Dates`.`Full_Date`=`qry_Register_Under5ct`.`Shift_Date`))
)
where
(date_format(`dbo_List_Dates`.`Full_Date`,'%Y-%m') = date_format(now(),'%Y-%m'))
order by
`dbo_List_Dates`.`Full_Date`
limit 0,31
Running totals in mysql require the use of variables.
For example given
create table register (id int, dt date, carats int);
insert into register values
(1,'2016-11-01',10),(2,'2016-11-01',10),(3,'2016-11-01',1),
(4,'2016-11-02',1),
(5,'2016-11-03',10),
(6,'2016-11-05',10),(7,'2016-11-05',1);
and a dates table like this
+------------+
| dte |
+------------+
| 2016-11-01 |
| 2016-11-02 |
| 2016-11-03 |
| 2016-11-04 |
| 2016-11-05 |
+------------+
select s.* ,#RunningTotal:=#RunningTotal + s.LT5_GE5 RunningTotal
from
(
select d.dte,
sum(case when r.dt is not null and r.carats >= 5 then 1 else 0 end) GE5,
sum(case when r.dt is not null and r.carats < 5 then 1 else 0 end) LT5,
sum(case when r.dt is not null and r.carats >= 5 then 1 else 0 end) +
sum(case when r.dt is not null and r.carats < 5 then 1 else 0 end) LT5_GE5
from dates d
left join register r on r.dt = d.dte
where dte between '2016-11-01' and '2016-11-05'
group by dte
) s ,(select #RunningTotal:=0) rt
Results
+------------+------+------+---------+--------------+
| dte | GE5 | LT5 | LT5_GE5 | RunningTotal |
+------------+------+------+---------+--------------+
| 2016-11-01 | 2 | 1 | 3 | 3 |
| 2016-11-02 | 0 | 1 | 1 | 4 |
| 2016-11-03 | 1 | 0 | 1 | 5 |
| 2016-11-04 | 0 | 0 | 0 | 5 |
| 2016-11-05 | 1 | 1 | 2 | 7 |
+------------+------+------+---------+--------------+
And if you want column totals you could include a rollup in the group by
select s.* ,
cast(if(dte is null,#RunningTotal,#RunningTotal:=#RunningTotal + s.LT5_GE5) as int) RunningTotal
from
(
select d.dte,
sum(case when r.dt is not null and r.carats >= 5 then 1 else 0 end) GE5,
sum(case when r.dt is not null and r.carats < 5 then 1 else 0 end) LT5,
sum(case when r.dt is not null and r.carats >= 5 then 1 else 0 end) +
sum(case when r.dt is not null and r.carats < 5 then 1 else 0 end) LT5_GE5
from dates d
left join register r on r.dt = d.dte
where dte between '2016-11-01' and '2016-11-05'
group by dte with rollup
) s ,(select #RunningTotal:=0) rt
Result
+------------+------+------+---------+--------------+
| dte | GE5 | LT5 | LT5_GE5 | RunningTotal |
+------------+------+------+---------+--------------+
| 2016-11-01 | 2 | 1 | 3 | 3 |
| 2016-11-02 | 0 | 1 | 1 | 4 |
| 2016-11-03 | 1 | 0 | 1 | 5 |
| 2016-11-04 | 0 | 0 | 0 | 5 |
| 2016-11-05 | 1 | 1 | 2 | 7 |
| NULL | 4 | 3 | 7 | 7 |
+------------+------+------+---------+--------------+
6 rows in set (0.02 sec)

Count by average in multiple groups

I have data where each row represents one sentence in full review. Each row has a score (-1 to 1).
From that view I currently make 3 views where each has different group by, either by day, month or week and which aggregates average score for a review and
counts how many of them are positive and how many negative.
For example daily query from view:
SELECT
`review_score_view`.`review_date` AS `review_date`,
COUNT(`review_score_view`.`review_id`) AS `review_count`,
(AVG(`review_score_view`.`score`) * 100) AS `average_score`,
SUM((CASE
WHEN (`review_score_view`.`score` >= 0) THEN 1
ELSE 0
END)) AS `positive_count`,
SUM((CASE
WHEN (`review_score_view`.`score` < 0) THEN 1
ELSE 0
END)) AS `negative_count`
FROM
`review_score_view`
GROUP BY `review_score_view`.`review_date`
And I get result like this:
| id | review_date | review_count | average_score | positive_count | negative_count |
|-----|--------------|--------------|-----------------|----------------|----------------|
| 521 | 2015-01-01 | 4 | -25.0000 | 2 | 2 |
| 519 | 2015-01-07 | 1 | -100.0000 | 0 | 1 |
| 518 | 2015-01-25 | 1 | 100.0000 | 1 | 0 |
| 516 | 2015-03-09 | 7 | 57.1429 | 6 | 1 |
| 515 | 2015-04-26 | 2 | -50.0000 | 1 | 1 |
| 224 | 2015-06-01 | 68 | -23.5294 | 40 | 28 |
| 222 | 2015-06-02 | 26 | -100.0000 | 1 | 25 |
| 221 | 2015-06-03 | 41 | -36.5854 | 19 | 22 |
| 220 | 2015-06-04 | 6 | -50.0000 | 2 | 4 |
Question: How I can make another query from these view results based on average score. For each result (score categories), based on group by, either monthly, weekly or daily, I'd like to get a sum of these 5 results. Seems like an easy thing but I can't get my head around it.
SUM((CASE
WHEN (average_score >= 75) THEN 1
ELSE 0
END)) AS very_positive,
SUM((CASE
WHEN (average_score between 4 and 74) THEN 1
ELSE 0
END)) AS positive,
SUM((CASE
WHEN (average_score between -5 and 5) THEN 1
ELSE 0
END)) AS neutral,
SUM((CASE
WHEN (average_score between -4 and -74) THEN 1
ELSE 0
END)) AS negative,
SUM((CASE
WHEN (average_score <= -75) THEN 1
ELSE 0
END)) AS very_negative
Finally I would just plot the data. Quick example made in Excel:
Thanks in advance.
Hmmm. It looks like you want the results in rows, not columns. This suggests group by:
SELECT (CASE WHEN average_score >= 75 THEN 'VeryPositive'
WHEN average_score >= 4 THEN 'Positive'
WHEN average_score >= -5 THEN 'Neutral'
WHEN average_score >= -74 THEN 'Negative'
ELSE 'VeryNegative'
END) as ScoreGroup,
COUNT(*) as cnt
FROM dailyview v
GROUP BY ScoreGroup
ORDER BY ScoreGroup;
Note: The case returns the first expression that matches, so the between is unnecessary.

MySQL pivot from two mysql results

My scenario is... I have 2 type of judges who will give different set of points to members (eg. Judge Type A(55%) and Judge Type B(45%)) Both set of points will sum separately and averaged and then will sum together (which is 100% in total of 2 judges' points). I want to union both results into expected output below.
Table Member
-----------------------------
ID | Name
1 | John
2 | Doe
-----------------------------
Table Judge
-----------------------------
ID | Type | Name
1 | 1 | Judge A
2 | 1 | Judge B
3 | 2 | Judge C
4 | 1 | Judge D
-----------------------------
Table Point
-----------------------------
ID | FK | Judge | Type | Point
1 | 1 | 1 | 1 | 10
2 | 1 | 1 | 2 | 15
3 | 1 | 1 | 3 | 15
4 | 2 | 2 | 1 | 8
5 | 2 | 2 | 2 | 6
6 | 2 | 2 | 3 | 5
7 | 2 | 3 | 4 | 3
8 | 2 | 3 | 5 | 2
9 | 2 | 3 | 6 | 1
10 | 2 | 4 | 1 | 3
11 | 2 | 4 | 2 | 6
12 | 2 | 4 | 3 | 12
-----------------------------
Output Total Points
-----------------------------
ID | Name | Type1 | Type2 | Type3 | Total1 | CountJudge1 | Type4 | Type5 | Type6 | Total2 | CountJudge2
1 | John | 10 | 15 | 15 | 40 | 1 | 0 | 0 | 0 | 0 | 0
2 | Doe | 11 | 12 | 17 | 40 | 2 | 3 | 2 | 1 | 6 | 1
-----------------------------
Expected Output
-----------------------------
ID | Name | Type1 | Type2 | Type3 | Total1 | CountJudge1 | Type4 | Type5 | Type6 | Total2 | CountJudge2 | TotalPoint
1 | John | 10 | 15 | 15 | 40 | 1 | 0 | 0 | 0 | 0 | 0 | 40
2 | Doe | 5.5 | 6 | 8.5 | 20 | 2 | 3 | 2 | 1 | 6 | 1 | 26
-----------------------------
The sql (Output Total Points) that I could do at this moment which is still wrong is below and I can't figure out how to average all types of point and total points.
SELECT m.Name,
COUNT( CASE WHEN j.Type=1 THEN j.ID ELSE 0 END ) AS Judge1,
SUM( CASE WHEN p.Type=1 THEN p.Point ELSE 0 END ) AS Type1,
SUM( CASE WHEN p.Type=2 THEN p.Point ELSE 0 END ) AS Type2,
SUM( CASE WHEN p.Type=3 THEN p.Point ELSE 0 END ) AS Type3,
SUM( p.Score ) AS Total1,
COUNT( CASE WHEN j.Type=2 THEN j.ID ELSE 0 END ) AS Judge2,
SUM( CASE WHEN p.Type=4 THEN p.Point ELSE 0 END ) AS Type4,
SUM( CASE WHEN p.Type=5 THEN p.Point ELSE 0 END ) AS Type5,
SUM( CASE WHEN p.Type=6 THEN p.Point ELSE 0 END ) AS Type6,
SUM( p.Score ) AS Total2
FROM table_point AS p
LEFT JOIN table_member AS m ON ( m.ID = p.FK)
LEFT JOIN table_judge AS j ON ( j.ID = p.Judge )
GROUP BY m.ID
* UPDATE 1 *
I figured out the statement for expected result. However, as data grows bigger, my statement get slower. (250 members, 13 judges, 9750 scores (3 scores per judge per student) takes bout +-6 seconds). When I doubled members and scores, the execution time also doubled. Any idea to optimize this query?
SELECT MemberID, MemberName,
Judge1, Score1, Score2, Score3,
Judge2, Score4, Score5, Score6,
(((Score1 + Score2 + Score3) / (Judge1 / 3)) + ((Score4 + Score5 + Score6) / (Judge2 / 3))) AS TotalScore
FROM
(
SELECT m.ID AS MemberID, m.Name AS MemberName,
COUNT(j1.ID) AS Judge1,
SUM(CASE WHEN p.TypeID=1 THEN p.Point ELSE 0 END) AS Score1,
SUM(CASE WHEN p.TypeID=2 THEN p.Point ELSE 0 END) AS Score2,
SUM(CASE WHEN p.TypeID=3 THEN p.Point ELSE 0 END) AS Score3,
COUNT(j2.ID) AS Judge2,
SUM(CASE WHEN p.TypeID=4 THEN p.Point ELSE 0 END) AS Score4,
SUM(CASE WHEN p.TypeID=5 THEN p.Point ELSE 0 END) AS Score5,
SUM(CASE WHEN p.TypeID=6 THEN p.Point ELSE 0 END) AS Score6
FROM table_point AS p
LEFT JOIN table_member AS m ON ( m.ID = p.FK)
LEFT JOIN table_judge AS j1 ON ( j1.ID = p.Judge AND j1.Type=1 )
LEFT JOIN table_judge AS j2 ON ( j2.ID = p.Judge AND j2.Type=2 )
GROUP BY m.ID
) as tbl1
ORDER BY TotalScore DESC