I have written a pivot query in sql server 2012. It works fine and it shows the result of usernames under Rows and sum of production hours in seconds under columns. But I need the seconds to be splitted into hours:minutes format. Please help me on query.
declare #empid nvarchar(20), #fromdate date, #todate date, #cols nvarchar(max), #query AS VARCHAR(MAX), #dt varchar(20), #dt1 varchar(20)
set #empid = 'EC0100'
set #fromdate = '10/01/13'
set #todate = '10/31/13'
set #dt='Exceptions'
set #dt1='Tech Issues'
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(convert(nvarchar(20),c._Date, 101))
FROM MIS_BM_Calendar c
where c._Date between #fromdate and #todate and _Day not in
('Sunday')
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query= 'select UserName, '+#cols+' from
(select e.UserName, c._Date , (SUM(DATEDIFF(SS,0,c.TimeTaken))) As TimeTaken
from MIS_BM_Users e
inner join MIS_Opus c
on e.EmpId=c.EmpId
where (e.AccountManagerID='''+#empid+''') and c.Category not in ('''+#dt+''','''+#dt1+''')
group by c._Date, e.UserName
) As SourceTable
Pivot
(
SUM(TimeTaken) for _Date in ('+#cols+')
) As Pvt'
execute(#query)
The amount of seconds in a minute and in an hour is constant. 60 seconds in a minute, 3600 seconds in an hour. Pivot query has no impact on this.
Thus, your question becomes how can I convert seconds into hours, minutes and seconds. Since you're on 2012, you have the handy dandy TimeFromParts function and so you can see you need to feed it hours, minutes and seconds
WITH SRC (TimeTaken) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM sys.all_columns AS AC
)
, Proof AS
(
-- this logic is valid as long as TimeTaken is less than 24 hours
-- or 86400 (24 * 60 * 60) seconds
SELECT
SRC.TimeTaken
-- integer division drops remainder
, SRC.TimeTaken / 3600 AS Hours
-- integer division and modulo operator to keep legit values
, (SRC.TimeTaken / 60) % 60 AS Minutes
-- modulo to have values 0 - 59
, SRC.TimeTaken % 60 AS Seconds
FROM
SRC
)
SELECT
P.TimeTaken
, P.Hours
, P.Minutes
, P.Seconds
, TIMEFROMPARTS(P.Hours, P.Minutes, P.Seconds, 0, 0) AS TimeTakenTimeType
FROM
Proof AS P;
SQLFiddle
I have created a scalar function and converted the seconds to HHMMSS. Then include the function in the main query.
ALTER function [dbo].[ConvertSecondsToHHMMSS]
(
#TotalSeconds int
)
Returns nvarchar(20)
As
Begin
declare #hours int, #minutes int, #seconds int, #result nvarchar(20)
set #hours = #TotalSeconds / 3600
set #minutes = (#TotalSeconds % 3600) / 60
set #seconds = #TotalSeconds % 60
set #result = CONVERT(nvarchar(20),#hours) + ':' + CONVERT(nvarchar(20),#minutes) + ':' +CONVERT(nvarchar(20),#seconds)
return #result
end
Main Query:
set #query= 'select UserName, '+#cols+' from
(
select e.UserName, c._Date, dbo.ConvertSecondsToHHMMSS(SUM(DATEDIFF(SS,0,c.TimeTaken))) As TimeTaken from MIS_BM_Users e
inner join MIS_Opus c on e.EmpId=c.EmpId
where (e.AccountManagerID='''+#AccountManagerId+''')
and c.Category not in ('''+#Condition1+''', '''+#condition2+''')
group by c._Date, e.UserName
) As SourceTable
Pivot
(
MIN(TimeTaken) for _Date in ('+#cols+')
) As Pvt'
execute(#query)
Related
I need to write such query:
SELECT CAST (date_time_column AS DATE) as 'Date',
AVG(CASE WHEN FORMAT(date_time_column, 'HH:mm') = '00:00' then my_values ELSE NULL end) as '00:00',
........
AVG(CASE WHEN FORMAT(date_time_column, 'HH:mm') = '23:59' then my_values ELSE NULL end) as '23:59'
FROM table
where date_time_column > '2021-08-12'
GROUP BY CAST (date_time_column AS DATE)
What is a way to avoid writing 1440 lines in a query?
Try the below method (Change the variables to match your table and field)
/*Generate all minutes in a day in a string variable*/
Declare #timeRange varchar(max)=null
declare #startdate Datetime='2021-08-12 00:00';
; WITH cte AS
(
SELECT 1 i, #startdate AS resultDate
UNION ALL
SELECT i + 1, DATEADD(minute, i, #startdate )
FROM cte
WHERE DATEADD(minute, i, #startdate ) < DateAdd(day,1,#startdate)
)
SELECT #timeRange=Coalesce(#timeRange +',' + '['+Format(resultDate,'HH:mm')+']','['+Format(resultDate,'HH:mm')+']') FROM cte
OPTION (MAXRECURSION 2000);
/* (Change These variables to match your table & fields */
declare #filterQuery varchar(300)=' where {date_time_column}>''2021-01-01''';
declare #dateTimeColumn varchar(30)='{date_time_column}';
declare #valueColumn varchar(30)='{value_column}';
declare #myTable varchar(20)='{my_table}';
/*Generate Pivot Query */
DECLARE #query AS NVARCHAR(MAX);
set #query= '
SELECT *
FROM (SELECT Cast('+ #dateTimeColumn +' as Date) Resultdate,
FORMAT(' + #dateTimeColumn + ', ''HH:mm'') DateMinute,
' + #valueColumn + ' FROM ' + #myTable + ' ' + #filterQuery +'
) FormattedData
PIVOT( AVG('+ #valueColumn + ')
FOR DateMinute IN ('+ #timeRange +')
) AS pivotTable
';
/*Execute Generated Query*/
execute(#query)
What you want to do is to return your data in rows, not columns. That is a row for every minute rather than a column for every minute.
Try it something like this:
WITH
cteNums AS
(
Select TOP (1440) ROW_NUMBER() OVER(order by (Select Null)) - 1 as num
From sys.all_columns
Order By num
)
, cteMinutes AS
(
Select FORMAT(CAST(num/cast(1440.0 as real) as DATETIME), N'HH:mm') as Minutes
From cteNums
)
select CAST(t.date_time_column AS DATE) as 'Date',
m.Minutes,
AVG(CASE WHEN FORMAT(t.date_time_column, 'HH:mm') = m.Minute then t.my_values ELSE NULL end) as AvgValues
FROM cteMinutes m
LEFT JOIN [table] t ON m.Minutes = FORMAT(t.date_time_column, 'HH:mm')
where t.date_time_column > '2021-08-12'
GROUP BY CAST(t.date_time_column AS DATE), m.Minutes
ORDER BY CAST(t.date_time_column AS DATE), m.Minutes
;
I cannot, of course, test this as no test data or table definitions were provided.
I'm going to write a code that round a datetime value but i can't compare which ones is more efficient:
DECLARE #DateValue DATETIME = '2021-01-13 11:59:59'
---- FIRST SOLUSTION:
SELECT CAST(#DateValue AS smalldatetime) AS DateRoundS1
---- SECOND SOLUTION:
SELECT CONVERT(smalldatetime, #DateValue) AS DateRoundS2
---- THIRD SOLUTION:
SELECT DATEADD(HOUR, DATEDIFF(HOUR, 0, #DateValue), 0) AS DateRoundS3
---- FORTH SOLUTION:
DECLARE #DateValue DATETIME = '2021-01-13 11:59:59'
DECLARE #DiffMinsTime INT = DATEPART(MINUTE,#DateValue)
DECLARE #DiffSecsTime INT = DATEPART(SECOND,#DateValue)
DECLARE #DiffMSTime INT = DATEPART(MILLISECOND,#DateValue)
IF( #DiffMinsTime > 0 )
BEGIN
SELECT DATEADD(MINUTE,DATEDIFF(MINUTE,#DiffMinsTime,#DateValue),0)
END
IF(#DiffSecsTime > 0)
BEGIN
SELECT DATEADD(SECOND,DATEDIFF(SECOND,#DiffSecsTime,#DateValue),0)
END
IF(#DiffMSTime > 0)
BEGIN
SELECT DATEADD(MILLISECOND,DATEDIFF(MILLISECOND,#DiffMSTime,#DateValue),0)
END
PS: I know the last one has overflow!!
Is there any more efficient way to do that?!
With conversions and transformations it always depends on lots of factors.
Best way: Build a test rig and set aside a few minutes or better hours.
The test rig creates numbers from 0 to 9 999 999 - about 10 million rows. Kudos to Jeff Moden for the SQL spackle that creates the number list.
DROP TABLE IF EXISTS #numbers
;
DROP TABLE IF EXISTS #dates
;
CREATE TABLE
#numbers
(
number integer NOT NULL
)
;
CREATE TABLE
#dates
(
dated datetime2(7) NOT NULL
)
;
WITH
cteNum
(
smallnum
)
AS
(
SELECT Cast( 1 AS integer )
UNION ALL
SELECT Cast( 2 AS integer )
UNION ALL
SELECT Cast( 3 AS integer )
UNION ALL
SELECT Cast( 4 AS integer )
UNION ALL
SELECT Cast( 5 AS integer )
UNION ALL
SELECT Cast( 6 AS integer )
UNION ALL
SELECT Cast( 7 AS integer )
UNION ALL
SELECT Cast( 8 AS integer )
UNION ALL
SELECT Cast( 9 AS integer )
UNION ALL
SELECT Cast( 0 AS integer )
)
INSERT INTO
#numbers
(
number
)
SELECT
num1.smallnum * 1000000 + num2.smallnum * 100000 + num3.smallnum * 10000 + num4.smallnum * 1000
+ num5.smallnum * 100 + num6.smallnum * 10 + num7.smallnum
FROM
cteNum AS num1
CROSS JOIN cteNum AS num2
CROSS JOIN cteNum AS num3
CROSS JOIN cteNum AS num4
CROSS JOIN cteNum AS num5
CROSS JOIN cteNum AS num6
CROSS JOIN cteNum AS num7
;
INSERT INTO
#dates
(
dated
)
SELECT
Cast( DateAdd( ms, nums.number, dated.basedate) AS datetime2(7) ) AS dated
FROM
#numbers AS nums
CROSS JOIN
(
SELECT Cast(GetDate() AS datetime2(7) ) AS basedate
) dated
;
DROP TABLE IF EXISTS #numbers
;
This will take from a couple of seconds to a couple of minutes to create.
Using a table minimises the amount of effort to table maintenance done by the query analyzer.
The test of conversion times can then be done in a controlled environment. Tip: Using a start and end time capture eliminates the overhead for a SQL trace.
SET NOCOUNT ON
;
DECLARE
#datetimestarted datetime2(7),
#datetimeended datetime2(7),
#result varchar(200),
#crlf char(2) = char(13) + char(10)
;
SET #datetimestarted = Getdate()
;
SELECT
dated,
datedrounded = Cast( dated AS datetime2(0) )
FROM
#dates
;
SET #datetimeended = Getdate()
;
SET #result = 'Processing time = ' + Cast( Datediff( ms, #datetimestarted, #datetimeended ) / 1000 AS varchar(12) ) + ' seconds' + #crlf
+ ' > Start time: ' + Convert( varchar(20), #datetimestarted, 126 ) + #crlf
+ ' > End time: ' + Convert( varchar(20), #datetimeended, 126 ) + #crlf
;
PRINT #result
;
SET #datetimestarted = Getdate()
;
SELECT
dated,
datedrounded = Convert( datetime2(0), dated )
FROM
#dates
;
SET #datetimeended = Getdate()
;
SET #result = 'Processing time = ' + Cast( Datediff( ms, #datetimestarted, #datetimeended ) / 1000 AS varchar(12) ) + ' seconds' + #crlf
+ ' > Start time: ' + Convert( varchar(20), #datetimestarted, 126 ) + #crlf
+ ' > End time: ' + Convert( varchar(20), #datetimeended, 126 ) + #crlf
;
PRINT #result
;
Run time on my VM is about 3 minutes for each option with Cast taking a tad bit longer than Convert. Now this depends on what one wants to achieve so feel free to change the transformations as required.
And finally the clean up.
DROP TABLE IF EXISTS #dates
;
Final tip: date and datetime are kind of obsolete datetime types in SQL Server. Calculation with these is actually easier than the new datetime2 type allowing mathematical calculations.
datetime2 requires the Dateadd function use.
However datetime2 is easier to convert to current database interactions between different types of RDBMS. Also easier to work in standard ISO 8601 formats which makes transferring data between different parts of the world a whole lot easier.
I have an attendance table with the result as shown below
I need to get the result in below format:
Below is sample PIVOT query I used. Please help me with a query to get the desired result.
CREATE TABLE #Temp
(
[empcode] varchar(15),
[empDesc] nvarchar(300),
[hrlog_Date] date,
[hrlog_in] datetime,
[hrlog_Out] datetime,
[hrlog_Latein] datetime,
[hrlog_EarlyOut] datetime,
)
DECLARE #Start datetime,#End datetime,#DList varchar(2000),#Sql varchar(max),#Sql1 varchar(max);
SET #Start=#StartDate;
SET #End=#EndDate;
INSERT INTO #Temp
Select h.emp_code,e.emp_desc as emp_Desc,h.hrlog_Date, CONVERT(datetime,h.hrlog_in, 105) as hrlog_in
,CONVERT(datetime,h.hrlog_out,105) as hrlog_out
,
case when cast(h.hrlog_in as time)> cast('8:45' as time) then
cast(h.hrlog_Date as NVARCHAR(50))+' ' +CAST(DATEDIFF(second, cast('8:45' as time), cast(h.hrlog_in as time) ) / 60 / 60 % 24 AS NVARCHAR(50))+ ':' +
CAST(DATEDIFF(second,cast('8:45' as time), cast(h.hrlog_in as time) ) / 60 % 60 AS NVARCHAR(50))
else cast(h.hrlog_Date as nvarchar(50)) +' 0:00' end
mrngLate
,case when cast(h.hrlog_out as time) < cast('17:15' as time) then
cast(h.hrlog_Date as NVARCHAR(50))+' ' +CAST(DATEDIFF(second, cast(h.hrlog_out as time), cast('17:15' as time) ) / 60 / 60 % 24 AS NVARCHAR(50))+ ':' +
CAST(DATEDIFF(second, cast(h.hrlog_out as time),cast('17:15' as time) ) / 60 % 60 AS NVARCHAR(50))
else cast(h.hrlog_Date as nvarchar(50)) +' 0:00' end EvenEarly
from HRDailyLog h INNER JOIN FTEmployee e ON h.emp_code=e.Emp_Code
LEFT OUTER JOIN sys_CostCenterDep c ON e.sys_ccdepcode = c.sys_ccDepCode
LEFT OUTER JOIN FTJobDesc j ON e.job_code = j.Job_code
LEFT OUTER JOIN sysDep d ON j.sys_depcode = d.Sys_Depcode
LEFT OUTER JOIN HrShift s ON e.SFT_Code = s.SFT_Code
Where (h.hrlog_date between #Start and #End and e.SFT_Code is not null)
and CASE WHEN #sftCode = '-1' then #sftCode else e.SFT_Code end= #sftCode
and CASE WHEN #depCode = '-1' then #depCode else d.Sys_Depcode end= #depCode
and h.sys_bRcode = #brCode
SELECT DATEADD(dd,number,#Start) AS Date INTO #Date
FROM master..spt_values
WHERE type='p'
AND DATEADD(dd,number,#Start)<=#End
SELECT #DList=LEFT(dl.Dates,LEN(dl.Dates)-1)
FROM
(SELECT CAST(Date as varchar(11)) + ','
FROM #Date
FOR XML PATH('')
)dl(Dates)
SET #Sql='SELECT * FROM (
SELECT d.Date as dateIn ,t.empcode as empcode,t.empDesc as empDesc,(CONVERT(nvarchar(5),t.hrlog_in, 108)+'' to ''+CONVERT(nvarchar(5),t.hrlog_Out, 108)) as hrlog_in
FROM #Date d
LEFT JOIN #Temp t
ON CONVERT(nvarchar(10), t.hrlog_in, 111)=d.Date
)tm
PIVOT (MIN(hrlog_in) FOR dateIn IN ([' + REPLACE(#DList,',','],[')+ ']))p
WHERE empcode IS NOT NULL'
EXEC(#Sql)
I have a requirement to equally split the duration for records which has overlapping in the datetime.
Example:
As per the example, if I calculate total runtime of the machine for Order 1, it is 3 hours. But I want it to be 2 hours because in the same machine another order ran between that duration (From 9 AM to 11 AM).
I tried searching the form, and all are pointed to exclude the overlapping duration or doing some other functionality. But I want to split the overlapping duration for all the records.
Sample Table Structure:
declare #st datetime, #et datetime;
DECLARE #table TABLE (Machine varchar(4),OrderId varchar(6),StartTime DateTime2, EndTime DateTime2)
INSERT INTO #table SELECT 'M2','ORD1','2017-11-01 10:30:00.000', '2017-11-01 12:00:00.000'
INSERT INTO #table SELECT 'M2','ORD2','2017-11-01 11:00:00.000', '2017-11-01 12:30:00.000'
INSERT INTO #table SELECT 'M2','ORD3','2017-11-01 11:30:00.000', '2017-11-01 13:00:00.000'
Expected Result:
Expected Result
Based on the above picture,
Duration for ORD1 = 30 MIN + 15 MIN (30 MIN overlap between ORD1 and ORD2) + 10 MIN (30 MIN overlap between ORD1, ORD2 and ORD3)
Duration for ORD2 = 15 MIN + 10 MIN + 15 MIN
Duration for ORD3 = 10 MIN + 15 MIN + 30 MIN
Total Machine Run time will be 55 + 40 + 55 = 150 MIN (2 Hours and 30 MIN)
Thanks,
Aravinth
You should be able to use "window functions" to determine the overall span then divide by number of orders processed, along these lines:
select
machine, order, datetime_start, datetime_end
, min(datetime_start) over(partition by machine, order, date(datetime_start)) min_dt
, max(datetime_start) over(partition by machine, order, date(datetime_start)) max_dt
, count(*) over(partition by machine, order, date(datetime_start)) num
, datediff(ss,min(datetime_start) over(partition by machine, order, date(datetime_start))
,max(datetime_start) over(partition by machine, order, date(datetime_start)))
/ count(*) over(partition by machine, order) as equal_duration
from (
select * from your_query here
)
For more; we would need much more detail from you.
Thanks for all the response. Finally this scenario has been covered with the help of one of our team member. Below is the solution,
DECLARE #table TABLE (OrdId varchar(12),MId varchar(4), ST DateTime, ET DateTime)
INSERT INTO #table SELECT '10001','M1','2017-11-01 10:30:00.000', '2017-11-01 12:00:00.000' INSERT INTO #table SELECT '10002','M1','2017-11-01 11:00:00.000', '2017-11-01 12:30:00.000' INSERT INTO #table SELECT '10003','M1','2017-11-01 11:30:00.000', '2017-11-01 14:00:00.000' INSERT INTO #table SELECT '10004','M2','2017-11-01 14:30:00.000', '2017-11-01 16:00:00.000'
DECLARE #ST datetime, #ET datetime, #NEXT_ST datetime, #RC smallint, #MCHr smallint; set #MCHr = 0; set #ST = (select MIN(ST) AS ST from #table where MId = 'M1' and OrdId = '10001') set #ET = (select MAX(ET) AS ET from #table where MId = 'M1' and OrdId = '10001') WHILE #ST < #ET BEGIN
set #NEXT_ST = (select MIN(ST) AS ST from #table where MId = 'M1' and ST > #ST)
if #NEXT_ST is not null
begin
set #RC = ( SELECT count(*) from #table where MId = 'M1' and (#ST >= ST and #ST < #NEXT_ST))
if #RC > 0
begin
SET #MCHr = #MCHr + (select DATEDIFF(MI,0,#NEXT_ST-#ST) / #RC);
end;
set #ST = #NEXT_ST;
end;
else
begin
set #NEXT_ST = (select MIN(ET) AS ET from #table where MId = 'M1' and (#ST >= ST and #ST < ET))
set #RC = ( SELECT count(*) from #table where MId = 'M1' and (#ST >= ST and #ST < ET))
if #RC > 0
SET #MCHr = #MCHr + (select DATEDIFF(MI,0,#NEXT_ST-#ST) / #RC)
set #ST = #NEXT_ST;
end; END; select #MCHr as MCHr
I have a table with these columns:
Id, Method, DateTime, time taken
Ex
1, Done, 2014-06-22 08:18:00.000, 2000
2, Not Done, 2014-06-23 04:15:00.000, 5000
3, Done, 2014-06-23 14:15:00.000, 6000
I want to have a result set as, "average time taken by DONE methods in each 15 min interval between 8AM to 15PM"
Please guide me on how to proceed on this, I am not sure if cursor fits in this req.
You can use a CTE to generate a list of quarters. Then left join to look up the run times per quarter. A group by will allow you to calculate the average.
In SQL Server 2012, the time type is available, and you can:
; with quarters as
(
select cast('08:00' as time) as time
union all
select dateadd(minute, 15, time)
from quarters
where time <= '14:30'
)
select q.time
, avg(rt.time_taken) as avg_time_taken
from quarters q
left join
RunTime rt
on q.time <= cast(rt.dt as time)
and cast(rt.dt as time) < dateadd(minute, 15, q.time)
and method = 'Done'
group by
q.time
Live example at SQL Fiddle.
For SQL Server 2008R2 and earler, you can use integer math instead:
; with quarters as
(
select 8*60 as min
union all
select min + 15
from quarters
where min < 15*60
)
select q.min / 60 as hour
, q.min % 60 as minute
, avg(rt.time_taken) as avg_time_taken
from quarters q
left join
(
select datepart(minute, dt) +
60 * datepart(hour, dt) as min
, time_taken
from RunTime
where method = 'Done'
) rt
on q.min <= rt.min and rt.min < q.min + 15
group by
q.min;
Live example at SQL Fiddle.
I'm not entirely sure if this is what you want, but here ist the code:
CREATE TABLE #Test(
id int IDENTITY(1,1) PRIMARY KEY,
Method nvarchar(50),
[Datetime] datetime,
timeTaken Bigint
)
CREATE TABLE #Result(
[Between] datetime,
[And] datetime,
[Avg] bigint)
INSERT INTO #Test (Method,Datetime,timeTaken)
VALUES(
'Done', '2014-06-22 08:18:00.000', 2000),
('Not Done', '2014-06-23 04:15:00.000', 5000),
('Done', '2014-06-23 14:15:00.000', 6000)
DECLARE #MaxTime datetime,#StartTime datetime,#Next datetime
SELECT #MaxTime = MAX([datetime]),
#StartTime = MIN([datetime])
FROM #TEST
WHILE #StartTime <= #MaxTime
BEGIN
SET #Next = (SELECT Dateadd(MINUTE,15,#StartTime))
INSERT INTO #Result
SELECT #StartTime AS [Between], #Next AS [And],AVG(timeTaken) AS [AVG]
FROM #Test
WHERE [Datetime] Between #StartTime AND #Next
AND Method = 'Done'
SET #StartTime = #Next
END
SELECT * FROM #Result
DROP TABLE #Test
DROP TABLE #Result
You can now set a where to the Select * from #result in which you can say between 8 AM and 3 PM
Please let me know if this is what you want
Etienne