I have tried three ways, all work but get different results:
SELECT #age = DATEDIFF(YY, x.BirthDate, x.LastVisitDate)
SELECT #age = (CONVERT(int,CONVERT(char(8),x.LastVisitDate,112)) - CONVERT(char(8),x.BirthDate,112)) / 10000
SELECT #age = FLOOR(DATEDIFF(DAY, x.BirthDate , x.LastVisitDate) / 365.25)
The first I get 63, the second and third I get 62, which one is accurate?
Just use the ancient algorithm from the mainframe era:
SELECT #age = (
(YEAR(x.LastVisitDate) * 10000 + MONTH(x.LastVisitDate) * 100 + DAY(x.LastVisitDate))
-
(YEAR(x.BirthDate)* 10000 + MONTH(x.BirthDate) * 100 + DAY(x.BirthDate))
) / 10000
A person's age at their birthday in a year is (year - BirthYear)
This is what datediff with the yy parameter works out. If they have not had their birthday, you have to subtract one year, that's what my IIF does.
As you've found, other methods are flawed. For example I experienced that the days/365.25 will sometimes go wrong around a person's birthday, and is extra trick if they were born on feb 29th
SELECT #age = DATEDIFF(YY, x.BirthDate, x.LastVisitDate) -
IIF(MONTH(x.LastVisitDate) < MONTH(x.BirthDate)
OR MONTH(x.LastVisitDate) = MONTH(x.BirthDate) AND DAY(x.LastVisitDate) < DAY(x.BirthDate)
, 1
, 0
)
Try using this scalar function.
It takes three parameters.
The start and the end date and an extra option to add one day.
The function returns the result in the form "YY MM DD". There are a few examples
SELECT dbo.CalcDate(NULL, GETDATE(), 0); --> NULL
SELECT dbo.CalcDate(GETDATE(), GETDATE(), 0); --> 0 0 0
SELECT dbo.CalcDate(GETDATE(), GETDATE(), 1); ---> 0 0 1
SELECT dbo.CalcDate('20150101', '20161003', 0); ---> 1 9 2
SELECT dbo.CalcDate('20031101', '20161003', 0); --->12 11 2
SELECT dbo.CalcDate('20040731', '20040601', 0); ---> 0 1 30
SELECT dbo.CalcDate('20040731', '20040601', 1); ---> 0 2 0
And the source code is listed in the snippet below.
CREATE FUNCTION [dbo].[CalcDate]
(
#dwstart datetime, #dwend datetime,#extraDay bit
)
RETURNS nvarchar(20)
BEGIN
DECLARE #yy int;
DECLARE #mm int;
DECLARE #dd int;
DECLARE #increment int;
SET #increment = 0;
DECLARE #monthDay TABLE
(
monthno int, monthdayno int
);
DECLARE #dStart AS datetime;
DECLARE #dEnd AS datetime;
INSERT INTO #monthDay
VALUES (1, 31);
INSERT INTO #monthDay
VALUES (2, -1);
INSERT INTO #monthDay
VALUES (3, 31);
INSERT INTO #monthDay
VALUES (4, 30);
INSERT INTO #monthDay
VALUES (5, 31);
INSERT INTO #monthDay
VALUES (6, 30);
INSERT INTO #monthDay
VALUES (7, 31);
INSERT INTO #monthDay
VALUES (8, 31);
INSERT INTO #monthDay
VALUES (9, 30);
INSERT INTO #monthDay
VALUES (10, 31);
INSERT INTO #monthDay
VALUES (11, 30);
INSERT INTO #monthDay
VALUES (12, 31);
--The order of the arguments is not important
IF #dwStart > #dWEnd
BEGIN
SET #dStart = #dWEnd;
SET #dEnd = #dWStart;
END;
ELSE
BEGIN
SET #dStart = #dWStart;
SET #dEnd = #dWEnd;
END;
--
DECLARE #d1 AS INT;
SET #d1 = DAY(#dStart);
DECLARE #d2 AS int;
SET #d2 = DAY(#dEnd);
IF #d1 > #d2
BEGIN
SET #increment = (SELECT
monthdayno
FROM #monthDay
WHERE monthno = MONTH(#dStart));
END;
IF #increment = -1
BEGIN
--Is it a leap year
SET #increment = (SELECT
CASE
WHEN ISDATE(CAST(YEAR(#dStart) AS CHAR(4)) + '0229') = 1 THEN 29
ELSE 28
END);
END;
IF #increment != 0
BEGIN
SET #DD = DAY(#dEnd) + #increment - DAY(#dStart) + (CASE
WHEN #extraDay = 1 THEN 1
ELSE 0
END);
SET #increment = 1;
END;
ELSE
BEGIN
SET #dd = DAY(#dEnd) - DAY(#dStart) + (CASE
WHEN #extraDay = 1 THEN 1
ELSE 0
END);
END;
IF (MONTH(#dStart) + #increment) > MONTH(#dEnd)
BEGIN
SET #mm = MONTH(#dEnd) + 12 - (MONTH(#dStart) + #increment);
SET #increment = 1;
END;
ELSE
BEGIN
SET #mm = MONTH(#dEnd) - (MONTH(#dStart) + #increment);
SET #increment = 0;
END;
SET #yy = YEAR(#dEnd) - (YEAR(#dStart) + #increment);
IF #dd >= 31
BEGIN
SET #mm = #mm + 1;
SET #dd = #dd - 31;
END;
IF #mm >= 12
BEGIN
SET #yy = #yy + 1;
SET #mm = #mm - 12;
END;
RETURN (CONVERT(NVARCHAR(2), #yy) + ' ' + CONVERT(NVARCHAR(2), #mm) + ' ' + CONVERT(NVARCHAR(2), #dd));
END;
If you want someone's age, take the difference of "year" and then subtract one based on the ordering of MMDD. So:
select (year(x.LastVisitDate) - year(x.BirthDate) -
(case when month(x.LastVisitDate) < month(x.BirthDate)
then 1
when month(x.LastVisitDate) = month(x.BirthDate) and
day(x.LastVisitDate) < day(x.BirthDate)
then 1
else 0
end)
) as age
This should be accurate for leap years and leap days and only increment the age on someone's birthday (or if the birthday is Feb 29th, then on Mar 1st).
You can also phrase the case expression using MMDD representation and doing:
(case when ( month(x.LastVisitDate) * 100 + day(x.LastVisitDate) <
month(x.BirthDate) * 100 + day(x.BirthDate
)
then 1
then 1
else 0
end)
Methods using DATEDIFF() simply do not work (easily) because DATEDIFF() is not counting the difference between two periods, but rather the number of time boundaries between them.
Using the difference in days and dividing by 365.25 is an approximation and is going to be off right around the birthday.
Using calendar rules (such as above) should produce the correct results.
Related
I have a Price list table in SQL Server 2008R2 and want to calculate the Price for a Service according to the time where the Service has been made.
timefrom |timeto |Price
-------- |------ |-----
1900-01-01 00:00:00|1900-01-01 07:00:00|20.00
1900-01-01 07:00:00|1900-01-01 19:00:00|15.00
1900-01-01 19:00:00|1900-01-02 00:00:00|20.00
This pricelist Shows different Price during night time starting at 19:00 and Lasting until 07.00 AM and daytime from 07:00 to 19:00.
Minutes have to be rounded up and down to quarter hours.
There is some more to take care of, such as Minimum notice Periode (#Vorlaufzeit) and if weekday of Weekend. Both conditions are met and not the Problem. My Problem is the first and the last record, where I have to round up and/or round down, which are incorrect. Both lines have 1 in the loop and should be corrected correctly in the both updates, but does not.
So, for example a Service from 2016-11-04 10:50 (rounding down to 10:45 which is 0.25 hours) to 2016-11-04 19:25 (rounded up to 19:30 which is 0.5 hours) is 0.25+8+0.5= 8.75 hours and costs 8.25*15 + 0.5*20 = 133.75.
I tried with this code, but it does not bring me the correct result. It is only the first and the last record where I have to round up or down. It is only correct, when there are full hours.
DECLARE #Dauer int
DECLARE #X int --Loopcounter für Stunden
declare #Y int --Loopcounter für Tageszahler
declare #Anfangszeit datetime
declare #Anfangsstunde datetime
declare #Endzeit datetime
declare #Vorlaufzeit int --in Minuten
declare #ErsteZeitvon datetime
declare #SummeAnzStunden decimal(8,2)
declare #MinimumZeit int
declare #ZeitvonVolleStunde int -- aus 07:25 mach 7 Uhr
declare #ZeitbisVolleStunde int
declare #AnfangsDatumZeit as datetime
declare #EndDatumZeit as datetime
declare #AnfangsDatumZeitLoop as datetime
declare #AnfangsZeitLoop as datetime
declare #TagesZaehler int
set #AnfangsDatumZeit = #Datumvon+#Zeitvon
set #EndDatumZeit = #Datumbis+#Zeitbis
set #Tageszaehler=datediff(day,#AnfangsDatumZeit, #EndDatumZeit)
declare #t1 table ( PreisID int, AnzStunden decimal(5,2), Preis decimal(8,2), Anfangszeit datetime, Prüfzeit datetime, startzeit datetime, endezeit datetime, Vorlaufzeit int, Dauer int, PreisFT decimal(8,2), DatZeitvon datetime, DatZeitbis datetime, Tageszaehler int )
-- Insert statements for procedure here
set #ZeitvonVolleStunde=Datediff(hour, '00:00:00', #Zeitvon)
set #ZeitbisVolleStunde=Datediff(minute, '00:00:00', #Zeitbis)
set #Dauer=ceiling(Datediff(minute, #AnfangsDatumZeit, #EndDatumZeit)/60.00)
set #Vorlaufzeit=datediff(minute,#Bestelldatum, #AnfangsDatumZeit)
SET #X = 0
if Datediff(minute, #AnfangsDatumZeit, #EndDatumZeit) > 360
begin
WHILE (#X <=#Dauer) --z.b. 13
begin
--set #Y = datediff(day,#AnfangsDatumZeit,#AnfangsDatumZeitLoop)
set #Y = datediff(day,#AnfangsDatumZeit,dateadd(hour,#X, #AnfangsDatumZeit))
set #AnfangsDatumZeitLoop=dateadd(hour,#X, #AnfangsDatumZeit)
set #AnfangsZeitLoop=dateadd(hour,#X, #Zeitvon)
insert into #t1 ( PreisID, AnzStunden, Preis , Anfangszeit, Prüfzeit, DatZeitvon , DatZeitbis )
SELECT top 1 preisID, 1, Preis, #AnfangsZeitLoop, #AnfangsDatumZeitLoop, Zeitvon, Zeitbis
FROM dbo.Mypricetable
where SdlID=#Leistungsart --SdlID
and Wochentag=case when DATEPART(dw,#AnfangsDatumZeitLoop) < 6 then 'W' else 'S' end --Wochentag
and #Vorlaufzeit BETWEEN Vorlaufzeitvon and Vorlaufzeitbis --Vorlaufzeit in Minuten
AND #Dauer*60 BETWEEN Dauervon AND Dauerbis --DauerInMinuten
and #AnfangsZeitLoop between Zeitvon and Zeitbis --sucht die von/bis Zeitgruppe
order by zeitvon
SET #X = #X + 1
end
--check and udate of the first record in #t1 rounding down to 15 minutes
update #t1 set Anzstunden= Anzstunden + CONVERT(DECIMAL(6, 2), ROUND(((datediff(minute,[dbo].[sfRoundToHourParts](#AnfangsDatumZeit,1), [dbo].[sfRoundToHourParts](#AnfangsDatumZeit,4)) + 7) / 60.00) / 25, 2) * 25)
from #t1 c where c.Prüfzeit=(SELECT Top 1 Prüfzeit from #t1 order by Prüfzeit)
--check and udate of the last record in #t1 rounding up to 15 minutes
update #t1 set Anzstunden= round(convert(decimal(5,2),datepart(minute,#EndDatumZeit)+7)/60/25,2)*25
from #t1 c where c.Prüfzeit=(SELECT Top 1 Prüfzeit from #t1 order by Prüfzeit DESC)
end
select * from #t1 order by Prüfzeit
Thanks your help!
Michael
This code is a bit verbose, but wanted to share it with you as it produces the desired result of 133.75
DECLARE #x table (
timefrom datetime
, timeto datetime
, price decimal(14,4)
);
INSERT INTO #x (timefrom, timeto, price)
VALUES ('1900-01-01T00:00:00', '1900-01-01T07:00:00', 20.00)
, ('1900-01-01T07:00:00', '1900-01-01T19:00:00', 15.00)
, ('1900-01-01T19:00:00', '1900-01-02T00:00:00', 20.00)
;
-- You should have your own, physical tally table!
DECLARE #numbers table (
number tinyint
);
INSERT INTO #numbers (number)
SELECT DISTINCT
number
FROM master.dbo.spt_values
WHERE number BETWEEN 0 AND 255
;
DECLARE #start datetime = '2016-11-04T10:50:00'
, #end datetime = '2016-11-04T19:25:00'
;
-- first, let's do some rounding of our inputs
DECLARE #rounded_start_time time
, #rounded_end_time time
;
-- Illustrate the steps to round the time to quarters... this might not be the simplest method; but it works!
/*
SELECT #start AS start
, DateAdd(hh, DateDiff(hh, 0, #start), 0) AS truncate_hour
, Round(DatePart(mi, #start) / 15.0, 0) * 15 AS rounded_mins
, DateAdd(mi, Round(DatePart(mi, #start) / 15.0, 0) * 15, DateAdd(hh, DateDiff(hh, 0, #start), 0)) AS truncate_hour_then_add_mins
;
*/
SET #rounded_start_time = DateAdd(mi, Round(DatePart(mi, #start) / 15.0, 0) * 15, DateAdd(hh, DateDiff(hh, 0, #start), 0));
SET #rounded_end_time = DateAdd(mi, Round(DatePart(mi, #end ) / 15.0, 0) * 15, DateAdd(hh, DateDiff(hh, 0, #end ), 0));
PRINT 'Start: ' + Format(#rounded_start_time, 'HH:mm');
PRINT 'End: ' + Format(#rounded_end_time , 'HH:mm');
--SELECT *
--FROM #x
--;
; WITH intervals AS (
SELECT number * 15 AS minute_increments
, DateAdd(mi, number * 15, 0) AS interval_start
, DateAdd(mi, (number + 1) * 15, 0) AS interval_end
FROM #numbers
WHERE number >= 0
AND number < 24 * 4 --number of 15 minute increments in a day
)
, costed_intervals AS (
SELECT intervals.interval_start
, intervals.interval_end
, Cast(intervals.interval_start AS time) As interval_start_time
, Cast(intervals.interval_end AS time) As interval_end_time
, x.price / 4.0 AS interval_price
FROM #x AS x
INNER
JOIN intervals
ON intervals.interval_end <= x.timeto
AND intervals.interval_start >= x.timefrom
)
, applicable_intervals AS (
SELECT interval_start
, interval_end
, interval_start_time
, interval_end_time
, interval_price
FROM costed_intervals
WHERE interval_start_time < #rounded_end_time
AND interval_end_time > #rounded_start_time
)
SELECT Sum(interval_price) AS total_price
FROM applicable_intervals
;
This could use a lot of cleaning up and optimising.
It also only works when the start and end times are within the same day, among other bugs and fun stuff.
thanks to everyones contribution I could find my way and corrected my Duration and the two updates.
Duration:
set #Dauer=datediff(hh, DateAdd(hh, DateDiff(hh, 0, #Datumvon+#Zeitvon), 0),DateAdd(hh, DateDiff(hh, 0, #datumbis+#Zeitbis), 0))
Update the first record
update #t1 set Anzstunden =Anzstunden + (datediff(mi,DateAdd(mi, Round((DatePart(mi, #Zeitvon)-7) / 15.0, 0) * 15, DateAdd(hh, DateDiff(hh, 0, #Zeitvon), 0)),DateAdd(hh, DateDiff(hh, 0, #Zeitvon), 0))/60.00)
-- case when CONVERT(DECIMAL(6, 2), ROUND(((datediff(minute,[dbo].[sfRoundToHourParts](#AnfangsDatumZeit,1), [dbo].[sfRoundToHourParts](#AnfangsDatumZeit,4)) + 7) / 60.00) / 25, 2) * 25) *-1 > 0 then
-- CONVERT(DECIMAL(6, 2), ROUND(((datediff(minute,[dbo].[sfRoundToHourParts](#AnfangsDatumZeit,1), [dbo].[sfRoundToHourParts](#AnfangsDatumZeit,4)) + 7) / 60.00) / 25, 2) * 25) *-1 else Anzstunden end
from #t1 c where c.Prüfzeit=(SELECT Top 1 Prüfzeit from #t1 order by Prüfzeit)
Update the last record
--Prüft und korrigiert den LETZTEN Datensatz der #t1 auf 15 Minuten-Takt
update #t1 set Anzstunden= Anzstunden + ((datediff(mi,DateAdd(mi, Round((DatePart(mi, #Zeitbis)+7) / 15.0, 0) * 15, DateAdd(hh, DateDiff(hh, 0, #Zeitbis), 0)),DateAdd(hh, DateDiff(hh, 0, #Zeitbis), 0))/60.00)*-1)
from #t1 c where c.Prüfzeit=(SELECT Top 1 Prüfzeit from #t1 order by Prüfzeit DESC)
now everything is correct.
Thx to everyone.
Michael
I know that there are other posts with code that solve my problem but I don't want to take another's code so I'm trying to do it by myself and I'm stuck with the month not increasing problem, so if anyone can help me with that mistake it will be awesome.
The problem is:
I have to populate the table Time from year 1990 to 2016 with all the months and days, I have already achieved that the code works and it populates correctly the years and the days but months increases to January (1) and then is not increasing so the table is filled with all months being January (LOL)
Here's my code:
create table Time
(
Year int,
Month int,
Day int
)
create procedure pTime
as
declare #year int, #month int, #day int;
set #year = 1990;
set #month = 12;
set #day = 10;
while(#year<=2016)
Begin
If(#day = 29)
Begin
set #month = #month + 1;
If(#month = 13)
Begin
set #month = 1;
set #day = 1;
set #year = #year + 1;
insert into Time values (#year, #month, #day);
End
End
else
Begin
If(#day = 29)
Begin
set #month = #month + 1;
set #day = 1;
insert into Time values (#year, #month, #day);
End
Else
Begin
insert into Time values (#year, #month, #day);
set #day = #day + 1;
End
End
End
Any idea where is my mistake or any suggestion?
I didn't look very closely for your mistake because SQL Server has some helpful date arithmetic functions. Here's simplified version of your stored procedure:
create procedure pTime
as
declare #theDate date = '12/10/1990', #days int = 0
while #theDate < '1/1/2016'
begin
insert into Time (Year, Month, Day) values (datepart(year, #theDate), datepart(month, #theDate), datepart(day, #theDate));
set #theDate = dateadd(day, 1, #theDate)
end
Another faster approach would be to use a tally table. Note the code below:
WITH
E(N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1),
iTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1))-1 FROM E a,E b,E c,E d,E e),
dates(dt) AS
(
SELECT TOP(datediff(DAY,'19900101','20160101')) DATEADD(day,N,'19900101')
FROM iTally
)
--INSERT [time] --uncomment for the insert, leave commented to see what will be inserted
SELECT YEAR(dt), MONTH(dt), DAY(dt)
FROM dates;
Why do you need If(#year = 29) condition? In your code this block never will be executed. try this:
create procedure pTime
as
declare #year int, #month int, #day int;
set #year = 1990;
set #month = 12;
set #day = 10;
while(#year<=2016)
Begin
If(#day = 29)
Begin
set #month = #month + 1;
set #day = 1;
If(#month = 13)
Begin
set #month = 1;
set #year = #year + 1;
insert into Time values (#year, #month, #day);
End
End
else
Begin
If(#day = 29)
Begin
set #month = #month + 1;
set #day = 1;
insert into Time values (#year, #month, #day);
End
Else
Begin
insert into Time values (#year, #month, #day);
set #day = #day + 1;
End
End
End
I think first assignment set #day = 1; wasn't in right place. After increasing #month value you should set also #day to 1;
I have a table that has the following column (with some sample entries provided):
COLUMN NAME: pt_age_old
20 Years 8 Months 3 Weeks
1 Year 3 Months 2 Weeks
58 Years 7 Months
1 Year
7 Years 11 Months 2 Weeks
26 Years 6 Months
56 Years 6 Months
48 Years 6 Months 4 Weeks
29 Years 11 Months
4 Years 3 Months
61 Years 8 Months 4 Weeks
I have tried to cast it to datetime, of course this did not work. Same with convert.
Keep getting the following message:
Msg 241, Level 16, State 1, Line 2
Conversion failed when converting date and/or time from character string.
Main 2 questions are:
Is this type of conversion even possible with this existing string format?
If so, can you steer me in the right direction so I can make this happen?
Thanks!
This could be done using custom code such as below - I've assumed the values you're using are people's ages, and you're trying to work out their date of birth given their age today.
You can see the below code in action here: http://sqlfiddle.com/#!3/c757c/2
create function dbo.AgeToDOB(#age nvarchar(32))
returns datetime
as
begin
declare #pointer int = 0
, #pointerPrev int = 1
, #y nvarchar(6)
, #m nvarchar(6)
, #w nvarchar(6)
, #d nvarchar(6)
, #result datetime = getutcdate() --set this to the date we're working to/from
--convert various ways of expressing units to a standard
set #age = REPLACE(#age,'Years','Y')
set #age = REPLACE(#age,'Year','Y')
set #age = REPLACE(#age,'Months','M')
set #age = REPLACE(#age,'Month','M')
set #age = REPLACE(#age,'Weeks','W')
set #age = REPLACE(#age,'Week','W')
set #age = REPLACE(#age,'Days','D')
set #age = REPLACE(#age,'Day','D')
set #pointer = isnull(nullif(CHARINDEX('Y',#age),0),#pointer)
set #y = case when #pointer > #pointerprev then SUBSTRING(#age,#pointerprev,#pointer - #pointerprev) else null end
set #pointerPrev = #pointer + 1
set #pointer = isnull(nullif(CHARINDEX('M',#age),0),#pointer)
set #m = case when #pointer > #pointerprev then SUBSTRING(#age,#pointerprev,#pointer - #pointerprev) else null end
set #pointerPrev = #pointer + 1
set #pointer = isnull(nullif(CHARINDEX('W',#age),0),#pointer)
set #w = case when #pointer > #pointerprev then SUBSTRING(#age,#pointerprev,#pointer - #pointerprev) else null end
set #pointerPrev = #pointer + 1
set #pointer = isnull(nullif(CHARINDEX('D',#age),0),#pointer)
set #d = case when #pointer > #pointerprev then SUBSTRING(#age,#pointerprev,#pointer - #pointerprev) else null end
set #result = dateadd(YEAR, 0 - isnull(cast(#y as int),0), #result)
set #result = dateadd(MONTH, 0 - isnull(cast(#m as int),0), #result)
set #result = dateadd(Week, 0 - isnull(cast(#w as int),0), #result)
set #result = dateadd(Day, 0 - isnull(cast(#d as int),0), #result)
return #result
end
go
select dbo.AgeToDOB( '20 Years 8 Months 3 Weeks')
NB: there's a lot of scope for optimisation here; I've left it simple above to help keep it clear what's going on.
Technically it is possible to convert the relative time label into a datetime, but you will need a reference date though (20 Years 8 Months 3 Weeks as of '2013-10-16'). The reference date is most likely today (using GETDATE() or CURRENT_TIMESTAMP), but there is a chance it is a different date. You will have to parse the label, convert it to a duration, and then apply the duration to the reference time. This will be inherently slow.
There are at least two possible ways of doing this, write a FUNCTION to parse and convert the relative time label or use a .NET extended function to do the conversion. You would need to identify all of the possible labels otherwise the conversion will fail. Keep in mind that the reference date is important since 2 months is not a constant number of days (Jan-Feb = either 58 or 59 days).
Here is a sample of what the function might look like:
-- Test data
DECLARE #test varchar(50)
, #ref_date datetime
SET #test = '20 Years 8 Months 3 Weeks'
SET #ref_date = '2013-10-16' -- or use GETDATE() / CURRENT_TIMESTAMP
-- Logic in function
DECLARE #pos int
, #ii int
, #val int
, #label varchar(50)
, #result datetime
SET #pos = 0
SET #result = #ref_date
WHILE (#pos <= LEN(#test))
BEGIN
-- Parse the value first
SET #ii = NULLIF(CHARINDEX(' ', #test, #pos), 0)
SET #label = RTRIM(LTRIM(SUBSTRING(#test, #pos, #ii - #pos)))
SET #val = CAST(#label AS int)
SET #pos = #ii + 1
-- Parse the label next
SET #ii = NULLIF(CHARINDEX(' ', #test, #pos), 0)
IF (#ii IS NULL) SET #ii = LEN(#test) + 1
SET #label = RTRIM(LTRIM(SUBSTRING(#test, #pos, #ii - #pos)))
SET #pos = #ii + 1
-- Apply the value and offset
IF (#label = 'Days') OR (#label = 'Day')
SET #result = DATEADD(dd, -#val, #result)
ELSE IF (#label = 'Weeks') OR (#label = 'Week')
SET #result = DATEADD(ww, -#val, #result)
ELSE IF (#label = 'Months') OR (#label = 'Month')
SET #result = DATEADD(mm, -#val, #result)
ELSE IF (#label = 'Years') OR (#label = 'Year')
SET #result = DATEADD(yy, -#val, #result)
END
SELECT #result
-- So if this works correctly,
-- 20 Years 8 Months 3 Weeks from 2013-10-16 == 1993-01-26
Make a function to split it up:
create function f_tst
(
#t varchar(200)
) returns date
begin
declare #a date = current_timestamp
;with split1 as
(
select 1 start, charindex(' ', #t + ' ', 4)+1 stop
where #t like '% %'
union all
select stop, charindex(' ', #t + ' ', stop + 4)+1
from split1
where charindex(' ', #t, stop) > 0
), split2 as
(
select cast(left(x.sub, charindex(' ', x.sub)) as int) number,
substring(x.sub, charindex(' ', x.sub) + 1, 1) unit
from split1
cross apply (select substring(#t, start, stop - start) sub) x
)
select #a = case unit when 'W' then dateadd(ww, -number, #a)
when 'Y' then dateadd(yy, -number, #a)
when 'M' then dateadd(mm, -number, #a)
end
from split2
return #a
end
Test function:
select dbo.f_tst(age)
from (values('20 Years 8 Months 3 Weeks'),('1 Month') ) x(age)
Result:
1993-01-27
2013-09-17
No, date time type presents actual date, like YYYY-MM-DD HH:MM, your strings are not DATE fields, they are age, to have date time you need date of birth and them somehow add this age to it
This is the scenario I would like to have in my INSERT in a stored procedure.
Tables:
tblRate
RateID (pk)
Rate money
Days int
isDailyRate bit
tblBooking
Totals money
In my vb app this is the statement. How would I translate this into T-SQL?
if !isDaily = True then
!Totals = (!Days * !Rate)
else
!Totals = !Rate
end if
This is my stored procedure:
Create PROCEDURE [dbo].[sp_tblBooking_Add]
(
#RateID bigint,
#Rate money,
#Days int,
#CheckOUT datetime
)
AS
BEGIN
--Below is the logic I want. I can't get the right syntax
--Declare #myTotals as money
--Declare #myCheckOut as DateTime
--if (Select isDailyRate FROM tblRates WHERE (RateID = #RateID)) = True THEN
-- set myTotals = (#Rate * #Days)
-- set #CheckOUT = DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()) + #Days, '12:00')
--Else
-- set myTotals = #Rate
-- set #CheckOUT = GETDATE()
--End if
INSERT INTO tblBooking(Totals, CheckOUT)
VALUES(#myTotals, #myCheckOut);
END
Use the CASE expression:
INSERT INTO tblBooking (Totals, CheckOUT)
SELECT
CASE
WHEN idDailyRate = 1 THEN #Rate * #Days
ELSE #rate
END,
CASE
WHEN idDailyRate = 1 THEN DATEADD(DAY,
DATEDIFF(DAY, 0, GETDATE()) + #Days,
'12:00')
ELSE GETDATE()
END
FROM tblRates
WHERE RateID = #RateID;
Or, if they are scalar values, then you can select them into a variables and insert them instead of INSERT ... INTO ... SELECT.
Update 1
Like this:
Declare #myTotals as money;
Declare #myCheckOut as DateTime;
SELECT
#myTotals = CASE
WHEN idDailyRate = 1 THEN #Rate * #Days
ELSE #rate
END,
#myCheckOut = CASE
WHEN idDailyRate = 1 THEN DATEADD(DAY,
DATEDIFF(DAY, 0, GETDATE()) + #Days,
'12:00')
ELSE GETDATE()
END
FROM tblRates
WHERE RateID = #RateID;
INSERT INTO tblBooking (Totals, CheckOUT) VALUES(#myTotals, #myCheckOut );
But this will give you an error, if there is more than value returned from this table tblRates into those variables.
Can you please help:
I have an arrival date column that needs 5000 rows adding to it with random dates from the year 2010.
For example: 01/01/2010, 25/8/2010, 03/05/2010, 31/12/2010, etc
I think it would be an INSERT statement but I am unsure.
Thanks
Wayne
USE Occupancy
CREATE TABLE Book
(BookNumber varchar(30),
ArrivalDate int)
DECLARE #BookNumber varchar(30)
DECLARE #ArrivalDate int
SET #BookNumber = 1
SET #ArrivalDate = 0
WHILE #BookNumber <= 5000
WHILE #ArrivalDate <= 5000
BEGIN
INSERT INTO Book (BookNumber,ArrivalDate)
SELECT 'B' + Right ('00000000' + CAST(#BookNumber AS varchar(30)),8),
DATEADD(day, CAST(RAND() * 365 as int), '2010-1-1')
SET #BookNumber = #BookNumber + 1
SET #ArrivalDate = #ArrivalDate + 1
END
DECLARE #counter int
SET #counter = 0
WHILE #counter < 5000
BEGIN
INSERT INTO myTable (arrivalDate)
SELECT DATEADD(day, CAST(RAND() * 365 as int), '2010-1-1')
SET #counter = #counter + 1
END
And if you have a numbers table or generator, you can avoid the loop:
INSERT INTO myTable (arrivalDate)
SELECT DATEADD(day, ABS(CHECKSUM(NEWID()) % 365), '2010-1-1')
FROM ( GenerateRowNumbers(5000, 1) ) t