I am creating a SQL report for someone in which exists a column called Labour Hours of Engineer. In the column, the answers are shown as "3H 30M".
I want to change it to decimal. For example, "3H 30M" becomes 3.5.
Or another example is if an engineer works for 23 minutes, in the column, the answer should be 0.38 (rounded to 2 decimal places). 0.38 is the answer when you divide 23 mins by 60.
My current Formula is:
CONVERT(VARCHAR(40), CAST(Labour AS INT)%(24*60)/60) + '.' + CONVERT(VARCHAR(40), CAST(Labour AS INT)%60)
Any way to achieve this, please share.
Assuming that the 'H' and 'M' are always there, one method would be some use of CHARINDEX to find the 'H' and return the "first" characters and also strip those first characters. For minutes, you can just use simple division to get the decimal value
DECLARE #Time varchar(7) = '3H 30M';
SELECT #Time,
CONVERT(int,LEFT(#Time,CHARINDEX('H',#Time)-1)) + (CONVERT(decimal(2,0),REPLACE(STUFF(#Time,1,CHARINDEX('H',#Time),''),'M',''))/60);
This code will cover Hour minute or only minute
select Convert(decimal(26,2),
IIF(Labour like '%H%',(Convert(int, substring(Labour,0,(CHARINDEX('H',Labour)))) * 60 )+IIF(Labour like '%M%',Convert(int, substring(Labour,CHARINDEX('H',Labour)+1,CHARINDEX('M',Labour)-CHARINDEX('H',Labour)-1)),0)
,IIF(Labour like '%M%',Convert(int, substring(Labour,0,CHARINDEX('M',Labour))),0)) /60.00)
Related
I am working with a table that has a variety of column types in rather specific and strange formats. Specifically I have a column, 'Total_Time' that measures a duration in the format:
days:hours:minutes (d:hh:mm)
e.g 200:10:03 represents 200 days, 10 hours and 3 minutes.
I want to be able to run queries against this duration in order to filter upon time durations such as
SELECT * FROM [TestDB].[dbo].[myData] WHERE Total_Time < 0:1:20
Ideally this would provide me with a list of entries whose total time duration is less than 1 hour and 20 minutes. I'm not aware of how this is possible in an nvarchar format so I would appreciate any advice on how to approach this problem. Thanks in advance...
I would suggest converting that value to minutes, and then passing the parametrised value as minutes as well.
If we can assume that there will always be a days, hours, and minutes section (so N'0:0:10' would be used to represent 10 minutes) you could do something like this:
SELECT *
FROM (VALUES(N'200:10:03'))V(Duration)
CROSS APPLY (VALUES(CHARINDEX(':',V.Duration)))H(CI)
CROSS APPLY (VALUES(CHARINDEX(':',V.Duration,H.CI+1)))M(CI)
CROSS APPLY (VALUES(TRY_CONVERT(int,LEFT(V.Duration,H.CI-1)),TRY_CONVERT(int,SUBSTRING(V.Duration,H.CI+1, M.CI - H.CI-1)),TRY_CONVERT(int, STUFF(V.Duration,1,M.CI,''))))DHM(Days,Hours,Minutes)
CROSS APPLY (VALUES((DHM.Days*60*24) + (DHM.Hours * 60) + DHM.Minutes))D(Minutes)
WHERE D.[Minutes] < 80; --1 hour 20 minutes = 80 minutes
If you can, then ideally you should be fixing your design and just storing the value as a consumable value (like an int representing the number of minutes), or at least adding a computed column (likely PERSISTED and indexed appropriately) so that you can just reference that.
If you're on SQL Server 2022+, you could do something like this, which is less "awful" to look at:
SELECT *
FROM (VALUES(N'200:10:03'))V(Duration)
CROSS APPLY(SELECT SUM(CASE SS.ordinal WHEN 1 THEN TRY_CONVERT(int,SS.[value]) * 60 * 24
WHEN 2 THEN TRY_CONVERT(int,SS.[value]) * 60
WHEN 3 THEN TRY_CONVERT(int,SS.[value])
END) AS Minutes
FROM STRING_SPLIT(V.Duration,':',1) SS)D
WHERE D.[Minutes] < 80; --1 hour 20 minutes = 80 minutes;
One of the columns of my table has values that can typically range from 3500 to 8 million. Is it possible to specify a format that can divide the number when formatting?
For example, I have the following values:
3500
81000
1678500
Ideally I would like a format value (coming from another config table) that would format the numbers in "thousands" with 1 decimal place:
3.5
81.0
1678.5
But this format value could also be different for other cases, so they could be formatted in millions with two decimal places:
0.00
0.08
1.68
Is this possible, or do I need to divide the numbers myself before applying the formatting?
Is this possible, or do I need to divide the numbers myself before applying the formatting?
You need to do the division. You can use CASE WHEN, by the way, if you aren't having numeric values to track what to divide by:
SELECT tablevalue / CASE divby WHEN 'thousand' THEN 1000 WHEN 'million' THEN 1000000 ELSE 1 END
I presume you'll have some column in your "format" table that also specifies what to divide by..
So you don't want to add a column.. you can store the info in the existing column.. you just have to work more to get it out:
SELECT
TO_CHAR(
somenumber / CASE RIGHT(format, 1) WHEN 'k' THEN 1000 WHEN 'M' THEN 1000000 END,
LEFT(format, -1)
)
So now you can make your format like 99D99k and the k will cause a divide by 1000 and the result is formatted to 99.99, so if you have 1234, format 9.99k you'll get '1.23'out of it.
If you want the [k] at the start it's just some jiggling of the LEFT and RIGHT functions..
TO_CHAR(
somenumber / CASE LEFT(format, 3) WHEN '[k]' THEN 1000 WHEN '[M]' THEN 1000000 END,
RIGHT(format, -3)
)
Regarding DateDiff function
select datediff(current_date, '-2018-01-21');
what is - here as I know datediff(enddata,startdate)
if we mention minus for startdate it getting number values 1474138
can please help to understand
Below query confirms that a negative date is similar to a negative integer. If you subtract a negative number to a positive number, it is the same as adding their absolute values (ignoring the signs). For example; 8 - (-4) = 8 + 4
Thus, since the minimum date value for date type is '0000-01-01', we measure the number of days from year -2018 to 0000 and add to the number of days from 0000 to 2018. Then, we get 1474137 ( = 737122 + 737015). Hope this helps. Thanks.
Query:
select datediff('2018-03-02', '0000-01-01'), datediff('0000-01-01', '-2018-03-01'), datediff('2018-03-02', '-2018-03-01')
Result:
737122 737015 1474137
Again, 737122 + 737015 = 1474137. There are 1474137 days since 2018-Mar-01 BC.
I'm trying to get an estimate of how many hours people worked during a set period of time. I want to show this by department and by what area they were working in. Right now I have this:
SELECT M.MemberDepartmentID,T.TaskName,
COUNT(DATEDIFF(HOUR, TT.StartTime, TT.EndTime)) 'Hours',
AVG(DATEDIFF(HOUR, TT.StartTime, TT.EndTime)) Average
FROM Member.TaskTracking TT
LEFT OUTER JOIN Member.Task T
ON TT.TaskID=T.TaskID
JOIN dbo.tblMember M
ON TT.MemberID=M.MemberID
WHERE M.FullTime=1
AND M.EmployeeSalary=1
AND (TT.StartTime >= '2013-10-01'
AND TT.EndTime < '2013-11-01')
GROUP BY M.MemberDepartmentID,T.TaskName
ORDER BY M.MemberDepartmentID,T.TaskName
I don't know how to confirm if it's correct, but some are definitely showing averages of zero even if there were hours worked. And some averages are way higher than the hours worked. For instance, here are some of my results:
MemberDepartmentID TaskName Hours Average
---------------------------------------------------
1 Packing 25 0
1 Picking 6 0
1 PreScanning 38 7
4 Picking 2 104
Suggestions?
First, it is important to note that DATEDIFF(HOUR) returns an integer, and it does not necessarily give a good reflection of how much time has actually passed. For example, these both yield 1:
SELECT DATEDIFF(HOUR, '03:59', '04:01'); -- 2 minutes (0.033333 hours)
SELECT DATEDIFF(HOUR, '03:01', '04:59'); -- 118 minutes (1.966666 hours)
And these both yield 0:
SELECT DATEDIFF(HOUR, '03:01', '03:59'); -- 58 minutes (0.966666 hours)
SELECT DATEDIFF(HOUR, '03:01', '03:02'); -- 1 minute (0.016666 hours)
Next, if you give SQL Server integers to divide, it's going to perform integer math. Meaning it will divide, but it will discard any remainder. This yields 0:
SELECT 3/4;
Even though really it's 0.75, and if it rounded up it should be 1. (Not that either of those results are particularly meaningful). Now, extend that to average.
DECLARE #d1 TABLE(a INT);
INSERT #d1 VALUES(3),(4);
SELECT AVG(a) FROM #d1;
This yields 3, not 3.5, which you would probably expect. For the same reasons as above.
Remembering that some of your tasks may have lasted up to 59 minutes, but would still yield an hour differential of 0, you could have, say, 4 tasks, three that lasted > 1 hour, and one that lasted < 1 hour. So your average calculation would essentially be:
SELECT (1+1+1+0)/4;
Which, as above, still yields 0.
If you want a meaningful average there, you should calculate the time spent more granularly than by hours. For example, you could perform the datediff in minutes:
SELECT DATEDIFF(MINUTE, '03:01', '04:59');
This yields 118. If you want to express that in hours, you could divide by 60.0 (the decimal is important) or multiply by 1.0:
SELECT DATEDIFF(MINUTE, '03:01', '04:59')/60.0;
SELECT 1.0*DATEDIFF(MINUTE, '03:01', '04:59')/60;
These both yield 1.966666. Much more meaningful to average such a result. So perhaps change your expression to:
Average = AVG(1.0*DATEDIFF(MINUTE, TT.StartTime, TT.EndTime)/60)
About the count, not sure what you're attempting to do there, but you may want to make similar adjustments to the calculation and probably consider using SUM. If you show some sample data and the results you expect, we can help more.
Also I recommend not escaping keyword aliases using 'single quotes' - some forms of this syntax are deprecated, and it makes your alias look like a string literal. First, try not to use keywords or otherwise invalid identifiers as aliases; but if you must, escape them with [square brackets].
I am trying to do a query that chart date is the system time the data was charted is more than 8 hrs after the perform date time.
I am using the following query:
select * from pat_results where app_type like 'L' and (chart_dt_utc > perform_dt_utc +8)
The date and time format for both columns are 2012-12-29 11:44:00
Is the +8 correct?
No. In databases that allow you to add a number to a date, the number is measured in days.
The value you want to add is 8/24.0 -- include the decimal place, because some databases calculate 8/24 as integers and give you 0.
No, + 8 adds 8 days. You want:
select * from pat_results where app_type like 'L' and datediff(hour, chart_dt_utc, perform_dt_utc) > 8
Edit: Oh. For some reason I thought you were using SQL server. Well, suffice it to say, use whatever equivalent exists in your RDBMS.
Edit 2: In Oracle you can do this:
select * from pat_results where app_type like 'L'
and (chart_dt_utc > perform_dt_utc + (8 / 24))