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
Related
I have a table holding various information change related to employees. Some information change over time, but not alltogether, and changes occur periodically but not regularly. Changes are recorded by date, and if an item is not changed for the given employee at the given time, then the item's value is Null for that record. Say it looks like this:
employeeId
Date
Salary
CommuteDistance
1
2000-01-01
1000
Null
2
2000-01-15
2000
20
3
2000-01-30
3000
Null
2
2010-02-15
2100
Null
3
2010-03-30
Null
30
1
2020-02-01
1100
10
1
2030-03-01
Null
100
Now, how can I write a query to fill the null values with the most recent non-null values for all employees at all dates, while keeping the value Null if there is no such previous non-null value? It should look like:
employeeId
Date
Salary
CommuteDistance
1
2000-01-01
1000
Null
2
2000-01-15
2000
20
3
2000-01-30
3000
Null
2
2010-02-15
2100
20
3
2010-03-30
3000
30
1
2020-02-01
1100
10
1
2030-03-01
1100
100
(Note how the bolded values are taken over from previous records of same employee).
I'd like to use the query inside a view, then in turn query that view to get the picture at an arbitrary date (e.g., what were the salary and commute distance for the employees on 2021-08-17? - I should be able to do that, but I'm unable to build the view). Or, is there a better way to acomplish this?
There's no point in showing my attempts, since I'm quite inexperienced with advanced sql (I assume the solution empolys advanced knowledge, since I found my basic knowledge insufficient for this) and I got nowhere near the desired result.
You may get the last not null value for employee salary or CommuteDistance using the following:
SELECT T.employeeId, T.Date,
COALESCE(Salary, MAX(Salary) OVER (PARTITION BY employeeId, g1)) AS Salary,
COALESCE(CommuteDistance, MAX(CommuteDistance) OVER (PARTITION BY employeeId, g2)) AS CommuteDistance
FROM
(
SELECT *,
MAX(CASE WHEN Salary IS NOT null THEN Date END) OVER (PARTITION BY employeeId ORDER BY Date) AS g1,
MAX(CASE WHEN CommuteDistance IS NOT null THEN Date END) OVER (PARTITION BY employeeId ORDER BY Date) AS g2
FROM TableName
) T
ORDER BY Date
See a demo.
We group by employeeId and by Salary/CommuteDistance and all the nulls after them by Date. Then we fill in the blanks.
select employeeId
,Date
,max(Salary) over(partition by employeeId, s_grp) as Salary
,max(CommuteDistance) over(partition by employeeId, d_grp) as CommuteDistance
from (
select *
,count(case when Salary is not null then 1 end) over(partition by employeeId order by Date) as s_grp
,count(case when CommuteDistance is not null then 1 end) over(partition by employeeId order by Date) as d_grp
from t
) t
order by Date
employeeId
Date
Salary
CommuteDistance
1
2000-01-01
1000
null
2
2000-01-15
2000
20
3
2000-01-30
3000
null
2
2010-02-15
2100
20
3
2010-03-30
3000
30
1
2020-02-01
1100
10
1
2030-03-01
1100
100
Fiddle
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
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;
First 12 rows of Table T1:
Name Status Duration
Todd Active 60
Todd Active 60
Todd Active 60
Todd Schedu 60
Todd Schedu 60
Todd Schedu 120
Todd Schedu 120
Bran Active 30
Bran Active 30
Bran Active 60
Bran No Show 120
Bran No Show 120
If I run this query (or use a DISTINCT without the GROUP BY):
SELECT Name, Status, Duration
FROM Table T1
GROUP BY Name,Status,Duration
I get:
Name Status Duration
Todd Active 60
Todd Schedu 60
Todd Schedu 120
Bran Active 30
Bran Active 60
Bran No Show 120
From the above result, I want the desired result as SUM(Duration) GROUPED BY Name, Status:
Name Status Duration
Todd Active 60
Todd Schedu 180
Bran Active 90
Bran No Show 120
I'm trying this query to achieve the desired result:
SELECT Name, Status, SUM(Duration)
FROM Table T1
GROUP BY Name,Status
But I'm getting huge numbers for SUM(Duration) - It's probably adding all the durations and not the distinct durations for each group of Name and Status.
One method to get what you want uses a subquery:
SELECT Name, Status, SUM(Duration)
FROM (SELECT Name, Status, Duration
FROM Table T1
GROUP BY Name,Status,Duration
) NSD
GROUP BY Name, Status;
You can use Distinct inside SUM function. It will give you expected result.
SELECT Name, Status, SUM(DISTINCT Duration)
FROM T1
GROUP BY Name,Status
You could use CTE,
WITH C1 AS(
SELECT Name, Status, Duration
FROM Table T1
GROUP BY Name,Status,Duration
)
SELECT Name,Status,SUM(Duration) FROM C1 GROUP BY Name,Status
with temp_cte
as
(select Name, Status, Duration
FROM dbo.test2
group by name,status,duration
)
select tc.name,tc.status,sum(tc.duration) from temp_cte as tc
group by tc.name,tc.status
order by name
I have a txn table with columns ac_id, txn_amt. It will store the data txn amounts along with account ids. Below is example of data
AC_ID TXN_AMT
10 1000
10 1000
10 1010
10 1030
10 5000
10 5010
10 10000
20 32000
20 32200
20 5000
I want to write a query in such a way that all the amounts which are within 10% range of the previous amounts should be grouped together. Output should be something like this:
AC_ID TOTAL_AMT TOTAL_CNT GROUP
10 4040 4 1
10 10010 2 2
20 64200 2 3
20 5000 1 4
I tried with LAG function but still clueless. This is the code snippet I tried:
select ac_id, txn_amt, round((((txn_amt - lag(txn_amt, 1) over (partition by ac_id order by ac_id, txn_amt))/txn_amt)*100,2) as amt_diff_pct from txn;
Any clue or help will be highly appreciated.
If by previous you mean "the largest amount less than", then you can do this. You can find where the gaps are (i.e. larger than a 10% difference). Then you can assign a group by counting the number of gaps:
select ac_id, sum(txn_amt) as total_amt, count(*) as total_cnt, grp
from (select t.*,
sum(case when prev_txn_amt * 1.1 > txn_amt then 0 else 1 end) over
(partition by ac_id order by txn_amt) as grp
from (select t.*,
lag(txn_amt) over (partition by ac_id order by txn_amt) as prev_txn_amt
from txn t
) t
) t
group by ac_id, grp;