SQL to show concurrent cases - sql

Need help with SQL to show concurrency by person for every minute in a day.
for a data set below:
drop table test
create table test (person varchar(2), caseid varchar(3), starttime datetime, endtime datetime)
insert into test values ('aa', '1', '01/01/2013 06:42', '01/01/2013 07:06')
insert into test values ('aa', '1', '01/01/2013 07:31', '01/01/2013 09:38')
insert into test values ('aa', '2', '01/01/2013 08:37', '01/01/2013 11:44')
insert into test values ('aa', '3','01/01/2013 09:39', '01/01/2013 11:31')
insert into test values ('aa', '4','01/01/2013 11:09', '01/01/2013 13:30')
insert into test values ('aa', '5','01/01/2013 12:05', '01/01/2013 15:38')
insert into test values ('aa', '6', '01/01/2013 13:58', '01/01/2013 14:13')
insert into test values ('aa', '7', '01/01/2013 15:53', '01/01/2013 16:14')
insert into test values ('bb', '8', '01/01/2013 08:42', '01/01/2013 09:06')
insert into test values ('bb', '8', '01/01/2013 10:31', '01/01/2013 19:38')
insert into test values ('bb', '8','01/01/2013 20:37', '01/01/2013 21:44')
insert into test values ('bb', '9', '01/01/2013 09:39', '01/01/2013 11:31')
insert into test values ('bb', '9', '01/01/2013 11:45', '01/01/2013 13:30')
insert into test values ('bb', '9', '01/01/2013 12:05', '01/01/2013 15:38')
insert into test values ('bb', '10', '01/01/2013 13:58', '01/01/2013 14:13')
insert into test values ('bb', '10', '01/01/2013 15:53', '01/01/2013 16:14')
the result needs to be similar to the following:
aa 01/01/2013 6:42 1
aa 01/01/2013 6:43 1
aa 01/01/2013 6:44 1
....
....
aa 01/01/2013 8:37 2
aa 01/01/2013 8:38 2
....
....
bb 01/01/2013 8:42 1
bb 01/01/2013 8:43 1
bb 01/01/2013 10:31 2
....
....
Thanks

You can do this with a correlated subquery:
Select t.*,
(Select count(*)
From t t2
Where t2. Start <= t.start and
T2.end >= t.end
) numoverlaps
From t
(Apologies for syntax errors; I'm on a mobile device)
This finds concurrency at every time in the input data. It does not do it for every minute of time.

This seems to work, but there may be a more elegant solution:
-- get range of days involved
declare #minDate date = (select MIN(starttime) from test)
declare #maxDate date = (select MAX(endtime) from test)
-- create table containing all days
if OBJECT_ID('tempdb..#days') is not null
drop table #days
create table #days (d date)
declare #day date = #minDate
while #day <= #maxDate
begin
insert #days (d) values (#day)
set #day = DATEADD(day, 1, #day)
end
-- create table containing all minutes in the day
if OBJECT_ID('tempdb..#minutes') is not null
drop table #minutes
create table #minutes (m int)
declare #minute int = 0
while #minute < 24*60
begin
insert #minutes (m) values (#minute)
set #minute = #minute + 1
end
select person, dateadd(minute, m, convert(datetime, startdate)), c from
(
select person, m, startdate, count(m) c from
(
-- cross join to select all days and minutes
select d.d, m.m from #days d cross join #minutes m
)
t0
inner join
(
select
person,
convert(date, starttime) startdate,
datediff(minute, convert(date, starttime), starttime) startmin,
datediff(minute, convert(date, endtime), endtime) endmin
from test
)
t1
on t0.m between t1.startmin and t1.endmin
and t0.d = t1.startdate
group by person, m, startdate
)
t2
order by person, startdate, m, c

Here is how I would approach it if using a database that supports CTE and inline views:
First a CTE to generate a two-col listing of the (24*60) minutes of the day for a specified date:
time1 time2
2013-02-12 00:00, 2013-02-12 00:01
.
.
.
2013-02-12 23:59, 2013-02-13 00:00
Left join that CTE to your cases table where cases.starttime between time1 and time2 or cases.endtime between time1 and time2. That brings back either nulls where no part of the case was ongoing during that minute or the caseid and personid when a case was ongoing during that minute.
Make the above an inline view. You end up with a set of all minutes in the day and the left-joined caseid and personid, or nulls:
time1, time2, caseid, personid
If you select from that inline view where caseid is not null you end up with the minutes where one or more cases was ongoing; if you then group by personid, time1 and count(caseid) you get the tally of cases per person in that particular one-minute time slot.

Related

SQL : 12 hr shift function from 7am - 7am

I need to query all production records by 12 hr. shift. 7am-7am. if the date is after midnight and before 7am it's actually the previous day shift. In the below example I need to make them all 2022-01-01 like the last column. If I query by 2022-01-01 I don't get all the rows. Can I use a function for this to compare the time and make it the previous day?
declare #temp table
(
Emp_id int,
Time datetime,
shiftDate date
);
insert into #temp values (1, '2022-01-01 08:10:00:000', '2022-01-01')
insert into #temp values (1, '2022-01-01 10:21:00:000', '2022-01-01')
insert into #temp values (1, '2022-01-01 13:10:00:000', '2022-01-01')
insert into #temp values (1, '2022-01-01 22:22:00:000', '2022-01-01')
insert into #temp values (1, '2022-01-02 02:15:00:000', '2022-01-01')
insert into #temp values (1, '2022-01-02 04:22:00:000', '2022-01-01')
insert into #temp values (1, '2022-01-02 06:18:00:000', '2022-01-01')
insert into #temp values (1, '2022-01-02 06:55:00:000', '2022-01-01')
select * from #temp
select * from #temp
where convert(date, [time]) = '2022-01-01'

Partition the date into a weeks from a given date to the last date in the record

I wanted to count the time gap between two rows for the same id if the second is less than an hour after the first, and partition the count for the week.
Suppose given date with time is 2020-07-01 08:00
create table #Temp (
Id integer not null,
Time datetime not null
);
insert into #Temp values (1, '2020-07-01 08:00');
insert into #Temp values (1, '2020-07-01 08:01');
insert into #Temp values (1, '2020-07-01 08:06');
insert into #Temp values (1, '2020-07-01 08:30');
insert into #Temp values (1, '2020-07-08 09:35');
insert into #Temp values (1, '2020-07-15 16:10');
insert into #Temp values (1, '2020-07-15 16:20');
insert into #Temp values (1, '2020-07-17 06:40');
insert into #Temp values (1, '2020-07-17 06:41');
insert into #Temp values (2, '2020-07-01 08:30');
insert into #Temp values (2, '2020-07-01 09:26');
insert into #Temp values (2, '2020-07-01 10:25');
insert into #Temp values (2, '2020-07-09 08:30');
insert into #Temp values (2, '2020-07-09 09:26');
insert into #Temp values (2, '2020-07-09 10:25');
insert into #Temp values (3, '2020-07-21 08:30');
insert into #Temp values (3, '2020-07-21 09:26');
insert into #Temp values (3, '2020-07-21 10:25');
The week should extend up to the last date in the record. Here, the last date is
2020-07-21 10:25
Have to transform the output from this piece of code and divide the duration weekly.
select Id, sum(datediff(minute, Time, next_ts)) as duration_minutes
from (select t.*,
lead(Time) over (partition by id order by Time) as next_ts
from #Temp t
) t
where datediff(minute, Time, next_ts) < 60
group by Id;
Output:
id duration_minutes
1 41
2 230
3 115
The desired output should divide this duration on a weekly basis,
like Week 1, Week 2, Week 3, and so on.
Desired Output:
If the
start date is 2020-07-01 08:00
end date is 2020-07-21 10:25
id | Week 1 | Week 2 | Week 3
--------------------------------------
1 | 30 | 0 | 11
2 | 115 | 115 | 0
3 | 0 | 0 | 115
similarly, if the
start date is 2020-07-08 08:00
id | Week 1 | Week 2
---------------------------
1 | 11 | 0
2 | 115 | 0
3 | 0 | 115
Is this what you want?
select Id,
1 + datediff(second, '2020-07-01 06:00', time) / (24 * 60 * 60 * 7) as week_num,
sum(datediff(minute, Time, next_ts)) as duration_minutes
from (select t.*,
lead(Time) over (partition by id order by Time) as next_ts
from Temp t
) t
where datediff(minute, Time, next_ts) < 60
group by Id, datediff(second, '2020-07-01 06:00', time) / (24 * 60 * 60 * 7)
order by id, week_num;
Here is a db<>fiddle.
I am not able to understand the logic behind the week periods. Anyone, in the example below I am using the following code to set the week:
'Week ' + CAST(DENSE_RANK() OVER (ORDER BY DATEDIFF(DAY, #FirstDate, next_ts) / 7) AS VARCHAR(12))
You can adjust it to ignore the ours, be more precise or something else to match your real requirements.
Apart from that, you just need to perform a dynamic PIVOT. Here is the full working example:
DROP TABLE IF EXISTS #Temp;
create table #Temp (
Id integer not null,
Time datetime not null
);
insert into #Temp values (1, '2020-07-01 08:00');
insert into #Temp values (1, '2020-07-01 08:01');
insert into #Temp values (1, '2020-07-01 08:06');
insert into #Temp values (1, '2020-07-01 08:30');
insert into #Temp values (1, '2020-07-08 09:35');
insert into #Temp values (1, '2020-07-15 16:10');
insert into #Temp values (1, '2020-07-15 16:20');
insert into #Temp values (1, '2020-07-17 06:40');
insert into #Temp values (1, '2020-07-17 06:41');
insert into #Temp values (2, '2020-07-01 08:30');
insert into #Temp values (2, '2020-07-01 09:26');
insert into #Temp values (2, '2020-07-01 10:25');
insert into #Temp values (2, '2020-07-09 08:30');
insert into #Temp values (2, '2020-07-09 09:26');
insert into #Temp values (2, '2020-07-09 10:25');
insert into #Temp values (3, '2020-07-21 08:30');
insert into #Temp values (3, '2020-07-21 09:26');
insert into #Temp values (3, '2020-07-21 10:25');
DROP TABLE IF EXISTS #TEST
CREATE TABLE #TEST
(
[ID] INT
,[week_day] VARCHAR(12)
,[time_in_minutes] BIGINT
)
DECLARE #FirstDate DATE;
SELECT #FirstDate = MIN(Time)
FROM #Temp
INSERT INTO #TEST
select id
,'Week ' + CAST(DENSE_RANK() OVER (ORDER BY DATEDIFF(DAY, #FirstDate, next_ts) / 7) AS VARCHAR(12))
,datediff(minute, Time, next_ts)
from (select t.*,
lead(Time) over (partition by id order by Time) as next_ts
from #Temp t
) t
where datediff(minute, Time, next_ts) < 60
DECLARE #columns NVARCHAR(MAX);
SELECT #columns = STUFF
(
(
SELECT ',' + QUOTENAME([week_day])
FROM
(
SELECT DISTINCT CAST(REPLACE([week_day], 'Week ', '') AS INT)
,[week_day]
FROM #TEST
) DS ([rowID], [week_day])
ORDER BY [rowID]
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)')
,1
,1
,''
);
DECLARE #DanymicSQL NVARCHAR(MAX);
SET #DanymicSQL = N'
SELECT [ID], ' + #columns + '
FROM #TEST
PIVOT
(
SUM([time_in_minutes]) FOR [week_day] IN (' + #columns + ')
) PVT';
EXEC sp_executesql #DanymicSQL;

Get all dates between provided dates

I have this table and sample data. I want to get the entire month's or specific dates attendance and information like hours he worked or days he was absent.
CREATE TABLE Attendance
(
[EmpCode] int,
[TimeIn] datetime,
[TimeOut] datetime
)
INSERT INTO Attendance VALUES (12, '2018-08-01 09:00:00', '2018-08-01 17:36:00');
INSERT INTO Attendance VALUES (12, '2018-08-02 09:00:00', '2018-08-02 18:10:00');
INSERT INTO Attendance VALUES (12, '2018-08-03 09:25:00', '2018-08-03 16:56:00');
INSERT INTO Attendance VALUES (12, '2018-08-04 09:13:00', '2018-08-05 18:09:00');
INSERT INTO Attendance VALUES (12, '2018-08-06 09:00:00', '2018-08-07 18:15:00');
INSERT INTO Attendance VALUES (12, '2018-08-07 09:27:00', '2018-08-08 17:36:00');
INSERT INTO Attendance VALUES (12, '2018-08-08 09:35:00', '2018-08-09 17:21:00');
INSERT INTO Attendance VALUES (12, '2018-08-10 09:00:00', '2018-08-10 17:45:00');
INSERT INTO Attendance VALUES (12, '2018-08-11 09:50:00', '2018-08-11 17:31:00');
INSERT INTO Attendance VALUES (12, '2018-08-13 09:23:00', '2018-08-13 17:19:00');
INSERT INTO Attendance VALUES (12, '2018-08-15 09:21:00', '2018-08-15 17:36:00');
INSERT INTO Attendance VALUES (12, '2018-08-16 09:00:00', '2018-08-16 17:09:00');
INSERT INTO Attendance VALUES (12, '2018-08-17 09:34:00', '2018-08-17 17:29:00');
INSERT INTO Attendance VALUES (12, '2018-08-18 09:00:00', '2018-08-18 17:10:00');
INSERT INTO Attendance VALUES (12, '2018-08-20 09:34:00', '2018-08-20 17:12:00');
INSERT INTO Attendance VALUES (12, '2018-08-21 09:20:00', '2018-08-21 17:15:00');
INSERT INTO Attendance VALUES (12, '2018-08-22 09:12:00', '2018-08-22 17:19:00');
INSERT INTO Attendance VALUES (12, '2018-08-23 09:05:00', '2018-08-23 17:21:00');
INSERT INTO Attendance VALUES (12, '2018-08-24 09:07:00', '2018-08-24 17:09:00');
INSERT INTO Attendance VALUES (12, '2018-08-25 09:12:00', '2018-08-25 17:05:00');
INSERT INTO Attendance VALUES (12, '2018-08-27 09:21:00', '2018-08-27 17:46:00');
INSERT INTO Attendance VALUES (12, '2018-08-28 09:17:00', '2018-08-28 17:12:00');
INSERT INTO Attendance VALUES (12, '2018-08-29 09:00:00', '2018-08-29 17:36:00');
INSERT INTO Attendance VALUES (12, '2018-08-30 09:12:00', '2018-08-30 17:24:00');
I have a query that tells how many hours employee have worked, but it is only showing days on which data was present in table. I want to show all dates between provided dates and in case there is no data it should NULL in columns.
Here is the query:
SELECT
[EmpCode],
FirstIN = CAST(MIN([TimeIn]) AS TIME),
LastOUT = CAST(MAX([TimeOut]) AS TIME),
CONVERT(VARCHAR(6), Datediff(second, CAST(MIN([TimeIn]) AS TIME), CAST(MAX([TimeOut]) AS TIME))/3600)
+ ':'
+ RIGHT('0' + CONVERT(VARCHAR(2), (Datediff(second, CAST(MIN([TimeIn]) AS TIME), CAST(MAX([TimeOut]) AS TIME)) % 3600) / 60), 2)
+ ':'
+ RIGHT('0' + CONVERT(VARCHAR(2), Datediff(second, CAST(MIN([TimeIn]) AS TIME), CAST(MAX([TimeOut]) AS TIME)) % 60) , 2 ) AS HoursSpent,
CAST(COALESCE(TimeIn, TimeOut) AS DATE) [Date]
FROM Attendance
WHERE CAST(COALESCE(TimeIn, TimeOut) AS DATE) BETWEEN '2018-08-01' AND '2018-08-25'
GROUP BY EmpCode, TimeIn, TimeOut
For that you need to use recursive way to generate possible dates :
with t as (
select '2018-08-01' as startdt
union all
select dateadd(day, 1, startdt)
from t
where startdt < '2018-08-25'
)
select . . .
from t left join
Attendance at
on cast(coalesce(at.TimeIn, at.TimeOut) as date) = t.startdt;
Just make sure to use date from t instead of Attendance table in SELECT statement.
Note : If you have a large no of date period, then don't forgot to use Query hint OPTION (MAXRECURSION 0), By defalut it has 100 recursion levels.
You May Try Recursive CTE to populate the Dates and Then Join With that to Get the Interval
DECLARE #From DATETIME = '2018-08-01' ,#To DATETIME= '2018-08-25'
;WITH CTE
AS
(
SELECT
[EmpCode] EmpId,
MyDate = #From
FROM Attendance A
UNION ALL
SELECT
EmpId,
MyDate = DATEADD(DAY,1,MyDate)
FROM CTE
WHERE MyDate < #To
)
SELECT
[EmpCode] = CTE.EmpId,
CTE.MyDate,
FirstIN = CAST(MIN([TimeIn]) AS TIME),
LastOUT = CAST(MAX([TimeOut]) AS TIME),
CONVERT(VARCHAR(6), Datediff(second, CAST(MIN([TimeIn]) AS TIME), CAST(MAX([TimeOut]) AS TIME))/3600)
+ ':'
+ RIGHT('0' + CONVERT(VARCHAR(2), (Datediff(second, CAST(MIN([TimeIn]) AS TIME), CAST(MAX([TimeOut]) AS TIME)) % 3600) / 60), 2)
+ ':'
+ RIGHT('0' + CONVERT(VARCHAR(2), Datediff(second, CAST(MIN([TimeIn]) AS TIME), CAST(MAX([TimeOut]) AS TIME)) % 60) , 2 )
AS HoursSpent,
CAST(CTE.MyDate AS DATE) [Date]
FROM CTE
LEFT JOIN Attendance A
ON A.EmpCode = CTE.EmpId
AND CAST(CTE.MyDate AS DATE) = CAST(COALESCE(TimeIn, TimeOut) AS DATE)
GROUP BY CTE.EmpId, TimeIn, TimeOut,CTE.MyDate
ORDER BY 6
A different method, using a Tally Table. The advantage here is that an rCTE is a form of RBAR. The idea of a Tally table isn't as obvious, but is quicker, and also, won't need the OPTION (MAXRECURSION 0) added if you have more than 100 days. in fact, this example handles up to 10,000 days, which shuold be more than enough:
DECLARE #EmpCode int = 12;
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)),
Tally AS(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1 --10
CROSS JOIN N N2 --100
CROSS JOIN N N3 --1000
CROSS JOIN N N4 --10000
),
Dates AS(
SELECT DATEADD(DAY, T.I, TT.MinTimeIn) AS CalendarDate,
#EmpCode AS EmpCode
FROM Tally T
CROSS APPLY (SELECT MIN(CONVERT(date,TimeIn)) AS MinTimeIn,
MAX(CONVERT(date,TimeOut)) AS MaxTimeOut
FROM Attendance
WHERE EmpCode = #EmpCode) TT
WHERE DATEADD(DAY, T.I, TT.MinTimeIn) <= CONVERT(date, TT.MaxTimeOut))
SELECT CalendarDate
EmpCode,
TimeIn,
TimeOut
FROM Dates D
LEFT JOIN Attendance A ON D.CalendarDate = CONVERT(date,A.TimeIn)
AND D.EmpCode = A.EmpCode;

DateDiff and Continuous periods

I need to see if one date span, in number of days, matches that of two date spans that cover the same period, but are added together.
For example...
DECLARE #Table1 TABLE
(
Id INT,
StartDate DATETIME,
EndDate DATETIME
)
INSERT INTO #Table1 VALUES (1, '2015-07-01 00:00:00.000', '2016-06-30 00:00:00.000')
DECLARE #Table2 TABLE
(
Id INT,
Fk INT,
StartDate DATETIME,
EndDate DATETIME
)
INSERT INTO #Table2 VALUES (1, 1, '2015-07-01', '2015-08-31')
INSERT INTO #Table2 VALUES (2, 1, '2015-09-01', '2016-03-31')
INSERT INTO #Table2 VALUES (3, 1, '2016-04-01', NULL)
SELECT DATEDIFF(DAY, T1.StartDate, T1.EndDate) AS SiteContractDays,
DATEDIFF(DAY, T2.StartDate, ISNULL(T2.EndDate, T1.EndDate)) AS SummedDayes
FROM #Table1 t1
INNER JOIN #Table2 t2
ON t2.fk = t1.Id
SELECT T1.Id, DATEDIFF(DAY, T1.StartDate, T1.EndDate) AS SiteContractDays,
SUM(DATEDIFF(DAY, T2.StartDate, ISNULL(T2.EndDate, T1.EndDate))) AS SummedDayes
FROM #Table1 t1
INNER JOIN #Table2 t2
ON t2.fk = t1.Id
GROUP BY T1.id, T1.StartDate, T1.EndDate
Dates are continuous.. they follow on for the full period. However, when I sum them up, we're short a few days. I'm not sure I can simply add a day to each DateDiff, because... then the total goes to 366, and the summed up values will go up as well.
I could add " + COUNT(*) -1 AS" to the SUM of the days when grouping them up, but that seems like a hack.
Maybe the + 1 is better but if you calculate by seconds it will be 1460 days in the end
SELECT (DATEDIFF(SECOND, '2015-07-01 00:00:00', '2016-06-30 23:59:59') + DATEDIFF(SECOND, '2016-07-01 00:00:00', '2019-06-30 23:59:59')) / 60 / 60 / 24
-- 1460
Two different statements that you need to understand.
SELECT DATEDIFF(DAY, '2015-07-01 00:00:00.000', '2019-06-30 00:00:00.000') -- 1460
No break thus count is continuous from day 1.
Day 1 from 2015-07-02
SELECT DATEDIFF(DAY, '2015-07-01', '2016-06-30') + DATEDIFF(DAY, '2016-07-01', '2019-06-30 00:00:00.000') -- 1459
Two different starting dates thus two different day 1s so you won't assume this is continuous...
day 1 from 2015-07-02 and day 1 from 2016-07-02... Day between 2016-06-30 and 2016-07-01 is your break and not counted.
Perhaps in breaking periods, you always need to add that 1 missing second explicitly.
SELECT
DATEDIFF(DAY, '2015-07-01 00:00:00.000', '2016-06-30') +
DATEDIFF(DAY, '2016-07-01', DATEADD(SECOND,1,'2019-06-30 23:59:59.000')) --1460

Summing up the records as per given conditions

I have a table like below, What I need that for any particular fund and up to any particular date logic will sum the amount value. Let say I need the sum for 3 dates as 01/28/2015,03/30/2015 and 04/01/2015. Then logic will check for up to first date how many records are there in table . If it found more than one record then it'll sum the amount value. Then for next date it'll sum up to the next date but from the previous date it had summed up.
Id Fund Date Amount
1 A 01/20/2015 250
2 A 02/28/2015 300
3 A 03/20/2015 400
4 A 03/30/2015 200
5 B 04/01/2015 500
6 B 04/01/2015 600
I want result to be like below
Id Fund Date SumOfAmount
1 A 02/28/2015 550
2 A 03/30/2015 600
3 B 04/01/2015 1100
Based on your question, it seems that you want to select a set of dates, and then for each fund and selected date, get the sum of the fund amounts from the selected date to the previous selected date. Here is the result set I think you should be expecting:
Fund Date SumOfAmount
A 2015-02-28 550.00
A 2015-03-30 600.00
B 2015-04-01 1100.00
Here is the code to produce this output:
DECLARE #Dates TABLE
(
SelectedDate DATE PRIMARY KEY
)
INSERT INTO #Dates
VALUES
('02/28/2015')
,('03/30/2015')
,('04/01/2015')
DECLARE #FundAmounts TABLE
(
Id INT PRIMARY KEY
,Fund VARCHAR(5)
,Date DATE
,Amount MONEY
);
INSERT INTO #FundAmounts
VALUES
(1, 'A', '01/20/2015', 250)
,(2, 'A', '02/28/2015', 300)
,(3, 'A', '03/20/2015', 400)
,(4, 'A', '03/30/2015', 200)
,(5, 'B', '04/01/2015', 500)
,(6, 'B', '04/01/2015', 600);
SELECT
F.Fund
,D.SelectedDate AS Date
,SUM(F.Amount) AS SumOfAmount
FROM
(
SELECT
SelectedDate
,LAG(SelectedDate,1,'1/1/1900') OVER (ORDER BY SelectedDate ASC) AS PreviousDate
FROM #Dates
) D
JOIN
#FundAmounts F
ON
F.Date BETWEEN DATEADD(DAY,1,D.PreviousDate) AND D.SelectedDate
GROUP BY
D.SelectedDate
,F.Fund
EDIT: Here is alternative to the LAG function for this example:
FROM
(
SELECT
SelectedDate
,ISNULL((SELECT TOP 1 SelectedDate FROM #Dates WHERE SelectedDate < Dates.SelectedDate ORDER BY SelectedDate DESC),'1/1/1900') AS PreviousDate
FROM #Dates Dates
) D
If i change your incorrect sample data to ...
CREATE TABLE TableName
([Id] int, [Fund] varchar(1), [Date] datetime, [Amount] int)
;
INSERT INTO TableName
([Id], [Fund], [Date], [Amount])
VALUES
(1, 'A', '2015-01-28 00:00:00', 250),
(2, 'A', '2015-01-28 00:00:00', 300),
(3, 'A', '2015-03-30 00:00:00', 400),
(4, 'A', '2015-03-30 00:00:00', 200),
(5, 'B', '2015-04-01 00:00:00', 500),
(6, 'B', '2015-04-01 00:00:00', 600)
;
this query using GROUP BY works:
SELECT MIN(Id) AS Id,
MIN(Fund) AS Fund,
[Date],
SUM(Amount) AS SumOfAmount
FROM dbo.TableName t
WHERE [Date] IN ('01/28/2015','03/30/2015','04/01/2015')
GROUP BY [Date]
Demo
Initially i have used Row_number and month function to pick max date of every month and in 2nd cte i did sum of amounts and joined them..may be this result set matches your out put
declare #t table (Id int,Fund Varchar(1),Dated date,amount int)
insert into #t (id,Fund,dated,amount) values (1,'A','01/20/2015',250),
(2,'A','01/28/2015',300),
(3,'A','03/20/2015',400),
(4,'A','03/30/2015',200),
(5,'B','04/01/2015',600),
(6,'B','04/01/2015',500)
;with cte as (
select ID,Fund,Amount,Dated,ROW_NUMBER() OVER
(PARTITION BY DATEDIFF(MONTH, '20000101', dated)ORDER BY dated desc)AS RN from #t
group by ID,Fund,DATED,Amount
),
CTE2 AS
(select SUM(amount)Amt from #t
GROUP BY MONTH(dated))
,CTE3 AS
(Select Amt,ROW_NUMBER()OVER (ORDER BY amt)R from cte2)
,CTE4 AS
(
Select DISTINCT C.ID As ID,
C.Fund As Fund,
C.Dated As Dated
,ROW_NUMBER()OVER (PARTITION BY RN ORDER BY (SELECT NULL))R
from cte C INNER JOIN CTE3 CC ON c.RN = CC.R
Where C.RN = 1
GROUP BY C.ID,C.Fund,C.RN,C.Dated )
select C.R,C.Fund,C.Dated,cc.Amt from CTE4 C INNER JOIN CTE3 CC
ON c.R = cc.R
declare #TableName table([Id] int, [Fund] varchar(1), [Date] datetime, [Amount] int)
declare #Sample table([SampleDate] datetime)
INSERT INTO #TableName
([Id], [Fund], [Date], [Amount])
VALUES
(1, 'A', '20150120 00:00:00', 250),
(2, 'A', '20150128 00:00:00', 300),
(3, 'A', '20150320 00:00:00', 400),
(4, 'A', '20150330 00:00:00', 200),
(5, 'B', '20150401 00:00:00', 500),
(6, 'B', '20150401 00:00:00', 600)
INSERT INTO #Sample ([SampleDate])
values ('20150128 00:00:00'), ('20150330 00:00:00'), ('20150401 00:00:00')
-- select * from #TableName
-- select * from #Sample
;WITH groups AS (
SELECT [Fund], [Date], [AMOUNT], MIN([SampleDate]) [SampleDate] FROM #TableName
JOIN #Sample ON [Date] <= [SampleDate]
GROUP BY [Fund], [Date], [AMOUNT])
SELECT [Fund], [SampleDate], SUM([AMOUNT]) FROM groups
GROUP BY [Fund], [SampleDate]
Explanation:
The CTE groups finds the earliest SampleDate which is later than (or equals to) your
data's date and enriches your data accordingly, thus giving them the group to be summed up in.
After that, you can group on the derived date.