Calculate every participant's points, based on winners result in age group - sql

The winner of the competition stage in the respective age group is the participant who made the distance fastest. Each participant in the respective group receives for each stage points. The points obtained are calculated by dividing the time of the group winner by the time of the participant and multiplied by 1000. The total score is calculated by the sum of the points of the 5 best stages.
I have participant data like this.
AgeGroup Start_Nr First_name Last_Name Distance_result
-------------------------------------------------------------
M30 5 John Lala 180
M35 1 Paul Baba 175
M35 6 Patric Ziza 192
M30 3 Peter Mikel 190
S30 2 Sandra Lilua 250
S30 4 Julia Parker 260
And I want to calculate and display point like this
Age_Group|Start_Nr|First_name|Last_Name|Distance_result|Points
----------------------------------------------------------
M30 5 John Lala 180 1000
M30 3 Peter Mikel 190 947
M35 6 Patric Ziza 175 1000
M35 1 Paul Babas 185 946
S30 2 Sandra Lilua 250 1000
S30 4 Julia Parker 260 962
Each winner in age group gets 1000points, others in that age group get points calculating -> (MIN(Distance_result)/(Distance_result) * 1000)
SELECT [Age_group],
[Start_number] ,
[First_name],
[Last_name],
[Stage_Nr],
[Distance_result], (180/[Distance_result]*1000) AS Points,
DENSE_RANK() OVER (PARTITION BY [Age_group] ORDER BY [Distance_result] ASC) AS PlaceRank
FROM [ParticipantDetails].[dbo].[ParticipantForm]
How can I use MIN(Distance_result) in each age group to do point calculations?
In my solution I can calculate points only inserting MIN(Distance_result) manually, but even then it's not correct to other age groups. In every age group there is a different best result.

In my Example I have solved your problem with a subquery.
I have also added round to remove the decimals and before that I have added cast to get some result, otherwise I would receive only 1 or 0, or you can do this: p1.[Distance_result] instead of cast...
SELECT [AgeGroup],
[Start_Nr] ,
[First_name],
[Last_name],
[Distance_result],
(select round(min(p2.Distance_result)/p1.[Distance_result] * 1000, 0) AS Points
from ParticipantForm p2
where p2.AgeGroup = p1.AgeGroup
group by p2.AgeGroup) as Points,
DENSE_RANK() OVER (PARTITION BY [AgeGroup] ORDER BY [Distance_result] ASC) AS PlaceRank
FROM [ParticipantForm] p1
Here you can see the demo.

You can use a sub-query to get the best times and join the table to it.
From a performance point of view it is preferable to have a sub-query in the join which is run only once than a row-level sub-query which is run for every line.
We avoid the need to cast as float and use round() by doing the multiplication by 1000 before the division.
SELECT
[Age_group],
[Start_number] ,
[First_name],
[Last_name],
[Stage_Nr],
[Distance_result],
(b.best*1000)/[Distance_result AS Points,
DENSE_RANK() OVER (PARTITION BY [Age_group] ORDER BY [Distance_result] ASC) AS PlaceRank
FROM [ParticipantDetails].[dbo].[ParticipantForm]
JOIN ( SELECT [Age_group] AgeGroup,
MIN([Distance_result]) AS best
FROM [ParticipantDetails].[dbo].[ParticipantForm]
GROUP BY [Age_group]) AS b
ON b.[AgeGroup] = [ParticipantForm].[Age_group];

Try calculating the minimum Distance_Result partitioned by Age_Group. Then calculate the points.
WITH cte AS (
SELECT *
, DENSE_RANK() OVER (PARTITION BY [Age_Group] ORDER BY [Distance_Result] ASC) AS Place_Rank
, MIN(Distance_Result) OVER (PARTITION BY [Age_Group] ORDER BY [Distance_Result] ASC) AS Min_Distance
FROM [ParticipantForm]
)
SELECT [Age_group]
, [First_Name]
, [Last_Name]
, [Start_Number]
, [Distance_Result]
, CAST(ROUND( [Min_Distance] * 1000.0 / [Distance_Result], 0 ) AS INT) AS Points
FROM cte
Results:
Age_group
First_Name
Last_Name
Start_Number
Distance_Result
Points
M30
John
Lala
5
180
1000
M30
Peter
Mikel
3
190
947
M35
Paul
Baba
1
175
1000
M35
Patric
Ziza
6
192
911
S30
Sandra
Lilua
2
250
1000
S30
Julia
Parker
4
260
962
db<>fiddle here

Related

SQL query count rows with the same entry

Given a dataset Roster_table as such:
Group ID
Group Name
Name
Phone
42
Red Dragon
Jon
123455678
32
Green Lizard
Liz
932143211
19
Blue Falcon
Ben
134554678
42
Red Dragon
Reed
432143211
42
Red Dragon
Brad
231314155
19
Blue Falcon
Chad
214124412
How do I get the following query output combining rows with the same Group ID from the dataset, and the new column Count in descending order:
Group ID
Group Name
Count
42
Red Dragon
3
19
Blue Falcon
2
32
Green Lizard
1
SELECT * FROM Roster_table
Please try this where alias tot_count is used in ORDER BY clause.
-- PostgreSQL(v11)
SELECT Group_ID
, MAX(Group_Name) Group_Name
, COUNT(1) tot_count
FROM Roster_table
GROUP BY Group_ID
ORDER BY tot_count DESC;
Please check from url https://dbfiddle.uk/?rdbms=postgres_11&fiddle=b66f9f0d40e804e89be12e3530fe00a0
Based on Rahul Biswas's answer:
Solution without using Max function
SELECT Group_ID, Group_Name, COUNT(*)
FROM Roster_table
GROUP BY Group_ID, Group_Name
ORDER BY COUNT(*) DESC
Credit goes to Eric S.

Need sum of column while selecting other value

I have a table like this:
empID name amt Date
------------------------------------
1 mark 20 22-10
1 mark 30 22-10
2 kane 50 22-12
2 kane 60 22-12
3 mike 60 22-10
and I want to get an output like that
empID name amt Date TOTAL
-----------------------------------------
1 mark 20 22-10 220
1 mark 30 22-10 220
2 kane 50 22-12 220
2 kane 60 22-12 220
3 mike 60 22-10 220
I have used sum(amt) but it is returning only 1 row; I want other rows as well.
You can use the window function sum() over() without any partition or order by
Example
Select *
,[Total] = sum(amt) over()
From YourTable
You need a windowing function
SELECT
empid
,name
,amt
,[date]
,SUM(amt) OVER(PARTITION BY '') AS Total -- as you show it
,SUM(amt) OVER(PARTITION BY empID) AS Total -- as I think you want it
FROM t
Documentation: https://learn.microsoft.com/en-us/sql/t-sql/queries/select-over-clause-transact-sql?view=sql-server-ver15
Since you need to display the grand total value for each row you don't need to partition or group by. Therefore, you can use SUM(amt) OVER () .
SELECT *,
SUM(amt) OVER () AS [Total]
FROM tabe_c

joining 2 tables in sql which has no dependency on each other

I have 2 tables in the following way
Table 1:
e_id e_name e_salary e_age e_gender e_dept
---------------------------------------------------
1 sam 95000 45 male operations
2 bob 80000 21 male support
3 ann 125000 25 female analyst
Table 2:
d_salary d_age d_gender e_dept
----------------------------------
34000 25 male Admin
56000 41 female Tech
77000 35 female HR
I want the output something like this:
e_id e_name e_salary e_age e_gender e_dept d_salary d_age d_gender e_dept
1 sam 95000 45 male operations 34000 25 male Admin
2 bob 80000 21 male support 56000 41 female Tech
3 ann 125000 25 female analysts 77000 35 female HR
There is no dependency between the tables. No common columns. No primary or foreign key.
I tried using cross join that results in duplicate rows because it works on M X N
I am new to this SQL thing. Can someone help me, please? Thanks in advance
Though I didn't get the reason behind your desired output but you can get that with below query:
select a.e_id ,a.e_name ,a.e_salary ,a.e_age ,a.e_gender ,a.e_dept,b.d_salary ,b.d_age ,b.d_gender ,b.e_dept
from
(select e_id ,e_name ,e_salary ,e_age ,e_gender ,e_dept, row_number()over(order by e_id)rn
from table1)a
inner join
(select d_salary d_age d_gender e_dept,row_number()over(order by d_salary) rn
from table 2) b
on a.rn=b.rn
Generally you can create a row count using the row_number() window function on both tables and use this as join criterion. But this requires a certain order for both tables, which means that you have explicitly tell the query why is the Admin record ordered first and must be joined on the first record of table 1:
SELECT
*
FROM (
SELECT
*,
row_number() OVER (ORDER BY e_id) as row_count -- assuming e_id is your order criterion
FROM table1
) t1
JOIN (
SELECT
*,
row_number() OVER (ORDER BY /*whatever you expect to be ordered*/) as row_count
FROM table2
) t2
ON t1.row_count = t2.row_count

Get next value for each of values from next table CTE

I have the following table:
dbo.split
Name Time
Alex 120
John 80
John 300
Mary 500
Bob 900
And then another table dbo.travel
Name Time
Alex 150
Alex 160
Alex 170
John 90
John 100
John 310
Mary 550
Mary 600
Mary 499
Bob 800
Bob 700
For each value in table split I need to find the next value in table travel. I tried to do it with CTE a with ROW_NUMBER() to get next by group, but there's no way I can group by correct value, since dbo.split can containt multiple values for the same name.
I'm looking for the following output:
Name Time TravelTime
Alex 120 150
John 80 90
John 300 310
Mary 500 550
Bob 900 NULL
Here's what I have so far but it fails because split table can have multiple records per person:
;with result as (
select t.*,
ROW_NUMBER() OVER (Partition BY t.Name order by t.Time) as rn
from travel t join split s
on t.Name = s.Name and t.TIME>s.Time
)
I would use apply:
select s.*, t.time
from split s outer apply
(select top (1) t.*
from travel t
where t.name = s.name and t.time > s.time
order by t.time asc
) t;
In this case, apply is doing essentially the same thing as a correlated subquery, so you could phrase it that way as well.
You can try as below
Select * from(Select
Name,t.time,t1.time,
Row_number() over (partition by
Name,t.time order by t1.time) rn
from split t
Join travel t1 on t.time <t1.time and
t.name =t1.name)
where
rn=1;

SQL counting with condition

If I have a table called Buildings.
Room_No Bldg Capacity
112 SCEN 23
242 JBHT 25
542 SCEN 4
324 JBHT 24
What I want is to print out the Bldg name and the total number of rooms that have a capacity more than 20 in each building. So it is supposed to look like:
Bldg Total
SCEN 1
JBHT 2
Am I going on the right track:
Select Bldg, Count(Capacity > 20) as Total from Buildings Group By Total Desc
You could use CASE:
Select Bldg, Count(CASE WHEN Capacity > 20 THEN 1 END) as Total
from Buildings
Group By Bldg
ORDER BY Total DESC;
If you are using Postgresql you could rewrite it as:
Select Bldg, Count(1) FILTER(WHERE Capacity>20) as Total
from Buildings
Group By Bldg
ORDER BY Total DESC;
Rextester Demo
The other answers seem overly complicated for this problem. The solution is rather simple:
SELECT Bldg, COUNT(*) AS count
FROM Buildings
WHERE Capacity > 20
GROUP BY Bldg
Here is the Fiddle: http://sqlfiddle.com/#!9/308a6/1