SQL - Sum Top N values in each row - mysql

This is my SQL table.
+-------+------+------+------+------+
| name | q1 | q2 | q3 | q4 |
+-------+------+------+------+------+
| Alex | 5 | 4 | 10 | 7 |
| Brown | 7 | 6 | 4 | 1 |
| Chris | 10 | 10 | 9 | 10 |
| Dave | 8 | 4 | 6 | 0 |
+-------+------+------+------+------+
I'd like to sum the top 2 scores from each user in my SQL query above.
For example, the top 2 scores of Alex are 10 and 7, and so the sum is 10 + 7 = 17
I have tried the following query:
SELECT NewStudents.name, SUM(q1+q2+q3+q4) FROM NewStudents
GROUP BY NewStudents.name;
To sum all q1, q2, q3, q4 but this query sums all q1 to q4, not the top 2 scores among q1 to q4.
How can I construct the statement that I want to do in mySQL?

As stated in #Shadow in comment.. Your database need restructure again.. Because that's not database work.. You can restructure and make design like this..
+-------+----+--------+
| name | q | point |
+-------+----+--------+
| Alex | 1 | 5 |
| Alex | 2 | 4 |
| Alex | 3 | 10 |
| Alex | 4 | 7 |
| Brown | 1 | 7 |
| Brown | 2 | 6 |
| Brown | 3 | 4 |
| Brown | 4 | 1 |
| Chris | 1 | 10 |
| Chris | 2 | 10 |
| Chris | 3 | 9 |
| Chris | 4 | 10 |
| Dave | 1 | 8 |
| Dave | 2 | 4 |
| Dave | 3 | 6 |
| Dave | 4 | 0 |
+-------+----+--------+
And for the query you can do like this:
select
name, sum(point)
from(
select
name, q, point,
ROW_NUMBER() OVER (PARTITION BY name ORDER BY point DESC) as ranked
from newstudents) rankedSD
where
ranked in (1,2)
group by
name
You can check the demo here:
Demo<>Fiddle
Edit : You can use Window Function. You can read Row_Number() Function

A normalised design might look like this:
name q score
Alex 1 5
Alex 2 4
Alex 3 10
Alex 4 7

In older versions of MySQL, you can use variables for this purpose:
select name, sum(q)
from (select nq.*,
(#rn := if(#n = name, #rn + 1,
if(#n := name, 1, 1)
)
) as rn
from (select nq.*
from ((select name, q1 as q from t
) union all
(select name, q2 as q from t
) union all
(select name, q3 as q from t
) union all
(select name, q4 as q from t
)
) nq
order by name, q desc
) nq cross join
(select #n := '', #rn := 0) params
) nq
where rn <= 2;

Related

I need to select a group total as well as the individual rows?

I have the following result set...
Name | Team | Score
A | 1 | 10
B | 1 | 11
C | 2 | 9
D | 2 | 15
and I want to add an extra column to the results set for the team score so I can sort on it and end up with the following data set...
Name | Team | Score | TeamScore
D | 2 | 15 | 24
C | 2 | 9 | 24
B | 1 | 11 | 21
A | 1 | 10 | 21
So I end up with the top team first with the members in order.
My actual data is way more complicated than this and pulls in data from several tables but if you can solve this one I can solve my bigger issue!
Join the table to a query that returns the total for each team:
select t.*, s.teamscore
from tablename t
inner join (
select team, sum(score) teamscore
from tablename
group by team
) s on s.team = t.team
order by s.teamscore desc, t.team, t.score desc
See the demo.
Results:
| Name | Team | Score | teamscore |
| ---- | ---- | ----- | --------- |
| D | 2 | 15 | 24 |
| C | 2 | 9 | 24 |
| B | 1 | 11 | 21 |
| A | 1 | 10 | 21 |
In MySQL 8+, we can simplify and just use SUM as an analytic function:
SELECT
Name,
Team,
Score,
SUM(Score) OVER (PARTITION BY Team) AS TeamScore
FROM yourTable
ORDER BY
TeamScore DESC,
Score;

MYSQL Ranking Issue

I am stuck on how to modify my query to get the ranking results that I am looking for. I have been looking at the questions and queries on SO but can not get the results. I can get the query to do the calculation and return data but the "rank" is not correct. Any nudge would be fantastic!
Table 1 contains school data:
+-----+------------+---------------+
| SID | schoolName | schoolCountry |
+-----+------------+---------------+
| 1 | ASD | UAE |
| 2 | ASIJ | Japan |
| 3 | ASP | France |
+-----+------------+---------------+
Table 2 contains review data (my query has more columns, but this is how it works).
+-----+----------+--------+----+----+----+----+----+
| RID | schoolID | active | Q1 | Q2 | Q3 | Q4 | Q5 |
+-----+----------+--------+----+----+----+----+----+
| 1 | 1 | 1 | 8 | 9 | 5 | 1 | 9 |
| 2 | 2 | 1 | 7 | 6 | 6 | 7 | 9 |
| 3 | 1 | 0 | 1 | 4 | 7 | 8 | 5 |
| 4 | 3 | 1 | 2 | 10 | 6 | 7 | 5 |
+-----+----------+--------+----+----+----+----+----+
I am trying to create different ranks (Country, Region, Overall) by averaging the overall review score for a school. I am currently working on the country ranking and my query so far is.
SELECT SID, schoolName, rank, average
FROM (
SELECT (#rank := #rank + 1) AS rank,schools.SID, schools.schoolName,
ROUND(AVG(IF(reviews.active = 1, ((Q1+Q2+Q3+Q4+Q5+Q6+Q7+Q8+Q9+Q10+Q11+Q12+Q13+Q14+Q15+Q16+Q17+Q18+Q19+Q20+Q21+Q22+Q23+Q24+Q25+Q26+Q27+Q28+Q29+Q30+Q31+Q32+Q33+Q34+Q35+Q36+Q37+Q38+Q39+Q40+Q41+Q42+Q43+Q44+Q45+Q46+Q47+Q48+Q49+Q50+Q51+Q52)/(52*10)*10), NULL)) ,1) AS average
FROM schools
RIGHT JOIN reviews ON reviews.schoolID = schools.SID
CROSS JOIN (SELECT #rank := 0) AS vars
WHERE schools.schoolCountry = 'United Arab Emirates'
GROUP BY schools.SID
) as order_ranked
ORDER BY `order_ranked`.`average` DESC
My output comes back as:
+-----+----------------------------------------+------+---------+--+
| SID | schoolName | rank | average | |
+-----+----------------------------------------+------+---------+--+
| 568 | GEMS Wellington Primary School | 3 | 8.3 | |
| 1 | American School of Dubai | 1 | 8.1 | |
| 561 | Dubai American Academy | 4 | 7.9 | |
| 560 | Deira International School | 11 | 7.7 | |
| 569 | GEMS World Academy Dubai | 10 | 7.0 | |
| 570 | Greenfield Community School | 8 | 6.7 | |
| 565 | GEMS American Academy Abu Dhabi | 6 | 6.0 | |
| 584 | Universal American School | 5 | 5.9 | |
| 558 | American Academy In Al Mizhar | 7 | 5.5 | |
| 579 | The Cambridge High School Abu Dhabi | 9 | 4.8 | |
| 576 | Ras Al Khaimah English Speaking School | 2 | 4.3 | |
+-----+----------------------------------------+------+---------+--+
As you can see it ranks, but not correctly. I just can't figure out why.
I'm not sure why you would be using right join for this. In MySQL, you often have to sort in the subquery before using the variables.
SELECT SID, schoolName, (#rank := #rank + 1) AS rank, average
FROM (SELECT s.SID, s.schoolName,
ROUND(AVG(Q1+Q2+Q3+Q4+Q5+Q6+Q7+Q8+Q9+Q10+Q11+Q12+Q13+Q14+Q15+Q16+Q17+Q18+Q19+Q20+Q21+Q22+Q23+Q24+Q25+Q26+Q27+Q28+Q29+Q30+Q31+Q32+Q33+Q34+Q35+Q36+Q37+Q38+Q39+Q40+Q41+Q42+Q43+Q44+Q45+Q46+Q47+Q48+Q49+Q50+Q51+Q52)/(52*10)*10)) AS average
FROM schools s JOIN
reviews r
ON r.schoolID = s.SID
WHERE s.schoolCountry = 'United Arab Emirates' AND r.isactive = 1
GROUP BY schools.SID
ORDER BY average DESC
) sr CROSS JOIN
(SELECT #rank := 0) AS vars
ORDER BY average DESC
Rank should depend on the ordering of average..but there is no order by in the inner query. First compute the average and calculate rank thereafter. Also, i moved the where condition to join because the way you have it is equivalent to an inner join.
SELECT SID, schoolName, #rank := #rank + 1 AS rank, average
FROM (
SELECT schools.SID, schools.schoolName,
ROUND(AVG(IF(reviews.active = 1, ((Q1+Q2+Q3+Q4+Q5+Q6+Q7+Q8+Q9+Q10+Q11+Q12+Q13+Q14+Q15+Q16+Q17+Q18+Q19+Q20+Q21+Q22+Q23+Q24+Q25+Q26+Q27+Q28+Q29+Q30+Q31+Q32+Q33+Q34+Q35+Q36+Q37+Q38+Q39+Q40+Q41+Q42+Q43+Q44+Q45+Q46+Q47+Q48+Q49+Q50+Q51+Q52)/(52*10)*10), NULL)) ,1) AS average
FROM schools
RIGHT JOIN reviews ON reviews.schoolID = schools.SID AND schools.schoolCountry = 'United Arab Emirates'
GROUP BY schools.SID,schools.schoolName
) as order_ranked
CROSS JOIN (SELECT #rank := 0) AS vars
ORDER BY `average` DESC

Select second min() or second smallest from mysql table

I'm wondering how to select the second smallest value from a mysql table, grouped on a non-numeric column. If I have a table that looks like this:
+----+----------+------------+--------+------------+
| id | customer | order_type | amount | created_dt |
+----+----------+------------+--------+------------+
| 1 | 1 | web | 5 | 2017-01-01 |
| 2 | 1 | web | 7 | 2017-01-05 |
| 3 | 2 | web | 2 | 2017-01-07 |
| 4 | 3 | web | 2 | 2017-02-01 |
| 5 | 3 | web | 3 | 2017-02-01 |
| 6 | 2 | web | 5 | 2017-03-15 |
| 7 | 1 | in_person | 7 | 2017-02-01 |
| 8 | 3 | web | 8 | 2017-01-01 |
| 9 | 2 | web | 1 | 2017-04-01 |
+----+----------+------------+--------+------------+
I want to count the number of second orders in each month/year. I also have a customer table (which is where the customer ids come from). I can find the number of customers with more than at least 2 orders by the customer's created date by querying
select date(c.created_dt) as create_date, count(c.id)
from customer c
where c.id in
(select or.identity_id
from orders or
where
(select count(o.created_dt)
from orders o
where or.customer = o.customer and o.order_tpe in ('web')
) > 1
)
group by 1;
However, that result gives customer by their created date, and I can't seem to figure out how to find the the number of second orders by date.
The desired output i'd like to see, based on the data above, is:
+-------+------+---------------+
| month | year | second_orders |
+-------+------+---------------+
| 1 | 2017 | 1 |
| 2 | 2017 | 1 |
| 3 | 2017 | 1 |
+-------+------+---------------+
One way to approach this
SELECT YEAR(created_dt) year, MONTH(created_dt) month, COUNT(*) second_orders
FROM (
SELECT created_dt,
#rn := IF(#c = customer, #rn + 1, 1) rn,
#c := customer
FROM orders CROSS JOIN (
SELECT #c := NULL, #rn := 1
) i
WHERE order_type = 'web'
ORDER BY customer, id
) q
WHERE rn = 2
GROUP BY YEAR(created_dt), MONTH(created_dt)
ORDER BY year, month
Here is a dbfiddle demo
Output:
+------+-------+---------------+
| year | month | second_orders |
+------+-------+---------------+
| 2017 | 1 | 1 |
| 2017 | 2 | 1 |
| 2017 | 3 | 1 |
+------+-------+---------------+

How to do such a mysql query?

I will ask for help from yours.
Example table:
| ID | NAME |POINT|
| 1 | alex | 2 |
| 2 | alex | 2 |
| 3 | jenn | 4 |
| 4 | shama| 3 |
| 5 | jenn | 4 |
| 6 | Mike | 1 |
I want to find repetitive name and change name value and sum repetitive value.
Like
| ID | NAME |POINT|
| 1 | alexander| 4 |
| 2 | jennifer | 8 |
Is it possible mysql query?
Thanks.
try this
select id,
case when name = 'alex' then replace(NAME,'alex','alexander')
when name = 'jenn' then replace(NAME,'jenn','jennifer')
end as d,sum(point)
from name group by d having d is not null;
Is this what you want?
select (#rn := #rn + 1) as id, name, sum(point) as point
from t cross join
(select #rn := 0) params
group by name
having count(*) >= 2;

How to get Latest N Records of selected Group

I want to run a query on MySql version 5.1.9 that returns me only top two (order by JoiningDate) of selected Dept.
For example, my data is like:
+-------+------------------------------------------+----------+------------+
| empid | title | Dept | JoiningDate|
+-------+------------------------------------------+----------+------------+
| 1 | Research and Development | 1 | 2015-08-06 |
| 2 | Consultant | 2 | 2015-08-06 |
| 3 | Medical Consultant | 3 | 2015-08-06 |
| 4 | Officer | 4 | 2015-08-06 |
| 5 | English Translator | 5 | 2015-08-06 |
| 6 | Teacher | 1 | 2015-08-01 |
| 7 | Physical Education | 2 | 2015-08-01 |
| 8 | Accountant | 3 | 2015-08-01 |
| 9 | Science Teacher | 4 | 2015-08-01 |
| 10 | Home Science | 5 | 2015-08-01 |
| 11 | Research Assistant | 1 | 2015-08-05 |
| 12 | Consultant | 2 | 2015-08-05 |
| 13 | Consultant HR | 3 | 2015-08-05 |
| 14 | Technical Lead | 4 | 2015-08-05 |
| 15 | Hindi Translator | 5 | 2015-08-05 |
| 16 | Urdu Teacher | 1 | 2015-08-02 |
| 17 | Physical Education | 2 | 2015-08-02 |
| 18 | Accountant | 3 | 2015-08-02 |
| 19 | Science | 4 | 2015-08-02 |
| 20 | Home Science | 5 | 2015-08-02 |
+-------+------------------------------------------+----------+------------+
I want the query to output the latest joined two empid's of Dept (1,2,3) i.e:
+-------+------------------------------------------+----------+------------+
| empid | title | Dept | JoiningDate|
+-------+------------------------------------------+----------+------------+
| 1 | Research and Development | 1 | 2015-08-06 |
| 11 | Research Assistant | 1 | 2015-08-05 |
| 2 | Consultant | 2 | 2015-08-06 |
| 12 | Consultant | 2 | 2015-08-05 |
| 3 | Medical Consultant | 3 | 2015-08-06 |
| 13 | Consultant HR | 3 | 2015-08-05 |
+-------+------------------------------------------+----------+------------+
In mysql you can use user defined variables to achieve you desired results
SELECT
t.empid,
t.title,
t.Dept,
t.JoiningDate
FROM
(
SELECT
*,
#r:= CASE WHEN #g = b.Dept THEN #r + 1 ELSE 1 END rounum,
#g:= b.Dept
FROM (
SELECT *
FROM table1
CROSS JOIN (SELECT #r:= NULL,#g:=NULL) a
WHERE Dept IN(1,2,3)
ORDER BY Dept,JoiningDate DESC
) b
) t
WHERE t.rounum <=2
DEMO
Use a correlated sub-select to count number of rows with same date but a later JoiningDate. If less than 2, return the row.
select empid, title, Dept, JoiningDate
from tablename t1
where (select count(*) from tablename t2
where t2.Dept = t1.Dept
and t2.JoiningDate > t1.JoiningDate) < 2
Query
select *
from emp_ t1
where
(
select count(*) from emp_ t2
where t2.Dept = t1.Dept
and t2.JoiningDate > t1.JoiningDate
) <= 1
and t1.Dept in (1,2,3)
order by t1.Dept;
SQL Fiddle
Can also achieve it by giving a rownumber.
Query
select t2.empid,
t2.title,
t2.Dept,
t2.JoiningDate
from
(
select empid,
title,
Dept,
JoiningDate,
(
case Dept
when #curA
then #curRow := #curRow + 1
else #curRow := 1 and #curA := Dept end
) as rn
from employee t,
(select #curRow := 0, #curA := '') r
where Dept in (1,2,3)
order by Dept,JoiningDate desc
)t2
where rn < 3;
SQL Fiddle