SQL Server - converting 31's to 30's on MonthEnd - sql-server

I have a little problem with my code, I just want to convert all 31's end of the month to 30's. I know its a there's a little trick behind it.
DECLARE #StartYear DATE = '20170101'
DECLARE #EndYear DATE = '20171231'
;WITH n AS (SELECT n FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, Dates AS (
SELECT TOP (DATEDIFF(DAY, #StartYear, #EndYear)+1)
DatesOnSelect = CONVERT(DATE,DATEADD(DAY,ROW_NUMBER() OVER(ORDER BY (SELECT 1))-1,#StartYear))
FROM n AS deka CROSS JOIN n AS hecto CROSS JOIN n AS kilo
CROSS JOIN n AS tenK CROSS JOIN n AS hundredK
ORDER BY DatesOnSelect
)
SELECT DISTINCT MonthEnd = CONVERT(DATE, DATEADD(DAY,-1,DATEADD(MONTH, DATEDIFF(MONTH, 0,DatesOnSelect )+1, 0)), 101) FROM dates

Using a case expression to subtract an extra day if the month end is 31.
DECLARE #StartYear DATE = '20170101'
DECLARE #EndYear DATE = '20171231'
;WITH n AS (SELECT n FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, Dates AS (
SELECT TOP (DATEDIFF(DAY, #StartYear, #EndYear)+1)
DatesOnSelect = CONVERT(DATE,DATEADD(DAY,ROW_NUMBER() OVER(ORDER BY (SELECT 1))-1,#StartYear))
FROM n AS deka CROSS JOIN n AS hecto CROSS JOIN n AS kilo
CROSS JOIN n AS tenK CROSS JOIN n AS hundredK
ORDER BY DatesOnSelect
)
SELECT DISTINCT MonthEnd
= CONVERT(varchar(10),
case when day(DATEADD(DAY,-1,DATEADD(MONTH, DATEDIFF(MONTH, 0,DatesOnSelect )+1, 0)))=31
then DATEADD(DAY,-2,DATEADD(MONTH, DATEDIFF(MONTH, 0,DatesOnSelect )+1, 0))
else DATEADD(DAY,-1,DATEADD(MONTH, DATEDIFF(MONTH, 0,DatesOnSelect )+1, 0))
end , 101)
FROM dates
rextester demo: http://rextester.com/NEXST15257
returns:
+------------+
| MonthEnd |
+------------+
| 01/30/2017 |
| 02/28/2017 |
| 03/30/2017 |
| 04/30/2017 |
| 05/30/2017 |
| 06/30/2017 |
| 07/30/2017 |
| 08/30/2017 |
| 09/30/2017 |
| 10/30/2017 |
| 11/30/2017 |
| 12/30/2017 |
+------------+

A calendar/tally table would do the trick, but you can all accomplish this with an ad-hoc tally table. If you need more than 6 years just change the sub-query to From master..spt_values n1,master..spt_values n2
Declare #Date1 date = '2017-01-01'
Declare #Date2 date = '2017-12-31'
Select D=max(D)
From (Select Top (DateDiff(DD,#Date1,#Date2)+1) D=DateAdd(DD,-1+Row_Number() Over (Order By (Select Null)),#Date1) From master..spt_values) A
Where Day(D) between 28 and 30
Group By Year(D),Month(D)
Returns
D
2017-01-30
2017-02-28
2017-03-30
2017-04-30
2017-05-30
2017-06-30
2017-07-30
2017-08-30
2017-09-30
2017-10-30
2017-11-30
2017-12-30

Related

Referencing the current row outer apply column within separate outer join

Recently I've been tasked with creating a report that outputs sales information by Date of Business and Hour of the Day.
Here is the query I have currently written.
WITH CTE AS
(
SELECT 0 AS Count
UNION ALL
SELECT Count + 1
FROM CTE
WHERE Count + 1 <= 23
),
ALLDATES AS
(
SELECT CONVERT(datetime, #startDate) AS [DOB]
UNION ALL
SELECT DATEADD(DAY, 1, [DOB])
FROM AllDates
WHERE [DOB] < #endDate
)
SELECT D.DOB, A.Count AS [Hour], CONCAT(A.Count, ':00') AS [DisplayHour]
, B.OrderModeName, COALESCE(B.Sales_Total, 0) AS [Sales]
, COALESCE(B.Comps, 0) AS Comps, COALESCE(B.Promos, 0) AS Promos
FROM CTE AS A
OUTER APPLY (SELECT DOB FROM ALLDATES) D
LEFT OUTER JOIN (
SELECT DATEPART(HH, ItemDetail.TransactionTime) AS [Hour]
, OrderMode.OrderModeName, SUM(ItemDetail.GrossPrice) Sales_Total
, SUM(CompAmount) AS Comps, SUM(PromoAmount) AS Promos
FROM ItemDetail
INNER JOIN OrderMode ON OrderMode.OrderModeID = ItemDetail.OrderModeID
WHERE ItemDetail.DOB = D.DOB /*NEED HELP HERE*/ AND LocationID IN (
SELECT LocationID
FROM LocationGroupMember
WHERE LocationGroupID = '#locationGroupID'
)
GROUP BY ItemDetail.DOB, DATEPART(HH, ItemDetail.TransactionTime), OrderMode.OrderModeName
) AS B
ON A.Count = B.Hour
ORDER BY D.DOB, A.Count
Where I am struggling is being able to reference the current row's DOB column that is coming from the OUTER APPLY.
I have tried WHERE ItemDetail.DOB = D.DOB, however I receive an error that the identifier can't be bound. Am I correct that in understanding that the outer applied data is not visible to the subquery within the join?
Here is an example of the output I'm expecting:
DOB | Hour | Display Hour | OrderModeName | Sales | Comps | Promos
1/8/2020 | 17 | 17:00 | Order | 163.17 | 0 | 0 <-- Sales for Hour and Order Mode present
1/8/2020 | 23 | 23:00 | | 0 | 0 | 0 <-- No sales at all for a given hour
Thanks in advance for any direction and advice!
The basic pattern here is to CROSS JOIN to define the result "grain" and then LEFT JOIN the fact table to populate the rows for which data exists. EG
WITH CTE AS
(
SELECT 0 AS Count
UNION ALL
SELECT Count + 1
FROM CTE
WHERE Count + 1 <= 23
),
ALLDATES AS
(
SELECT CONVERT(datetime, #startDate) AS [DOB]
UNION ALL
SELECT DATEADD(DAY, 1, [DOB])
FROM AllDates
WHERE [DOB] < #endDate
),
ALLHOURS as
(
SELECT D.DOB, A.Count AS [Hour], CONCAT(A.Count, ':00') AS [DisplayHour]
FROM CTE AS A
CROSS JOIN ALLDATES D
),
ITEM_SUMMARY as
(
SELECT DOB, DATEPART(HH, ItemDetail.TransactionTime) AS [Hour], OrderMode.OrderModeName, SUM(ItemDetail.GrossPrice) Sales_Total, SUM(CompAmount) AS Comps, SUM(PromoAmount) AS Promos
FROM ItemDetail
INNER JOIN OrderMode ON OrderMode.OrderModeID = ItemDetail.OrderModeID
AND LocationID IN (SELECT LocationID FROM LocationGroupMember WHERE LocationGroupID = #locationGroupID)
where DOB >= #startDate
and DOB < #endDate
GROUP BY ItemDetail.DOB, DATEPART(HH, ItemDetail.TransactionTime), OrderMode.OrderModeName
)
select ALLHOURS.DOB,
ALLHOURS.Count AS [Hour],
CONCAT(ALLHOURS.Count, ':00') AS [DisplayHour],
ITEM_SUMMARY.OrderModeName,
COALESCE(ITEM_SUMMARY.Sales_Total, 0) AS [Sales],
COALESCE(ITEM_SUMMARY.Comps, 0) AS Comps,
COALESCE(ITEM_SUMMARY.Promos, 0) AS Promos
from ALLHOURS
LEFT OUTER JOIN ITEM_SUMMARY
on ITEM_SUMMARY.DOB = ALLHOURS.DOB
and ITEM_SUMMARY.Hour = ALLHOURS.Hour

How to group by week even when the count is 0

My below example works fine, the only challenge i am facing is that weeks with 0 results do not show.
Here is a sample of my code:
SELECT
DATENAME (WK, DATE) AS WEEK,
COUNT (DISTINCT COMPANY_ID) AS AMOUNT
FROM
(
SELECT COMPANY, DATE = MIN(DATE)
FROM TABLE1 A INNER JOIN TABLE2 B
ON A.ID = B.ID
WHERE YEAR(A.DATE) = '2019' AND COMPANY_ID NOT IN(SELECT COMPANY_ID FROM
TABLE1 A INNER JOIN TABLE2 B ON A.ID = B.ID AND DATE < '2019-01-01') GROUP
BY COMPANY_ID) d
GROUP BY dateadd(wk, datediff(wk, 0, DATE), 0), DATENAME(WK, DATE)
ORDER BY dateadd(wk, datediff(wk, 0, DATE), 0)
My current output looks like this:
week | amount
4 | 354
6 | 222
7 | 144
8 | 354
9 | 45
10 | 55
11 | 76
12 | 98
13 | 45
14 | 344
The result above is missing many weeks (1,2,3 and 15,16,17 etc.)
How do i get to show those with 0 count?
My desired output:
week | amount
1 | 0
2 | 0
3 | 0
4 | 354
6 | 222
7 | 144
8 | 354
9 | 45
10 | 55
11 | 76
12 | 98
13 | 45
14 | 344
15 | 0
16 | 0
17 | 0
Couple of things to note -
1) Your current query is not correct (Possibly, you have removed some portion of it to hide confidential stuff).
Ex. In the subquery named "d", the GROUP BY is on "company_id" column but "company" has been SELECT-ed.
SELECT DATENAME (WK, DATE) AS WEEK,
COUNT (DISTINCT COMPANY_ID) AS AMOUNT
FROM
(
SELECT COMPANY /*Different from group_by clause*/, DATE = MIN(DATE)
FROM TABLE1 A INNER JOIN TABLE2 B ON (A.ID = B.ID)
WHERE YEAR(A.DATE) = '2019'
AND COMPANY_ID NOT IN
(
SELECT COMPANY_ID
FROM TABLE1 A INNER JOIN TABLE2 B ON A.ID = B.ID AND DATE < '2019-01-01'
)
GROUP BY COMPANY_ID
) d
GROUP BY dateadd(wk, datediff(wk, 0, DATE), 0), DATENAME(WK, DATE)
ORDER BY dateadd(wk, datediff(wk, 0, DATE), 0)
2) I hope while editing you have not remove any clauses mistakenly.
3) Could you please post some input data to understand the output better.
(Apologies for posting here, as I don't have privilege to comment.)
First Create a temp table that has all weeks numbers
then join it with your query
DECLARE #Weeks AS Table(ID int)
DECLARE #i int = 1
WHILE #i < 53
BEGIN
INSERT INTO #Weeks (ID)
VALUES(#i)
SET #i = #i + 1
END
SELECT * FROM #Weeks
SELECT
DATENAME (WK, DATE) AS WEEK,
COUNT (DISTINCT COMPANY_ID) AS AMOUNT
FROM
(
SELECT COMPANY, DATE = MIN(DATE)
FROM TABLE1 A INNER JOIN TABLE2 B
ON A.ID = B.ID
RIGHT OUTER JOIN #Weeks W ON W.ID = DATENAME (WK, DATE)
WHERE YEAR(A.DATE) = '2019' AND COMPANY_ID NOT IN(SELECT COMPANY_ID FROM
TABLE1 A INNER JOIN TABLE2 B ON A.ID = B.ID AND DATE < '2019-01-01') GROUP
BY COMPANY_ID) d
GROUP BY dateadd(wk, datediff(wk, 0, DATE), 0), DATENAME(WK, DATE)
ORDER BY dateadd(wk, datediff(wk, 0, DATE), 0)

SQL Server - Display the number of active sessions for each minute in a time frame

I have the below table:
SessionID | UserName | Started | Ended
----------------------------------------------------------------
100 Test1 2015-07-26 00:03:05 2015-07-26 00:08:12
As the title says, I need to extract between a given #FromDate and a #ToDate parameters, for each minute, how many active sessions were. What I have tried so far does not select the non-active session (when no customers were online in that minute) and I cannot figure it out how to do this.
My SQL Statement
CREATE PROCEDURE [dbo].[ActiveSessionsByMinute] #FromDate datetime, #ToDate datetime
AS
BEGIN
SET NOCOUNT ON
SELECT DATEADD(MINUTE, DATEPART(MINUTE, Started), CAST(CONVERT(varchar(20), Started, 112) AS datetime)) AS DateMinute,
COUNT(SessionID) AS ActiveSessions
FROM ApplicationSessionHistory
GROUP BY DATEADD(MINUTE, DATEPART(MINUTE, Started), CAST(CONVERT(varchar(20), Started, 112) AS datetime))
END
GO
Output
DateMinute | ActiveSessions
-----------------------------------------
2015-07-26 00:03:00.000 | 1
If I execute the below statement, I should get the desired output (below):
EXEC dbo.ActiveSessionsByMinute
#FromDate = '2015-07-26 00:00',
#ToDate = '2015-07-26 00:10'
Desired Output
DateMinute | ActiveSessions
-----------------------------------------
2015-07-26 00:00:00.000 | 0
2015-07-26 00:01:00.000 | 0
2015-07-26 00:02:00.000 | 0
2015-07-26 00:03:00.000 | 1
2015-07-26 00:04:00.000 | 1
2015-07-26 00:05:00.000 | 1
2015-07-26 00:06:00.000 | 1
2015-07-26 00:07:00.000 | 1
2015-07-26 00:08:00.000 | 1
2015-07-26 00:09:00.000 | 0
2015-07-26 00:00:00.000 | 0
Does anyone can give me a tip? Thanks
I would do this with a CTE tally table. Notice I added an extra Session in the sample data.
HERE IS A DEMO
--Sample data
declare #table table (SessionID int, UserName varchar(16), Started datetime, Ended datetime)
insert into #table
values
(100,'Test1','2015-07-26 00:03:05','2015-07-26 00:08:12')
,(101,'Test1','2015-07-26 00:04:05','2015-07-26 00:05:12')
--used as a beginning anchor for the tally table
declare #startDate datetime = (select min(cast(Started as date)) from #table)
--take the original data, and truncate the seconds
;with NewTable as(
select
SessionID
,UserName
,Started = CAST(DateAdd(minute, DateDiff(minute, 0, Started), 0) AS smalldatetime)
,Ended = CAST(DateAdd(minute, DateDiff(minute, 0, Ended), 0) AS smalldatetime)
from #table
),
--tally table to get 10K minutes.
--This can be expanded for larger date ranges, and is faster than recursive CTE
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT TallyDate = dateadd(minute,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),#startDate) FROM E4
)
--use cross apply and and a case statement to find if it falls in the range
select
DateMinute = N
,SessionID
,Started
,Ended
,IsActive = case when (Started <=N and Ended >= N) then 1 else 0 end
from NewTable t
cross apply cteTally
where N <= (select max(Ended) from #table)
order by SessionID, N
For the sum part, you can simply aggregate. Replace the last SELECT with this one
--based on the above output, just do the SUM
select
DateMinute = N
,ActiveSessions = sum(case when (Started <=N and Ended >= N) then 1 else 0 end)
from NewTable t
cross apply cteTally
where N <= (select max(dateadd(minute,1,Ended)) from #table)
group by N
order by N
You'll want to SELECT from a tally table with all the minutes and LEFT JOIN to your ApplicationSessionHistory table:
CREATE PROCEDURE [dbo].[ActiveSessionsByMinute]
#FromDate DATETIME
, #ToDate DATETIME
AS
BEGIN
SET NOCOUNT ON;
SELECT allminutes.alltimes AS DateMinute
, COUNT(SessionID) AS ActiveSessions
FROM
(
SELECT DATEADD(MINUTE, myrows.rn, #FromDate) AS alltimes
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY s.id) - 1 rn
FROM master.sys.syscolumns AS s
) myrows
) allminutes
LEFT OUTER JOIN ApplicationSessionHistory ON allminutes.alltimes BETWEEN ApplicationSessionHistory.Started AND ApplicationSessionHistory.Ended
WHERE allminutes.alltimes <= #ToDate
GROUP BY DATEADD(MINUTE, DATEPART(MINUTE, Started), CAST(CONVERT(VARCHAR(20), Started, 112) AS DATETIME));
END;

T-SQL - Minutes per hour between two datetimes

I have to following data:
| tid | startdate | enddate |
| 1 | 2016-12-26 12:30 | 2016-12-26 15:30 |
| 2 | 2016-12-26 13:15 | 2016-12-26 15:15 |
I would like to create a result with the hour number and then the amount of minutes the date time falls within that hour.
Example result:
| tid | hour | minutes_in |
| 1 | 12 | 30 |
| 1 | 13 | 60 |
| 1 | 14 | 60 |
| 1 | 15 | 30 |
| 2 | 13 | 45 |
| 2 | 14 | 60 |
| 2 | 15 | 15 |
Any suggestions?
First You need a numbers table to get your hours from 0 - 23, which can be fairly easily created on the fly with a table value constructor:
SELECT N
FROM (VALUES
(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),
(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23)
) n (N);
Then you can join this to your original data to split rows out into the number required. Then you just need a case expression to apply the correct logic for calculating the minutes:
WITH Numbers (Number) AS
( SELECT N
FROM (VALUES
(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),
(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23)
) n (N)
), SampleData (tid, StartDate, EndDate) AS
( SELECT tid, CONVERT(DATETIME2, StartDate), CONVERT(DATETIME2, EndDate)
FROM (VALUES
(1, '2016-12-26 12:30', '2016-12-26 15:30'),
(2, '2016-12-26 13:15', '2016-12-26 15:15')
) d (tid, StartDate, EndDate)
)
SELECT d.tid,
[Hour] = n.Number,
Minutes_in = CASE
-- SPECIAL CASE: START HOUR = END HOUR
WHEN DATEPART(HOUR, d.StartDate) = DATEPART(HOUR, d.EndDate)
THEN DATEDIFF(MINUTE, d.StartDate, d.EndDate)
-- FULL HOURS IN BETWEEN START AND END
WHEN n.Number > DATEPART(HOUR, d.StartDate)
AND n.Number < DATEPART(HOUR, d.EndDate) THEN 60
-- START HOUR
WHEN n.Number = DATEPART(HOUR, d.StartDate)
THEN 60 - DATEPART(MINUTE, d.StartDate)
-- END HOUR
WHEN n.Number = DATEPART(HOUR, d.EndDate)
THEN DATEPART(MINUTE, d.EndDate)
END
FROM SampleData d
INNER JOIN Numbers n
ON n.Number >= DATEPART(HOUR, d.StartDate)
AND n.Number <= DATEPART(HOUR, d.EndDate);
ADDENDUM
If you need to span days, then you could alter the logic slightly, generate a larger set of numbers to cover more hours difference, then rather than joining on the hour of the day, join the numbers on the hours difference from the start datetime to the end datetime:
SELECT *
FROM SampleData d
INNER JOIN Numbers n
ON n.Number <= DATEDIFF(HOUR, d.StartDate, d.EndDate)
This means where the range crosses over days, then there is no issue, the hours just keep incrementing. e.g.
WITH Numbers (Number) AS
( SELECT ROW_NUMBER() OVER(ORDER BY N1.N) - 1
FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N1(N)
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N2 (N)
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N3 (N)
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N4 (N)
), SampleData (tid, StartDate, EndDate) AS
( SELECT tid, CONVERT(DATETIME2, StartDate), CONVERT(DATETIME2, EndDate)
FROM (VALUES
(1, '2016-12-26 12:30', '2016-12-26 15:30'),
(2, '2016-12-26 13:15', '2016-12-26 15:15'),
(3, '2016-12-26 13:15', '2016-12-27 15:15')
) d (tid, StartDate, EndDate)
)
SELECT d.tid,
[Date] = CONVERT(DATE, d.StartDate),
[Hour] = CONVERT(TIME(0), DATEADD(HOUR, DATEPART(HOUR, d.StartDate) + n.Number, 0)),
Minutes_in = CASE
-- SPECIAL CASE: START HOUR = END HOUR
WHEN DATEPART(HOUR, d.StartDate) = DATEPART(HOUR, d.EndDate)
AND DATEDIFF(DAY, d.StartDate, d.EndDate) = 0
THEN DATEDIFF(MINUTE, d.StartDate, d.EndDate)
-- START HOUR
WHEN n.Number = 0
THEN 60 - DATEPART(MINUTE, d.StartDate)
-- END HOUR
WHEN n.Number = DATEDIFF(HOUR, d.StartDate, d.EndDate)
THEN DATEPART(MINUTE, d.EndDate)
-- FULL HOURS IN BETWEEN START AND END
ELSE 60
END
FROM SampleData d
INNER JOIN Numbers n
ON n.Number <= DATEDIFF(HOUR, d.StartDate, d.EndDate)
ORDER BY d.tid, n.Number;
Method -I
You can achieve this with a UDF (Another Simplest Way)
Lets build schema of your provided data
CREATE TABLE #TAB ( TID INT, STARTDATE DATETIME, ENDDATE DATETIME)
INSERT INTO #TAB
SELECT 1,'2016-12-26 12:30','2016-12-26 15:30'
UNION ALL
SELECT 2,'2016-12-26 13:15','2016-12-26 15:15'
Create one UDF to generate values between from and To
ALTER FUNCTION [dbo].[FN_GENERATE] (#FROM_NBR INT, #TO_NBR INT)
RETURNS
#RESULT TABLE(HR INT)
AS
BEGIN
;WITH CTE AS
(
SELECT #FROM_NBR AS FROM_NBR,#TO_NBR AS TO_NBR
UNION ALL
SELECT FROM_NBR+1 ,TO_NBR FROM CTE WHERE FROM_NBR<TO_NBR
)
INSERT INTO #RESULT
SELECT FROM_NBR FROM CTE
RETURN
END
Now query for data by calling the function.
;WITH CTE AS (
SELECT TID,STARTDATE,ENDDATE,DATEPART(HH,STARTDATE) FROM_HR , DATEPART(HH,ENDDATE) TO_HR FROM #TAB T
)
SELECT C1.TID,F.HR, COALESCE(DATEPART(MINUTE,FRM_HR_MINUTS.STARTDATE),DATEPART(MINUTE,TO_HR_MINUTS.ENDDATE),60 )
FROM CTE C1
CROSS APPLY
(
SELECT * FROM DBO.[FN_GENERATE] (C1.FROM_HR, C1.TO_HR)
)AS F
LEFT JOIN CTE FRM_HR_MINUTS ON C1.TID= FRM_HR_MINUTS.TID AND DATEPART(HH,FRM_HR_MINUTS.STARTDATE)= F.HR
LEFT JOIN CTE TO_HR_MINUTS ON C1.TID= TO_HR_MINUTS.TID AND DATEPART(HH,TO_HR_MINUTS.ENDDATE)= F.HR
Edit :
Method - II
Without using UDF & using MASTER.DBO.SPT_VALUES
;WITH CTE AS (
--PREPARING START HR, END HR, START_MIN, END_MIN FROM #TAB
SELECT TID,STARTDATE,ENDDATE
,DATEPART(HH,STARTDATE) FROM_HR
, DATEPART(HH,ENDDATE) TO_HR
, DATEPART(MINUTE, STARTDATE) AS STARTMIN
, DATEPART(MINUTE, ENDDATE) ENDMIN
FROM #TAB T
)
SELECT TID
, NUMBER AS HRS
--if Outer APply produce Null Display Minutes from CTE else 60 Mins
, CASE ISNULL(OA.FRM_MINS, C1.STARTMIN) + ISNULL(TO_MINS,C1.ENDMIN)
WHEN 0
THEN 60
ELSE ISNULL(OA.FRM_MINS, C1.STARTMIN) + ISNULL(TO_MINS,C1.ENDMIN)
END AS MINS
FROM CTE C1
OUTER APPLY --JOINING NUMBERS BETWEEN FROM_HR & TO_HR using MASTER.DBO.SPT_VALUES
(
SELECT NUMBER
--IF FROM_HR matched NULL Else 0
, CASE C1.FROM_HR WHEN NUMBER THEN NULL ELSE 0 END AS FRM_MINS
--IF TO_HR matched NULL Else 0
,CASE C1.TO_HR WHEN NUMBER THEN NULL ELSE 0 END AS TO_MINS
FROM MASTER.DBO.SPT_VALUES
WHERE [type]='P' AND number>0 AND number BETWEEN FROM_HR AND TO_HR
)AS OA

Group by a set of Values (Check-In or Check-Out)

I have a simple table which records people clocking-in and clocking out like so.
Id | EmployeeNumber | InOutId | InOutDateTime
-----------------------------------------------------
1 | 505 | IN | 2015-03-24 08:32:42:000
1 | 506 | IN | 2015-03-24 08:35:47:000
1 | 507 | IN | 2015-03-24 08:46:12:000
1 | 505 | OUT | 2015-03-24 16:59:00:000
1 | 506 | OUT | 2015-03-24 17:05:00:000
I want to show the total people currently IN and those currently OUT. In other words:
- Total IN means those that do not have a corresponding OUT for that given day. - Total OUT means those that do have an IN and an OUT for that given day.
So, based on my table above, I want to get the following results:
TotalCurrentlyIn | TotalCurrentlyOut
-----------------------------------------
1 | 2
This is what I have so far:
DECLARE #d date;
set #d = cast('2015-03-24 15:02:42.000' as date)
select EmployeeNumber, InOutId, InOutDateTime from MyAttendance
where
InOutDateTime >= DATEADD(day, DATEDIFF(day, 0, #d), 0)
and InOutDateTime < DATEADD(day, DATEDIFF(day, 0, #d) +1, 0)
order by
EmployeeNumber, InOutId
I need to be able to sum and group by - any ideas?
try,
DECLARE #d date;
set #d = cast('2015-03-24 15:02:42.000' as date)
;with cte as(
select t.EmployeeNumber,t.InOutId as in1,
t1.InOutId out1,t.InOutDateTime from #t t
left join (select EmployeeNumber,InOutId,InOutDateTime from #t
where InOutId='OUT' and cast(InOutDateTime as date)=cast(#d as date) ) t1
on t.EmployeeNumber=t1.EmployeeNumber and
cast(t.InOutDateTime as date)=cast(t1.InOutDateTime as date)
where t.InOutId='IN' and cast(t.InOutDateTime as date)=cast(#d as date))
select count(in1) Totalin,count(out1) Totalout, sum(case when out1 is null then 1 else 0 end) TotalCurrentlyIn
,count(out1) TotalCurrentlyOut from cte
data
declare #t table (Id int,EmployeeNumber int, InOutId varchar(3), InOutDateTime datetime)
insert into #t(Id, EmployeeNumber,InOutId, InOutDateTime) values
(1 , 505 , 'IN' , '2015-03-24 08:32:42:000'),
(1 , 506 , 'IN' , '2015-03-24 08:35:47:000'),
(1 , 507 , 'IN' , '2015-03-24 08:46:12:000'),
(1 , 505 , 'OUT' , '2015-03-24 16:59:00:000'),
(1 , 506 , 'OUT' , '2015-03-24 17:05:00:000')
CheckIn = 1 and CheckOut = 2 so you need to check last entry of all the uses.
Select EmployeeId, ActionType, Max(ActionDateTime)
From AttendanceLog
Where
ActionDateTime >= DATEADD(day, DATEDIFF(day, 0, #d), 0)
and ActionDateTime < DATEADD(day, DATEDIFF(day, 0, #d) +1, 0)
Group by
EmployeeId, ActionType
Order by
EmployeeId,ActionType
If I understand the question. you need to know how much person is in the office right now:
the first query return the max date for any employee, than you join it with the actionType
select
EmployeeId , max(ActionDateTime) as MaxActionDateTime into #temptable
from table
group by EmployeeId
select count (EmployeeId), ActionType
from table inner join #temptable
on table.EmployerId == #temptable.EmployerId
and table.ActionDateTime == #temptable.MaxActionDateTime
group by ActionType
Using a windowing function you can get the last action for every employee and count those
With data As (
Select id, EmployeeNumber, InOutId
, lastAction = ROW_NUMBER() OVER (PARTITION BY EmployeeNumber
ORDER BY InOutDateTime DESC)
From table1
)
Select Count(CASE InOutId WHEN 'IN' THEN 1 END) TotalCurrentlyIn
, Count(CASE InOutId WHEN 'OUT' THEN 1 END) TotalCurrentlyOut
From data
Where lastAction = 1

Resources