I have multiple monotonic counters that can be reset ad-hoc. These counters exhibit sawtooth behavior when graphed (however they are not strictly increasing). I want a monthly report showing daily sums of the maxima for each counter.
My strategy so far is to put a '1' on the rows where the counter is less than the previous sampling of the counter (also less than or equal to the next). Then calculate a running total on that column to identify series without resets.
Then I group over the daily intervals to calculate max-min for each series in the day, then sum those portions to get grand totals for the day.
What I have works, but it takes ~10s to run. The execution plan shows two big sorts: one in cteData and I think the other is in cteSeries. I feel like I should be able to eliminate one of them but I'm at a loss how to do it.
The result of this code is (which I can now see is actually skipping a sample across the interval boundary):
interval tagname total
2020-01-01 alpha 3
2020-01-01 bravo 4
2020-01-02 alpha 3
2020-01-02 bravo 4
IF OBJECT_ID('tempdb..#counter_data') IS NOT NULL
DROP TABLE #counter_data;
CREATE TABLE #counter_data(
t_stamp DATETIME NOT NULL
,tagname VARCHAR(32) NOT NULL
,val REAL NULL
PRIMARY KEY(t_stamp, tagname)
);
INSERT INTO #counter_data(t_stamp, tagname, val)
VALUES
('2020-01-01 04:00', 'alpha', 0)
,('2020-01-01 04:00', 'bravo', 0)
,('2020-01-01 08:00', 'alpha', 1)
,('2020-01-01 08:00', 'bravo', 1)
,('2020-01-01 12:00', 'alpha', 2)
,('2020-01-01 12:00', 'bravo', 2)
,('2020-01-01 16:00', 'alpha', 0)
,('2020-01-01 16:00', 'bravo', 3)
,('2020-01-01 20:00', 'alpha', 1)
,('2020-01-01 20:00', 'bravo', 4)
,('2020-01-02 04:00', 'alpha', 2)
,('2020-01-02 04:00', 'bravo', 5)
,('2020-01-02 08:00', 'alpha', 3)
,('2020-01-02 08:00', 'bravo', 6)
,('2020-01-02 12:00', 'alpha', 0)
,('2020-01-02 12:00', 'bravo', 7)
,('2020-01-02 16:00', 'alpha', 1)
,('2020-01-02 16:00', 'bravo', 8)
,('2020-01-02 20:00', 'alpha', 2)
,('2020-01-02 20:00', 'bravo', 9)
;
DECLARE #dateStart AS DATETIME = '2020-01-01';
DECLARE #dateEnd AS DATETIME = DATEADD(month, 2, #dateStart);
WITH cteData AS(
SELECT
t_stamp
,tagname
,val
,CASE
WHEN val < LAG(val) OVER(PARTITION BY tagname ORDER BY t_stamp)
AND val <= LEAD(val) OVER(PARTITION BY tagname ORDER BY t_stamp)
THEN 1
ELSE 0
END AS rn
FROM #counter_data
WHERE
t_stamp >= #dateStart AND t_stamp < #dateEnd
AND tagname IN(
'alpha'
,'bravo'
)
)
,cteSeries AS(
SELECT
CAST(t_stamp AS DATE) AS interval
,tagname
,val
,SUM(rn) OVER(PARTITION BY tagname ORDER BY t_stamp) AS series
FROM cteData
)
,cteSubtotal AS(
SELECT
interval
,tagname
,MAX(val) - MIN(val) AS subtotal
FROM cteSeries
GROUP BY interval, tagname, series
)
,cteGrandTotal AS(
SELECT
interval
,tagname
,SUM(subtotal) AS total
FROM cteSubtotal
GROUP BY interval, tagname
)
SELECT *
FROM cteGrandTotal
ORDER BY interval, tagname
I would just calculate the increase of the counter in each row by comparing it to the previous row:
with cte
as
(
SELECT *,isnull(lag(val) over (partition by tagname order by t_stamp),0) as previousVal
FROM counter_data
)
SELECT cast(t_stamp as date),tagname, sum(case when val>previousVal then val-previousval else val end )
FROM cte
GROUP BY cast(t_stamp as date),tagname;
This looks like a gaps-and-islands problem. I think that you want lag() to get the "previous" value and a conditional sum to compute the daily count.
select
tag_name,
cast(t_stamp as date) t_date,
sum(case when val = lag_val + 1 the 1 else 0 end) total
from (
select
c.*,
lag(val) over(
partition by tagname, cast(t_stamp as date)
order by t_stamp
) lag_val
from #counter_data c
) c
group by tagname, cast(t_stamp as date)
order by t_date, tagname
Related
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;
I have this query:
declare #values table
(
Id int,
Dept varchar(1),
CounterL int,
CounterU int,
InsertDate datetime
)
insert into #values
select 1, 'L', 5, null, '2017-10-28 4:00:00.000'
union
select 1, 'L', 8, null, '2017-10-28 4:00:00.000'
union
select 1, 'U', null, 30, '2017-10-28 3:00:00.000'
union
select 1, 'U', null, 40, '2017-10-28 3:00:00.000'
select id, sum(counterl), sum(counteru) from #values
where (datepart(hh, insertdate) = 4 or datepart(hh, insertdate) = 3)
group by id, cast(InsertDate as date)
The following returns the sum of both columns, but I would like to be able to include the date of each of these groupings.
The example would look something like this:
id ColumnL, ColumnU, Date ValueU ValueL
1 13 70 2017-10-28 '2017-10-28 3:00:00.000' '2017-10-28 4:00:00.000'
There will always be two hours for the day, either HR 3 or 4.
Thanks.
Isn't this sufficient?
select id, sum(counterl), sum(counteru), cast(InsertDate as date) as dte
from #values v
where datepart(hour, insertdate) in (3, 4)
group by id, cast(InsertDate as date);
I mean, you can also add the hour:
select id, sum(counterl), sum(counteru), cast(InsertDate as date) as dte,
dateadd(hour, 3, cast(InsertDate as date)),
dateadd(hour, 4, cast(InsertDate as date))
from #values v
where datepart(hour, insertdate) in (3, 4)
group by id, cast(InsertDate as date);
But that seems unnecessary.
Notice that I replaced the or expressions with a single in. And, I've spelled out hour so the code is easier to read.
EDIT:
Based on your comment, you want conditional aggregation:
select id, sum(counterl), sum(counteru), cast(InsertDate as date) as dte,
min(case when dept = 'L' then InsertDate end) as l_insertdate,
min(case when dept = 'U' then InsertDate end) as u_insertdate
from #values v
where datepart(hour, insertdate) in (3, 4)
group by id, cast(InsertDate as date);
SELECT DISTINCT Id,
SUM(CounterL) OVER(PARTITION BY ID, CAST(InsertDate AS DATE)) AS [ColumnL],
SUM(CounterU) OVER(PARTITION BY ID, CAST(InsertDate AS DATE)) As [ColumnU],
CAST(InsertDate AS DATE) [Date],
DATEADD(HOUR, 3-DATEPART(HOUR, InsertDate), InsertDate) AS [ValueU],
DATEADD(HOUR, 4-DATEPART(HOUR, InsertDate), InsertDate) AS [ValueL]
FROM #values
WHERE DATEPART(HH, INSERTDATE) IN (3,4)
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
I have a business that rents out one particular product. I would like to know the duration in minutes of when a particular location is out of stock.
The first data set has transaction history with the location ID, rental starting time stamp, and rental ending time stamp.
The second data set has the location ID, date, and number of units available for rent that day. The number of units can change day to day as units are added/removed frequently.
I need to calculate how many minutes per day per location that all available units were out on rent.
Ex: Location A has 3 units on 2/1/2016. How many minutes (if any) on 2/1/2016 were there 3 units out on rent at the same time?
SQL is the language I need to use.
See sample data set Y below:
LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS
A 30Jan2016 19:54:37.000 01Feb2016 10:00:24.053
A 31Jan2016 16:30:23.000 01Feb2016 9:07:06.588
A 01Feb2016 9:22:22.000 02Feb2016 10:00:23.716
A 01Feb2016 9:36:11.000 01Feb2016 11:05:34.249
A 01Feb2016 10:27:34.000 01Feb2016 12:59:29.883
A 01Feb2016 10:40:38.000 01Feb2016 15:36:27.119
A 01Feb2016 12:43:10.000 01Feb2016 14:23:15.914
A 01Feb2016 13:28:20.000 01Feb2016 14:40:15.573
A 01Feb2016 17:03:13.000 01Feb2016 19:02:57.413
A 01Feb2016 17:17:12.000 01Feb2016 18:54:14.708
Sample data set Z below:
LOC_ID, Date, Unit_Count
A 01Feb2016 3
A 02Feb2016 4
A 03Feb2016 3
B 01Feb2016 2
B 02Feb2016 2
B 03Feb2016 2
Since Location A has 3 total units on Feb 1st then the desired output would be 25 minutes which is the total amount of time that 3 units were out on rent at the same time on Feb 1st at Location A. Between 10:40am and 11:05am 3 units were out on rent at the same time.
Here is some T-SQL code (you didn't indicate which DBMS you were using)
-- DECLARING SAMPLE DATA
DECLARE #Z table (LOC_ID char, Date date, Unit_Count int)
INSERT #Z ( LOC_ID, Date, Unit_Count ) VALUES ( 'A', '2016-02-01', 3)
INSERT #Z ( LOC_ID, Date, Unit_Count ) VALUES ( 'A', '2016-02-02', 4)
INSERT #Z ( LOC_ID, Date, Unit_Count ) VALUES ( 'A', '2016-02-03', 3)
INSERT #Z ( LOC_ID, Date, Unit_Count ) VALUES ( 'B', '2016-02-01', 2)
INSERT #Z ( LOC_ID, Date, Unit_Count ) VALUES ( 'B', '2016-02-02', 2)
INSERT #Z ( LOC_ID, Date, Unit_Count ) VALUES ( 'B', '2016-02-03', 2)
DECLARE #Y table (LOC_ID char, ACT_RNTL_BGN_TS datetime, ACT_RNTL_END_TS datetime)
INSERT #Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-01-30 19:54:37.000', '2016-02-01 10:00:24.053')
INSERT #Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-01-31 16:30:23.000', '2016-02-01 09:07:06.588')
INSERT #Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 09:22:22.000', '2016-02-02 10:00:23.716')
INSERT #Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 09:36:11.000', '2016-02-01 11:05:34.249')
INSERT #Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 10:27:34.000', '2016-02-01 12:59:29.883')
INSERT #Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 10:40:38.000', '2016-02-01 15:36:27.119')
INSERT #Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 12:43:10.000', '2016-02-01 14:23:15.914')
INSERT #Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 13:28:20.000', '2016-02-01 14:40:15.573')
INSERT #Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 17:03:13.000', '2016-02-01 19:02:57.413')
INSERT #Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 17:17:12.000', '2016-02-01 18:54:14.708')
;
-- START OF QUERY
WITH SplittedRentedIntervals AS ( -- Intervals that span more than one day are splited.
SELECT Z.LOC_ID
, Z.Date
, BGN = CASE WHEN Y.ACT_RNTL_BGN_TS > CAST(Z.Date AS datetime) THEN Y.ACT_RNTL_BGN_TS ELSE CAST(Z.Date AS datetime) END
, [END] = CASE WHEN Y.ACT_RNTL_END_TS < CAST(DATEADD(DAY, 1, Z.Date) AS datetime) THEN Y.ACT_RNTL_END_TS ELSE CAST(DATEADD(DAY, 1, Z.Date) AS datetime) END
FROM #Z Z
JOIN #Y Y
ON Y.LOC_ID = Z.LOC_ID
AND (Z.Date = CAST(Y.ACT_RNTL_BGN_TS AS date)
OR Z.Date = CAST(Y.ACT_RNTL_END_TS AS date)
OR (Z.Date BETWEEN Y.ACT_RNTL_BGN_TS AND Y.ACT_RNTL_END_TS))
), Times AS ( -- All times starting and ending any interval, including start and end of day.
SELECT DISTINCT LOC_ID, Date, TIME = BGN FROM SplittedRentedIntervals
UNION SELECT DISTINCT LOC_ID, Date, TIME = [END] FROM SplittedRentedIntervals
UNION SELECT LOC_ID, Date, TIME = CAST(Date AS datetime) FROM #Z
UNION SELECT LOC_ID, Date, TIME = CAST(DATEADD(DAY, 1, Date) AS datetime) FROM #Z
), OrderedTimes AS (
SELECT LOC_ID
, Date
, TIME
, NUM = ROW_NUMBER() OVER (PARTITION BY LOC_ID, Date ORDER BY TIME ASC)
FROM Times
), Intervals AS ( -- Intervals are conformed by two consecutives times.
SELECT OT1.LOC_ID
, OT1.Date
, BGN = OT1.TIME
, [END] = OT2.TIME
FROM OrderedTimes OT1
JOIN OrderedTimes OT2
ON OT2.LOC_ID = OT1.LOC_ID
AND OT2.Date = OT1.Date
AND OT2.NUM = OT1.NUM + 1
), StockByInterval AS ( -- Intersect all time intervals with rented intervals to calculate rented units.
SELECT I.LOC_ID
, I.Date
, I.BGN
, I.[END]
, STOCK = Z.Unit_Count
- (SELECT COUNT(*)
FROM SplittedRentedIntervals SRI
WHERE I.Date = SRI.Date
AND I.LOC_ID = SRI.LOC_ID
AND SRI.BGN < I.[END]
AND SRI.[END] > I.BGN)
FROM Intervals I
JOIN #Z Z
ON Z.Date = I.Date
AND Z.LOC_ID = I.LOC_ID
), WithuotStock AS ( -- Sum the minutes of intervals where there is no stock.
SELECT LOC_ID
, Date
, MinutesWithoutStock = SUM(DATEDIFF(MINUTE, BGN, [END]))
FROM StockByInterval
WHERE STOCK <= 0 -- Sample data has some intervals where there are more items rented than are available.
GROUP BY LOC_ID, Date
)
SELECT Z.LOC_ID
, Z.Date
, MinutesWithoutStock = ISNULL(WS.MinutesWithoutStock, 0)
FROM #Z Z
LEFT JOIN WithuotStock WS
ON WS.Date = Z.Date
AND WS.LOC_ID = Z.LOC_ID
ORDER BY Z.LOC_ID, Z.Date
Sample output
LOC_ID Date MinutesWithoutStock
------ ---------- -------------------
A 2016-02-01 374
A 2016-02-02 0
A 2016-02-03 0
B 2016-02-01 0
B 2016-02-02 0
B 2016-02-03 0
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.