Dateadd without using function - sql

We keep our dates in the DWH in int format.
I would like to do some simple Dateadd calculations (adding and subtract months from a date) without having to convert to date and convert back to int.
I have been able to reach something closing to working using 2 methods:
1. Using Modulo and Absolute functions:
DECLARE #EffectiveDate INT = 20121003,
#Diff INT = -12
SELECT #EffectiveDate + ( Abs(#Diff) / #diff ) * ( Abs(#Diff) + mnt ) / 12 *
10000 + (
Abs(#Diff) / #diff ) * ( ( Abs(#Diff) + mnt - 1 )%12 - mnt + 1 ) *
100
FROM (SELECT #EffectiveDate / 100%100 Mnt)T
2.By calculating the year in months, adding/subtracting the required months and dividing by 12:
DECLARE #EffectiveDate INT = 20121003,
#Diff INT = -12
SELECT ( yr * 12 + mnt + #Diff ) / 12 * 10000 +
( yr * 12 + mnt + #Diff )%12 * 100 + 1
FROM (SELECT #EffectiveDate / 100%100 Mnt,
#EffectiveDate / 10000 Yr)T
In both cases I come up with the same problem, December is represented incorrectly as 0 and effects the year outcome as well.
Any ideas?

Your logic is wrong:
SELECT ( yr * 12 + mnt + #Diff - 1) / 12 * 10000 +
(( yr * 12 + mnt + #Diff - 1) % 12 + 1) * 100 + 1
FROM (SELECT #EffectiveDate / 100%100 Mnt,
#EffectiveDate / 10000 Yr)T
Of course everything would be much easier if your store dates in DATEs :)

Related

How to calculate the sum of time with SQL SERVER? [duplicate]

I have a column called "WrkHrs" and the data type is time(hh:mm:ss). I want to sum up the working hours for employees. But since it's time data type sql server doesn't let me use like sum(columnname).
How can I sum up the time data type fieled in sql query?
SELECT EmployeeID, minutes_worked = SUM(DATEDIFF(MINUTE, '0:00:00', WrkHrs))
FROM dbo.table
-- WHERE ...
GROUP BY EmployeeID;
You can format it pretty on the front end. Or in T-SQL:
;WITH w(e, mw) AS
(
SELECT EmployeeID, SUM(DATEDIFF(MINUTE, '0:00:00', WrkHrs))
FROM dbo.table
-- WHERE ...
GROUP BY EmployeeID
)
SELECT EmployeeID = e,
WrkHrs = RTRIM(mw/60) + ':' + RIGHT('0' + RTRIM(mw%60),2)
FROM w;
However, you're using the wrong data type. TIME is used to indicate a point in time, not an interval or duration. Wouldn't it make sense to store their work hours in two distinct columns, StartTime and EndTime?
In order to sum up the working hours for an employee you can calculate the difference between the shift start time and end time in minutes and convert it to readable format as following:
DECLARE #StartTime datetime = '08:00'
DECLARE #EndTime datetime = '10:47'
DECLARE #durMinutes int
DECLARE #duration nvarchar(5)
SET #durMinutes = DATEDIFF(MINUTE, #StartTime, #EndTime)
SET #duration =
(SELECT RIGHT('00' + CAST((#durMinutes / 60) AS VARCHAR(2)),2) + ':' +
RIGHT('00' + CAST((#durMinutes % 60) AS VARCHAR(2)), 2))
SELECT #duration
The result : 02:47
two hours and 47 minutes
select DATEDIFF(MINUTE, '0:00:00', '00:02:08')
results in :- 2
select DATEDIFF(SECOND, '0:00:00', '00:02:08')
results in :- 128
Using seconds gives a better answer.
So I think the answer can be
SELECT
EmployeeId
, seconds_worked = SUM (DATEDIFF (SECOND, '0:00:00', WrkHrs))
FROM
tbl_employee
GROUP BY
EmployeeId;
DECLARE #Tab TABLE
(
data CHAR(5)
)
INSERT #Tab
SELECT '25:30' UNION ALL
SELECT '31:45' UNION ALL
SELECT '16:00'
SELECT STUFF(CONVERT(CHAR(8), DATEADD(SECOND, theHours + theMinutes,
'19000101'), 8), 1, 2, CAST((theHours + theMinutes) / 3600 AS VARCHAR(12)))
FROM (
SELECT ABS(SUM(CASE CHARINDEX(':', data) WHEN 0 THEN 0 ELSE 3600 *
LEFT(data, CHARINDEX(':', data) - 1) END)) AS theHours,
ABS(SUM(CASE CHARINDEX(':', data) WHEN 0 THEN 0 ELSE 60 *
SUBSTRING(data, CHARINDEX(':', data) + 1, 2) END)) AS theMinutes
FROM #Tab
) AS d
For MS SQL Server, when your WorkingTime is stored as a time, or a varchar in order to sum it up you should consider that:
1) Time format is not supporting sum, so you need to parse it
2) 23:59:59.9999999 is the maximum value for the time.
So, the code that will work to get you the total number of WorkingHours:WorkingMinutes:WorkingSeconds would be the following:
SELECT
CAST(FORMAT((SUM((DATEPART("ss",WorkingTime) + DATEPART("mi",WorkingTime) * 60 + DATEPART("hh",WorkingTime) * 3600)) / 3600),'00') as varchar(max)) + ':' +
CAST(FORMAT((SUM((DATEPART("ss",WorkingTime) + DATEPART("mi",WorkingTime) * 60 + DATEPART("hh",WorkingTime) * 3600)) % 3600 / 60),'00') as varchar(max)) + ':' +
CAST(FORMAT((SUM((DATEPART("ss",WorkingTime) + DATEPART("mi",WorkingTime) * 60 + DATEPART("hh",WorkingTime) * 3600)) % 3600 % 60),'00') as varchar(max)) as WorkingTimeSum
FROM TableName
It must be as simple as that.
Steps
convert time to seconds
sum the RESULT
convert the sum to time
Eg:
take a case you might want to sum the following time:
| present_hours |
|-----------------|
| 00:01:20.000000 |
|-----------------|
| 00:01:13.000000 |
|-----------------|
| 00:01:45.000000 |
|-----------------|
| 00:01:03.000000 |
|-----------------|
| 00:01:10.000000 |
|-----------------|
| 00:00:56.000000 |
SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(present_hours))) as total_present_hours FROM time_booking;

Find records between two Quarters in SQL Server?

I want to find records between two-quarters of different years in SQL Server.
SELECT ('Q'+cast(DATEPART(QUARTER,calldate) as varchar(3))+'-'+cast(YEAR(calldate) as varchar(4)))
period,providerid,volume as volume,type
FROM table V
where DATEPART(QUARTER,calldate) between #Q1 and #Q2 and
datepart(year,calldate) in (#Y1,#Y2) and providerid=#carrierID
Here Q1=4 and Q2=3 and #Y1=2014,#Y2=2016
Now,I have only records from Q2-2016, So it should return available records
but I am getting blank rows.
if I change the parameter like this
Here Q1=3 and Q2=4 and #Y1=2014,#Y2=2016 then i am getting records.
I want all records between these two-quarters like (Q3-2014 and Q2-2016).?
Here is one method:
where datename(year, calldate) + datename(quarter, calldate)
between #Y1 + #Q1 and #Y2 + #Q2;
This assumes that the variables are actually strings. You could also do this using numbers:
where datepart(year, calldate) * 10 + datename(quarter, calldate)
between #Y1 * 10 + #Q1 and #Y2 * 10 + #Q2;
And, here is a way that would use indexes:
where calldate >= datefromparts(#Y1, #Q1*3 - 2, 1) and
calldate < (case when #Q2 = 4
then datefromparts(#Y2 + 1, 1, 1)
else datefromparts(#Y2, #Q2*3 - 2 + 3, 1)
end)
could try something like this
SELECT
('Q'+cast(DATEPART(QUARTER,calldate) as varchar(3))+'-'+cast(YEAR(calldate) as varchar(4)))
period,providerid,volume as volume,type
FROM table V
where DATEPART(QUARTER,calldate) + datepart(year,calldate) *4 between #Q1 + #Y1 * 4 and #Q2 + #Y2 * 4

Calculated Date filed from another Date field that is Randomised

I have to create some random data and so far with the help of this forum I have done 80% of it but I am now stuck (again)
What I need is this I have a column called Requested Date, this date should randomised but be between 1 and 10 days later than the Order Date. Can this be done?
DECLARE #OrderNumber varchar (30)
DECLARE #OrderDate int
DECLARE #OrderLineNumber varchar(50)
DECLARE #CustomerSkey int
DECLARE #ProductSkey int
DECLARE #OrderMethodSkey int
DECLARE #Quantity int
DECLARE #Cost Decimal(18,3)
DECLARE #RequestedDate int
SET #OrderNumber = 1
SET #OrderDate = 0
SET #OrderLineNumber = 1
SET #CustomerSkey = 1
SET #ProductSkey = 1
SET #OrderMethodSkey = 1
SET #Quantity = 1
SET #Cost = 1
SET #RequestedDate = 0
WHILE #OrderNumber <= 100
WHILE #OrderDate <= 100
WHILE #OrderLineNumber <= 100
WHILE #CustomerSkey <= 100
WHILE #ProductSkey <= 100
WHILE #OrderMethodSkey <= 100
WHILE #Quantity <= 100
WHILE #Cost <= 100
WHILE #RequestedDate <= 100
BEGIN
INSERT INTO Orders
(OrderNumber
, OrderDate
, OrderLineNumber
, CustomerSkey
, ProductSkey
, OrderMethodSkey
, OrderTime
, Quantity
, Cost
, RequestedDate)
SELECT
'ORD' + Right ('000000' + CAST (#OrderNumber AS varchar (30)), 6)
,DATEADD (day, CAST (RAND () * 1500 as int), '2008-1-1')
,(Right ('0' + CAST (#OrderLineNumber AS varchar (30)), 6))
,(99 * RAND()) + 1
,(99 * RAND()) + 1
,(2 * RAND()) + 1
,DATEADD(ms, cast(86400000 * RAND() as int), convert(time, '00:00'))
,(190 * RAND()) + 10
,(40 * RAND()) + 10
,DATEADD (day, CAST (RAND () * 10 as int), #RequestedDate)
SET #OrderNumber = #OrderNumber + 1
SET #OrderDate = #OrderDate + 1
SET #OrderLineNumber = #OrderLineNumber + 1
SET #CustomerSkey = #CustomerSkey + 1
SET #ProductSkey = #ProductSkey + 1
SET #OrderMethodSkey = #OrderMethodSkey + 1
SET #Quantity = #Quantity + 1
SET #Cost = #Cost + 1
SET #RequestedDate = #RequestedDate + 1
END
In PostgreSQL you can do:
with t as (select '2013-01-01'::date as order_date)
select
order_date,
(order_date + round(.5 + 10*random()) * interval '1 day')::date as requested_date,
(order_date + round(.5 + 10*random()) * interval '1 day')::date - order_date as difference from t
0) random() function returns random number between 0 and 1
1) round(.5 + 10*random() will give you number between 1 and 10
2) random number is added to date as an interval in days
3) result is casted to date (because step 2 will return timestamp)
To prove that round(.5 + 10*random() gets random number between 1 and 10:
with t as (select round(.5 + 10*random()) rnd from generate_series(1,1000000,1))
select rnd, count(*) cnt from t group by rnd order by rnd
rnd cnt
1 100249
2 100817
3 99550
4 99813
5 100065
6 99468
7 100089
8 99652
9 99889
10 100408
you can calculate the value of order_date before the insert statement
add a new variable with the other declares
declare #od datetime
inside the while loops but before the insert statement set up the order date
select #od = DATEADD (day, CAST (RAND () * 1500 as int), '2008-1-1')
use this variable in the insert statement so
,DATEADD (day, CAST (RAND () * 1500 as int), '2008-1-1')
becomes
,#od
and use this value to calculate the request date, so
becomes
,DATEADD (day, CAST (RAND () * 10 as int), #RequestedDate)
and
,DATEADD (day, CAST (RAND () * 10 as int), #od)
sql fiddle

How to sum up time field in SQL Server

I have a column called "WrkHrs" and the data type is time(hh:mm:ss). I want to sum up the working hours for employees. But since it's time data type sql server doesn't let me use like sum(columnname).
How can I sum up the time data type fieled in sql query?
SELECT EmployeeID, minutes_worked = SUM(DATEDIFF(MINUTE, '0:00:00', WrkHrs))
FROM dbo.table
-- WHERE ...
GROUP BY EmployeeID;
You can format it pretty on the front end. Or in T-SQL:
;WITH w(e, mw) AS
(
SELECT EmployeeID, SUM(DATEDIFF(MINUTE, '0:00:00', WrkHrs))
FROM dbo.table
-- WHERE ...
GROUP BY EmployeeID
)
SELECT EmployeeID = e,
WrkHrs = RTRIM(mw/60) + ':' + RIGHT('0' + RTRIM(mw%60),2)
FROM w;
However, you're using the wrong data type. TIME is used to indicate a point in time, not an interval or duration. Wouldn't it make sense to store their work hours in two distinct columns, StartTime and EndTime?
In order to sum up the working hours for an employee you can calculate the difference between the shift start time and end time in minutes and convert it to readable format as following:
DECLARE #StartTime datetime = '08:00'
DECLARE #EndTime datetime = '10:47'
DECLARE #durMinutes int
DECLARE #duration nvarchar(5)
SET #durMinutes = DATEDIFF(MINUTE, #StartTime, #EndTime)
SET #duration =
(SELECT RIGHT('00' + CAST((#durMinutes / 60) AS VARCHAR(2)),2) + ':' +
RIGHT('00' + CAST((#durMinutes % 60) AS VARCHAR(2)), 2))
SELECT #duration
The result : 02:47
two hours and 47 minutes
select DATEDIFF(MINUTE, '0:00:00', '00:02:08')
results in :- 2
select DATEDIFF(SECOND, '0:00:00', '00:02:08')
results in :- 128
Using seconds gives a better answer.
So I think the answer can be
SELECT
EmployeeId
, seconds_worked = SUM (DATEDIFF (SECOND, '0:00:00', WrkHrs))
FROM
tbl_employee
GROUP BY
EmployeeId;
DECLARE #Tab TABLE
(
data CHAR(5)
)
INSERT #Tab
SELECT '25:30' UNION ALL
SELECT '31:45' UNION ALL
SELECT '16:00'
SELECT STUFF(CONVERT(CHAR(8), DATEADD(SECOND, theHours + theMinutes,
'19000101'), 8), 1, 2, CAST((theHours + theMinutes) / 3600 AS VARCHAR(12)))
FROM (
SELECT ABS(SUM(CASE CHARINDEX(':', data) WHEN 0 THEN 0 ELSE 3600 *
LEFT(data, CHARINDEX(':', data) - 1) END)) AS theHours,
ABS(SUM(CASE CHARINDEX(':', data) WHEN 0 THEN 0 ELSE 60 *
SUBSTRING(data, CHARINDEX(':', data) + 1, 2) END)) AS theMinutes
FROM #Tab
) AS d
For MS SQL Server, when your WorkingTime is stored as a time, or a varchar in order to sum it up you should consider that:
1) Time format is not supporting sum, so you need to parse it
2) 23:59:59.9999999 is the maximum value for the time.
So, the code that will work to get you the total number of WorkingHours:WorkingMinutes:WorkingSeconds would be the following:
SELECT
CAST(FORMAT((SUM((DATEPART("ss",WorkingTime) + DATEPART("mi",WorkingTime) * 60 + DATEPART("hh",WorkingTime) * 3600)) / 3600),'00') as varchar(max)) + ':' +
CAST(FORMAT((SUM((DATEPART("ss",WorkingTime) + DATEPART("mi",WorkingTime) * 60 + DATEPART("hh",WorkingTime) * 3600)) % 3600 / 60),'00') as varchar(max)) + ':' +
CAST(FORMAT((SUM((DATEPART("ss",WorkingTime) + DATEPART("mi",WorkingTime) * 60 + DATEPART("hh",WorkingTime) * 3600)) % 3600 % 60),'00') as varchar(max)) as WorkingTimeSum
FROM TableName
It must be as simple as that.
Steps
convert time to seconds
sum the RESULT
convert the sum to time
Eg:
take a case you might want to sum the following time:
| present_hours |
|-----------------|
| 00:01:20.000000 |
|-----------------|
| 00:01:13.000000 |
|-----------------|
| 00:01:45.000000 |
|-----------------|
| 00:01:03.000000 |
|-----------------|
| 00:01:10.000000 |
|-----------------|
| 00:00:56.000000 |
SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(present_hours))) as total_present_hours FROM time_booking;

SQL: float number to hours format

Is there a easy way to format a float number in hours in Ms SQL server 2008?
Examples:
1.5 -> 01:30
9.8 -> 09:48
35.25 -> 35:15
Thanks a lot.
I like this question!
DECLARE #input float = 1.5;
DECLARE #hour int = FLOOR(#input);
DECLARE #minutes int = (SELECT (#input - FLOOR(#input)) * 60);
SELECT RIGHT('00' + CONVERT(varchar(2), #hour), 2) + ':' + RIGHT('00' + CONVERT(varchar(2), #minutes), 2);
SELECT SUBSTRING(CONVERT(NVARCHAR, DATEADD(MINUTE, 1.5*60, ''), 108), 1, 5)
This works by:
starting from the "zero" date
adding 1.5 x 60 minutes (i.e. 1.5 hours)
formatting the result as a time, hh:mm:ss (i.e. format "108")
trimming off the seconds part
It is necessary to use 1.5 x 60 minutes instead of 1.5 hours as the DATEADD function truncates the offset to the nearest integer. If you want high-resolution offsets, you can use SECOND instead, suitable scaled (e.g. hours * 60 * 60).
Sure. Easy, but not exactly...straightforward:
declare #hours float
set #hours = -9.8
select substring('- ',2+convert(int,sign(#hours)),1) -- sign
+ right('00' + convert(varchar, floor(abs(#hours))) , 2 ) -- hours component
+ ':' -- delimiter
+ right('00' + convert(varchar,round( 60*(abs(#hours)-floor(abs(#hours))) , 0 ) ) , 2 ) -- minutes
Another option that will give the correct result. You might need to tweak it to round minutes and to ensure that both fields are 2 digits wide.
declare #hours float
set #hours = -9.8
select convert(varchar, datediff(minute,dateadd(minute,#hours*60,convert(datetime,'')),'') / 60 )
+ ':' + convert(varchar, datediff(minute,dateadd(minute,#hours*60,convert(datetime,'')),'') % 60 )
WITH m AS
SELECT Minutes = CAST(#hours * 60 AS int)
)
SELECT CAST(Minutes / 60 AS varchar) + ':' + RIGHT(100 + Minutes % 60, 2)
FROM m
select dateadd(MINUTE, cast((8.18 % 1) * 60 as int), dateadd(hour, cast(8.18 as int), convert(varchar(10), getdate(), 10)))