Calculate total Working hours from sql column - sql

Expected result is 3201:20. I have done this with split with ":". Please suggested best way to achieve this.
DECLARE #tmpTime TABLE
(
RowId INT IDENTITY(1, 1),
EmployeeId INT,
TotalWorkingTime NVARCHAR(10)
);
INSERT INTO #tmpTime
(
EmployeeId, TotalWorkingTime
)
VALUES
(1,N'1500:30'),
(2,N'1700:50');
SELECT SUM(TotalWorkingTime) FROM #tmpTime

SQL Server doesn't off a time type with more than 24 hours. So, don't think of what you are doing in terms of time. It is just a funky string representation of numbers.
So, you can parse the value into numbers, do the summation, and then reconstruct the value:
select (cast(sum(hh) + sum(mm) / 60 as varchar(255)) + ':' +
right('00' + cast(sum(mm) % 60 as varchar(255)), 2)
) as hhmm
from ( VALUES (1,N'1500:30'), (2,N'1700:50') ) t(EmployeeId, TotalWorkingTime) cross apply
(values (cast(left(TotalWorkingTime, charindex(':', TotalWorkingTime) - 1) as int),
cast(stuff(TotalWorkingTime, 1, charindex(':', TotalWorkingTime), '') as int)
)
) v(hh, mm)

As you can see from #GordonLinoff's answer, your query is quite complex when using a VARCHAR to represent what is really a duration of time. If you represent your data in a more natural way, your query becomes much simpler. For example, if you store your time worked as an integer (total minutes), you can use an intermediate CTE and a couple of CROSS APPLYs to get what you need:
-- note that TotalWorkingTime is now TotalWorkingTimeMinutes
DECLARE #tmpTime TABLE
(
RowID INT IDENTITY(1,1),
EmployeeID INT,
TotalWorkingTimeMinutes INT
);
-- while I'm using a calculation to show
-- how the minutes get added, this would likely
-- be done by the application, before it gets
-- sent to the database.
INSERT INTO #tmpTime
(EmployeeID, TotalWorkingTimeMinutes)
VALUES
(1, (1500 * 60) + 30),
(2, (1700 * 60) + 50);
-- I think this intermediate CTE makes things a bit clearer.
-- but of course, you can inline it as well.
WITH SummedMinutesWorked(SummedMinutes) AS
(
SELECT SUM(TotalWorkingTimeMinutes)
FROM #tmpTime
)
-- you can use the CROSS APPLY to get the hours,
-- then reference those to get the "remainder minutes"
-- the SELECT has to cast your hours and minutes to a VARCHAR
-- for concatenation
SELECT CAST(H AS VARCHAR(255)) + ':' + CAST(M AS VARCHAR(255))
FROM SummedMinutesWorked
CROSS APPLY (SELECT SummedMinutes / 60 AS H) AS HoursWorked
CROSS APPLY (SELECT SummedMinutes - (H * 60) AS M) AS RemainderMinutes

You can try using left() and right() function for finding character before and afer ':'
select
concat
(
sum(cast(left(TotalWorkingTime,CHARINDEX(':',TotalWorkingTime)-1) as int)),
':',
case when sum(cast(right(TotalWorkingTime,CHARINDEX(':',TotalWorkingTime)-3) as int))>60 then sum(cast(right(TotalWorkingTime,CHARINDEX(':',TotalWorkingTime)-3) as int))-60 else sum(cast(right(TotalWorkingTime,CHARINDEX(':',TotalWorkingTime)-3) as int)) end
) FROM #tmpTime

Related

T-SQL / Find all dots in a column

I have query like that :
DECLARE #TABLE TABLE (
ID int primary key identity(1,1),
CODE nvarchar(max)
);
INSERT INTO #TABLE VALUES ('320.01.001'),('320.01.002'),('320.001.002'),('320.01.002.0003.0002')
SELECT * FROM #TABLE
Result:
I want to count of dots in a column .
My excepted result:
A pretty simple method is:
select t.*, s.num_dots
from #table t cross apply
(select count(*) - 1 as num_dots
from string_split(t.code, '.') s
) s;
A more traditional method uses the difference between the lengths of two strings:
select t.*,
len(t.code) - len(replace(t.code, '.', '')) as num_dots
from #table t;
I actually do not have a sense of which of these is faster. If I had to guess, I would guess the second, but if performance is an issue, you should test the two versions.

Querying varchars as decimals in SQL Server

I have a problem in SQL Server, I am trying to query
where x.numbers >= '9'
where x.numbers was stored as varchar and some of the values I have in that field are
8.9
9.3
6.7
>10
8.3
>= 9
If I try isnumeric(x.numbers), then it is excluding those values that start with > or >=. and I tried cast(x.numbers as decimal) but it is not working as well. Please advise
This is my attempt. It's not pretty, but might get you what you're after. It increases/decreases the values of values that have either < or >:
CREATE TABLE #Sample (N varchar(5));
INSERT INTO #Sample
VALUES ('7.9'),('4.5'),('9'),('>10'),('6.7'),('11.7'),('>12'),('<=10'),('<9');
GO
SELECT *
FROM #Sample;
GO
WITH CTE AS (
SELECT *,
CASE WHEN N LIKE '>=%' OR N LIKE '<=%' THEN TRY_CONVERT(decimal(5,1),REPLACE(REPLACE(REPLACE(N,'>',''),'<',''),'=',''))
WHEN N LIKE '>%' THEN TRY_CONVERT(decimal(5,1),REPLACE(REPLACE(REPLACE(N,'>',''),'<',''),'=','')) + 0.1 --Adding .1 as it needs to be more than it's value
WHEN N LIKE '<%' THEN TRY_CONVERT(decimal(5,1),REPLACE(REPLACE(REPLACE(N,'>',''),'<',''),'=','')) - 0.1
ELSE TRY_CONVERT(decimal(5,1),N)
END AS Nr
FROM #Sample S)
SELECT N
FROM CTE
WHERE Nr >= 9;
GO
DROP TABLE #Sample;
--SQL 2008, just CONVERT
WITH CTE AS (
SELECT *,
CASE WHEN N LIKE '>=%' OR N LIKE '<=%' THEN CONVERT(decimal(5,1),REPLACE(REPLACE(REPLACE(N,'>',''),'<',''),'=',''))
WHEN N LIKE '>%' THEN CONVERT(decimal(5,1),REPLACE(REPLACE(REPLACE(N,'>',''),'<',''),'=','')) + 0.1 --Adding .1 as it needs to be more than it's value
WHEN N LIKE '<%' THEN CONVERT(decimal(5,1),REPLACE(REPLACE(REPLACE(N,'>',''),'<',''),'=','')) - 0.1
ELSE CONVERT(decimal(5,1),N)
END AS Nr
FROM #Sample S)
SELECT N
FROM CTE
WHERE Nr >= 9;
I would use try_convert():
where try_convert(decimal(38, 6), field) > 9
Now, this works for many circumstances and assumes that you want to ignore non-numeric values.
You can modify this to get rid of various "other" characters:
where try_convert(decimal(38, 6), replace(replace(replace(replace(field, ' ', ''), '=', ''), '>', ''), '<', '')) > 9
This ignores the "operator" characters.
However, your problem is incompletely specified. If the field were '< 12' or '> 7' what do you want it to return?
try cast(n as float) as shown below:
create table #tmp(numbers varchar(10))
insert into #tmp values('8.9')
insert into #tmp values('9.3')
insert into #tmp values('6.7')
insert into #tmp values('11')
insert into #tmp values('8.3')
insert into #tmp values('9')
insert into #tmp values('10')
Select * from #tmp where cast(numbers as float)> = cast('9' as float)
Drop table #tmp

Convert time interval into decimal hours in SQL Server

I have a time interval in the format '88:52:57'
I need to convert it into a decimal hours in the format 88.88
How can I do this?
I have the data initially loaded as a varchar
You can use left, right and substring to extract the values and then do some calculations.
declare #S varchar(8) = '88:52:57';
select left(#S, 2) + substring(#S, 4, 2)/60.0 + right(#S, 2)/60.0/60.0;
If you not always have two digit values you can use parsename to get the values instead.
declare #S varchar(20) = '088:052:057';
select parsename(S.X, 3) + parsename(S.X, 2)/60.0 + parsename(S.X, 1)/60.0/60.0
from (select replace(#S, ':', '.')) as S(X)
Try it like this (best create an UDF from this):
First I split the Time-Variable on its double dots via XML. The rest is simple calculation...
DECLARE #YourTime VARCHAR(100)='88:52:57';
WITH Splitted AS
(
SELECT CAST('<x>' + REPLACE(#YourTime,':','</x><x>') + '</x>' AS XML) TimeParts
)
,TimeFract AS
(
SELECT TimeParts.value('/x[1]','float') AS HourPart
,CAST(TimeParts.value('/x[2]','int') * 60 + TimeParts.value('/x[3]','int') AS FLOAT) Seconds
FROM Splitted
)
SELECT HourPart + Seconds/3600
FROM TimeFract
The result
88,8825
Try this, solution is based on conversions, making it safe, if the format is always (h)h:mi:ss:
DECLARE #S varchar(8) = '88:52:57';
SELECT
CAST(REPLACE(left(#S, 2), ':', '') as int)+
CAST(CAST(CAST('0:'+RIGHT(#S, 5) as datetime) as decimal(10,10)) * 24 as decimal(2,2))
Result:
88.88

Formatting a number as a monetary value including separators

I need some help with a sql transformation. This part of query that I have been provided with:
'$' + replace(cast((CAST(p.Price1 AS decimal(10,2)) * cast(isnull(p.Multiplier,1) as decimal(10,2))) as varchar), '.0000', '')
Basically, it ends up being a varchar that looks like this: $26980
I need to insert a comma at the thousand and million mark (if applicable). So in this instance, $26,980
What's the easiest way to do that without having to rewrite the whole thing?
Do it on the client side. Having said that, this example should show you the way.
with p(price1, multiplier) as (select 1234.5, 10)
select '$' + replace(cast((CAST(p.Price1 AS decimal(10,2)) * cast(isnull(p.Multiplier,1) as decimal(10,2))) as varchar), '.0000', ''),
'$' + parsename(convert(varchar,cast(p.price1*isnull(p.Multiplier,1) as money),1),2)
from p
The key is in the last expression
'$' + parsename(convert(varchar,cast(p.price1*isnull(p.Multiplier,1) as money),1),2)
Note: if p.price1 is of a higher precision than decimal(10,2), then you may have to cast it in the expression as well to produce a faithful translation since the original CAST(p.Priced1 as decimal(10,2)) will be performing rounding.
If you really must do it in TSQL you can use CONVERT(), but this sort of thing really doesn't belong in the database:
declare #m money = 12345678
-- with decimal places
select '$' + convert(varchar, #m, 1)
-- without decimal places
select '$' + replace(convert(varchar, #m, 1), '.00', '')
You could turn this into a function, it only goes 50 characters back.
DECLARE #input VARCHAR(50)
SELECT #input = '123123123.00'
SELECT #input = CASE WHEN CHARINDEX('.', #input) > offset +1
THEN STUFF(#input, CHARINDEX('.', #input) - offset, 0, ',')
ELSE #input END
FROM (SELECT 3 offset UNION SELECT 7 UNION SELECT 12 UNION SELECT 18 UNION SELECT 25 UNION SELECT 33 UNION SELECT 42) b
PRINT #input
The offset grows by +1 for each position, because it's assuming you've already inserted the commas for the previous positions.

t-sql: return range in which a given number falls

I am trying to figure out a good way to return a string 'name' of a range in which a given number falls. Ranges are spans of 1000, so the first range is '0000-0999', the second is '1000-1999' etc. For example, given the number 1234, I want to return the literal string '1000-1999'.
It seems to me that I could maintain a reference table with these ranges, like this
--create & populate temp table with ranges
create table #ranges (st int,en int)
go
insert into #ranges values(0,999)
insert into #ranges values(1000,1999)
insert into #ranges values(2000,2999)
go
--example query
select replace(str(st,4),' ','0') + '-' + replace(str(en,4),' ','0') as TheStringIWant
from #ranges
where 1234 between st and en
...but it seems to me that the ranges should be able to be determined from the given number itself, and that I shouldn't need the (redundant) reference table (or, for that matter, a function) just for this.
It also seems to me that I should be able to figure this out with just a bit of brain power, except that I've just had 2 beers in quick succession this evening...
Another way;
select case when value / 1000 < 1
then '0000-0999'
else cast(value / 1000 * 1000 as varchar(16)) + '-' + cast(value / 1000 * 1000 + 999 as varchar(16))
end
You can use mathematical functions to avoid using the temp table:
SELECT 1234,
RIGHT('0000' + CAST(FLOOR(1234/1000.0) * 1000 AS VARCHAR(11)),4)
+ '-'
+ RIGHT('0000' + CAST( (FLOOR(1234/1000.0) * 1000) + 999 AS VARCHAR(11)),4)
In the shell, I can use integer-arithmetic to cut off the 234, and calculate the string with a simple formular, however it wouldn't produce 0000-0999 but 0-999 for the first 1000.
v=1234
echo $(((v/1000)*1000))-$(((v/1000)*1000+999))
1000-1999
I don't know how to adapt it to tsql - whether possible at all.
declare #range int;
set #range = 1000;
select replace(str(st,4),' ','0') + '-' +
replace(str(st + #range,4),' ','0') as TheStringIWant
from (
select st = v / #range * #range
from (select v = 1234) s
) s