sql query PIVOT - sql-server

I would like to arrange the sum of OrderValue by the period. My SQL query now displays in a tabular format and I want it to be in one line. If the OrderValue due is in this current, it should be under column 0, and if due next month, then it must me under column 1 and so on. Please see my SQL query
ALTER PROCEDURE [dbo].[sp_GetInvoicedPayments]
#CustomerID int
AS
BEGIN
DECLARE #endOfCurrentMonth DATE = EOMONTH(GETDATE())
SELECT [data].CustomerID, [data].[Period], SUM([data].OrderValue) AS
OrderValue
FROM (
SELECT pms.CustomerID, pms.OrderValue,
CASE
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= paymentInfo.CurrentDueMonth) THEN 0
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 1, paymentInfo.CurrentDueMonth)) + 1,- 1)) ) THEN 1
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 2, paymentInfo.CurrentDueMonth)) + 1,- 1))) THEN 2
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 3, paymentInfo.CurrentDueMonth)) + 1,- 1))) THEN 3
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 4, paymentInfo.CurrentDueMonth)) + 1,- 1))) THEN 4
END AS [Period]
FROM PaymentMilestoneSummary pms
INNER JOIN (
SELECT cus.ID AS CustomerID,
CASE
WHEN cus.PaymentStatusID = 1 THEN #endOfCurrentMonth
WHEN cus.PaymentStatusID = 2 THEN (SELECT CAST(DATEADD(month, - 1, #endOfCurrentMonth) AS DATE))
WHEN cus.PaymentStatusID = 3 THEN (SELECT CAST(DATEADD(month, - 2, #endOfCurrentMonth) AS DATE))
WHEN cus.PaymentStatusID = 4 THEN (SELECT CAST(DATEADD(month, - 3, #endOfCurrentMonth) AS DATE))
WHEN cus.PaymentStatusID = 5 THEN (SELECT CAST(DATEADD(month, - 4, #endOfCurrentMonth) AS DATE))
END AS CurrentDueMonth
FROM Company cus
) paymentInfo ON pms.CustomerID = paymentInfo.CustomerID AND paymentInfo.CustomerID= #CustomerID
)[data]
GROUP BY [data].CustomerID, [data].[Period]
END
This is what I get:
This is an example of how I would like it to be:

You can do it like following using PIVOT and CTE
;with cte as
(
--wrap you existing query
)
SELECT
[0], [1], [2], [3], [4]
FROM
(select Period, OrderValue from cte) AS SourceTable
PIVOT
(
max(OrderValue)
FOR Period IN ([0], [1], [2], [3], [4])
) AS PivotTable;
Your procedure should look like this.
ALTER PROCEDURE [dbo].[sp_GetInvoicedPayments]
#CustomerID int
AS
BEGIN
DECLARE #endOfCurrentMonth DATE = EOMONTH(GETDATE())
;with CTE AS
(
SELECT [data].CustomerID, [data].[Period], SUM([data].OrderValue) AS
OrderValue
FROM (
SELECT pms.CustomerID, pms.OrderValue,
CASE
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= paymentInfo.CurrentDueMonth) THEN 0
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 1, paymentInfo.CurrentDueMonth)) + 1,- 1)) ) THEN 1
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 2, paymentInfo.CurrentDueMonth)) + 1,- 1))) THEN 2
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 3, paymentInfo.CurrentDueMonth)) + 1,- 1))) THEN 3
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 4, paymentInfo.CurrentDueMonth)) + 1,- 1))) THEN 4
END AS [Period]
FROM PaymentMilestoneSummary pms
INNER JOIN (
SELECT cus.ID AS CustomerID,
CASE
WHEN cus.PaymentStatusID = 1 THEN #endOfCurrentMonth
WHEN cus.PaymentStatusID = 2 THEN (SELECT CAST(DATEADD(month, - 1, #endOfCurrentMonth) AS DATE))
WHEN cus.PaymentStatusID = 3 THEN (SELECT CAST(DATEADD(month, - 2, #endOfCurrentMonth) AS DATE))
WHEN cus.PaymentStatusID = 4 THEN (SELECT CAST(DATEADD(month, - 3, #endOfCurrentMonth) AS DATE))
WHEN cus.PaymentStatusID = 5 THEN (SELECT CAST(DATEADD(month, - 4, #endOfCurrentMonth) AS DATE))
END AS CurrentDueMonth
FROM Company cus
) paymentInfo ON pms.CustomerID = paymentInfo.CustomerID AND paymentInfo.CustomerID= #CustomerID
)[data]
GROUP BY [data].CustomerID, [data].[Period]
)
SELECT [0], [1], [2], [3], [4]
FROM
(select Period, OrderValue from cte) AS SourceTable
PIVOT
(
max(OrderValue)
FOR Period IN ([0], [1], [2], [3], [4])
) AS PivotTable;
END
Edit:
how would I then sum up those values I get from that one line?
To sum, you can change the pivot query like following.
SELECT
[0], [1], [2], [3], [4] , s as [Sum]
FROM
(select Period, OrderValue, sum(OrderValue) over() s from cte) AS SourceTable
PIVOT
(
max(OrderValue)
FOR Period IN ([0], [1], [2], [3], [4])
) AS PivotTable;

Related

Creating a Calendar table without recursion

I want to create a Calendar Table without using the recursion as I have prepared earlier. How can I achieve this task. All the required columns are mentioned in code below and few other details in code comments.
...........................................................................................................................................................................................................................................................................
DECLARE #StartDate date = '20200101'
DECLARE #CutoffDate date = GETDATE()
;WITH seq(n) AS
(
SELECT 0 UNION ALL SELECT n + 1 FROM seq
WHERE n < DATEDIFF(DAY, #StartDate, #CutoffDate)
),
d(d) AS
(
SELECT DATEADD(DAY, n, #StartDate) FROM seq
),
src AS /*SOURCE TABLE WITH OBJECT DEFINITION*/
(
SELECT
TheDate = CONVERT(date, d),
TheDay = DATEPART(DAY, d),
TheDayName = DATENAME(WEEKDAY, d),
TheWeek = DATEPART(WEEK, d),
TheDayOfWeek = DATEPART(WEEKDAY, d),
TheMonth = DATEPART(MONTH, d),
TheMonthName = DATENAME(MONTH, d),
TheQuarter = Concat('Q',DATEPART(Quarter, d)),
Financial_Year = DATEPART(YEAR, d),
Financial_Quarter=Datepart(QUARTER,d),
TheYear = DATEPART(YEAR, d),
TheFirstOfMonth = DATEFROMPARTS(YEAR(d), MONTH(d), 1),
TheFirstOfFYear = DATEFROMPARTS(YEAR(d), 4, 1),
TheFirstOfYear = DATEFROMPARTS(YEAR(d), 1, 1),
TheLastOfYear = DATEFROMPARTS(YEAR(d), 12, 31),
TheDayOfYear = DATEPART(DAYOFYEAR, d)
FROM d
),
Dimension AS
(
SELECT
TheDate,
TheDay,
TheDayName,
TheDayOfWeek,
TheDayOfWeekInMonth = CONVERT(tinyint, ROW_NUMBER() OVER
(PARTITION BY TheFirstOfMonth, TheDayOfWeek ORDER BY TheDate)),
TheDayOfYear,
TheWeek,
TheFirstOfWeek = DATEADD(DAY, 1 - TheDayOfWeek, TheDate),
TheLastOfWeek = DATEADD(DAY, 6, DATEADD(DAY, 1 - TheDayOfWeek, TheDate)),
TheWeekOfMonth = CONVERT(tinyint, DENSE_RANK() OVER
(PARTITION BY TheYear, TheMonth ORDER BY TheWeek)),
TheMonth,
TheMonthName,
TheFirstOfMonth,
TheLastOfMonth = MAX(TheDate) OVER (PARTITION BY TheYear, TheMonth),
TheFirstOfNextMonth = DATEADD(MONTH, 1, TheFirstOfMonth),
TheLastOfNextMonth = DATEADD(DAY, -1, DATEADD(MONTH, 2, TheFirstOfMonth)),
TheQuarter,
TheFirstOfQuarter = MIN(TheDate) OVER (PARTITION BY TheYear, TheQuarter),
TheLastOfQuarter = MAX(TheDate) OVER (PARTITION BY TheYear, TheQuarter),
TheYear,
TheFirstOfYear = DATEFROMPARTS(TheYear, 1, 1),
TheFirstOfFYear = DATEFROMPARTS(TheYear, 4, 1),
TheLastOfYear,
MMYYYY = CONVERT(char(2), CONVERT(char(8), TheDate, 101))
+ CONVERT(char(4), TheYear),
Financial_Quarter = Datepart(Quarter,DATEADD(MONTH, -3, TheFirstOfMonth)), /*Starting Financial Quarter from April*/
Financial_Year =CASE
WHEN Financial_Quarter = 1 THEN DATEPART(Year,Dateadd(Year,-1,TheFirstofYear)) ELSE THEYEAR END
FROM src
)
SELECT * FROM Dimension
ORDER BY TheDate
OPTION (MAXRECURSION 0);
As I mentioned in the comments, use a Tally. These are significantly faster than a rCTE as they aren't recursive. I use an inline tally here:
DECLARE #StartDate date = '20200101';
DECLARE #CutoffDate date = GETDATE();
/*
; is a terminator, not a "beginingator". It goes at the end of ALL your statements,
not at the start of statements that require the PREVIOUS statement to be properly terminated.
*/
WITH N AS
(SELECT N
FROM (VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)),
Tally AS
(SELECT 0 AS I
UNION ALL
SELECT TOP (DATEDIFF(DAY, #StartDate, #CutoffDate))
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1,N N2,N N3), --Up to 1000 rows. Add more cross joins for more rows
D AS
(SELECT DATEADD(DAY, T.I, #StartDate) AS d
FROM Tally T),
Src AS /*SOURCE TABLE WITH OBJECT DEFINITION*/
(SELECT CONVERT(date, d) AS TheDate,
DATEPART(DAY, d) AS TheDay,
DATENAME(WEEKDAY, d) AS TheDayName,
DATEPART(WEEK, d) AS TheWeek,
DATEPART(WEEKDAY, d) AS TheDayOfWeek,
DATEPART(MONTH, d) AS TheMonth,
DATENAME(MONTH, d) AS TheMonthName,
CONCAT('Q', DATEPART(QUARTER, d)) AS TheQuarter,
DATEPART(YEAR, d) AS Financial_Year,
DATEPART(QUARTER, d) AS Financial_Quarter,
DATEPART(YEAR, d) AS TheYear,
DATEFROMPARTS(YEAR(d), MONTH(d), 1) AS TheFirstOfMonth,
DATEFROMPARTS(YEAR(d), 4, 1) AS TheFirstOfFYear,
DATEFROMPARTS(YEAR(d), 1, 1) AS TheFirstOfYear,
DATEFROMPARTS(YEAR(d), 12, 31) AS TheLastOfYear,
DATEPART(DAYOFYEAR, d) AS TheDayOfYear
FROM d),
Dimension AS
(SELECT TheDate,
TheDay,
TheDayName,
TheDayOfWeek,
CONVERT(tinyint, ROW_NUMBER() OVER (PARTITION BY TheFirstOfMonth, TheDayOfWeek ORDER BY TheDate)) AS TheDayOfWeekInMonth,
TheDayOfYear,
TheWeek,
DATEADD(DAY, 1 - TheDayOfWeek, TheDate) AS TheFirstOfWeek,
DATEADD(DAY, 6, DATEADD(DAY, 1 - TheDayOfWeek, TheDate)) AS TheLastOfWeek,
CONVERT(tinyint, DENSE_RANK() OVER (PARTITION BY TheYear, TheMonth ORDER BY TheWeek)) AS TheWeekOfMonth,
TheMonth,
TheMonthName,
TheFirstOfMonth,
MAX(TheDate) OVER (PARTITION BY TheYear, TheMonth) AS TheLastOfMonth,
DATEADD(MONTH, 1, TheFirstOfMonth) AS TheFirstOfNextMonth,
DATEADD(DAY, -1, DATEADD(MONTH, 2, TheFirstOfMonth)) AS TheLastOfNextMonth,
TheQuarter,
MIN(TheDate) OVER (PARTITION BY TheYear, TheQuarter) AS TheFirstOfQuarter,
MAX(TheDate) OVER (PARTITION BY TheYear, TheQuarter) AS TheLastOfQuarter,
TheYear,
DATEFROMPARTS(TheYear, 1, 1) AS TheFirstOfYear,
DATEFROMPARTS(TheYear, 4, 1) AS TheFirstOfFYear,
TheLastOfYear,
CONVERT(char(2), CONVERT(char(8), TheDate, 101)) + CONVERT(char(4), TheYear) AS MMYYYY,
DATEPART(QUARTER, DATEADD(MONTH, -3, TheFirstOfMonth)) AS Financial_Quarter, /*Starting Financial Quarter from April*/
CASE
WHEN Financial_Quarter = 1 THEN DATEPART(YEAR, DATEADD(YEAR, -1, TheFirstOfYear))
ELSE TheYear
END AS Financial_Year
FROM src)
SELECT *
FROM Dimension
ORDER BY TheDate;

Is there a faster way of adding up time ranges, taking overlaps into account?

I have a subquery that is taking multiple minutes to execute. If I pull out just the initial rows that are being added up, it only takes half a second with 2,400ish rows so I don't understand why the main query doing the sum is taking so long.
What I'm trying to do is for all the transactions in a date range, for all the workers assigned to those transactions, add up the scheduled hours for each worker.
The query is returning the correct data, it's just taking FOREVER to do it.
QUERY
SELECT scheduled_hours = COALESCE(sum(hours), 0), worker_sysid
FROM (
SELECT DISTINCT
B.DateR1,
B.DateR2,
hours = ABS((B.DAteR1 - B.DateR2) / 3600),
B.worker_sysid
FROM Trans A
OUTER APPLY (
SELECT
DateR1 = MIN(TRANS_START),
DateR2 = MAX(TRANS_END),
worker_sysid
FROM Trans
JOIN trans_workers ON trans_workers.trans_sysid = Trans.SYSID
LEFT JOIN Service ON Service.SYSID = Trans.SERVICESYSID
WHERE
TRANS_START <= A.TRANS_END AND TRANS_END >= A.TRANS_START
AND TRANS_START IS NOT NULL AND TRANS_END IS NOT NULL
AND TRANS_START != '' AND TRANS_END != ''
AND Trans.CHARGEBY IN ('Hours', 'Hour')
AND (
COALESCE(Service.overnight, 0) != 1
OR
COALESCE(Service.active_overnight, 0) = 1
)
AND TRANSDATE BETWEEN 80387 AND 80400 ### These are Clarion dates
AND trans_workers.deleted_at IS NULL
GROUP BY worker_sysid
) B
) A
WHERE worker_sysid IS NOT NULL
GROUP BY worker_sysid
TABLES
Trans: SYSID (int, pk), TRANSDATE (int, clarion-formatted date), TRANS_START / TRANS_END (UNIX timestamp), SERVICESYSID (int, fk), CHARGEBY (varchar)
trans_workers: trans_sysid, worker_sysid, deleted_at
Service: SYSID (int, pk)
UPDATE
Moving the trans_workers join out of the OUTER APPLY has reduced the execution time from 1 minute down to 16 seconds, so that's an improvement.
SELECT scheduled_hours = COALESCE(sum(hours), 0), worker_sysid
FROM (
SELECT DISTINCT
B.DateR1,
B.DateR2,
hours = ABS((B.DateR1 - B.DateR2) / 3600),
worker_sysid
FROM Trans A
JOIN trans_workers ON A.SYSID = trans_workers.trans_sysid
OUTER APPLY (
SELECT
DateR1 = MIN(TRANS_START),
DateR2 = MAX(TRANS_END),
Trans.SYSID
FROM Trans
LEFT JOIN Service ON Service.SYSID = Trans.SERVICESYSID
WHERE
TRANS_START <= A.TRANS_END AND TRANS_END >= A.TRANS_START
AND TRANS_START IS NOT NULL AND TRANS_END IS NOT NULL
AND TRANS_START != '' AND TRANS_END != ''
AND Trans.CHARGEBY IN ('Hours', 'Hour')
AND COALESCE(Service.overnight, 0) != 1
AND TRANSDATE BETWEEN 80387 AND 80400
GROUP BY Trans.SYSID
) B
) A
WHERE worker_sysid IS NOT NULL
GROUP BY worker_sysid
ORDER BY worker_sysid
Thanks to https://www.sqlservercentral.com/forums/topic/consolidate-overlapping-date-periods I have a query that executes in under a second and returns what appear to be the correct hours. Only problem being I don't understand what's happening.
DECLARE #start INTEGER, #end INTEGER;
SET #start = 80401; --06/02/2021
SET #end = 80414; --19/02/2021
WITH cteTemp
AS (
SELECT
worker_sysid,
BeginDate =
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY theDate) - openCnt = 0 THEN theDate
END,
EndDate =
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY theDate) - closeCnt = 0 THEN theDate
END
FROM (
SELECT
worker_sysid,
theDate = DATEADD(day, 0, DATEDIFF(day, 0, (dateadd(day,[TRANSDATE]-(4),'1801-01-01')))) + DATEADD(day, 0 - DATEDIFF(day, 0, DATEADD(second, TRANS_START - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')), DATEADD(second, TRANS_START - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')),
closeCnt = NULL,
openCnt = (ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY DATEADD(day, 0, DATEDIFF(day, 0, (dateadd(day,[TRANSDATE]-(4),'1801-01-01')))) + DATEADD(day, 0 - DATEDIFF(day, 0, DATEADD(second, TRANS_START - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')), DATEADD(second, TRANS_START - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01'))) * 2) - 1
FROM
Trans
INNER JOIN trans_workers ON trans_workers.trans_sysid = Trans.SYSID
JOIN Service ON Service.SYSID = Trans.SERVICESYSID
WHERE
worker_sysid IS NOT NULL
AND Trans.deleted_at IS NULL
AND trans_workers.deleted_at IS NULL
AND Trans.CHARGEBY IN ('Hour', 'Hours')
AND (transCancelled IS NULL OR transCancelled != 1)
AND (
COALESCE(Service.overnight, 0) = 0
)
AND TRANSDATE BETWEEN #start AND #end
UNION ALL
SELECT
worker_sysid,
theDate = DATEADD(day, 0, DATEDIFF(day, 0, (dateadd(day,[TRANSDATE]-(4),'1801-01-01')))) + DATEADD(day, 0 - DATEDIFF(day, 0, DATEADD(second, TRANS_END - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')), DATEADD(second, TRANS_END - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')),
closeCnt = ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY worker_sysid, DATEADD(day, 0, DATEDIFF(day, 0, (dateadd(day,[TRANSDATE]-(4),'1801-01-01')))) + DATEADD(day, 0 - DATEDIFF(day, 0, DATEADD(second, TRANS_END - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')), DATEADD(second, TRANS_END - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01'))) * 2,
openCnt = NULL
FROM
Trans
JOIN trans_workers ON trans_workers.trans_sysid = Trans.SYSID
JOIN Service ON Service.SYSID = Trans.SERVICESYSID
WHERE
worker_sysid IS NOT NULL
AND Trans.deleted_at IS NULL
AND trans_workers.deleted_at IS NULL
AND Trans.CHARGEBY IN ('Hour', 'Hours')
AND (transCancelled IS NULL OR transCancelled != 1)
AND (
COALESCE(Service.overnight, 0) = 0
)
AND TRANSDATE BETWEEN #start AND #end
)
AS baseSelected
)
SELECT scheduled_hours = SUM(hours), worker_sysid
FROM (
SELECT
dt.worker_sysid,
hours = CAST(ABS(DATEDIFF(second, MIN(dt.BeginDate), MAX(dt.EndDate))) / 3600.0 AS DECIMAL(10,2))
FROM (
SELECT
worker_sysid,
BeginDate,
EndDate,
grpID =
IIF(BeginDate IS NOT NULL, ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY worker_sysid, BeginDate), ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY worker_sysid, EndDate))
FROM
cteTemp
WHERE
BeginDate IS NOT NULL OR EndDate IS NOT NULL
)
AS dt
GROUP BY dt.worker_sysid,grpID
) AS final_table
GROUP BY worker_sysid ORDER BY worker_sysid
Bonus points to myself for conversions because the DATE of each transaction is in Clarion and the TIME of each transaction is a Unix timestamp

Find gaps in timesheet data between certain hours

I am trying to find gaps in timesheets between the hours of 8AM and 6PM. I am able to find the gaps for records that are logged, but I cannot figure out how to determine if a record was "missed" - meaning if they started at 8:30 AM, I cannot figure out how to identify the 30 minute gap between 8AM and 8:30 AM (ie. they started work late).
In below example, I can find the two gaps between 12 and 12:30 pm, but not the 8am-8:30am gap and 5:30 to 6pm gap on 5/8, and 8am-8:30am gap on 5/10.
Any ideas to point me in the right direction on how I could approach this?
drop table #time;
create table #time (
TimesheetId int not null
, StartTime datetime not null
, EndTIme datetime not null
);
insert into #time (TimesheetId, StartTime, EndTime)
values (210, '2017-05-08 05:30:00.000', '2017-05-08 06:30:00.000')
, (210, '2017-05-08 06:30:00.000', '2017-05-08 08:30:00.000')
, (210, '2017-05-08 08:30:00.000', '2017-05-08 12:00:00.000')
, (210, '2017-05-08 12:30:00.000', '2017-05-08 18:30:00.000')
, (210, '2017-05-09 08:30:00.000', '2017-05-09 12:00:00.000')
, (210, '2017-05-09 12:30:00.000', '2017-05-09 17:30:00.000')
, (210, '2017-05-09 22:30:00.000', '2017-05-10 05:30:00.000')
, (210, '2017-05-10 08:30:00.000', '2017-05-10 18:00:00.000')
;
; with t1 as (
SELECT TimesheetId
, StartTime
, lag(EndTime) OVER (PARTITION BY TimesheetId ORDER BY StartTime) AS prev_endtime
FROM #time
where datepart(HH, StartTime) <= 18
and datepart(HH, EndTime) >= 8
)
select prev_endtime as gapStart
, StartTime as gapEnd
from t1
where StartTime <> prev_endtime
and cast(prev_endtime as date) = cast(StartTime as date)
;
WITH
a AS(SELECT DATEADD(hh, DATEDIFF(dd, 0, StartTime) * 24 + 8, 0) t,
TimesheetId FROM #time),
b AS(SELECT * FROM #time UNION ALL SELECT TimesheetId, t, t FROM a UNION ALL
SELECT TimesheetId, DATEADD(hh, 10, t), DATEADD(hh, 10, t) FROM a),
c AS(SELECT TimesheetId,
LAG(EndTime) OVER (
PARTITION BY TimesheetId ORDER BY StartTime
) prev_fin,
StartTime
FROM b),
d AS(SELECT *, DATEADD(hh, DATEDIFF(dd, 0, prev_fin) * 24 + 8, 0) beg,
DATEADD(hh, DATEDIFF(dd, 0, prev_fin) * 24 + 18, 0) fin
FROM c)
SELECT TimesheetId, prev_fin, StartTime
FROM d
WHERE prev_fin < StartTime AND
((prev_fin >= beg AND prev_fin < fin) OR
(StartTime > beg AND StartTime <= fin));
Check it on rextester.com.
You can use this to insert a record and then use what you have
Or you could use a UNION
select distinct t1.TimesheetId, dateadd(hh, 8, cast(CONVERT(date, StartTime) as datetime)) as StartTime, dateadd(hh, 8, cast(CONVERT(date, StartTime) as datetime)) as EndTime
from #time t1
where not exists ( select dateadd(hh, 8, cast(CONVERT(date, T2.StartTime) as datetime)), t2.*
from #time T2
where 1 = 1
and t2.TimesheetId = t1.TimesheetId
and CONVERT(date, T2.StartTime) = CONVERT(date, T1.StartTime)
and t2.StartTime = dateadd(hh, 8, cast(CONVERT(date, t2.StartTime) as datetime))
)

Get attendance by month name

I am trying to find attendance for any month like this:
DECLARE #StartDate DATE
DECLARE #EndDate DATE
SET #StartDate = '2016/10/01'
SET #EndDate = '2016/10/31'
SELECT
StaffAttendance.StaffId, DATENAME(MONTH, #StartDate) AS [ForMonth],
(DATEDIFF(dd, #StartDate, #EndDate) + 1)
-(DATEDIFF(wk, #StartDate, #EndDate) * 1)
-(CASE WHEN DATENAME(dw, #StartDate) = 'Sunday' THEN 1 ELSE 0 END) AS TotalWorkingDays,
SUM(StaffAttendance.AttendanceStatusId) AS TotalDaysWorked FROM StaffAttendance WHERE [Date] BETWEEN #StartDate AND #EndDate AND StaffAttendance.AttendanceStatusId = 1 GROUP BY StaffAttendance.StaffId
And this is what I am getting, which is just fine.
What I want to do next is omit the date range (#StartDate DATE, #EndDate DATE) and use month name instead like - 'October' and not month number like '10.' So that user gives a month name and start and end dates for that month are automatically calculated. I got help for that by Felix Pamittan.
DECLARE #month VARCHAR(9) = 'february';
WITH CteMonths(n, m) AS(
SELECT 1, 'January' UNION ALL
SELECT 2, 'February' UNION ALL
SELECT 3, 'March' UNION ALL
SELECT 4, 'April' UNION ALL
SELECT 5, 'May' UNION ALL
SELECT 6, 'June' UNION ALL
SELECT 7, 'July' UNION ALL
SELECT 8, 'August' UNION ALL
SELECT 9, 'September' UNION ALL
SELECT 10, 'October' UNION ALL
SELECT 11, 'November' UNION ALL
SELECT 12, 'December'
)
SELECT
DATEADD(MONTH, n - 1, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)) AS StartDate,
DATEADD(DAY, -1, DATEADD(MONTH, n, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0))) AS EndDate
FROM CteMonths
WHERE m = #month
Now, how do I combine these two and get same result as above?
Use CROSS APPLY:
DECLARE #month VARCHAR(9) = 'february';
WITH CteMonths(n, m) AS(
SELECT 1, 'January' UNION ALL
SELECT 2, 'February' UNION ALL
SELECT 3, 'March' UNION ALL
SELECT 4, 'April' UNION ALL
SELECT 5, 'May' UNION ALL
SELECT 6, 'June' UNION ALL
SELECT 7, 'July' UNION ALL
SELECT 8, 'August' UNION ALL
SELECT 9, 'September' UNION ALL
SELECT 10, 'October' UNION ALL
SELECT 11, 'November' UNION ALL
SELECT 12, 'December'
)
SELECT
sa.StaffId,
DATENAME(MONTH, t.StartDate) AS [ForMonth],
(DATEDIFF(dd, t.StartDate, t.EndDate) + 1)
- (DATEDIFF(wk, t.StartDate, t.EndDate) * 1)
- (CASE WHEN DATENAME(dw, t.StartDate) = 'Sunday' THEN 1 ELSE 0 END) AS TotalWorkingDays,
SUM(sa.AttendanceStatusId) AS TotalDaysWorked
FROM StaffAttendance sa
CROSS APPLY(
SELECT
DATEADD(MONTH, n - 1, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)) AS StartDate,
DATEADD(DAY, -1, DATEADD(MONTH, n, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0))) AS EndDate
FROM CteMonths
) t
WHERE
sa.[Date] >= t.StartDate
AND sa.[Date] < DATEADD(DAY, 1, t.EndDate)
AND sa.AttendanceStatusId = 1
GROUP BY sa.StaffId

Failure creating a function same as stored procedure

This is my stored procedure which I was able to create
CREATE PROCEDURE [dbo].[spGetAllStaffCollectiveAttendanceByMonth]
#Month nvarchar(9)
AS
BEGIN
Declare #StartDate DATE,
#EndDate DATE
;WITH CteMonths(n, m) AS(
SELECT 1, 'January' UNION ALL
SELECT 2, 'February' UNION ALL
SELECT 3, 'March' UNION ALL
SELECT 4, 'April' UNION ALL
SELECT 5, 'May' UNION ALL
SELECT 6, 'June' UNION ALL
SELECT 7, 'July' UNION ALL
SELECT 8, 'August' UNION ALL
SELECT 9, 'September' UNION ALL
SELECT 10, 'October' UNION ALL
SELECT 11, 'November' UNION ALL
SELECT 12, 'December'
)
SELECT #StartDate = DATEADD(MONTH, n - 1, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)),
#EndDate = DATEADD(DAY, -1, DATEADD(MONTH, n, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)))
FROM CteMonths
WHERE m = #month
SELECT
StaffAttendance.StaffId,
DATENAME(MONTH, #StartDate) AS [ForMonth], #StartDate AS StartDate, #EndDate AS EndDate,
(DATEDIFF(dd, #StartDate, #EndDate) + 1)-(DATEDIFF(wk, #StartDate, #EndDate) * 1)-(CASE WHEN DATENAME(dw, #StartDate) = 'Sunday' THEN 1 ELSE 0 END) AS TotalWorkingDays,
SUM(StaffAttendance.AttendanceStatusId) AS TotalDaysWorked
FROM StaffAttendance
WHERE [Date] BETWEEN #StartDate AND #EndDate AND StaffAttendance.AttendanceStatusId = 1 GROUP BY StaffAttendance.StaffId
END
GO
This, here, is my function which I am trying to create but can't. Its the same thing as above except for what makes it a function.
CREATE FUNCTION [dbo].[funcGetAllStaffCollectiveAttendanceByMonth]
(
#Month nvarchar(9)
)
RETURNS TABLE
AS
RETURN
(
Declare #StartDate DATE,
#EndDate DATE
;WITH CteMonths(n, m) AS(
SELECT 1, 'January' UNION ALL
SELECT 2, 'February' UNION ALL
SELECT 3, 'March' UNION ALL
SELECT 4, 'April' UNION ALL
SELECT 5, 'May' UNION ALL
SELECT 6, 'June' UNION ALL
SELECT 7, 'July' UNION ALL
SELECT 8, 'August' UNION ALL
SELECT 9, 'September' UNION ALL
SELECT 10, 'October' UNION ALL
SELECT 11, 'November' UNION ALL
SELECT 12, 'December'
)
SELECT #StartDate = DATEADD(MONTH, n - 1, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)),
#EndDate = DATEADD(DAY, -1, DATEADD(MONTH, n, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)))
FROM CteMonths
WHERE m = #month
SELECT
StaffAttendance.StaffId,
DATENAME(MONTH, #StartDate) AS [ForMonth], #StartDate AS StartDate, #EndDate AS EndDate,
(DATEDIFF(dd, #StartDate, #EndDate) + 1)-(DATEDIFF(wk, #StartDate, #EndDate) * 1)-(CASE WHEN DATENAME(dw, #StartDate) = 'Sunday' THEN 1 ELSE 0 END) AS TotalWorkingDays,
SUM(StaffAttendance.AttendanceStatusId) AS TotalDaysWorked
FROM StaffAttendance
WHERE [Date] BETWEEN #StartDate AND #EndDate AND StaffAttendance.AttendanceStatusId = 1 GROUP BY StaffAttendance.StaffId
END
)
But this is what I am getting
What am I doing wrong?
You have used the syntax for an inline table-valued function but you have multiple statements.
Comparison of inline and multi-statement table-valued functions.
So you could either refactor to use a single statement (higher performance) or use the multi-statement syntax as described in that link (easier).
If you wanted to do it inline you could do something along these lines (note, I don't have SSMS available right now so there may be some minor syntax errors like unmatched brackets):
CREATE FUNCTION [dbo].[funcGetAllStaffCollectiveAttendanceByMonth]
(
#Month nvarchar(9)
)
RETURNS TABLE
AS
RETURN
(
-- The StartDate should be the first day of the month that is passed as a parameter, in the current year.
-- The EndDate should be the last day of the month which the StartDate begins.
WITH cteDates AS (
SELECT StartDate = Convert(date, Concat('01', #Month, Convert(varchar(4), DatePart(Year, GetDate()))), 106),
EndDate = DateAdd(Day, -1,
DateAdd(Month, 1,
Convert(date, Concat('01', #Month, Convert(varchar(4), DatePart(Year, GetDate()))) , 106)
)
)
)
SELECT
Sa.StaffId,
DATENAME(MONTH, cteDates.StartDate) AS [ForMonth],
cteDates.StartDate,
cteDates.EndDate,
(DATEDIFF(dd, cteDates.StartDate, cteDates.EndDate) + 1)-
(DATEDIFF(wk, cteDates.StartDate, cteDates.EndDate) * 1)-
(CASE WHEN DATENAME(dw, cteDates.StartDate) = 'Sunday' THEN 1 ELSE 0 END) AS TotalWorkingDays,
SUM(sa.AttendanceStatusId) AS TotalDaysWorked
FROM StaffAttendance As sa
JOIN cteDates ON sa.[Date] BETWEEN cteDates.StartDate AND cteDates.EndDate
WHERE sa.AttendanceStatusId = 1
GROUP BY
Sa.StaffId,
DATENAME(MONTH, cteDates.StartDate),
cteDates.StartDate,
cteDates.EndDate,
(DATEDIFF(dd, cteDates.StartDate, cteDates.EndDate) + 1)-
(DATEDIFF(wk, cteDates.StartDate, cteDates.EndDate) * 1)-
(CASE WHEN DATENAME(dw, cteDates.StartDate) = 'Sunday' THEN 1 ELSE 0 END)
END
)

Resources