SUM of MAX(TOP x) - sql-server

Say I have a table like the following:
PK Code Value
1 A 200
2 A 300
3 A 25
4 A 75
5 A 50
6 A 15
7 A 300
8 A 75
How would I get the value of the top 4 highest values where code=A (i.e. just want the sum of 300 + 300 + 200 + 75)
Thanks

You can use a derived table or Common Table Expression to get the top 4 then SUM that.
SELECT SUM(Value) As Top4Sum
FROM
(
SELECT TOP (4) Value
FROM YourTable
WHERE Code = 'A'
ORDER BY Value DESC
) T
If you wanted the SUM of the TOP 4 for every Code you could do
;WITH CTE
AS (SELECT *,
ROW_NUMBER() OVER (PARTITION BY Code ORDER BY Value DESC) RN
FROM YourTable)
SELECT Code,
SUM(Value)
FROM CTE
WHERE RN <= 4
GROUP BY Code

Related

DENSE_RANK() OVER ( PARTITION by

I've: DENSE_RANK() OVER ( PARTITION BY state ORDER BY population desc) as ranking
I'd like to skip the first 10 results of my ranking and limit it to 50 results per state. Is this possible?
so:
with data(state, pop) as (
select * from values
(1,10),
(1,10),
(1,11),
(1,12),
(2,10),
(2,11),
(2,12),
(2,12)
)
select d.*
,DENSE_RANK() OVER ( PARTITION BY state ORDER BY pop desc) as ranking
from data as d
gives:
STATE
POP
RANKING
2
12
1
2
12
1
2
11
2
2
10
3
1
12
1
1
11
2
1
10
3
1
10
3
so we can use QAULIFY to only keep those >= 3 to avoid the first "2"
select d.*
,DENSE_RANK() OVER ( PARTITION BY state ORDER BY pop desc) as ranking
from data as d
qualify ranking >= 3
STATE
POP
RANKING
2
10
3
1
10
3
1
10
3
but this shows, as we had 4 per set, we have removed 2,3 respectively from the two sets.
This can also be down with the DENSE_RANK in the QUALIFY like:
select d.*
from data as d
qualify DENSE_RANK() OVER ( PARTITION BY state ORDER BY pop desc) >= 3
STATE
POP
2
10
1
10
1
10
thus skipping the 10's and take things up to the 50's can be done with:
qualify DENSE_RANK() OVER ( PARTITION BY state ORDER BY pop desc) between 10 and 50
but if you really want the absolute first 10 skipped and 50 or less rows, you should use a non-duplicating rank like ROW_NUMBER, thus to skip 10 and take make 50 per state:
qualify ROW_NUMBER() over (PARTITION by state order by pop desc) between 10 and 60

How to Sum (MAX values) from different value groups in same column SQL Server

I have a table like this:
Date
Consec_Days
2015-01-01
1
2015-01-03
1
2015-01-06
1
2015-01-07
2
2015-01-09
1
2015-01-12
1
2015-01-13
2
2015-01-14
3
2015-01-17
1
I need to Sum the max value (days) for each of the consecutive groupings where Consec_Days are > 1. So the correct result would be 5 days.
This is a type of gaps-and-islands problem.
There are many solutions, here is one simple one
Get the start points of each group using LAG
Calculate a grouping ID using a windowed conditional count
Group by that ID and take the highest sum
WITH StartPoints AS (
SELECT *,
IsStart = CASE WHEN LAG(Consec_Days) OVER (ORDER BY Date) = 1 THEN 1 END
FROM YourTable t
),
Groupings AS (
SELECT *,
GroupId = COUNT(IsStart) OVER (ORDER BY Date)
FROM StartPoints
WHERE Consec_Days > 1
)
SELECT TOP (1)
SUM(Consec_Days)
FROM Groupings
GROUP BY
GroupId
ORDER BY
SUM(Consec_Days) DESC;
db<>fiddle
with cte as (
select Consec_Days,
coalesce(lead(Consec_Days) over (order by Date), 1) as next
from YourTable
)
select sum(Consec_Days)
from cte
where Consec_Days <> 1 and next = 1
db<>fiddle

How can I use Row Num partition by different default value

I have something like
ID Mobile isOptOut
1 123 1
2 123 0
3 123 0
4 123 1
5 234 1
6 234 0
to have something like partition by mobile and isOptOut
if the isOptOut is equal to 1 start from 0
otherwise start from 6
ID Mobile isOptOut RowNum
1 123 1 0
4 123 1 1
2 123 0 6
3 123 0 7
5 234 1 0
6 234 0 6
select *,
case when isOptOut = 0 then ROW_Number() OVER(
PARTITION BY Mobile ,isOptOut
order by Mobile ,isOptOut
) as [Row Number]
from r
where isOptOut = 1
Thanks so much
You've on the right path with the window ROW_NUMBER function. However,
As you want to number all the rows, you'll need it for all rows (not just isOptOut = 0)
In the window function, I've ordered it by ID so it will always return the same values (if you order by the same fields as the partition, then they could come out in any order)
Once you have the row_numbers, add the modifier e.g., if isOptOut = 0, add 5 to the row number; or if it's 1, subtract 1
; WITH src AS
(select *,
ROW_Number() OVER(
PARTITION BY Mobile, isOptOut
ORDER BY ID -- Note I changed this to 'ID' for ordering
) as [rn]
FROM r
)
SELECT ID, Mobile, isOptOut,
CASE isOptOut
WHEN 0 THEN rn + 5
WHEN 1 THEN rn - 1
ELSE NULL
END AS RowNum
FROM src
SELECT ID,Mobile,isOptOut, IIF(isOptOut=1,ROWNUMBER-1,ROWNUMBER+5),ROWNUMBER FROM (
SELECT *, (ROW_Number() OVER(
PARTITION BY Mobile,isOptOut ORDER BY ID, Mobile,isOptOut )) ROWNUMBER
FROM #table) as T ORDER BY ID
I think subquery and IIF suit you to reach your target. I created a window function and in the main query the condition.

Accumulative Update for previous records

I have table that shows these information
Month NewClients OnHoldClients
5-2017 10 2
6-2017 16 4
7-2017 11 1
8-2017 15 6
9-2017 18 7
I am trying to find the accumulative total for each month
which is
(NewClients - OnHoldClients) + Previous Month Total
Something like this
Month NewClients OnHoldClients Total
5-2017 10 2 8
6-2017 16 4 20
7-2017 11 1 30
8-2017 15 6 39
9-2017 18 7 50
the query i tried to build was something like this but I think should be an easier way to do that
UPDATE MyTable
SET Total = (SELECT TOP 1 Total FROM MyTable B WHERE B.Month < A.Month) + NewClients - OnHoldClients
FROM MyTable A
Before we begin, note the mere fact that you're facing such calculative problem is a symptom that maybe you don't have the best possible design. Normally for this purpose calculated values are being stored along the way as the records are inserted. So i'd say you'd better have a total field to begin with and calculate it as records amass.
Now let's get down to the problem at hand. i composed a query which does that nicely but it's a bit verbose due to recursive nature of the problem. However, it yields the exact expected result:
DECLARE #dmin AS date = (SELECT min(mt.[Month]) from dbo.MyTable mt);
;WITH cte(_Month, _Total) AS (
SELECT mt.[Month] AS _Month, (mt.NewClients - mt.OnHoldClients) AS _Total
FROM dbo.MyTable mt
WHERE mt.[Month] = #dmin
UNION ALL
SELECT mt.[Month] AS _Month, ((mt.NewClients - mt.OnHoldClients) + ccc._Total) AS _Total
FROM dbo.MyTable mt
CROSS APPLY (SELECT cc._Total FROM (SELECT c._Total,
CAST((row_number() OVER (ORDER BY c._Month DESC)) AS int) as _Rank
FROM cte c WHERE c._Month < mt.[Month]) as cc
WHERE cc._Rank = 1) AS ccc
WHERE mt.[Month] > #dmin
)
SELECT c._Month, max(c._Total) AS Total
FROM cte c
GROUP BY c._Month
It is a recursive CTE structure that goes about each record all along the way to the initial month and adds up to the final Total value. This query only includes Month and Total fields but you can easily add the other 2 to the list of projection.
Try this
;WITH CTE([Month],NewClients,OnHoldClients)
AS
(
SELECT '5-2017',10,2 UNION ALL
SELECT '6-2017',16,4 UNION ALL
SELECT '7-2017',11,1 UNION ALL
SELECT '8-2017',15,6 UNION ALL
SELECT '9-2017',18,7
)
SELECT [Month],
NewClients,
OnHoldClients,
SUM(MonthTotal)OVER( ORDER BY [Month]) AS Total
FROM
(
SELECT [Month],
NewClients,
OnHoldClients,
SUM(NewClients-OnHoldClients)OVER(PArtition by [Month] Order by [Month]) AS MonthTotal
FROM CTE
)dt
Result,Demo:http://rextester.com/DKLG54359
Month NewClients OnHoldClients Total
--------------------------------------------
5-2017 10 2 8
6-2017 16 4 20
7-2017 11 1 30
8-2017 15 6 39
9-2017 18 7 50

How to write this query without cursor in SQL Server 2008 R2?

I have this table ScoreDetails, 2 columns (there are more, but only 2 needed or this query). One is ScoreDate, Score.
The structure is like
2012:03:27: 5:06:37:134 27
2012:03:27: 5:06:37:276 37
2012:03:28: 4:12:97:019 19
2012:03:29: 7:06:37:134 7
2012:03:29: 8:06:37:134 0
2012:04:03: 12:06:37:739 16
2012:04:04: 23:21:15:834 33
2012:04:04: 15:08:24:697 12
2012:04:06: 5:06:37:134 0
2012:04:09: 5:06:37:134 2
2012:04:13: 5:06:37:134 92
What I want is to write a select query, without using temp table or cursor. Such that, I have a column that starts from 1 and keeps on increasing as 2,3 and so on, upto when the score is non-zero. But as soon as a zero is encountered in score column, it resets to 1 and then start again. Like this...
2012:03:27: 5:06:37:134 27 1
2012:03:27: 5:06:37:276 37 2
2012:03:28: 4:12:97:019 19 3
2012:03:29: 7:06:37:134 7 4
2012:03:29: 8:06:37:134 0 0
2012:04:03: 12:06:37:739 16 1
2012:04:04: 23:21:15:834 33 2
2012:04:04: 15:08:24:697 12 3
2012:04:06: 5:06:37:134 0 0
2012:04:09: 5:06:37:134 2 1
2012:04:13: 5:06:37:134 92 2
I am using SQL Server 2008 R2.
You can use common table expressions for that. I defined 2 anchor queries: one for records with 0 score and the other for the first record. Then you build up the result based on previous results until you find 0 score.
with cte
as
(
select ScoreDate, Score, ScoreRank, 0 as Value
from (select ScoreDate, Score, dense_rank() over (order by ScoreDate) ScoreRank
from ScoreDetails) X
where Score = 0
union all
select ScoreDate, Score, ScoreRank, 1 as Value
from (select ScoreDate, Score, dense_rank() over (order by ScoreDate) ScoreRank
from ScoreDetails) X
where Score <> 0 and ScoreRank = 1
union all
select X.ScoreDate, X.Score, X.ScoreRank, cte.Value + 1 as Value
from (select ScoreDate, Score, dense_rank() over (order by ScoreDate) ScoreRank
from ScoreDetails) X
inner join cte
on X.ScoreRank = cte.ScoreRank + 1
and X.Score <> 0
)
select ScoreDate, Score, Value, ScoreRank
from cte
order by ScoreDate
SQL Fiddle Demo
I won't spoil the fun of finding the solution yourself, but I will give you some hints on how to split the problem into smaller pieces:
Find all the records where the score is reset. Let's call this subquery the resetRecords.
Join the records of the original table to the resetRecords, such that every record has "its" reset record (i.e., the reset record that provides the base for its count).
Use ROW_NUMBER() OVER (PARTITION BY ... ) to assign the numbers.
Try to do this one step at a time. Beware: It won't be a simple query, so a solution with temp tables or cursors might be easier to understand and maintain.
Try something like this:
with x as (
select *, sum(case when Score=0 then 1 else 0 end) over(order by ScoreDate) as grp
from ScoreDetails
)
select ScoreDate, Score, row_number() over (partition by grp order by ScoreDate)
from x
order by ScoreDate
(as soon as a zero is encountered in score column, it resets to 1 and then start again, you said)

Resources