How to split date column & sum it up - sql-server

I have a query where i have a date column (time) which tells about "IN" & "OUT" timing of the people attendance by this single column
My queries are :-
1) How to get the daily attendance of each employee
2) How to come to know if the employee is present less than 5 hours
Please let me know the queries in SQL server.

You'll need to group the query by the user and the items for a particular day then compare the maximum and minimum values, e.g.
declare #users table (
UserId int,
DateColumn datetime
)
insert into #users values (1, '2008-10-31 15:15')
insert into #users values (1, '2008-10-31 10:30')
insert into #users values (1, '2008-10-30 16:15')
insert into #users values (1, '2008-10-30 10:30')
select
UserID
, cast(dt as datetime) dt
, [in]
, [out]
, case when datepart(hour, [out]-[in]) >= 5 then 'yes' else 'no' end [5Hours?],
, cast(datediff(minute, [in], [out]) as float)/60 [hours]
from (
select
UserID
, convert(varchar, DateColumn, 112) dt
, min(DateColumn) [in]
, max(DateColumn) [out]
from #users
group by
UserID, convert(varchar, DateColumn, 112)
) a

To find the difference between two datetimes you can use the following:
SELECT Datepart(hour, dateTimeEnd - dateTimeStart)
The DatePart function returns part of a date time variable, and the dateTimeEnd - dateTimeStart returns the difference between two dates as a new DateTime

select
datediff(minute, TimeFrom, TimeTo) as AttendedTimeInMinutes,
case when datediff(minute, sTimeFrom, sTimeTo) < 5 * 60
then
'less than 5 hours'
else '5 hours or more'
end
from YourTable

Related

How to count number of report runs last 7 days? Year to date? All time?

I'm trying to create an SSRS report that looks similar to the table below:
Report
Earliest Run
Recent Run
Runs Last 7 days
Runs YTD
Runs All Time
Report 1
3/3/19 1:30
7/8/22 2:45
8
86
233
I know how to query the last 3 columns individually, but is it possible to get all 3 columns using 1 query? I have tried the query below to show my line of thinking but its not working as desired.
SELECT Report
,Min(TimeStart) AS EarliestRun
,Max(TimeStart) AS RecentRun
,CASE WHEN TimeStart BETWEEN GETDATE()-7 AND GETDATE() THEN COUNT(Report) END AS RunsLast7Days
FROM ReportHistory
WHERE TimeStart BETWEEN '1/1/2019 00:00' AND GETDATE()
GROUP BY Report
Yes - use conditional aggregation. Don't filter the query at all since you need an "all time" value. Instead, use sum with a conditional expression for the periods of interest.
select ...
sum(case when TimeStart >= dateadd(day, -7, getdate()) then 1 else 0 end) as [Runs Last 7 days],
sum(case when TimeStart >= datefromparts(year(getdate()), 1, 1) then 1 else 0 end) as [Runs YTD],
...
from dbo.ReportHistory
order by ...;
I was going to propose using CROSS APPLY but SMor has done it with less code
CREATE TABLE #Reports (
ReportId INT NOT NULL,
ReportName VARCHAR(20) NOT NULL
);
INSERT INTO #Reports(ReportId, ReportName)
VALUES(1, 'Report 1');
CREATE TABLE #ReportRun (
ReportId INT,
RunDateTime DATETIME2(2)
);
INSERT INTO #ReportRun(ReportId, RunDateTime)
VALUES
(1, '20220508 10:00:00'),
(1, '20220502 10:00:00'),
(1, '20220101 10:00:00'),
(1, '20210501 10:00:00'),
(1, '20210209 10:00:00'),
(1, '20200509 10:00:00'),
(1, '20190509 10:00:00');
GO
-- SELECT * FROM #Reports
-- SELECT * FROM #ReportRun
SELECT R.ReportName, B.RunLast7Days, C.RunYearToDate, D.RunAllTime
FROM #Reports AS R
CROSS APPLY (
SELECT TOP 1 RunDateTime
FROM #ReportRun
WHERE ReportId = R.ReportId
ORDER BY RunDateTime DESC
) AS ER
CROSS APPLY (
SELECT COUNT(*) AS RunLast7Days
FROM #ReportRun
WHERE ReportId = R.ReportId
AND RunDateTime >= DATEADD(day, -7, CONVERT(date, GETDATE())) -- best to set it to the start of the day
GROUP BY ReportId
) AS B
CROSS APPLY (
SELECT COUNT(*) AS RunYearToDate
FROM #ReportRun
WHERE ReportId = R.ReportId
AND RunDateTime >= DATEADD(yy, DATEDIFF(yy, 0, GETDATE()), 0)
GROUP BY ReportId
) AS C
CROSS APPLY (
SELECT COUNT(*) AS RunAllTime
FROM #ReportRun
WHERE ReportId = R.ReportId
GROUP BY ReportId
) AS D

SAP B1: How to check if new data was inserted in OIVL table

In SAP B1, I wanted to count data that is created 3 hours prior to current time.
I already have this logic
SELECT COUNT(*)
FROM OIVL T0
WHERE T0.CreateDate >= DATEADD(hour, -12, GETDATE())
But the problem is the CreateDate column only contains Date, without time.
It has time but in smallint
Given the discussion in comments about how CreateTime is encoded by SAP B1, you're going to need to compute each row's datetime value to compare it with the current time, e.g.:
create table #Example (
CreateDate date,
CreateTime smallint
);
insert #Example (CreateDate, CreateTime) values ('2020-08-30', 2324);
select
CreateDate,
CreateTime,
CreateDateTime --2020-08-30 23:24:00.000
from #Example
outer apply (
select dateadd(mi, (CreateTime/100)*60+CreateTime%100, cast(CreateDate as datetime)) as CreateDateTime
) Calc
where CreateDateTime >= dateadd(hour, -12, getdate());

Add a row for every day between two dates with number of hours in SQL Server

Starting data:
Desired results something like this:
So it calculated the number of hours until the end of StartDateTime, if the EndDateTime is greater than end of day for StartDateTime. Then for every full day in between, it calculates 24 hours (this could stretch numerous days). And then when it gets to the EndDateTime - it calculates time from midnight (morning) to EndDateTime
I'm reading that I will probably need to use a recursive CTE, but I don't have any experience with recursions and am struggling.
this might get tricky, but I guess it can be solved using so called number table - i.e. table which has only one column populated with number sequence. In our case 0 based sequence.
The trick here is to get the number of days between start and end datetime. This value used in join between the data table and the numbers table will create the needed extra rows for each per day interval.
Of course we also have to setup properly start and end datetime of each day interval (CASE terms in the CTE)
Then we get for each per day interval number of minutes and divide by 60 to get proper decimal value.
Hope this helps.
Lets see the code:
-- input data
DECLARE #v_Dates TABLE
(
id varchar(20),
StartDateTime SMALLDATETIME,
EndDateTime SMALLDATETIME
)
INSERT INTO #v_Dates (id, StartDateTime, EndDateTime)
VALUES ('example 1', '02-17-2019 0:45', '02-19-19 12:30'),
('example 2', '02-21-2019 18:00', '02-22-19 12:15'),
('example 3', '02-22-2019 20:15', '02-22-19 20:30');
-- so called Number table which holds numbers 0 - 9999 in this case
DECLARE #v_Numbers TABLE
(
Number INT
);
-- populating the number table
INSERT INTO #v_Numbers
SELECT TOP 10000 ROW_NUMBER() OVER(ORDER by t1.number) - 1 as Number
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
-- we parse the dates into the per day intervals
;WITH IntervalsParsed(id, StartDateTime, EndDateTime, Number, IntervalStartDateTime, IntervalEndDateTime) AS
(
SELECT id
,StartDateTime
,EndDateTime
,Number
, InervalStartDateTime = CASE
WHEN D.StartDateTime > DATEADD(day, DATEDIFF(day, 0, D.StartDateTime), N.Number) THEN D.StartDateTime
ELSE DATEADD(day, DATEDIFF(day, 0, D.StartDateTime), N.Number)
END
, IntervalEndDateTime = CASE
WHEN D.EndDateTime < DATEADD(day, DATEDIFF(day, 0, D.StartDateTime), N.Number + 1) THEN D.EndDateTime
ELSE DATEADD(day, DATEDIFF(day, 0, D.StartDateTime), N.Number + 1)
END
FROM #v_Dates D
--this join basically creates the needed number of rows
INNER JOIN #v_Numbers N ON DATEDIFF(day, D.StartDateTime, D.EndDateTime) + 1 > N.Number
)
-- final select
SELECT id
, StartDateTime
, EndDateTime
, IntervalStartDateTime
, IntervalEndDateTime
, Number
, DecimalValue = CAST( DATEDIFF(minute, IntervalStartDateTime, IntervalEndDateTime) AS DECIMAL)/60
FROM IntervalsParsed
ORDER BY id, Number
Just another option is an ad-hoc tally table in concert with a CROSS APPLY
Example
Select A.[column1]
,A.[StartDateTime]
,A.[EndDateTime]
,Hours = sum(1) / 60.0
From #YourTable A
Cross Apply (
Select Top (DateDiff(MINUTE,[StartDateTime],[EndDateTime])+1)
D=DateAdd(MINUTE,-1+Row_Number() Over (Order By (Select Null)),[StartDateTime])
From master..spt_values n1,master..spt_values n2
) B
Group By [column1],[StartDateTime],[EndDateTime],cast(D as Date)
Returns
This may be little complicated, but here is one way to use recursive cte to get the output. You can add the start date with one day as long as it is less than end date of your column. Also declared a Static value to make sure we can get difference of 24 hours.
--Create a table
Select 'example1' exm, '2019-02-17 00:45:00' startdate, '2019-02-19 12:30:00' Enddate into #temp union all
Select 'example2' exm, '2019-02-21 18:00:00' startdate, '2019-02-22 12:15:00' Enddate union all
Select 'example3' exm, '2019-02-22 20:15:00' startdate, '2019-02-22 20:30:00' Enddate
Declare #datevalue time = '23:59:59'
;with cte as (select exm, startdate, enddate, case when datediff(day, startdate, enddate) = 0 then datediff(SECOND, startdate, enddate)
when datediff(day, startdate, enddate)>0 then
datediff(SECOND, cast(startdate as time), #datevalue)
end as Hoursn, cast(dateadd(day, 1,cast(startdate as date)) as smalldatetime) valueforhours from #temp
union all
select exm, startdate, enddate, case when datediff(day, valueforhours, enddate) = 0 then datediff(SECOND, valueforhours, enddate)
when datediff(day, valueforhours, enddate)>0 then datediff(SECOND, cast(valueforhours as time), #datevalue) end as Hoursn, case when datediff(day,valueforhours, enddate) > 0 then dateadd(day,1,valueforhours) end as valueforhours
from cte
where
valueforhours <= cast(enddate as date)
)
select exm, startdate, Enddate, round(Hoursn*1.0/3600,2) as [hours] from cte
order by exm
Output:
exm startdate Enddate hours
example1 2019-02-17 00:45:00 2019-02-19 12:30:00 23.250000
example1 2019-02-17 00:45:00 2019-02-19 12:30:00 24.000000
example1 2019-02-17 00:45:00 2019-02-19 12:30:00 12.500000
example2 2019-02-21 18:00:00 2019-02-22 12:15:00 6.000000
example2 2019-02-21 18:00:00 2019-02-22 12:15:00 12.250000
example3 2019-02-22 20:15:00 2019-02-22 20:30:00 0.250000

Count the number of times a date is contained between 2 date columns

I have a table that looks like this
ID start_dt end_dt
--------------------------
1 1951-12-05 1951-12-21
2 1951-12-19 1951-12-31
3 1957-12-05 1957-12-19
4 1995-12-06 1995-12-20
5 1996-06-24 1996-07-08
6 1997-05-12 1997-05-26
7 1997-10-07 1997-10-21
8 1997-12-25 1998-01-08
9 1998-01-19 1998-02-02
10 1998-08-05 1998-08-19
I'd like to know how many times each individual date is contained between start_dt and end_dt.
From my example, the result set should look something like this
date count
------------------
1951-12-05 1
1951-12-06 1
...
1951-12-19 2
1951-12-20 2
1951-12-21 2
...
1998-08-19 1
What would be the best way to do this?
EDIT: To clarify, I need each date that appears at least once in a date range (between start_dt and end_dt) to get a row in my result set and I want the number of ranges that this date fits in next to it
hope this helps
When you need to turn 2 values (a range) into a series of rows you can use a number table (see Aaron Bertrand's The SQL Server Numbers Table article if you aren't familiar with the idea).
I've used shorter and simpler data but you should get the idea.
declare #dates table (id int not null, start_dt date not null, end_dt date not null)
insert #dates values (1, '20160601', '20160603'),
(2, '20160603', '20160605'),
(3, '20160610', '20160612')
;with cte as (
select
row_number() over (order by so1.object_id) - 1 as n
from
sys.objects so1
cross join sys.objects so2
)
select
dateadd(d, c.n, d.start_dt) as [date],
count(*)
from
#dates d
join cte c on dateadd(d, c.n, d.start_dt) <= d.end_dt
group by
dateadd(d, c.n, d.start_dt)
order by
dateadd(d, c.n, d.start_dt)
If there are no more than a few days (< 80 or so, depending in your sys.objects table) between start_dt and end_dt, you can use this approach (inspired on Rhys').
DECLARE #dates TABLE (id int not null, start_dt date not null, end_dt date not null)
INSERT #dates VALUES
(1, '1951-12-05', '1951-12-21'),
(2, '1951-12-19', '1951-12-31'),
(3, '1957-12-05', '1957-12-19'),
(4, '1995-12-06', '1995-12-20'),
(5, '1996-06-24', '1996-07-08'),
(6, '1997-05-12', '1997-05-26'),
(7, '1997-10-07', '1997-10-21'),
(8, '1997-12-25', '1998-01-08'),
(9, '1998-01-19', '1998-02-02'),
(10, '1998-08-05', '1998-08-19');
WITH RawData AS (
SELECT
DATEADD(d, n.n, d.start_dt) AS [date]
FROM #dates d
INNER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY object_id) - 1 AS n FROM sys.objects
) n ON DATEADD(d, n.n, d.start_dt) <= d.end_dt
)
SELECT [date], COUNT(*) [count]
FROM RawData
GROUP BY [date]
ORDER BY [date]
I don't think this could take long even with 1000 date ranges. Perhaps you are using a table with more fields and even missing some index?
You could use a CTE
WITH CTE AS(SELECT start_dt AS dates FROM Table
UNION ALL
SELECT end_dt AS dates FROM Table)
SELECT CAST(dates as DATE) as Date, COUNT(dates) AS Count
FROM CTE c
GROUP BY c.dates
order by Count desc
Or perhaps you need something broader if your columns are of DATETIME data type. This way will GROUP BY the whole day:
WITH CTE AS(SELECT CAST(start_dt AS DATE) AS dates FROM Table
UNION ALL
SELECT CAST(end_dt AS DATE) AS dates FROM Table)
SELECT Dates as Date, COUNT(Dates) AS Count
FROM CTE c
GROUP BY c.dates
order by Count desc

SQL stament to determine the number of Mondays in a month

Is there a way to get the number of Mondays in a given month (and year) without using T-SQL?
Thanks
I'm not sure what you mean by saying:
Is there a way to get the number of Mondays in a given month (and
year) without using T-SQL?
If you are hoping for a universal code fragment that will do this across all databases, forget it. I doubt that you'll even be able to get a version to run on two different databases. Dates and things like weekdays tend to be be implemented differently across database vendors.
Here is the TSQL way (Monday Month Count):
;with AllDates AS
(SELECT CONVERT(datetime,CONVERT(varchar(6),GETDATE(),112)+'01') AS DateOf
UNION ALL
SELECT DateOf+1
FROM AllDates
WHERE
MONTH(DateOf+1)=MONTH(CONVERT(datetime,CONVERT(varchar(6),GETDATE(),112)+'01'))
)
SELECT COUNT(DateOf) AS MondayCountMonth
FROM AllDates
WHERE DATENAME(weekday,DateOf)='Monday'
Here is the TSQL way (Monday Year Count):
;with AllDates AS
(SELECT CONVERT(datetime,CONVERT(varchar(4),GETDATE(),112)+'0101') AS DateOf
UNION ALL
SELECT DateOf+1
FROM AllDates
WHERE
YEAR(DateOf+1)=Year(CONVERT(datetime,CONVERT(varchar(4),GETDATE(),112)+'0101'))
)
SELECT COUNT(DateOf) AS MondayCountYear
FROM AllDates
WHERE DATENAME(weekday,DateOf)='Monday'
OPTION (MAXRECURSION 367)
EDIT based on OP comment, here is a version which finds the monthly and yearly Monday counts as sub-queries within another query:
DECLARE #YourTable table (Col1 int, Col2 varchar(5))
INSERT #YourTable VALUES (1,'aaa')
INSERT #YourTable VALUES (2,'bbb')
INSERT #YourTable VALUES (3,'ccc')
;with MonthMondayCount AS
(SELECT CONVERT(datetime,CONVERT(varchar(6),GETDATE(),112)+'01') AS DateOf
UNION ALL
SELECT DateOf+1
FROM MonthMondayCount
WHERE
MONTH(DateOf+1)=MONTH(CONVERT(datetime,CONVERT(varchar(6),GETDATE(),112)+'01'))
)
,YearMondayCount AS
(SELECT CONVERT(datetime,CONVERT(varchar(4),GETDATE(),112)+'0101') AS DateOf
UNION ALL
SELECT DateOf+1
FROM YearMondayCount
WHERE
YEAR(DateOf+1)=Year(CONVERT(datetime,CONVERT(varchar(4),GETDATE(),112)+'0101'))
)
SELECT
y.*
,(SELECT COUNT(DateOf) AS MondayCountMonth FROM MonthMondayCount WHERE DATENAME(weekday,DateOf)='Monday') AS MondayCountMonth
,(SELECT COUNT(DateOf) AS MondayCountYear FROM YearMondayCount WHERE DATENAME(weekday,DateOf)='Monday') AS MondayCountYear
FROM #YourTable y
OPTION (MAXRECURSION 367)
OUTPUT:
Col1 Col2 MondayCountMonth MondayCountYear
----------- ----- ---------------- ---------------
1 aaa 5 52
2 bbb 5 52
3 ccc 5 52
(3 row(s) affected)
Try this : )
DECLARE #tmpDate as date
set #tmpDate = getdate(); --you can add any date
DECLARE #Startdate as varchar( 8)
DECLARE #Enddate as varchar( 8)
SELECT #Startdate = replace(convert(varchar,cast(DATEADD(month, DATEDIFF(month, 0, #tmpDate), 0) as date) , 111), '/', '');
SELECT #Enddate = replace(convert(varchar, cast(DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#tmpDate)+1,0)) as date), 111), '/', '');
with [dates] as (
select convert(date , #Startdate ) as [date] --start
union all
select dateadd(day , 1 , [date])
from [dates]
where [date] < #Enddate )
Select X.WeekDayNumber, count(X.WeekDayNumber) as NumberOfDays
from (
SELECT [date] , DATEPART(weekday,[date] ) as WeekDayNumber
from [dates]
WHERE [date] IS NOT NULL)X
Group by X.WeekDayNumber
option (maxrecursion 0 )
( DATEADD( DAY, -1, DATEADD( MONTH, 1 , month + '-01' ) -
DATEADD( DAY, 7 - DATEPART( WEEKDAY, month + '-01' ), month + '-01' )
) DIV 7 + 1
I don't know how much ANSI SQL compatible this is but it works in MySql (not after the changes, it should work in SQL Server now).
month should be in 'yyyy-mm' format

Resources