ID_STUDENT | ID_CLASS | GRADE | RANK
2 | 1 | 90 | 1
1 | 1 | 90 | 1
3 | 1 | 90 | 1
4 | 1 | 70 | 4
6 | 2 | 90 | 1
1 | 2 | 80 | 2
5 | 2 | 78 | 3
7 | 3 | 90 | 1
6 | 3 | 50 | 2
How should i sort and rank the data to get the above result? Thanks in advance
One method is with a subquery:
select sc.*,
(select count(*) + 1
from studentclass sc2
where sc2.grade > sc.grade and sc2.id_class = sc.id_class
) as rank
from studentclass sc order by id_class, grade;
In ANSI SQL (and most other databases), this is provided by the rank() function. You might be interested in the differences between rank(), dense_rank(), and row_number().
Related
I currently have the following simplified tables in my database. The points table contains rows of points awarded to each user for every bid form they have voted in.
I would like to add a column to this table that for each row, it shows the AVERAGE of the previous TWO points awarded to THAT user.
Users
+----+----------------------+
| id | name |
+----+----------------------+
| 1 | Flossie Schamberger |
| 2 | Lawson Graham |
| 3 | Hadley Reilly |
+----+----------------------+
Bid Forms
+----+-----------------+
| id | name |
+----+-----------------+
| 1 | Summer 2017 |
| 2 | Winter 2017 |
| 3 | Summer 2018 |
| 4 | Winter 2019 |
| 5 | Summer 2019 |
+----+-----------------+
Points
+-----+---------+--------------------+------------+------------+
| id | user_id | leave_bid_forms_id | bid_points | date |
+-----+---------+--------------------+------------+------------+
| 1 | 1 | 1 | 6 | 2016-06-19 |
| 2 | 2 | 1 | 8 | 2016-06-19 |
| 3 | 3 | 1 | 10 | 2016-06-19 |
| 4 | 1 | 2 | 4 | 2016-12-18 |
| 5 | 2 | 2 | 8 | 2016-12-18 |
| 6 | 3 | 2 | 4 | 2016-12-18 |
| 7 | 1 | 3 | 10 | 2017-06-18 |
| 8 | 2 | 3 | 12 | 2017-06-18 |
| 9 | 3 | 3 | 4 | 2017-06-18 |
| 10 | 1 | 4 | 4 | 2017-12-17 |
| 11 | 2 | 4 | 4 | 2017-12-17 |
| 12 | 3 | 4 | 2 | 2017-12-17 |
| 13 | 1 | 5 | 16 | 2018-06-17 |
| 14 | 2 | 5 | 12 | 2018-06-17 |
| 15 | 3 | 5 | 10 | 2018-06-17 |
+-----+---------+--------------------+------------+------------+
For each row in the points table I would like an average_points column to be calculated like follows.
The average point column is the average of that users PREVIOUS 2 points. So for the first entry in the table for each user, the average is obviously 0 because there were no previous points awarded to them.
The previous 2 points for each user should be determined using the date column.
The table below is what I would like to have as the final output.
For clarity, to the side of the table, I have added the calculation and numbers used to arrive at the value in the averaged_points column.
+-----+---------+--------------------+------------+-----------------+
| id | user_id | leave_bid_forms_id | date | averaged_points |
+-----+---------+--------------------+------------+-----------------+
| 1 | 1 | 1 | 2016-06-19 | 0 | ( 0 + 0 ) / 2
| 2 | 2 | 1 | 2016-06-19 | 0 | ( 0 + 0 ) / 2
| 3 | 3 | 1 | 2016-06-19 | 0 | ( 0 + 0 ) / 2
| 4 | 1 | 2 | 2016-12-18 | 3 | ( 6 + 0 ) / 2
| 5 | 2 | 2 | 2016-12-18 | 4 | ( 8 + 0 ) / 2
| 6 | 3 | 2 | 2016-12-18 | 5 | ( 10 + 0) / 2
| 7 | 1 | 3 | 2017-06-18 | 5 | ( 4 + 6 ) / 2
| 8 | 2 | 3 | 2017-06-18 | 8 | ( 8 + 8 ) / 2
| 9 | 3 | 3 | 2017-06-18 | 7 | ( 4 + 10) / 2
| 10 | 1 | 4 | 2017-12-17 | 7 | ( 10 + 4) / 2
| 11 | 2 | 4 | 2017-12-17 | 10 | ( 12 + 8) / 2
| 12 | 3 | 4 | 2017-12-17 | 4 | ( 4 + 4 ) / 2
| 13 | 1 | 5 | 2018-06-17 | 7 | ( 4 + 10) / 2
| 14 | 2 | 5 | 2018-06-17 | 8 | ( 4 + 12) / 2
| 15 | 3 | 5 | 2018-06-17 | 3 | ( 2 + 4 ) / 2
+-----+---------+--------------------+------------+-----------------+
I've been trying to use subqueries to solve this issue as AVG doesn't seem to be affected by any LIMIT clause I have.
So far I have come up with
select id, user_id, leave_bid_forms_id, `date`,
(
SELECT
AVG(bid_points)
FROM (
Select `bid_points`
FROM points as p2
ORDER BY p2.date DESC
Limit 2
) as thing
) AS average_points
from points as p1
This is in this sqlfiddle but to be honest I'm out of my depth here.
Am I on the right path? Wondering if someone would be able to show me where I need to tweak things please!
Thanks.
EDIT
Using the the answer below as a basis I was able to tweak the sql to work with the tables provided in the original sqlfiddle.
I have added that to this sqlfiddle to show it working
The corrected sql to match the code above is
select p.*,
IFNULL(( (coalesce(points_1, 0) + coalesce(points_2, 0)) /
( (points_1 is not null) + (points_2 is not null) )
),0) as prev_2_avg
from (select p.*,
(select p2.bid_points
from points p2
where p2.user_id = p.user_id and
p2.date < p.date
order by p2.date desc
limit 1
) as points_1,
(select p2.bid_points
from points p2
where p2.user_id = p.user_id and
p2.date < p.date
order by p2.date desc
limit 1, 1
) as points_2
from points as p
) p;
Although I am about to ask another question about the best way to make this dynamic with the number of previous poingt that need to be averaged.
You can use window functions, which were introduced in MySQL 8.
select p.*,
avg(points) over (partition by user_id
order by date
rows between 2 preceding and 1 preceding
) as prev_2_avg
from p;
In earlier versions, this is a real pain, because MySQL does not support nested correlation clauses. One method is with a separate column for each one:
select p.*,
( (coalesce(points_1, 0) + coalesce(points_2, 0)) /
( (points_1 is not null) + (points_2 is not null) )
) as prev_2_avg
from (select p.*,
(select p2.points
from points p2
where p2.user_id = p.user_id and
p2.date < p.date
order by p2.date desc
limit 1
) as points_1,
(select p2.points
from points p2
where p2.user_id = p.user_id and
p2.date < p.date
order by p2.date desc
limit 1, 1
) as points_2
from p
) p;
I need single SQL query for SELECT list of jobs including SUM() of sme specific detail type.
I have database with transport data. Tables looks like that:
job:
idjob | customer
1 | 45
2 | 38
3 | 15
job-detail:
iddet | idjob | type | value
1 | 1 | range | 100
2 | 1 | range | 85
3 | 1 | range | 12
4 | 1 | price | 64
4 | 1 | price | 5
5 | 1 | note | Some text here
6 | 2 | range | 150
7 | 2 | price | 32
8 | 2 | note | Some text here
9 | 2 | range | 35
I need this output:
idjob | customer | total_range | total_price
1 | 45 | 197 | 69
2 | 38 | 185 | 32
3 | 15 | 0 | 0
you can use left join with conditional aggregation
select a.idjob,customer,
sum(case when type='range' then value end) as total_range,
sum(case when type='price' then value end) as total_price
from job a
left join job-detail b on a.idjob=b.idjob
group by a.idjob,customer
SELECT DISTINCT J.idjob, J.customer,
(SELECT SUM(CONVERT(INT, [VALUE])) FROM [dbo].[job-detail] WHERE TYPE = 'range' AND J.idjob = idjob) AS total_range,
(SELECT SUM(CONVERT(INT, [VALUE])) FROM [dbo].[job-detail] WHERE TYPE = 'price' AND J.idjob = idjob) AS total_price
FROM [dbo].[job] j
LEFT JOIN [dbo].[job-detail] JD ON J.idjob = JD.idjob
I have a MySQL database with the following structure:
custodian | counta | countc | countc | total | date
-------------------------------------------------------
ed | 1 | 2 | 3 | 6 | 1/1/2016
ed | 2 | 3 | 5 | 10 | 1/2/2016
ed | 2 | 3 | 6 | 11 | 1/3/2016
ed | 1 | 3 | 5 | 9 | 1/4/2016
fred | 1 | 2 | 3 | 6 | 1/1/2016
fred | 2 | 3 | 5 | 10 | 1/2/2016
fred | 2 | 3 | 6 | 11 | 1/3/2016
fred | 1 | 3 | 5 | 9 | 1/4/2016
How do I return the latest record for a custodian? I've been playing around with this condition where date >= DATE_SUB(NOW(),INTERVAL 59 MINUTE) since the table is updated hourly, but if I update the script twice in an hour, I would return more than one result per custodian.
Any advice?
You need to combine ORDER BY and LIMIT:
SELECT *
FROM yourTableName
WHERE custodian = 123
ORDER BY `date` DESC
LIMIT 1
You could try this
SELECT * FROM tbl ORDER BY date DESC LIMIT 1
The most recent date will be the first record when ordered decendingly, and limiting the select to 1 means you get only the latest record.
I have four MySql tables (simplified here):
Table 1: factions (just a list to reference)
id | name
1 | FactionName1
2 | FactionName2
Table 2: currencies (just a list to reference)
id | name
1 | Currency1
2 | Currency2
3 | Currency3
Table 3: events (just a list to reference)
id | name | date
1 | Evebt1 | 2013-10-16
2 | Event2 | 2013-10-18 (Note: date out of order)
3 | Event3 | 2013-10-17
Table 4: event_banking (data entered after each event, remaining amount of each currency for each group)
id | faction_id | currency_id | event_id | amount
1 | 1 | 1 | 1 | 10
2 | 1 | 1 | 2 | 20
3 | 1 | 1 | 3 | 30
4 | 1 | 2 | 1 | 40
5 | 1 | 2 | 2 | 50
6 | 1 | 2 | 3 | 60
7 | 1 | 3 | 1 | 70
8 | 1 | 3 | 2 | 80
9 | 1 | 3 | 3 | 90
10 | 2 | 1 | 1 | 100
11 | 2 | 1 | 2 | 110
12 | 2 | 1 | 3 | 120
13 | 2 | 2 | 1 | 130
14 | 2 | 2 | 2 | 140
15 | 2 | 2 | 3 | 150
16 | 2 | 3 | 1 | 160
17 | 2 | 3 | 3 | 170
Note: Faction 2 didn't bank Currency 3 for Event 2
What I'm looking to be able to do is to get, for each currency, the total of the last banked (date wise) for each faction. (ie How much of each currency is currently banked in total if all factions are merged)
So, I need a table looking something like:
currency_id | total
1 | 130 (eg 20 + 110)
2 | 190 (eg 50 + 140)
3 | 250 (eg 80 + 170) <- Uses Event 3 for Group 2 as Event 2 doesn't exist
I can do basic joins etc, but I'm struggling to be able to filter the results so that I get the latest results for each Faction x Currency x Event so I can then sum them together to get the final total amounts for each currency.
I've tried various permutations of LEFT OUTER JOINs, GROUP BYss & HAVING COUNTs, and had some interesting (but incorrect results), and a variety of different error codes, but nothing remotely close to what I need.
Can anyone help?
I guess you can go on with something like this:
select eb.currency_id, sum(amount) as total
from events e
inner join (
select faction_id, currency_id, max(date) as md
from event_banking eb
inner join events e
on eb.event_id = e.id
group by faction_id, currency_id
) a
on e.date = a.md
inner join event_banking eb
on e.id = eb.event_id
and a.faction_id = eb.faction_id
and a.currency_id = eb.currency_id
group by currency_id;
Here is SQL Fiddle
I have the following table:
+-----+-----------+----------+------------+------+
| key | idStudent | idCourse | hourCourse | mark |
+-----+-----------+----------+------------+------+
| 0 | 1 | 1 | 10 | 78 |
| 1 | 1 | 2 | 20 | 60 |
| 2 | 1 | 4 | 10 | 45 |
| 3 | 3 | 1 | 10 | 90 |
| 4 | 3 | 2 | 20 | 70 |
+-----+-----------+----------+------------+------+
Using a simple query, I can show student with their weighted average according to hourCourse and mark:
SELECT idStudent,
SUM( hourCourse * mark ) / SUM( hourCourse ) AS WeightedAvg
FROM `test`.`test`
GROUP BY idStudent;
+-----------+-------------+
| idStudent | WeightedAvg |
+-----------+-------------+
| 1 | 60.7500 |
| 3 | 76.6667 |
+-----------+-------------+
But now I need to select the registers until the cumulative sum of hourCourse per student reaches a threshold. For example, for a threshold of 30 hourCourse, only the following registers should be taken into account:
+-----+-----------+----------+------------+------+
| key | idStudent | idCourse | hourCourse | mark |
+-----+-----------+----------+------------+------+
| 0 | 1 | 1 | 10 | 78 |
| 1 | 1 | 2 | 20 | 60 |
| 3 | 3 | 1 | 10 | 90 |
| 4 | 3 | 2 | 20 | 70 |
+-----+-----------+----------+------------+------+
key 2 is not taken into account, because idStudent 1 already reached 30 hourCourse with idCourse 1 and 2.
Finally, the query solution should be the following:
+-----------+-------------+
| idStudent | WeightedAvg |
+-----------+-------------+
| 1 | 66.0000 |
| 3 | 76.6667 |
+-----------+-------------+
Is there any way to create an inline query for this? Thanks in advance.
Edit: The criteria while selecting the courses is from highest to the lowest mark.
Edit: Registers are included while the cumulative sum of hourCourse is less than 30. For instance, two registers of 20 hours each would be included (sum 40), and the following not.
You can calculate the cumulative sums per idStudent in a sub-query, then only select the results where the cumulative sum is <= 30:
select idStudent,
SUM( hourCourse * mark ) / SUM( hourCourse ) AS WeightedAvg
from
(
SELECT t.*,
case when #idStudent<>t.idStudent
then #cumSum:=hourCourse
else #cumSum:=#cumSum+hourCourse
end as cumSum,
#idStudent:=t.idStudent
FROM `test` t,
(select #idStudent:=0,#cumSum:=0) r
order by idStudent, `key`
) t
where t.cumSum <= 30
group by idStudent;
Demo: http://www.sqlfiddle.com/#!2/f5d07/23