Table1
sub-id ref-id Name
1 1 Project 1
2 1 Project 2
3 2 Project 3
4 2 Project 4
Table2
sub-id ref-id log_stamp Recepient log_type
----------------------------------------------------
1 1 06/06/2011 person A 1
1 1 06/14/2011 person B 2
1 1 06/16/2011 person C 2
1 1 06/17/2011 person D 3
2 1 06/18/2011 person E 2
2 1 06/19/2011 person F 2
3 2 06/20/2011 person G 1
4 2 06/23/2011 person H 3
Result
Name ref-id start_date Recepient latest_comment Recepient completion_date Receipient
Project1 1 06/06/2011 person A 06/19/2011 person F 06/17/2011 person D
Project3 2 06/20/2011 person G NULL NULL 06/23/2011 person H
log_type of 1 stands for start_date
log_type of 2 stands for latest_comment
log_type of 3 stands for completion_date
The Name of the project is just the name of the top-most name in the same group of ref-id
have tried this for now
;with T as (select
Table2.ref-id,
Table2.log_stamp,
Table2 log.log_type
when 1 then '1'
when 2 then '2'
when 3 then '3'
end as title
from
Submission sb inner join submission_log log on Table1.[sub-id] = Table2.[sub-id]
)
select * from T
pivot (
max(log_stamp)
for title IN ([1],[2],[3],[5],[6],[9],[11])
I was unable to do it as a pivot, I dont think it is possible as described
DECLARE #table1 TABLE (sub_id INT, ref_id INT, name VARCHAR(50))
INSERT #table1 VALUES (1, 1, 'Project 1')
INSERT #table1 VALUES (2, 1, 'Project 2')
INSERT #table1 VALUES (3, 2, 'Project 3' )
INSERT #table1 VALUES (4, 2, 'Project 4')
DECLARE #Table2 TABLE (sub_id INT, ref_id INT, log_stamp DATETIME, recepient VARCHAR(10), logtype INT)
INSERT #table2 VALUES(1,1,'06/06/2011','person A',1)
INSERT #table2 VALUES(1,1,'06/14/2011','person B',2)
INSERT #table2 VALUES(1,1,'06/16/2011','person C',2)
INSERT #table2 VALUES(1,1,'06/17/2011','person D',3)
INSERT #table2 VALUES(2,1,'06/18/2011','person E',2)
INSERT #table2 VALUES(2,1,'06/19/2011','person F',2)
INSERT #table2 VALUES(3,2,'06/20/2011','person G',1)
INSERT #table2 VALUES(3,2,'06/23/2011','person H',3)
;WITH a as (
SELECT RN = ROW_NUMBER() OVER (PARTITION BY t1.sub_id, t1.ref_id, t1.name, t2.logtype ORDER BY log_stamp DESC), t1.sub_id, t1.ref_id, t1.name, t2.Recepient , t2.logtype ,log_stamp
FROM #table1 t1 JOIN #table2 t2 ON t1.ref_id = t2.ref_id AND
t1.sub_id = t2.sub_id),
b as (SELECT * FROM a WHERE RN = 1)
SELECT b1.name, b1.ref_id,b1.log_stamp start_date , b1.Recepient, b2.log_stamp latest_comment , b2.Recepient, b3.log_stamp completion_date , b3.Recepient
FROM b b1
LEFT JOIN b b2 ON b1.sub_id=b2.sub_id AND b1.ref_id = b2.ref_id AND b2.logtype = 2
LEFT JOIN b b3 ON b1.sub_id=b3.sub_id AND b1.ref_id = b3.ref_id AND b3.logtype = 3
WHERE b1.logtype = 1
Result:
name ref_id start_date Recepient latest_comment Recepient completion_date Recepient
------------ ----------- ----------------------- ---------- ----------------------- ---------- ----------------------- ----------
Project 1 1 2011-06-06 00:00:00.000 person A 2011-06-16 00:00:00.000 person C 2011-06-17 00:00:00.000 person D
Project 3 2 2011-06-20 00:00:00.000 person G NULL NULL 2011-06-23 00:00:00.000 person H
Related
is it possible to make a statistic with the queries starting from the data so configured?
Table a: registry
id (key)
name
able b: holidays
id (key)
id_anagrafica (foreign key)
data_start
data_end
Query:
SELECT b.id, a.name, b.start_date, b.end_date
FROM registry to INNER JOIN
holidays b ON (a.id = b.id_anagrafica)
WHERE b.start_date> = getdate ()
So doing I get:
id, name, start_date, end_date
1, Mario, 01/06/2018, 30/06/2018
2, Marino, 08/06/2018, 25/06/2018
3, Maria, 01/07/2018, 05/07/2018
-
-
-
Having only a start_date and end_date I can not know in a day how many people are on holidays.
What I need is:
data, num_pers_in_ferie
01/06/2018, 1
06/02/2018, 1
03/06/2018, 1
-
-
08/06/2018, 2
Can you help me?
Thanks in advance
Check the approach below
create table #registry (id int, name nvarchar(50))
insert into #registry values
(1, 'Mario'),
(2, 'Marino'),
(3, 'Maria')
create table #holidays (id int,id_anagrafica int,data_start date,data_end date)
insert into #holidays
select id, id, '2018-06-01', '2018-06-30'
from #registry
update #holidays set data_start = dateadd(day, 20, data_start), data_end = dateadd(day, -5, data_end)
where id = 2
update #holidays set data_start = dateadd(day, 14, data_start)--, data_end = dateadd(day, -10, data_end)
where id = 3
SELECT b.id, a.name, b.data_start, b.data_end
FROM #registry a
INNER JOIN
#holidays b ON (a.id = b.id_anagrafica)
WHERE b.data_start > = getdate ()
DECLARE #startDate DATETIME=CAST(MONTH(GETDATE()) AS VARCHAR) + '/' + '01/' + + CAST(YEAR(GETDATE()) AS VARCHAR) -- mm/dd/yyyy
DECLARE #endDate DATETIME= GETDATE() -- mm/dd/yyyy
select [DATA] = convert(date, DATEADD(Day,Number,#startDate)),
--se ti serve in italiano usa la riga sotto
--[DATA] = CONVERT(varchar, DATEADD(Day,Number,#startDate), 103)
SUM(case when DATEADD(Day,Number,#startDate) between data_start and data_end then 1 else 0 end) Pers_in_Ferie
from master..spt_values c,
#registry a
INNER JOIN
#holidays b ON (a.id = b.id_anagrafica)
where c.Type='P' and DATEADD(Day,Number,#startDate) >=data_start and DATEADD(Day,Number,#startDate) <=data_end
group by DATEADD(Day,Number,#startDate)
order by [DATA]
drop table #holidays
drop table #registry
Output:
DATA Pers_in_Ferie
---------- -------------
2018-06-01 1
2018-06-02 1
2018-06-03 1
2018-06-04 1
2018-06-05 1
2018-06-06 1
2018-06-07 1
2018-06-08 1
2018-06-09 1
2018-06-10 1
2018-06-11 1
2018-06-12 1
2018-06-13 1
2018-06-14 1
2018-06-15 2
2018-06-16 2
2018-06-17 2
2018-06-18 2
2018-06-19 2
2018-06-20 2
2018-06-21 3
2018-06-22 3
2018-06-23 3
2018-06-24 3
2018-06-25 3
2018-06-26 2
2018-06-27 2
2018-06-28 2
2018-06-29 2
2018-06-30 2
(30 rows affected)
I have a table customer_history which log customer_id and modification_date.
When customer_id is not modified there is no entry in the table
I can find when customer_id haven't been modified (=last_date_with_no_modification). I look for when the date is missing (= Gaps and Islands problem).
But in the same query if no date is missing the value last_date_with_no_modification should
be DATEADD(DAY,-1,min(modification_date)) for the customer_id.
I don't know how to add this last condition in my SQL query?
I use following tables:
"Customer_history" table:
customer_id modification_date
1 2017-12-20
1 2017-12-19
1 2017-12-17
2 2017-12-20
2 2017-12-18
2 2017-12-17
2 2017-12-15
3 2017-12-20
3 2017-12-19
"#tmp_calendar" table:
date
2017-12-15
2017-12-16
2017-12-17
2017-12-18
2017-12-19
2017-12-20
Query used to qet gap date:
WITH CTE_GAP AS
(SELECT ch.customer_id,
LAG(ch.modification_date) OVER(PARTITION BY ch.customer_id ORDER BY ch.modification_date) as GapStart,
ch.modification_date as GapEnd,
(DATEDIFF(DAY,LAG(ch.modification_date) OVER(PARTITION BY ch.customer_id ORDER BY ch.modification_date), ch.modification_date)-1) GapDays
FROM customer_history ch )
SELECT cg.customer_id,
DATEADD(DAY,1,MAX(cg.GapStart)) as last_date_with_no_modification
FROM CTE_GAP cg
CROSS JOIN #tmp_calendar c
WHERE cg.GapDays >0
AND c.date BETWEEN DATEADD(DAY,1,cg.GapStart) AND DATEADD(DAY,-1,cg.GapEnd)
GROUP BY cg.customer_id
Result:
customer_id last_date_with_no_modification
1 2017-12-18
2 2017-12-19
3 2017-12-19 (Row missing)
How to get customer_id 3?
Something this should work:
WITH CTE_GAP
AS
(
SELECT
ch.customer_id,
LAG(ch.modification_date) OVER(PARTITION BY ch.customer_id ORDER BY ch.modification_date) as GapStart,
ch.modification_date as GapEnd,
(DATEDIFF(DAY,LAG(ch.modification_date) OVER(PARTITION BY ch.customer_id ORDER BY ch.modification_date), ch.modification_date)-1) GapDays
FROM #customer_history ch
)
SELECT DISTINCT
C.customer_id
, ISNULL(LD.last_date_with_no_modification, LD_NO_GAP.last_date_with_no_modification) last_date_with_no_modification
FROM
customer_history C
LEFT JOIN
(
SELECT
cg.customer_id,
DATEADD(DAY, 1, MAX(cg.GapStart)) last_date_with_no_modification
FROM
CTE_GAP cg
CROSS JOIN #tmp_calendar c
WHERE
cg.GapDays >0
AND c.date BETWEEN DATEADD(DAY, 1, cg.GapStart) AND DATEADD(DAY, -1, cg.GapEnd)
GROUP BY cg.customer_id
) LD
ON C.customer_id = LD.customer_id
LEFT JOIN
(
SELECT
customer_id
, DATEADD(DAY, -1, MIN(modification_date)) last_date_with_no_modification
FROM customer_history
GROUP BY customer_id
) LD_NO_GAP
ON C.customer_id = LD_NO_GAP.customer_id
Really struggling to understand the best way of doing this...
I have a table of data
**StudentID AssessmentCode ResultGroup Result**
46933 12ENG IBLevel HL
46933 12ENG Mark 6
46933 12ECO IBLevel HL
46933 12ECO Mark 5
46933 12GEO IBLevel SL
46933 12GEO Mark 6
46933 12LAN IBLevel HL
46933 12LAN Mark 4
46933 12PED IBLevel SL
46933 12PED Mark 5
46933 12SCI IBLevel SL
46933 12SCI Mark 3
67767 12FRE IBLevel HL
67767 12FRE Mark 4
67767 12MAT IBLevel SL
67767 12MAT Mark 5
and so on...
Unfortunately the result column holds 2 different bits of info. The level a student does (HL = High Level; SL = Standard Level) and then the result for that subject at that level. Note that for each student, 2 rows are generated per subject a student does, one row has the IBLevel and the next row has the result for that level.
How can I rearrange the data to get something like
StudentID HLResult SLResult TotalResult CountofHL CountofSL
46933 15 14 29 3 3
67767 13 10 23 4 2
So each student has one row of data with totals for HL, SL, both together and then a count of the number of HL and SL subjects. As I said, really not sure of the best way of going about this. In the end, I would also like to extend this to get some columns which, based on the results give some warning messages eg the TotalResult has to be great than 24 so I would love a column that simply returns whether this has been achieved or not...
StudentID HLResult SLResult TotalResult CountofHL CountofSL MoreThan24
46933 15 14 29 3 3 True
67767 13 10 23 4 2 False
Any help would be greatly appreciated...
Your table seems to have some serious normalization issues. You have to perform an INNER JOIN on AssessmentCode first:
SELECT t1.StudentID, t1.AssessmentCode,
t1.Result AS Level, CAST(t2.Result AS INT) AS Mark
FROM (
SELECT StudentID, AssessmentCode, Result
FROM mytable
WHERE ResultGroup = 'IBLevel' ) AS t1
INNER JOIN (
SELECT StudentID, AssessmentCode, Result
FROM mytable
WHERE ResultGroup = 'Mark' ) AS t2
ON t1.StudentID = t2.StudentID AND t1.AssessmentCode = t2.AssessmentCode
to get one row per AssessmentCode.
Output:
StudentID AssessmentCode Level Mark
-----------------------------------------
46933 12ENG HL 6
46933 12ECO HL 5
46933 12GEO SL 6
46933 12LAN HL 4
46933 12PED SL 5
46933 12SCI SL 3
67767 12FRE HL 4
67767 12MAT SL 5
You can now wrap the above query in a CTE and perform conditional aggregation to get required result:
;WITH CTE AS (
... above query here
)
SELECT StudentID,
SUM(CASE WHEN Level = 'HL' THEN Mark ELSE 0 END) AS HLResult,
SUM(CASE WHEN Level = 'SL' THEN Mark ELSE 0 END) AS SLResult,
SUM(Mark) AS TotalResult,
COUNT(CASE WHEN Level = 'HL' THEN 1 END) AS CountofHL,
COUNT(CASE WHEN Level = 'SL' THEN 1 END) AS CountofSL,
CASE WHEN SUM(Mark) > 24 THEN 'True'
ELSE 'False'
END AS MoreThan24
FROM CTE
GROUP BY StudentID
Output:
StudentID HLResult SLResult TotalResult CountofHL CountofSL MoreThan24
46933 15 14 29 3 3 True
67767 4 5 9 1 1 False
Demo here
My Version of Your Table
DECLARE #yourTable TABLE
(
StudentID INT,
AssessmentCode CHAR(5),
ResultGroup VARCHAR(10),
Result VARCHAR(5)
)
INSERT INTO #yourTable
VALUES (46933,'12ENG','IBLevel','HL'),
(46933,'12ENG','Mark','6'),
(46933,'12ECO','IBLevel','HL'),
(46933,'12ECO','Mark','5'),
(46933,'12GEO','IBLevel','SL'),
(46933,'12GEO','Mark','6'),
(46933,'12LAN','IBLevel','HL'),
(46933,'12LAN','Mark','4'),
(46933,'12PED','IBLevel','SL'),
(46933,'12PED','Mark','5'),
(46933,'12SCI','IBLevel','SL'),
(46933,'12SCI','Mark','3'),
(67767,'12FRE','IBLevel','HL'),
(67767,'12FRE','Mark','4'),
(67767,'12MAT','IBLevel','SL'),
(67767,'12MAT','Mark','5');
Actual Query
WITH CTE_ResultValue
AS
(
SELECT StudentID,
AssessmentCode,
ResultGroup,
CAST(Result AS INT) AS ResultValue
FROM #yourTable
WHERE ISNUMERIC(Result) = 1
),
CTE_IBLevel
AS
(
SELECT StudentID,
AssessmentCode,
Result AS IBLevel
FROM #yourTable
WHERE ISNUMERIC(Result) = 0
),
CTE_Normalized
AS
(
SELECT A.StudentID,
A.AssessmentCode,
A.ResultGroup,
A.ResultValue,
B.IBLevel
FROM CTE_ResultValue AS A
INNER JOIN CTE_IBLevel AS B
ON A.StudentID = B.StudentID
AND A.AssessmentCode = B.AssessmentCode
)
SELECT [StudentID],
[HLResult] = SUM(CASE WHEN IBLevel = 'HL' THEN ResultValue END),
[SLResult] = SUM(CASE WHEN IBLevel = 'SL' THEN ResultValue END),
[TotalResult] = SUM(ResultValue),
[CountOfHL] = SUM(CASE WHEN IBLevel = 'HL' THEN 1 END),
[CountOfSL] = SUM(CASE WHEN IBLevel = 'SL' THEN 1 END)
FROM CTE_Normalized
GROUP BY StudentID
Normalization
Your table is in serious need of normalization. If you can change it, bare minimal change would look like this:
CREATE TABLE dbo.NormalizedTable
(
StudentID INT,
AssessmentCode CHAR(5),
ResultGroup VARCHAR(25),
ResultValue SMALLINT, --smallint range should be plenty. It can store values from -32,768 to 32,767
IBLevel CHAR(2)
)
INSERT INTO dbo.NormalizedTable
SELECT *
FROM CTE_Normalized
If you can't change the way your data is structured, I'd recommend creating a view from my CTE_normalized
CREATE VIEW vw_normalizedTable
AS
WITH CTE_ResultValue
AS
(
SELECT StudentID,
AssessmentCode,
ResultGroup,
CAST(Result AS INT) AS ResultValue
FROM #yourTable
WHERE ISNUMERIC(Result) = 1
),
CTE_IBLevel
AS
(
SELECT StudentID,
AssessmentCode,
Result AS IBLevel
FROM #yourTable
WHERE ISNUMERIC(Result) = 0
),
SELECT A.StudentID,
A.AssessmentCode,
A.ResultGroup,
A.ResultValue,
B.IBLevel
FROM CTE_ResultValue AS A
INNER JOIN CTE_IBLevel AS B
ON A.StudentID = B.StudentID
AND A.AssessmentCode = B.AssessmentCode
GO
Version1:
SELECT t1.StudentID,
SUM(CASE WHEN t1.Result = 'HL' THEN t2.Result ELSE 0 END) HLResult,
SUM(CASE WHEN t1.Result = 'SL' THEN t2.Result ELSE 0 END) SLResult,
SUM(CAST(t2.Result AS INT)) TotalResult,
SUM(CASE WHEN t1.Result = 'HL' THEN 1 ELSE 0 END) CountofHL,
SUM(CASE WHEN t1.Result = 'SL' THEN 1 ELSE 0 END) CountofSL,
CASE WHEN SUM(CAST(t2.Result AS INT)) > 24 THEN 'True' ELSE 'False' END MoreThan24
FROM #t t1
JOIN #t t2 ON t1.StudentID = t2.StudentID AND
t1.AssessmentCode = t2.AssessmentCode AND
t1.ResultGroup = 'IBLevel' AND
t2.ResultGroup = 'Mark'
GROUP BY t1.StudentID
Version2:
WITH cte1 AS(
SELECT *, ROW_NUMBER() OVER(PARTITION BY StudentID, AssessmentCode
ORDER BY CASE WHEN ResultGroup = 'IBLevel' THEN 1 ELSE 2 END) AS rn FROM #t),
cte2 AS(SELECT StudentID,
AssessmentCode,
SUM(CASE WHEN Result = 'HL' THEN 1 ELSE 0 END) HL,
SUM(CASE WHEN Result = 'SL' THEN 1 ELSE 0 END) SL,
MAX(CASE WHEN rn = 2 THEN Result END) R
FROM cte1
GROUP BY StudentID, AssessmentCode)
SELECT StudentID,
SUM(hl*R) HLResult,
SUM(sl*R) SLResult,
SUM((hl + sl)*r) TotalResult,
SUM(hl) CountofHL,
SUM(sl) CountofSL,
CASE WHEN SUM((hl + sl)*r) > 24 THEN 'True' ELSE 'False' END MoreThan24
FROM cte2
GROUP BY StudentID
Output:
StudentID HLResult SLResult TotalResult CountofHL CountofSL MoreThan24
46933 15 14 29 3 3 True
67767 4 5 9 1 1 False
Just replace #t with your table.
I have two tables and for each ID and Level combination in table1, I need to get a count of times matching ID appears in table2 in between sequential times for levels in table1.
So for example, for ID = 1 and Level=1 in table1, two Time entries from table2 for ID=1 fall between Time of Level=1 and Level=2 in table1, so result will be 2 in the result table.
table1:
ID Level Time
1 1 6/7/13 7:03
1 2 6/9/13 7:05
1 3 6/12/13 12:02
1 4 6/17/13 5:01
2 1 6/18/13 8:38
2 3 6/20/13 9:38
2 4 6/23/13 10:38
2 5 6/28/13 1:38
table2:
ID Time
1 6/7/13 11:51
1 6/7/13 14:15
1 6/9/13 16:39
1 6/9/13 19:03
2 6/20/13 11:02
2 6/20/13 15:50
Result would be
ID Level Count
1 1 2
1 2 2
1 3 0
1 4 0
2 1 0
2 3 2
2 4 0
2 5 0
select transformed_tab1.id, transformed_tab1.level, count(tab2.id)
from
(select tab1.id, tab1.level, tm, lead(tm) over (partition by id order by tm) as next_tm
from
(
select 1 as id, 1 as level, '2013-06-07 07:03'::timestamp as tm union
select 1 as id, 2 as level, '2013-06-09 07:05 '::timestamp as tm union
select 1 as id, 3 as level, '2013-06-12 12:02'::timestamp as tm union
select 1 as id, 4 as level, '2013-06-17 05:01'::timestamp as tm union
select 2 as id, 1 as level, '2013-06-18 08:38'::timestamp as tm union
select 2 as id, 3 as level, '2013-06-20 09:38'::timestamp as tm union
select 2 as id, 4 as level, '2013-06-23 10:38'::timestamp as tm union
select 2 as id, 5 as level, '2013-06-28 01:38'::timestamp as tm) tab1
) transformed_tab1
left join
(select 1 as id, '2013-06-07 11:51'::timestamp as tm union
select 1 as id, '2013-06-07 14:15'::timestamp as tm union
select 1 as id, '2013-06-09 16:39'::timestamp as tm union
select 1 as id, '2013-06-09 19:03'::timestamp as tm union
select 2 as id, '2013-06-20 11:02'::timestamp as tm union
select 2 as id, '2013-06-20 15:50'::timestamp as tm) tab2
on transformed_tab1.id=tab2.id and tab2.tm between transformed_tab1.tm and transformed_tab1.next_tm
group by transformed_tab1.id, transformed_tab1.level
order by transformed_tab1.id, transformed_tab1.level
;
SQL Fiddle
select t1.id, level, count(t2.id)
from
(
select id, level,
tsrange(
"time",
lead("time", 1, 'infinity') over(
partition by id order by level
),
'[)'
) as time_range
from t1
) t1
left join
t2 on t1.id = t2.id and t1.time_range #> t2."time"
group by t1.id, level
order by t1.id, level
The solution starts creating a range of timestamps using the lead window function. Notice the [) parameter to the tsrange constructor. It means to include the lower and exclude the upper bound.
Then it joins the two tables with the #> range operator. It means the range includes the element.
It is necessary to left join t1 to have the zero counts.
Imagine I have this table:
Month | Person | Value
----------------------
Jan | P1 | 1
Jan | P2 | 2
Jan | P3 | 3
Feb | P1 | 5
Feb | P2 | 4
Feb | P3 | 3
Feb | P4 | 2
...
How can I build a t-sql query to get the top 2 value rows and a third with the sum of others?
Something like this:
RESULT:
Month | Person | Value
----------------------
Jan | P3 | 3
Jan | P2 | 2
Jan | Others | 1 -(sum of the bottom value - in this case (Jan, P1, 1))
Feb | P1 | 5
Feb | P2 | 4
Feb | Others | 5 -(sum of the bottom values - in this case (Feb, P3, 3) and (Feb, P4, 2))
Thanks
In the assumption you are using SQL Server 2005 or higher, using a CTE would do the trick.
Attach a ROW_NUMBER to each row, starting with the highest value, resetting for each month.
SELECT the top 2 rows for each month from this query (rownumber <= 2)
UNION with the remaining rows (rownumber > 2)
SQL Statement
;WITH Months (Month, Person, Value) AS (
SELECT 'Jan', 'P1', 1 UNION ALL
SELECT 'Jan', 'P2', 2 UNION ALL
SELECT 'Jan', 'P3', 3 UNION ALL
SELECT 'Feb', 'P1', 5 UNION ALL
SELECT 'Feb', 'P2', 4 UNION ALL
SELECT 'Feb', 'P3', 3 UNION ALL
SELECT 'Feb', 'P4', 2
),
q AS (
SELECT Month
, Person
, Value
, RowNumber = ROW_NUMBER() OVER (PARTITION BY Month ORDER BY Value DESC)
FROM Months
)
SELECT Month
, Person
, Value
FROM (
SELECT Month
, Person
, Value
, RowNumber
FROM q
WHERE RowNumber <= 2
UNION ALL
SELECT Month
, Person = 'Others'
, SUM(Value)
, MAX(RowNumber)
FROM q
WHERE RowNumber > 2
GROUP BY
Month
) q
ORDER BY
Month DESC
, RowNumber
Kudo's go to Andriy for teaching me some new tricks.
;WITH atable (Month, Person, Value) AS (
SELECT 'Jan', 'P1', 1 UNION ALL
SELECT 'Jan', 'P2', 2 UNION ALL
SELECT 'Jan', 'P3', 3 UNION ALL
SELECT 'Feb', 'P1', 5 UNION ALL
SELECT 'Feb', 'P2', 4 UNION ALL
SELECT 'Feb', 'P3', 3 UNION ALL
SELECT 'Feb', 'P4', 2
),
numbered AS (
SELECT
Month, Person, Value,
rownum = ROW_NUMBER() OVER (PARTITION BY Month ORDER BY Value DESC)
FROM atable
),
grouped AS (
SELECT
Month, Person, Value,
Grp = CASE WHEN rownum < 3 THEN rownum ELSE 3 END
FROM numbered
)
SELECT
Month,
Person = CASE Grp WHEN 3 THEN 'Others' ELSE MAX(Person) END,
Value = SUM(Value)
FROM grouped
GROUP BY Month, Grp
ORDER BY Month DESC, Grp
WITH NTable AS
(
SELECT [Month],
Person,
Value,
ROW_NUMBER() OVER (PARTITION BY [Month] ORDER BY Value DESC)
AS Rownumber
FROM MyTable
)
SELECT t.[Month],
CASE Rownumber WHEN 1 THEN t.Person WHEN 2 THEN t.Person ELSE 'Others' END As Person,
SUM(t.Value) As [Sum]
FROM NTable t
GROUP BY t.[Month], CASE Rownumber WHEN 1 THEN t.Person WHEN 2 THEN t.Person ELSE 'Others' END
ORDER BY t.[Month]