Calculate Sum of times - sql

Hi I'm creating an RDLC Report for an attendance management system. In my report I want to Sum of total WorkedHours.
I Want exactly this output
EmployeeId EmployeeName WorkedHours
1 ABC 04:00:25
2 XYZ 07:23:01
3 PQR 11:02:15
SO i want to display total of all 3 employees at the end of report in RDLC.
like Total: 22:25:41

Try this
select EmpId,In_duration,out_duration,
datediff(mi,in_duration,out_duration) as Diff_mins
from table
the first parameter in DateDiff is mi (minutes). If you look up dateDiff in documentation, you'll see the other options. The example below shows difference in hours, minutes, and seconds as a string.
select
LTRIM(STR(DATEDIFF(mi,in_duration,out_duration) /60))+':'+
LTRIM(STR(DATEDIFF(mi,in_duration,out_duration) %60))+':'+
LTRIM(STR(DATEDIFF(ss,in_duration,out_duration) -
(DATEDIFF(ss,in_duration,out_duration)/60)*60))
as diff

declare #T table
(
EmployeeId int,
EmployeeName varchar(10),
WorkedHours varchar(8)
)
insert into #T
select 1, 'ABC', '04:00:02' union all
select 2, 'XYZ', '07:23:01' union all
select 3, 'PQR', '11:02:15'
select right(100+T2.hh, 2)+':'+right(100+T2.mm, 2)+':'+right(100+T2.ss, 2) as TotalHours
from (
select dateadd(second, sum(datediff(second, 0, cast(WorkedHours as datetime))), 0) as WorkedSum
from #T
) as T1
cross apply (select datediff(hour, 0, T1.WorkedSum) as hh,
datepart(minute, T1.WorkedSum) as mm,
datepart(second, T1.WorkedSum) as ss) as T2
Convert WorkedHours to datetime
Calculate the number of seconds since 1900-01-01T00:00:00
Sum the seconds for all rows
Convert seconds to datetime
Use datediff to calculate the number of hours
Use datepart to get minutes and seconds
Build the resulting string and use right(100+T...) to add 0 before value if neccesary.
The cross apply ... part is not necessary. I added that to make the code clearer. You can use the expressions in the cross apply directly in the field list if you like.

Related

T- SQL Split time in half hour intervals

I have a table calls that shows every call for every employee and looks like this:
date
employee
call_pick_up_time
2021-10-08
12345
2021-10-08 08:13:26
2021-10-08
123456
2021-10-08 08:16:42
Now I want to show the call count for each employee for every 30 minutes interval:
interval
employee
call_count
08:00
12345
4
08:00
123456
7
08:30
12345
5
08:30
123456
3
The considered period is 08:00 - 08:30 / 08:30 -09:00 and so on.
Is there an easy way to get the desired result?
Thanks in advance.
The way I like to round datetime values to the nearest n-minute interval is to take advantage of SQL Server's integer math behavior. If you take the difference in minutes between midnight and the time in question, then divide by n and then multiply by n, it gets rid of any remainder. So to round right now down to the previous 30-minute interval:
DECLARE #now datetime = GETDATE();
DECLARE #today datetime = CONVERT(date, #now);
SELECT DATEADD
(
MINUTE,
DATEDIFF(MINUTE, #today, #now)/30*30,
#today
);
We can apply this to your query by taking your source table and using CROSS APPLY as Charlie suggested and apply that same calculation to your source values (you have to do a little more conversion inline because you don't have nice, static variables to use):
DECLARE #WindowSizeInMinutes smallint = 30;
SELECT x.interval, c.employee, call_count = COUNT(*)
FROM dbo.Calls AS c
CROSS APPLY
(
VALUES
(
DATEADD
(
MINUTE,
DATEDIFF
(
MINUTE,
CONVERT(datetime, CONVERT(date, call_pick_up_time)),
call_pick_up_time
) / #WindowSizeInMinutes * #WindowSizeInMinutes,
CONVERT(datetime, CONVERT(date, call_pick_up_time))
)
)
) AS x(interval)
-- WHERE c.something something
GROUP BY c.employee, x.interval;
If there is an index on call_pick_up_time you were hoping to use, that's out the window.
Another approach that could make use of an index is to pre-determine all the possible 30-minute windows in the range you're after, and then inner join to those:
DECLARE #WindowSizeInMinutes smallint = 30,
#min_date datetime = '20211001',
#max_date datetime = '20211014';
;WITH n(n) AS
(
SELECT 0 UNION ALL
SELECT n + 1
FROM n WHERE n <= 24*60/#WindowSizeInMinutes
),
days(d) AS
(
SELECT #min_date UNION ALL
SELECT DATEADD(DAY, 1, d)
FROM days WHERE d < #max_date
),
intervals AS
(
SELECT interval_start = DATEADD(MINUTE, n*#WindowSizeInMinutes, d),
interval_end = DATEADD(MINUTE, (n+1)*#WindowSizeInMinutes, d)
FROM n CROSS JOIN days
)
SELECT interval = i.interval_start,
c.employee,
call_count = COUNT(c.employee)
FROM intervals AS i
INNER JOIN dbo.Calls AS c
ON c.call_pick_up_time >= i.interval_start
AND c.call_pick_up_time < i.interval_end
GROUP BY c.employee, i.interval_start;
While more complicated, one nice thing about this approach is if you want to show slots for windows where no employees had calls, you could just change the join to an inner join, and if you wanted a slot for each employee, you could just add a CTE with the list of employees and cross join to that.
Both examples on this db<>fiddle

Trying to convert a 7 digit julian date into MMDDYY format

I'm trying to convert a 7 digit julian/mainframe date into a calendar date of format mmddyy in SQL Server.
An example of this being julian date 2005020, where the first 4 digits are the year, 2005, and the last three are the day number in a calendar, so 020 = january 20. So I'd need to get this date as 012005(MMDDYY) in SQL Server.
I've been using the following query but keep getting an error after it loads a few records:
SELECT DATEADD(day,CAST(RIGHT([julianDateColumn],3) as int)-,LEFT([julianDateColumn],4))
The error I've been getting:
Conversion failed when converting date and/or time from character string.
Originally I was doing this in an Access DB using the "DATESERIAL" function but from what I've seen the closest thing to that in SQL Server was "DATEFROMPARTS", I tried using the following formula but it also didn't work:
DATEFROMPARTS([julianDateColumn]/1000,1,[julianDateColumn] % 1000)
Thanks in advance!
The simplest would seem to be to take the left as the year, and the add the days (-1) to make a date. Also, rather than using a format of MMDDYY I'm going to go straight a date datatype. If you want it in a specific format, that's for your presentation layer.
SELECT JulianDate,
CONVERT(date,DATEADD(DAY,RIGHT(JulianDate,3)-1,CONVERT(datetime,LEFT(JulianDate,4)))) AS ActualDate --4 int strings are iterpreted as the year, so I'm going to take advantage of that
FROM (VALUES('2005020'))V(JulianDate);
Based on the comments on the answer, it appears that the OP has some dates that don't conform to the format that stated (yyyyddd). Therefore what we could use here is a calendar table, here, and then LEFT JOIN to it and see what bad rows you get (and INNER JOIN to get the dates).
You can create the table with something like this:
CREATE TABLE dbo.CalendarTable (CalendarDate date NOT NULL PRIMARY KEY,
CalenderYear AS DATEPART(YEAR, CalendarDate) PERSISTED,
CalenderMonth AS DATEPART(MONTH, CalendarDate) PERSISTED,
CalenderDay AS DATEPART(DAY, CalendarDate) PERSISTED,
CalenderMonthName AS DATENAME(MONTH, CalendarDate),
JulianDate AS DATEPART(YEAR,CalendarDate) * 1000 + DATEDIFF(DAY,DATEFROMPARTS(DATEPART(YEAR, CalendarDate),1,1),CalendarDate) + 1 PERSISTED); --Some example columns
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, N N2, N N3, N N4, N N5, N N6),
Dates AS(
SELECT CONVERT(date,DATEADD(DAY, T.I, '19000101')) AS CalendarDate
FROM Tally T)
INSERT INTO dbo.CalendarTable(CalendarDate)
SELECT CalendarDate
FROM Dates
WHERE CalendarDate < '21000101';
GO
Then we can do something like this to get the bad rows:
SELECT YT.JulianDate
FROM dbo.YourTable YT
LEFT JOIN dbo.CalendarTable CT ON YT.JulianDate = CT.JulianDate
WHERE CT.JulianDate IS NULL;
I think I would use datefromparts() and dateadd():
select dateadd(day,
right(juliandate, 3) - 1),
datefromparts(left(juliandate, 4), 1, 1)
)
select Format(cast(concat(substring('2005020', 1, 4), '-', Month(cast(substring('2005020', 5, len('2005020')) as int)) ,'-', day(dateadd(day,-1,cast(substring('2005020', 5, len('2005020')) as int ) ) )) as date), 'MMddyy')

Check Missing Time Interval In SQL in Minutes

I have a SQL statement as below and I wish to check the data in time interval in minutes where the D.DatalogValue didn't have any value and it won't show as Null or zero value either. The sample as below output result will be show 2016-06-01 00:32:29 as missing createdDate.
SELECT
A.DefID, A.ObjID,
C.ObjName, C.Dev_ID,
A.Pro_ID, A.ArrayIndex,
A.DefType, A.TObjID, A.DimeId, A.DefId,
D.DatalogValue, D.PanelDt, D.CreatedDate
FROM
Table A, Table C, Table D
WHERE
A.ObjID = C.ObjID
AND C.ObjID = '2627'
AND A.DefID = D.DefID
AND D.CreatedDate BETWEEN '2016-06-01' AND '2016-06-02'
ORDER BY
C.ObID,C.ObjName;
Sample data:
Create Date DatalogValue
-------------------------------------
2016-06-01 00:29:29 0.01
2016-06-01 00:30:29 0.02
2016-06-01 00:31:29 0.03
2016-06-01 00:33:29 0.04
By using the solution provided i have come out a SQL statement but it still no able to show the result i want. I not sure which part i doing wrong my code as below:
DECLARE #StartDate DATETIME = '2016-07-01';
DECLARE #EndDate DATETIME = '2016-07-31';
WITH Check_Dates AS (
SELECT #StartDate [Date]
UNION ALL
SELECT DATEADD(MINUTE, 1, [Date]) FROM Check_Dates
WHERE [Date] < DATEADD(DAY, 1, #EndDate)
)
SELECT
FORMAT(d.Date, 'yyyy-MM-dd HH:mm') [Created Date]
FROM Check_Dates d
WHERE
NOT EXISTS(
SELECT
Format(D.CreatedDate, 'yyyy-MM-dd HH:mm')as created_dt
FROM TABLE A
,TABLE C
,TABLE D
WHERE A.ObjID=C.ObjID
AND C.ObjID IN('3915')
AND A.DefID=D.DefID
AND D.CreatedDate BETWEEN '2016-07-01' AND '2016-08-01'
)
OPTION (MAXRECURSION 0);
A solution is to use a CTE to create a list of DATETIMEs then LEFT JOIN these onto your original query. You can also create a pair of tables instead (as mentioned in the comments) - google DimDate and/or DimTime.
Something like (untested):
DECLARE #StartDate DATETIME = '2016-06-01';
DECLARE #EndDate DATETIME = '2016-06-02';
WITH Dates AS (
SELECT #StartDate [Date]
UNION ALL
SELECT DATEADD(SECOND, 1, [Date]) FROM Dates
WHERE [Date] < DATEADD(DAY, 1, #EndDate)
)
SELECT
d.Date [Created Date]
,COALESCE(Qry.DatalogValue, 0) DatalogValue
FROM Dates d
LEFT JOIN (
Your query goes here
) Qry
ON d.Date = Qry.CreatedDate
OPTION (MAXRECURSION 0)
Your solution seems very risky to me. Are you sure seconds should be compared? I would truncate to minutes. I suggest more robust solution:
WITH Dates AS
( --Your dates and values
SELECT * FROM (VALUES
('2016-06-01 00:29:29', 0.01),
('2016-06-01 00:30:29', 0.02),
('2016-06-01 00:31:29', 0.03),
('2016-06-01 00:33:29', 0.04)--,('2016-06-01 01:00:28', 0.05)
) T(CreateDate, CatalogValues)
), Minute10 AS --Generate numbers from 0-999999
(
SELECT * FROM (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(0)) T1(Value)
), Minute1000 AS
(
SELECT M1.Value FROM Minute10 M1 CROSS JOIN Minute10 M2 CROSS JOIN Minute10
), Minute1000000 AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1))-1 Value
FROM Minute1000
CROSS JOIN Minute1000 M2
), RangeValues AS --for simplicity, min and max values from dates
(
SELECT DATEADD(MINUTE, DATEDIFF(MINUTE, 0, MIN(CreateDate)), 0) MinDate,
DATEADD(MINUTE, DATEDIFF(MINUTE, 0, MAX(CreateDate)), 0) MaxDate
FROM Dates
)
SELECT TOP(1+DATEDIFF(MINUTE, (SELECT MinDate FROM RangeValues), (SELECT MaxDate FROM RangeValues)))
DATEADD(MINUTE,Value,MinDate) ExpectedDate, CreateDate, CatalogValues
FROM Minute1000000
CROSS APPLY (SELECT MinDate FROM RangeValues) T
LEFT JOIN Dates ON DATEADD(MINUTE,Value,MinDate)=DATEADD(MINUTE, DATEDIFF(MINUTE, 0, CreateDate), 0)
Note that all dates are truncated to minutes. You can simplyfy query by removing number generation part (numbers can be placed in utility table, 1440 values if that's all you need). Min and Max can be precalculated.
This results in following output (can handle ranges minDate+999999 minutes, can be simply extended):
ExprectedDate CreateDate CatalogValues
2016-06-01 00:29:00.000 2016-06-01 00:29:29 0.01
2016-06-01 00:30:00.000 2016-06-01 00:30:29 0.02
2016-06-01 00:31:00.000 2016-06-01 00:31:29 0.03
2016-06-01 00:32:00.000 NULL NULL
2016-06-01 00:33:00.000 2016-06-01 00:33:29 0.04
Explanation:
Dates is just source table. Tables Minute10..Minute1000000 are to generate numbers from 0 to 999999 (10 cross joined 10 = 100, 100 cross joined x3 = 100^3 = 1000000. Records from last table are numbered to get sequential values. Don't worry, TOP prevents from evaluating all 1000000 values. RangeValues contains MAX and MIN dates, for simplicity.
Algorithm:
Since you need records from MIN date to MAX date every minute, you evaluate TOP DATETIFF(MINUTE,MIN,MAX)+1 records (+1 to avoid Fencepost error). All required tables are joined (CROSS APLLY adds MIN column to every record), Expected date is calculated as MIN date + sequential value in minutes. Last join, LEFT one, matches date generated for every minute with source table. If there is match, record is appended (joined). If there is no match, NULL is appended. Note `DATEADD(MINUTE, DATEDIFF(MINUTE, 0, #someDate), 0)' truncates seconds from date.

SQL - How to ignore seconds and round down minutes in DateTime data type

At work we did a project that required a team to count students 8 times a day over 5 days at specific time periods. They are, as follows :-
09:00, 10:00, 11:00, 13:15, 14:15, 14:50, 15:50, 16:20.
Now, the data collected was put directly into a database via a web app. The problem is that database recorded each record using the standard YYYY-MM-DD HH:MM:SS.MIL, but if I were to order the records by date and then by student count it would cause the following problem;
e.g.:-
if the students counted in a room was 5 at 09:00:12, but another room had a count of 0 at 09:02:20 and I did the following:
select student_count, audit_date
from table_name
order by audit_date, student_count;
The query will return:
5 09:00:12
0 09:02:20
but I want:
0 09:00:00
5 09:00:00
because we're looking for the number of students in each room for the period 09:00, but unfortunately to collect the data it required us to do so within that hour and obviously the database will pick up on that accuracy. Furthermore, this issue becomes more problematic when it gets to the periods 14:15 and 14:50, where we will need to be able to distinguish between the two periods.
Is there a way to ignore the seconds part of the DateTime, and the round the minutes down to the nearest ten minute?
I'm using SQL Server Management Studio 2012. If none of this made sense, I'm sorry!
You may want some sort of Period table to store your segments. Then you can use that to join to your counts table.
CREATE TABLE [Periods]
( -- maybe [id] INT,
[start_time] TIME,
[end_time] TIME
);
INSERT INTO [Periods]
VALUES ('09:00','10:00'),
('10:00','11:00'),
('11:00','13:15'),
('13:15','14:15'),
('14:15','14:50'),
('14:50','15:50'),
('15:50','16:20'),
('16:20','17:00')
SELECT
student_count, [start_time]
FROM table_name A
INNER JOIN [Periods] B
ON CAST(A.[audit_date] AS TIME) >= B.[start_time]
AND CAST(A.[audit_date] AS TIME) < B.[end_time]
You can use the DATEADDand DATEPARTfunctions to accomplish this together with a CASEexpression. If you want more precise cutoffs between the .14and .50periods you can easily adjust the case statement and also if you want to minutes to be .10or.15
-- some test data
declare #t table (the_time time)
insert #t values ('09:00:12')
insert #t values ('14:16:12')
insert #t values ('09:02:12')
insert #t values ('14:22:12')
insert #t values ('15:49:12')
insert #t values ('15:50:08')
select
the_time,
case
when datepart(minute,the_time) < 15 then
dateadd(second, -datepart(second,the_time),dateadd(minute, -datepart(minute,the_time),the_time))
when datepart(minute,the_time) >= 15 and datepart(minute,the_time) < 50 then
dateadd(second, -datepart(second,the_time),dateadd(minute, -datepart(minute,the_time)+10,the_time))
else
dateadd(second, -datepart(second,the_time),dateadd(minute, -datepart(minute,the_time)+50,the_time))
end as WithoutSeconds
from #t
Results:
the_time WithoutSeconds
---------------- ----------------
09:00:12.0000000 09:00:00.0000000
14:16:12.0000000 14:10:00.0000000
09:02:12.0000000 09:00:00.0000000
14:22:12.0000000 14:10:00.0000000
15:49:12.0000000 15:10:00.0000000
15:50:08.0000000 15:50:00.0000000
Try this:
SELECT
CAST(
DATEADD(SECOND, - (CONVERT(INT, RIGHT(CONVERT(CHAR(2),
DATEPART(MINUTE, GETDATE())),1))*60) - (DATEPART(SECOND,GETDATE())), GETDATE())
AS SMALLDATETIME);
You can try ORDER BY this formula
DATEADD(minute, floor((DATEDIFF(minute, '20000101', audit_date) + 5)/10)*10, '20000101')
e.g.
WITH tbl AS(
SELECT * FROM ( VALUES (5,'2014-03-28 09:00:09.793'),(0,'2014-03-28 09:02:20.123')) a (student_count, audit_date)
)
SELECT *,DATEADD(minute, floor((DATEDIFF(minute, '20000101', audit_date) + 5)/10)*10, '20000101') as ORDER_DT
FROM tbl
ORDER BY ORDER_DT,student_count
SQL Fiddle

group data by any range of 30 days (not by range of dates) in SQL Server

I got a table with a list of transactions.
for the example, lets say it has 4 fields:
ID, UserID, DateAddedd, Amount
I would like to run a query that checks if there was a time, that in 30 days, a user made transactions in the sum of 100 or more
I saw lots of samples of grouping by month or a day but the problem is that if for example
a user made a 50$ transaction on the 20/4 and on the 5/5 he made another 50$ transaction, the query should show it. (its 100$ or more in a period of 30 days)
I think that this should work (I'm assuming that transactions have a date component, and that a user can have multiple transactions on a single day):
;with DailyTransactions as (
select UserID,DATEADD(day,DATEDIFF(day,0,DateAdded),0) as DateOnly,SUM(Amount) as Amount
from Transactions group by UserID,DATEADD(day,DATEDIFF(day,0,DateAdded),0)
), Numbers as (
select ROW_NUMBER() OVER (ORDER BY object_id) as n from sys.objects
), DayRange as (
select n from Numbers where n between 1 and 29
)
select
dt.UserID,dt.DateOnly as StartDate,MAX(ot.DateOnly) as EndDate, dt.Amount + COALESCE(SUM(ot.Amount),0) as TotalSpend
from
DailyTransactions dt
cross join
DayRange dr
left join
DailyTransactions ot
on
dt.UserID = ot.UserID and
DATEADD(day,dr.n,dt.DateOnly) = ot.DateOnly
group by dt.UserID,dt.DateOnly,dt.Amount
having dt.Amount + COALESCE(SUM(ot.Amount),0) >= 100.00
Okay, I'm using 3 common table expressions. The first (DailyTransactions) is reducing the transactions table to a single transaction per user per day (this isn't necessary if the DateAdded is a date only, and each user has a single transaction per day). The second and third (Numbers and DayRange) are a bit of a cheat - I wanted to have the numbers 1-29 available to me (for use in a DATEADD). There are a variety of ways of creating either a permanent or (as in this case) temporary Numbers table. I just picked one, and then in DayRange, I filter it down to the numbers I need.
Now that we have those available to us, we write the main query. We're querying for rows from the DailyTransactions table, but we want to find later rows in the same table that are within 30 days. That's what the left join to DailyTransactions is doing. It's finding those later rows, of which there may be 0, 1 or more. If it's more than one, we want to add all of those values together, so that's why we need to do a further bit of grouping at this stage. Finally, we can write our having clause, to filter down only to those results where the Amount from a particular day (dt.Amount) + the sum of amounts from later days (SUM(ot.Amount)) meets the criteria you set out.
I based this on a table defined thus:
create table Transactions (
UserID int not null,
DateAdded datetime not null,
Amount decimal (38,2)
)
If I understand you correctly, you need a calendar table and then check the sum between date and date+30. So if you want to check a period of 1 year you need to check something like 365 periods.
Here is one way of doing that. The recursive CTE creates the calendar and the cross apply calculates the sum for each CalDate between CalDate and CalDate+30.
declare #T table(ID int, UserID int, DateAdded datetime, Amount money)
insert into #T values(1, 1, getdate(), 50)
insert into #T values(2, 1, getdate()-29, 60)
insert into #T values(4, 2, getdate(), 40)
insert into #T values(5, 2, getdate()-29, 50)
insert into #T values(7, 3, getdate(), 70)
insert into #T values(8, 3, getdate()-30, 80)
insert into #T values(9, 4, getdate()+50, 50)
insert into #T values(10,4, getdate()+51, 50)
declare #FromDate datetime
declare #ToDate datetime
select
#FromDate = min(dateadd(d, datediff(d, 0, DateAdded), 0)),
#ToDate = max(dateadd(d, datediff(d, 0, DateAdded), 0))
from #T
;with cal as
(
select #FromDate as CalDate
union all
select CalDate + 1
from cal
where CalDate < #ToDate
)
select S.UserID
from cal as C
cross apply
(select
T.UserID,
sum(Amount) as Amount
from #T as T
where T.DateAdded between CalDate and CalDate + 30
group by T.UserID) as S
where S.Amount >= 100
group by S.UserID
option (maxrecursion 0)