Getting the max and the min paid departments - sql-server

I have the following query :
SELECT D.Dept,(SUM(D.AMOUNT) /SUM(A.AMOUNT)) AS Res
FROM Dept AS D
INNER JOIN Charge AS A ON D.DeptId=A.DeptId
GROUP BY D.Dept
Input :
Dept table :
Dep Amount
1 300
1 300
2 1000
3 3000
Charge table :
Dep Charge
1 150
1 150
2 200
3 300
I want to calculate the sum of the amount of the salaries for each department and divide it by the charges of each department
I want to have the dept having the max and the dept having the min like below :
Dept Res
3 10
2 5
1 2
To select the max and the min dept :
Dept Res
3 10
1 2

We can use a CTE with 2 RANK ordered in opposite ways and then get the first in each direction to get the mini et maxi
create table Dept (Dept int, Amount int);
insert into dept values
(1, 200),
(1, 345),
(2, 690),
(3, 3000);
create table Charge (Dept int, Amount int);
insert into Charge values
(1, 568),
(1, 657),
(2, 300),
(3, 300);
SELECT
D.Dept,
SUM(D.AMOUNT) Salaries,
SUM(A.AMOUNT) Expenses,
round((1e* SUM(D.AMOUNT) /SUM(A.AMOUNT)),2) AS Res
FROM Dept AS D
JOIN Charge AS A ON D.Dept=A.Dept
GROUP BY D.Dept
ORDER BY (1e* SUM(D.AMOUNT) /SUM(A.AMOUNT)) DESC
Dept | Salaries | Expenses | Res
---: | -------: | -------: | ---:
3 | 3000 | 300 | 10
2 | 690 | 300 | 2.3
1 | 1090 | 2450 | 0.44
with allDepts as(
SELECT
D.Dept,
rank() over ( order by (1e* SUM(D.AMOUNT) /SUM(A.AMOUNT)) DESC) maxi,
rank() over (order by (1e* SUM(D.AMOUNT) /SUM(A.AMOUNT)) asc) mini,
round((1e* SUM(D.AMOUNT) /SUM(A.AMOUNT)),2) AS Res
FROM Dept AS D
JOIN Charge AS A ON D.Dept=A.Dept
GROUP BY D.Dept
)
select
Dept, Res
from allDepts
where maxi = 1
or mini = 1;
Dept | Res
---: | ---:
1 | 0.44
3 | 10
db<>fiddle here

You can aggregate and join each query as a derived table, derive the max and min values using a windowed aggregate, then filter:
with r as (
select d.Dept, d.Amount / c.Amount Res,
Min(d.Amount / c.Amount) over() MinAmount,
Max(d.Amount / c.Amount) over() MaxAmount
from (
select dept, Sum(Amount) Amount
from Dept
group by Dept
)d join (
select dept, Sum(Amount) Amount
from Charge
group by Dept
) c on c.Dept=d.Dept
)
select Dept, Res
from r
where Res in (MinAmount, MaxAmount);
Demo Fiddle

First aggregate both in sub-queries.
This gets a 1-on-1 relationship on the DeptId.
Then to get the MIN/MAX Res you can use ROW_NUMBER or DENSE_RANK.
SELECT DeptId, Res
FROM
(
SELECT D.DeptId, DeptAmount, ChargeAmount
, CAST(1.0*DeptAmount/NULLIF(ChargeAmount, 0) AS INT) AS Res
, DENSE_RANK() OVER (ORDER BY 1.0*DeptAmount/NULLIF(ChargeAmount, 0) ASC) AS RNK_ASC
, DENSE_RANK() OVER (ORDER BY 1.0*DeptAmount/NULLIF(ChargeAmount, 0) DESC) AS RNK_DESC
FROM (
SELECT DeptId, SUM(Amount) AS DeptAmount
FROM Dept
GROUP BY DeptId
) D
INNER JOIN (
SELECT DeptId, SUM(Charge) AS ChargeAmount
FROM Charge
GROUP BY DeptId
) C ON C.DeptId = D.DeptId
) Q
WHERE (RNK_ASC = 1 OR RNK_DESC = 1)
ORDER BY RNK_DESC;
DeptId | Res
-----: | --:
3 | 10
1 | 2
Test on db<>fiddle here

Related

SQL Server how to sum max for specific category?

Got a problem when constructing a analysis SQL using SQL Server
The raw data as below
GameID | UsrRegID | Score_User
281 | 1 | 1
281 | 1 | 2
281 | 1 | 3
282 | 1 | 0
282 | 1 | 0
282 | 1 | 1
283 | 1 | 2
283 | 1 | 3
Below is the expect output result:
Distinct_Count_GameID | UsrRegID | Score_User
3 | 1 | 7
The logic for calculating the Score_user as below:
Sum(Max(Score_user) for each GemeID)
So the result need to be 3+1+3=7.
Can using the pure SQL to get the above expecting output?
I think we need to aggregate twice here. One option uses ROW_NUMBER:
WITH cte AS (
SELECT GameID, UsrRegID, Score_User,
ROW_NUMBER() OVER (PARTITION BY GameID, UsrRegID ORDER BY Score_User DESC) rn
FROM yourTable
)
SELECT
UsrRegID,
COUNT(DISTINCT GameID) AS Distinct_Count_GameID,
SUM(Score_User) AS Score_User
FROM cte
WHERE rn = 1
GROUP BY
UsrRegID;
You can't do an aggregate of an aggregate on the same SELECT, you can chain them together with CTE or subqueries.
;WITH Maxs AS
(
SELECT
T.GameID,
T.UsrRegID,
MaxScore = MAX(T.Score_User)
FROM
YourTable AS T
GROUP BY
T.GameID,
T.UsrRegID
)
SELECT
M.UsrRegID,
Distinct_Count_GameID = COUNT(DISTINCT(M.GameID)),
Score_User = SUM(M.MaxScore)
FROM
Maxs AS M
GROUP BY
M.UsrRegID
You can also try like following.
SELECT Count(DISTINCT [rgameid]) Distinct_Count_GameID,
Count(DISTINCT [usrregid]) UsrRegID,
(SELECT Sum(M)
FROM (SELECT Max([score_user]) M
FROM [TableName]
GROUP BY [rgameid])t) AS Score_User
FROM [TableName]
DEMO
First find maximum value of score for each GameId and UsrRegID and then find SUM() for the column, Score_User and group it by the columns, GameID and UsrRegID using GROUP BY clause.
Query
select count(distinct [t].[GameID]) as [GameID], [t].[UsrRegID],
sum([t].[Score_User]) as [Score_User] from(
select [GameID], [UsrRegID], max([Score_User]) as [Score_User]
from [your_table_name]
group by [GameID], [UsrRegID]
) as [t]
group by [t].[UsrRegID];
Or, give a row number based on the descending order of score value and group by GameID and UsrRegID. Then find the count of distinct GameId and sum of maximum score.
Query
;with cte as(
select [rn] = row_number() over(
partition by [GameID], [UsrRegID]
order by [Score_User] desc
), *
from [your_table_name]
)
select count(distinct [GameID]) as [GameID], [UsrRegID],
sum([Score_User]) as [Score_User] from cte
where [rn] = 1
group by [UsrRegID];
Aggregates and a COUNT(Distinct GameID):
declare #raw as table (GameID int, UsrRegID int, Score_user int)
insert into #raw values (281, 1, 1)
,(281, 1, 2)
,(281, 1, 3)
,(282, 1, 0)
,(282, 1, 0)
,(282, 1, 1)
,(283, 1, 2)
,(283, 1, 3)
select count(distinct GameID) as Distinct_Count_GameID, UsrRegID, sum(max_score_user)
from
(
select GameID
, UsrRegID
, max(score_user) as max_score_user
from #raw
group by GameID, UsrRegID
) a
group by a.UsrRegID

If Value is present in two consecutive months , display only one month in sql

I would want to check ID in consecutive months, IF Same ID is present in two consecutive months then consider that ID only for 1st month.
If ID's are not in consecutive month then show the distinct ID's grouped by start date month.(We consider only start date)
For example, ID 1 is present in start date months january and Feb , then Distinct count of this ID will be 1 in Jan, how ever ID 2 and 3 are
present in Jan and March and Feb and May Resp, now I would like to see this distinct count of ID in Jan and March.
Current Data
Table1:
ID StartDate EndDate
1 2017-01-12 2017-01-28
1 2017-01-19 2017-01-28
1 2017-01-29 2017-02-11
1 2017-02-01 2017-02-11
1 2017-02-19 2017-02-24
2 2017-01-12 2017-01-28
2 2017-01-19 2017-01-28
2 2017-03-09 2017-03-20
3 2017-02-12 2017-02-28
3 2017-02-19 2017-02-28
3 2017-05-05 2017-05-29
3 2017-05-09 2017-05-29
I tried with below logic bt I know I am missing on something here.
select t.* from Table1 t
join Table1 t t1
on t1.ID=t.ID
and datepart(mm,t.StartDate)<> datepart(mm,t1.StartDate)+1
Expected Result:
DistinctCount StartDateMonth(In Numbers)
1 1(Jan)
2 1(Jan)
2 3(March)
3 2(Feb)
3 5(May)
Any help is appreciated!
Here's my solution. The thinking for this is:
1) Round all the dates to the first of the month, then work with the distinct dataset of (ID, StartDateRounded). From your dataset, the result should look like this:
ID StartDateRounded
1 2017-01-01
1 2017-02-01
2 2017-01-01
2 2017-03-01
3 2017-02-01
3 2017-05-01
2) From this consolidated dataset, find all records by ID that do not have a record for the previous month (which means it's not a consecutive month and thus is a beginning of a new data point). This is your final dataset
with DatesTable AS
(
SELECT DISTINCT ID
,DATEADD(month,DateDiff(month,0,StartDate),0) StartDateRounded
,DATEADD(month,DateDiff(month,0,StartDate)+1,0) StartDateRoundedPlusOne
FROM Table1
)
SELECT t1.ID, DatePart(month,t1.StartDateRounded) AS StartDateMonth
FROM DatesTable t1
LEFT JOIN DatesTable t2
ON t1.ID = t2.ID
AND t1.StartDateRounded = t2.StartDateRoundedPlusOne
WHERE t2.ID IS NULL; --Verify no record exists for prior month
sqlfiddler for reference. Let me know if this helps
Just need to take advantage of the lag on the inner query to compare values between rows, and apply the logic in question on the middle query, and then do a final select.
/*SAMPLE DATA*/
create table #table1
(
ID int not null
, StartDate date not null
, EndDate date null
)
insert into #table1
values (1, '2017-01-12', '2017-01-28')
, (1, '2017-01-19', '2017-01-28')
, (1, '2017-01-29', '2017-02-11')
, (1, '2017-02-01', '2017-02-11')
, (1, '2017-02-19', '2017-02-24')
, (2, '2017-01-12', '2017-01-28')
, (2, '2017-01-19', '2017-01-28')
, (2, '2017-03-09', '2017-03-20')
, (3, '2017-02-12', '2017-02-28')
, (3, '2017-02-19', '2017-02-28')
, (3, '2017-05-05', '2017-05-29')
, (3, '2017-05-09', '2017-05-29')
/*ANSWER*/
--Final Select
select c.ID
, c.StartDateMonth
from (
--Compare record values to rule a record in/out based on OP's logic
select b.ID
, b.StartDateMonth
, case when b.StartDateMonth = b.StartDateMonthPrev then 0 --still the same month?
when b.StartDateMonth = b.StartDateMonthPrev + 1 then 0 --immediately prior month?
when b.StartDateMonth = 1 and b.StartDateMonthPrev = 12 then 0 --Dec/Jan combo
else 1
end as IncludeFlag
from (
--pull StartDateMonth of previous record into current record
select a.ID
, datepart(mm, a.StartDate) as StartDateMonth
, lag(datepart(mm, a.StartDate), 1, NULL) over (partition by a.ID order by a.StartDate asc) as StartDateMonthPrev
from #table1 as a
) as b
) as c
where 1=1
and c.IncludeFlag = 1
Output:
+----+----------------+
| ID | StartDateMonth |
+----+----------------+
| 1 | 1 |
| 2 | 1 |
| 2 | 3 |
| 3 | 2 |
| 3 | 5 |
+----+----------------+
Try the below query,
SELECT ID,MIN(YEARMONTH) AS YEARMONTH
FROM (
SELECT ID
,YEAR([StartDate])*100+MONTH([StartDate]) AS YEARMONTH
,LAG(YEAR([StartDate])*100+MONTH([StartDate]))
OVER(ORDER BY ID) AS PREVYEARMONTH
,ROW_NUMBER() OVER(ORDER BY ID) AS ROW_NO
FROM #Table1
GROUP BY ID,((YEAR([StartDate])*100)+MONTH([StartDate]))
) AS T
GROUP BY ID
,(CASE WHEN YEARMONTH - PREVYEARMONTH > 1 THEN ROW_NO ELSE 0 END)
ORDER BY ID
Output:
ID YEARMONTH
1 201701
2 201701
2 201703
3 201702
3 201705
Thank you all guys. most of the logic seemed to work..but I tried just with below one and I Was good with thiis.
SELECT t1.ID, DatePart(month,t1.Startdate) AS StartDateMonth
FROM DatesTable t1
LEFT JOIN DatesTable t2
ON t1.ID = t2.ID
AND DatePart(month,t1.Startdate) = DatePart(month,t2.Startdate)+1
WHERE t2.ID IS NULL;
Thanks again
Ok, I wrote my first query without checking, believed that will work correctly. This is my updated version, should be faster than other solutions
select
id
, min(st)%12 --this will return start month
, min(st)/12 + 1 --this will return year, just in case if you need it
from (
select
id, st, gr = st - row_number() over (partition by ID order by st)
from (
select
distinct ID, st = (year(StartDate) - 1) * 12 + month(StartDate)
from
#table2
) t
) t
group by id, gr

SELECT nearest under or equal to X based on SUM amount SQL Server

I have 2 tables:
Declare #tbitems TABLE (accntno varchar, saved_amount decimal)
INSERT INTO #tbitems
SELECT 001 , 25
Declare #tbtransact TABLE (idno INT , acctno varchar, amount decimal)
INSERT INTO #tbtransact
SELECT 1 , 001 , 10 UNION ALL
SELECT 2 , 001 , 10 UNION ALL
SELECT 3 , 001 , 10 UNION ALL
SELECT 4 , 001 , 10
tbitems:
accntno | saved_amount (decimal)
--------+-----------------------
001 | 25
tbtransact:
idno | acctno | amount (decimal)
------+---------+-----------------
1 | 001 | 10
2 | 001 | 10
3 | 001 | 10
4 | 001 | 10
How do I get the nearest idno with less than or equal to the saved_amount from tbitems by adding tbtransact amounts ordered by idno (I really don't know how to say it in words).
Anyway based on my table, my expected result should be idno 2 since the nearest under 25 is 20
If I would to do it in java, I would loop through tbtransact then add every row until I go higher than the saved_amount then get the idno before the current one. However I have no idea how to do it in sql.
Expected result is:
idno | acctno | amount
------+---------+-----------------
2 | 001 | 10
This query works for selecting the correct row for a specific account:
SELECT TOP 1
t.idno ,
s.*
FROM dbo.tbitems AS i
INNER JOIN dbo.tbtransact AS t ON i.accntno = t.acctno
CROSS APPLY ( SELECT SUM(l.amount) AS SumAmount
FROM dbo.tbtransact AS l
WHERE l.acctno = t.acctno
AND l.idno <= t.idno
) AS s
WHERE i.accntno = '001' AND s.SumAmount <= i.saved_amount
ORDER BY s.SumAmount DESC;
And this should word accross multipse accounts:
SELECT t.acctno, MAX(idno) AS idno, MAX(s.SumAmount) AS SumAmount
FROM dbo.tbitems AS i
INNER JOIN dbo.tbtransact AS t ON i.accntno = t.acctno
CROSS APPLY ( SELECT SUM(l.amount) AS SumAmount
FROM dbo.tbtransact AS l
WHERE l.acctno = t.acctno
AND l.idno <= t.idno
) AS s
WHERE s.SumAmount <= i.saved_amount
GROUP BY t.acctno
Here is an example using CTE and windowing function:
WITH cte1
AS ( SELECT t.acctno ,
t.amount ,
SUM(t.amount) OVER ( PARTITION BY t.acctno ORDER BY t.acctno, t.idno
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS RunningTotal
FROM dbo.tbtransact AS t
INNER JOIN dbo.tbitems AS i ON t.acctno = i.accntno
),
cte2
AS ( SELECT ROW_NUMBER() OVER ( PARTITION BY cte1.acctno ORDER BY cte1.RunningTotal DESC ) AS rn ,
cte1.acctno ,
cte1.amount ,
cte1.RunningTotal
FROM cte1
)
SELECT cte2.acctno ,
cte2.amount ,
cte2.RunningTotal
FROM cte2
WHERE cte2.rn = 1;
i tried ,it may helps you
Declare #tbitems TABLE (accntno INT , saved_amount decimal)
INSERT INTO #tbitems
SELECT 001 , 25
Declare #tbtransact TABLE (idno INT , acctno INT , amount decimal)
INSERT INTO #tbtransact
SELECT 1 , 001 , 10 UNION ALL
SELECT 2 , 001 , 10 UNION ALL
SELECT 3 , 001 , 10 UNION ALL
SELECT 4 , 001 , 10
SELECT *
FROM (SELECT t.idno,
t.acctno,
t.amount,
CASE
WHEN Sum(saved_amount)
OVER(
partition BY i.accntno
ORDER BY t.idno) <= i.saved_amount THEN i.saved_amount
ELSE 0
END AS Nearest_Saved_amount
FROM #tbtransact t
INNER JOIN #tbitems i
ON i.accntno = t.acctno)Dt
WHERE Dt.Nearest_Saved_amount <> 0
I misunderstood the question the first time. This should do it:
Declare #tbitems TABLE (accntno INT , saved_amount decimal)
INSERT INTO #tbitems
SELECT 001 , 20
Declare #tbtransact TABLE (idno INT , acctno INT , amount decimal)
INSERT INTO #tbtransact
SELECT 1 , 001 , 10 UNION ALL
SELECT 2 , 001 , 10 UNION ALL
SELECT 3 , 001 , 10 UNION ALL
SELECT 4 , 001 , 10
SELECT TOP 1 ttotal.*
FROM #tbtransact t
JOIN (
SELECT idno, acctno, (SELECT SUM(Amount) FROM #tbtransact WHERE acctno = ttemp.acctno and idno <= ttemp.idno) AS Amount
FROM #tbtransact ttemp
WHERE idno <= idno
GROUP BY idno, acctno
) AS ttotal on t.acctno = ttotal.acctno
INNER JOIN #tbitems i ON t.acctno = i.accntno
WHERE ttotal.Amount <= i.saved_amount
ORDER BY ttotal.Amount DESC

Getting a minimum total number of rows either side of a specific row

I have a table of players each having an ID (indexed primary key), a name, and a score. The table is not sorted except by index. e.g.
[dbo].[PlayerScores]
ID | Name | Score
=================
1 | Bob | 17
2 | Carl | 24
3 | Ann | 31
4 | Joan | 11
5 | Lou | 17
6 | Dan | 25
7 | Erin | 33
8 | Fred | 29
I've defined a leaderboard such that all of the players are ordered by their score and assigned a rank, so I'm using the RANK() function:
SELECT RANK() OVER (ORDER BY [Score] DESC) AS [Score_Rank],
[Name],
[Score]
FROM [dbo].[PlayerScores]
So far so good. For the above data, I'll get
Rank | Name | Score
=================
1 | Erin | 33
2 | Ann | 31
3 | Fred | 29
4 | Dan | 25
5 | Carl | 24
6 | Bob | 17
6 | Lou | 17
8 | Joan | 11
However, when I present this leaderboard to the players, I don't need or want to show them everything - only the players immediately above and below them (there won't be any paged navigation - players only get to see a snapshot of their overall position).
I'm therefore trying to retrieve (n) rows of data surrounding a specific player, such that:
If there are (n) or fewer rows in the table, all rows will be returned.
Where there are at least (n) rows in the table, (n) rows of data will be returned.
There should be (n/2) rows above and below the specified player.
If there aren't (n/2) rows above the specified player, return all the rows above, and enough rows below to make up (n) rows total.
If there aren't (n/2) rows below the specified player, return all the rows below, and enough rows above to make up (n) rows total.
How can I construct my query such that I can always return the minimum number of rows? E.g. for my above dataset and n=5, Erin would see
Rank | Name | Score
=================
1 | Erin | 33
2 | Ann | 31
3 | Fred | 29
4 | Dan | 25
5 | Carl | 24
While Dan would see
Rank | Name | Score
=================
2 | Ann | 31
3 | Fred | 29
4 | Dan | 25
5 | Carl | 24
6 | Bob | 17
And Lou would see
Rank | Name | Score
=================
4 | Dan | 25
5 | Carl | 24
6 | Bob | 17
6 | Lou | 17
8 | Joan | 11
I found a partial solution for this using a UNION on two queries (one getting n/2 rows above and one getting n/2 rows below the specified player), but it falls down if the player is at (or near) the top or bottom of the table - the resulting dataset is clipped, and I always want to retrieve a full (n) rows where possible.
I think the solution might have something to do with Window functions, making use of LAG and LEAD, but I honestly can't get my head around the syntax and most of the examples I've found don't care about not returning enough rows total. Thanks!
sql rank vs row number
Two versions of the same procedure, one outputs the result set in order, the second does not.
rextester link to try it out: http://rextester.com/JLQU48329
create table dbo.PlayerScores (Id int, Name nvarchar(64), Score int)
insert into dbo.PlayerScores (Id, Name, Score) values
(1,'Bob',17) ,(2,'Carl',24) ,(3,'Ann',31) ,(4,'Joan',11)
,(5,'Lou',17) ,(6,'Dan',25) ,(7,'Erin',33) ,(8,'Fred',29);
go
/* ordered resultset */
create procedure dbo.PlayerScores_getMiddle_byId (#PlayerId int, #Results int = 5) as
begin;
with cte as (
select
Score_Order = row_number() over (order by Score desc)
, Score_Rank = rank() over (order by Score desc)
, Id
, Name
, Score
from dbo.PlayerScores
)
select c.Score_Rank, c.Name, c.Score
from (
select top (#Results) i.*
from cte i
cross apply (select Score_Order from cte where Id = #PlayerId) as x
order by abs(i.Score_Order-x.Score_Order)
) as c
order by Score_Rank;
end
go
exec dbo.PlayerScores_getMiddle_byId 7,5; -- Erin
exec dbo.PlayerScores_getMiddle_byId 6,5; --Dan
exec dbo.PlayerScores_getMiddle_byId 5,5; --Lou
go
/* unordered result set */
/*
create procedure dbo.PlayerScores_getMiddle_byId (#PlayerId int,#Results int = 5) as
begin;
with cte as (
select
Score_Order = row_number() over (order by Score desc)
, Score_Rank = rank() over (order by Score desc)
, Id
, Name
, Score
from dbo.PlayerScores
)
select top (#Results) c.Score_Rank, c.Name, c.Score
from cte as c
cross apply (select
Score_Order
from cte
where Id = #PlayerId) as x
order by abs(c.Score_Order-x.Score_Order)
end
--go
exec dbo.PlayerScores_getMiddle_byId 7,5; -- Erin
exec dbo.PlayerScores_getMiddle_byId 6,5; --Dan
exec dbo.PlayerScores_getMiddle_byId 5,5; --Lou
--*/
This will do what you want.
WITH cte AS (
SELECT RANK() OVER (ORDER BY [Score] DESC) AS [Score_Rank],
ROW_NUMBER() OVER (ORDER BY [Score] DESC) AS [RowNum],
COUNT(ID) OVER (PARTITION BY (Select NULL)) AS MaxRow,
[Name],
[Score],
[ID]
FROM #playScores
)
SELECT Score_Rank, Name, Score
FROM
cte
CROSS APPLY (SELECT RowNum AS AnchorRN FROM cte WHERE ID = #playerID) tmp
WHERE
(
RowNum <=
CASE WHEN tmp.AnchorRN < ((#n)/2) THEN #n
ELSE tmp.AnchorRN + ((#n)/2) END
)
AND
(
RowNum >=
CASE WHEN tmp.AnchorRN > (MaxRow - (#n)/2) THEN (MaxRow -#n + 1)
ELSE tmp.AnchorRN - ((#n)/2) END
);
SELECT *
, ROW_NUMBER() OVER (ORDER BY Score) AS RowNum
FROM
#playScores
ORDER BY
RowNum;
This is the whole answer and test code.
DECLARE #playScores TABLE (
ID INT
, Name NVARCHAR(50)
, Score INT
);
INSERT INTO #playScores (ID, Name, Score)
VALUES
(1 ,' Bob ', 17),
(2 ,' Carl ', 24),
(3 ,' Ann ', 31),
(4 ,' Joan ', 11),
(5 ,' Lou ', 17),
(6 ,' Dan ', 25),
(7 ,' Erin ', 33),
(8 ,' Fred ', 29);
DECLARE #n INT = 5;
DECLARE #playerID INT =5;
SELECT *
FROM
#playScores
ORDER BY
Score DESC;
WITH cte AS (
SELECT RANK() OVER (ORDER BY [Score] DESC) AS [Score_Rank],
ROW_NUMBER() OVER (ORDER BY [Score] DESC) AS [RowNum],
COUNT(ID) OVER (PARTITION BY (Select NULL)) AS MaxRow,
[Name],
[Score],
[ID]
FROM #playScores
)
SELECT Score_Rank, Name, Score
FROM
cte
CROSS APPLY (SELECT RowNum AS AnchorRN FROM cte WHERE ID = #playerID) tmp
WHERE
(
RowNum <=
CASE WHEN tmp.AnchorRN < ((#n)/2) THEN #n
ELSE tmp.AnchorRN + ((#n)/2) END
)
AND
(
RowNum >=
CASE WHEN tmp.AnchorRN > (MaxRow - (#n)/2) THEN (MaxRow -#n + 1)
ELSE tmp.AnchorRN - ((#n)/2) END
);
SELECT *
, ROW_NUMBER() OVER (ORDER BY Score) AS RowNum
FROM
#playScores
ORDER BY
RowNum;
SELECT *
, ROW_NUMBER() OVER (ORDER BY Score) AS RowNum
FROM
#playScores
ORDER BY
RowNum;
or use standard SQL:
with pRank(id, name, rank)
as (Select p.Id, p.Name nam,
(Select count(*) from players
where score <= p.score) rnk
from players p)
Select p.id, p.nam, p.score,
n.id, n.nam, n.score
from pRank p join pRank n
on n.Rnk between
case when p.Rnk < #n/2 then 0
else p.Rnk - #n / 2 end
and case when p.Rnk < #n/2 then #n
else p.Rnk + #n / 2 end
order by p.rnk, p.Id, n.rnk
Test:
declare #t table
(id integer primary key not null,
nam varchar(30) not null, score int not null)
insert #t(id, nam, score)
values
(1, 'Bob ',17),
(2, 'Carl',24),
(3, 'Ann ',31),
(4, 'Joan',11),
(5, 'Lou ',17),
(6, 'Dan ',25),
(7, 'Erin',33),
(8, 'Fred',29)
declare #n int = 4;
with pRank(id, nam, rnk)
as (Select p.Id, p.Nam,
(Select count(*) from #t
where score <= p.score) rank
from #t p)
Select p.id, p.Nam, p.rnk,
n.id, n.nam, n.rnk
from pRank p join pRank n
on n.rnk between
case when p.rnk < #n/2 then 0
else p.rnk - #n / 2 end
and case when p.rnk < #n/2 then #n
else p.rnk + #n / 2 end
order by p.rnk, p.id, n.rnk .

different result Consecutive records in a table using SQL

I have the following Table definition with sample data. In the following table.
"TP" consecutive 3 records 2 times,then "SL" consecutive 1 records 2 times……
id | Result
1 | TP
2 | TP
3 | TP
4 | SL
5 | TP
6 | NONE
7 | NONE
8 | SL
9 | TP
10 | TP
11 | TP
12 | SL
13 | SL
14 | SL
And I am looking for a result like this:
comboNum | num
TP_3 | 2
SL_1 | 2
TP_1 | 1
SL_3 | 1
Any suggestions?
You can as the below
DECLARE #Tbl TABLE (Id INT, Result VARCHAR(10))
INSERT INTO #Tbl
VALUES
(1,'TP')
,(2,'TP')
,(3,'TP')
,(4,'SL')
,(5,'TP')
,(6,'NONE')
,(7,'NONE')
,(8,'SL')
,(9,'TP')
,(10,'TP')
,(11,'TP')
,(12,'SL')
,(13,'SL')
,(14,'SL')
;WITH CTE1
AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY Result, Id) RowId FROM #Tbl
),CTE2
AS
(
SELECT
Result,
MAX(C.Id) - MIN(C.Id) Cons,
MIN(C.Id) StartP,
MAX(C.Id) EndP
FROM
CTE1 C
WHERE
c.Result <> 'NONE'
GROUP BY
C.Result,
C.RowId - C.Id
)
SELECT
C.Result + '_' + CAST(C.Cons + 1 AS VARCHAR(50)) AS comboNum,
COUNT(*) AS Num
FROM
CTE2 C
GROUP BY
C.Result,
C.Cons
ORDER BY Num DESC
Result:
comboNum Num
------------------ -----------
TP_3 2
SL_1 2
TP_1 1
SL_3 1
Two CTEs with tricky ROW_NUMBER() sequence:
;WITH cte as (
SELECT id,
Result,
ROW_NUMBER() OVER (PARTITION BY Result ORDER BY id) - ROW_NUMBER() OVER (ORDER BY id) as seq
FROM YourTable
WHERE Result != 'NONE'
), final AS (
SELECT MIN(id) as mid,
Result +'_'+ CAST(MAX(id)-MIN(id)+1 as nvarchar(max)) as comboNum
FROM cte
GROUP BY Result, seq
)
SELECT comboNum,
COUNT(mid) as num
FROM final
GROUP BY comboNum
ORDER BY MIN(mid)
Output:
comboNum num
TP_3 2
SL_1 2
TP_1 1
SL_3 1
Declare #tblTest AS TABLE(
ID INT,
Result VARCHAR(50)
)
INSERT INTO #tblTest VALUES(1,'TP')
,(2,'TP')
,(3,'TP')
,(4,'SL')
,(5,'TP')
,(6,'NONE')
,(7,'NONE')
,(8,'SL')
,(9,'TP')
,(10,'TP')
,(11,'TP')
,(12,'SL')
,(13,'SL')
,(14,'SL')
;WITH X AS
(
SELECT
T.*,
ROW_NUMBER() OVER (ORDER BY ID) AS SrNo,
ROW_NUMBER() OVER (PARTITION BY Result ORDER BY id) AS PartNo
FROM #tblTest T
WHERE Result<>'NONE'
)
SELECT
ComboNum,
COUNT(Occurance) AS Num
FROM
(
SELECT
Result +'_'+ CAST((max(ID)-min(ID))+1 AS VARCHAR(5)) AS ComboNum,
(MAX(ID)-MIN(ID))+1 AS Occurance,
MIN(SrNo) AS SrNo
FROM X
GROUP BY Result, (SrNo - PartNo)
) Z
GROUP BY ComboNum,Occurance
ORDER BY MIN(SrNo)
Output:

Resources