Select up to x rows of each group - mysql

With a table of:
id | name | job | rank
01 john teacher 4
02 mark teacher 2
03 phil plummer 1
04 dave teacher 7
05 jim plummer 9
06 bill plummer 2
How can I select up to 2 rows of each job (if possible sorted by rank ASC in each group, so that the lowest two ranking of each group get picked). The result I'd be looking for is:
02 mark teacher 2
01 john teacher 4
03 phil plummer 1
06 bill plummer 2
This basically groups by job, with a limit to 2 and sorted by rank. I've been trying with GROUP BY as well as LEFT JOIN, but I just can't figure out how to do this. When creating a "temporary list" of jobs with GROUPING BY job, how do I join more than once onto that job?

SELECT id, name, job, rank
FROM TableName a
WHERE
(
SELECT COUNT(*)
FROM TableName as f
WHERE f.job = a.job AND
f.rank <= a.rank
) <= 2;
SQLFiddle Demo

Related

SQL COUNT giving same value

I have a table that looks like this...
Attendance_ID person_ID Meeting_ID date
--------------- --------- ---------- ----------
23 25 123 2013-03-21
24 25 456 2013-03-21
25 25 789 2013-03-21
26 13 147 2013-03-21
27 25 123 2013-03-22
28 82 147 2013-03-22
29 82 456 2013-03-22
30 13 147 2013-03-23
31 25 456 2013-03-23
32 13 456 2013-03-24
33 25 456 2013-03-28
The goal is to print out all the the times a employee has missed one or more meetings during the day. I have tried the following query but it gives me the same result of 2 for all dates...
SELECT
replace(replace(dayname(date),"Saturday", "Weekend"),"Sunday", "Weekend") AS day,
count(distinct personID) AS absences
from
AttendanceRecord
GROUP BY
day;
where it should be...
Friday 2
Thursday 3
Weekend 3
Where am I going wrong? Any help would be welcome as my SQL skills are a bit rusty. Thanks!
This is MySQL, and the table above is the dates of absences. So if the emplyee missed meeting 123, they would show up on this table. The query is to see the number of employees who missed a meeting on that day.
count(distinct personID) AS absences
should be
count(distinct employeeID) AS absences
because if you see your example data you have:
Employee ID person ID Meeting ID date
----------- --------- ---------- ----------
23 25 123 2013-03-21
24 25 456 2013-03-21
25 25 789 2013-03-21
where these 3 different Employees have the same Person ID
Either that, or you're already getting the correct values; there are 10 different EmployeeID but there are fewer than that PersonID
If you still want to count repeated personIDs, then change
count(distinct personID) AS absences
to
count(personID) AS absences
Could be, as long as every meeting was attended by at least one person.
WITH ALL_MEETING_EMPLOYEE_COMBOS AS
( SELECT EmployeeID,
Meeting_ID,
date
FROM (SELECT DISTINCT
Employee_ID
FROM Attendance_Record
)
CROSS JOIN
( SELECT DISTINCT
meeting_id,
date
FROM attendance_record
)
)
SELECT Employee_ID,
Date,
COUNT(1) AS MissedOnThisDayCount
FROM ALL_MEETING_EMPLOYEE_COMBOS T1
WHERE NOT EXISTS
( SELECT 1
FROM ATTENDANCE_RECORD AR
WHERE T1.employee_id = AR.employee_id
AND T1.meeting_id = AR.meeting_id
)
GROUP BY employee_id, date

Joining multiple SUM() across multiple tables

I have two tables, one with forecast production weights and one with actual production weight.
A customer can and will have multiple types and multiple seasons, and will also invoice those types over the year
Table Estimates
Customer Type Weight Season
John A 10 2018
John A 20 2018
John B 10 2018
Bill A 10 2018
Bill C 10 2017
Robert B 30 2017
Robert C 10 2018
Table Actual
Customer Type Weight InvoiceDate
John A 5 2018-10-30
John A 5 2018-10-30
John A 5 2018-10-30
John C 10 2018-10-30
Bill A 5 2018-11-1
Bill C 10 2017-11-30
Bill C 10 2017-11-30
Bill C 10 2017-11-30
Robert B 30 2017-11-10
Robert C 10 2019-2-20
Desired Query Would be as follows
select customer,
type,
sum(weight),
sum(weight)
from
estimates,
actual
where
season = 2018 and
InvoiceDate between 2018-7-1 and 2019-6-30 and
estimates.type = actual.type and
estimates.customer = actual.customer
group by
customer,
type
This give wildly large numbers
Desired result would be selecting for 2018
Customer Type Sum(Estimate) Sum(Actual)
John A 30 15
John B 10 0
John C 0 10
Bill A 10 5
Robert C 10 10
I have tried several join and union queries attempting to solve this issue
I cant quite get my head around which join to use to get the desired result
You can try below way -
select A.customer,A.type, estimated,actual
from
(
select customer,
type,sum(wieght) as estimated
from estimate where season=2018 group by customer,type
)A inner join
(
select customer,
type,sum(wieght) as actual
from actual where InvoiceDate between '2018-7-1' and '2019-6-30' group by customer,type
)B on A.customer=B.customer and A.type=B.type

How to get order and sum between three tables on mysql?

I'm trying to sort table 1 using the sort on table 2
and sum the value for each "order number" on table 1 and group by "order number"
and after that bring the client name from table 3 then the result should be like that:
sort ordernumber sumvalue name location
01 5555 120 Client 2 location 2
01 5498 50 Client 2 location 2
02 2324 50 Client 1 location1
02 4356 30 Client 1 location1
tables:
table 1 orders
===============
id ordernumber code_client value
1 2324 01 20
1 2324 01 30
1 4356 01 30
1 5555 02 40
1 5555 02 40
1 5498 02 50
1 5555 02 40
table 2 sort
============
sort code_client
1 02
2 01
table 3 client
===============
code_client name location
01 Client 1 location1
02 Client 2 location2
what i done in the following code is not respect the order even the sum for each ordernumber
SELECT orders.ordernumber,
SUM(orders.value) AS totalordersvalue,
orders.code_client,
sort.code_client,sort.sort,
client.code_client,client.name, client.location,
FROM orders
LEFT JOIN client
ON client.code_client = orders.code_client
LEFT JOIN sort
ON sort.code_client = orders.code_client
WHERE orders.id = 1
GROUP BY orders.ordernumber
ORDER BY sort.sort
there is any way to get the correct order and correct sum for each order as the final result ? thanks
You can try below -
select sort, ordernumber ,totalordersvalue as sumvalue,name,location from
(
SELECT orders.ordernumber, SUM(orders.value) AS totalordersvalue,
orders.code_client
FROM orders WHERE orders.id = 1 group by orders.ordernumber,orders.code_client
)A inner join client ON client.code_client = A.code_client
inner join sort ON sort.code_client = A.code_client
ORDER BY sort.sort
If the sequence of getting a result doesn't matter for you then you can use the following query
SELECT
DISTINCT o.ordernumber,
s.sort,
(SELECT sum(os.value) AS sum_value FROM orders AS os WHERE o.ordernumber =
os.ordernumber GROUP BY os.ordernumber) AS sum_value,
c.name,
c.location
FROM
sort AS s,
orders AS o,
client AS c
WHERE c.code_client = o.code_client AND o.code_client=s.code_client
ORDER BY s.sort;
Output:
ordernumber sort sumvalue name location
5555 01 120 Client 2 location 2
5498 01 50 Client 2 location 2
2324 02 50 Client 1 location1
4356 02 30 Client 1 location1

Count unique values from duplicates

I have following data on the table.
Uid | comm | status
-------------------
12 23 eve
15 23 eve
20 23 mon
12 23 mon
20 23 eve
17 23 mon
how do i query to get below result to avoid duplicates and make sure if i count uid for "eve" and same uid appears on "mon" then count only uid for "eve"?
count | status
-------------------
3 eve
1 mon
Thanks for the help!
You can use the following query in order to pick each Uid value once:
SELECT Uid, MIN(status)
FROM mytable
GROUP BY Uid
Output:
Uid MIN(status)
---------------
12 eve
15 eve
17 mon
20 eve
Using the above query you can get at the desired result like this:
SELECT status, count(*)
from (
SELECT Uid, MIN(status) AS status
FROM mytable
GROUP BY Uid ) AS t
GROUP BY status
Demo here

Select highest 3 scores in each day for every user

I have a MYSQL table like this:
id | userid | score | datestamp |
-----------------------------------------------------
1 | 1 | 5 | 2012-12-06 03:55:16
2 | 2 | 0,5 | 2012-12-06 04:25:21
3 | 1 | 7 | 2012-12-06 04:35:33
4 | 3 | 12 | 2012-12-06 04:55:45
5 | 2 | 22 | 2012-12-06 05:25:11
6 | 1 | 16,5 | 2012-12-06 05:55:21
7 | 1 | 19 | 2012-12-06 13:55:16
8 | 2 | 8,5 | 2012-12-07 06:27:16
9 | 2 | 7,5 | 2012-12-07 08:33:16
10 | 1 | 10 | 2012-12-07 09:25:19
11 | 1 | 6,5 | 2012-12-07 13:33:16
12 | 3 | 6 | 2012-12-07 15:45:44
13 | 2 | 4 | 2012-12-07 16:05:16
14 | 2 | 34 | 2012-12-07 18:33:55
15 | 2 | 22 | 2012-12-07 18:42:11
I would like to display user scores like this:
if a user on a certain day has more than 3 scores it would get only highest 3, repeat that for every day for this user and then add all days together. I want to display this sum for every user.
EDIT:
So in the example above for user 1 on 06.12. I would add top 3 scores together and ignore 4th score, then add to that number top 3 from the next day and so on. I need that number for every user.
EDIT 2:
Expected output is:
userid | score
--------------------
1 | 59 //19 + 16.5 + 7 (06.12.) + 10 + 6.5 (07.12.)
2 | 87 //22 + 0.5 (06.12.) + 34 + 22 + 8.5 (07.12.)
3 | 18 //12 (06.12.) + 6 (07.12.)
I hope this is more clear :)
I would really appreciate the help because I am stuck.
Please take a look at the following code, if your answer to my comment is yes :) Since your data all in 2012, and month of november, I took day.
SQLFIDDLE sample
Query:
select y.id, y.userid, y.score, y.datestamp
from (select id, userid, score, datestamp
from scores
group by day(datestamp)) as y
where (select count(*)
from (select id, userid, score, datestamp
from scores group by day(datestamp)) as x
where y.score >= x.score
and y.userid = x.userid
) =1 -- Top 3rd, 2nd, 1st
order by y.score desc
;
Results:
ID USERID SCORE DATESTAMP
8 2 8.5 December, 07 2012 00:00:00+0000
20 3 6 December, 08 2012 00:00:00+0000
1 1 5 December, 06 2012 00:00:00+0000
Based on your latter updates to question.
If you need some per user by year/month/day and then find highest, you may simply add aggregation function like sum to the above query. I am reapeating myself, since your sample data is for just one year, there's no point group by year or month. That's why I took day.
select y.id, y.userid, y.score, y.datestamp
from (select id, userid, sum(score) as score,
datestamp
from scores
group by userid, day(datestamp)) as y
where (select count(*)
from (select id, userid, sum(score) as score
, datestamp
from scores
group by userid, day(datestamp)) as x
where y.score >= x.score
and y.userid = x.userid
) =1 -- Top 3rd, 2nd, 1st
order by y.score desc
;
Results based on sum:
ID USERID SCORE DATESTAMP
1 1 47.5 December, 06 2012 00:00:00+0000
8 2 16 December, 07 2012 00:00:00+0000
20 3 6 December, 08 2012 00:00:00+0000
UPDATED WITH NEW SOURCE DATA SAMPLE
Simon, please take a look at my own sample. As your data was changing, I used mine.
Here is the reference. I have used pure ansi style without any over partition or dense_rank.
Also note the data I used are getting top 2 not top 3 scores. You can change is accordingly.
Guess what, the answer is 10 times simpler than the first impression your first data gave....
SQLFIDDLE
Query to 1:
-- for top 2 sum by user by each day
SELECT userid, sum(Score), datestamp
FROM scores t1
where 2 >=
(SELECT count(*)
from scores t2
where t1.score <= t2.score
and t1.userid = t2.userid
and day(t1.datestamp) = day(t2.datestamp)
order by t2.score desc)
group by userid, datestamp
;
Results for query 1:
USERID SUM(SCORE) DATESTAMP
1 70 December, 06 2012 00:00:00+0000
1 30 December, 07 2012 00:00:00+0000
2 22 December, 06 2012 00:00:00+0000
2 25 December, 07 2012 00:00:00+0000
3 30 December, 06 2012 00:00:00+0000
3 30 December, 07 2012 00:00:00+0000
Final Query:
-- for all two days top 2 sum by user
SELECT userid, sum(Score)
FROM scores t1
where 2 >=
(SELECT count(*)
from scores t2
where t1.score <= t2.score
and t1.userid = t2.userid
and day(t1.datestamp) = day(t2.datestamp)
order by t2.score desc)
group by userid
;
Final Results:
USERID SUM(SCORE)
1 100
2 47
3 60
Here goes a snapshot of direct calculations of data I used.
SELECT
*
FROM
table1
LEFT JOIN
(SELECT * FROM table1 ORDER BY score LIMIT 3) as lr on DATE(lr.datestamp) = DATE(table1.datastamp)
GROUP BY
datestamp