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
Related
In SQL, I am going to write a query which insert all data between 2 dates and also I want to bring then in a 1000 batch but since the number of data between those days are more than my limitation I was going to write a loop which makes the smaller period to bring the data.
here is my code:
DECLARE #StartDate DATETIME = CAST('2021-06-02 01:00:00.000' AS DATETIME)
DECLARE #EndDate DATETIME = CAST('2021-06-23 01:00:00.000' AS DATETIME)
DECLARE #RealRowCount INT = (SELECT DISTINCT SUM(##ROWCOUNT) OVER() FROM GetReport (
#StartDate, #EndDate))
DECLARE #TransactionCount INT = (SELECT DISTINCT TransactionCount FROM GetReport (
#StartDate, #EndDate))
WHILE #RealRowCount < #TransactionCount
BEGIN
DECLARE #DiffDate INT = (SELECT DATEDIFF(DAY, #StartDate, #EndDate))
SET #EndDate = DATEADD(DAY, #DiffDate/2 ,#StartDate)
SELECT *,#StartDate, #EndDate FROM GetReport (#StartDate, #EndDate)
END
PS: I was thinking about find the middle of the period of date and then change them into the new EneDate and StartDate but there is problem here!
Your question is not very clear. Suppose you have 10,000 records between two dates and you do not want to retrieve more than a thousand records at a time. In this case, you can use pagination. Both in the program code and in SQL.
DECLARE #StartDate DATETIME = CAST('2021-06-02 01:00:00.000' AS DATETIME)
DECLARE #EndDate DATETIME = CAST('2021-06-23 01:00:00.000' AS DATETIME)
DECLARE #RealRowCount INT = (SELECT DISTINCT COUNT(*) FROM Products WHERE InsertDate BETWEEN #StartDate AND #EndDate)
DECLARE #Counter INT = 0
WHILE #Counter <= #RealRowCount
BEGIN
SELECT *
FROM Products
WHERE InsertDate BETWEEN #StartDate AND #EndDate
ORDER BY InsertDate
OFFSET #Counter ROWS -- skip #Counter rows
FETCH NEXT 1000 ROWS ONLY -- take 1000 rows
SET #Counter = #Counter + 1000
END
Or you can get the time difference between the two dates and add the start date each time in a specific step and retrieve the data of that date.
For example, the date difference is 20 days. Increase the start date by 5 steps each time to the start date with the end date
I create another table to put Dates and if this table has any rows I can get 'EndDate', but if it has not any records I simply just use the date that I specified.
AccSync is the table that I insert details of my records and AccTransformation is the table wich I want to insert all of my records.
DECLARE #Count INT = (SELECT COUNT(*) FROM [AccTransaction])
DECLARE #Flag BIT = (SELECT IIF(#Count > 1, 1, 0))
DECLARE #End DATETIME = GETDATE();
DECLARE #Start DATETIME
IF(#Flag = 0)
BEGIN
SET #Start = CAST('2021-03-08' AS DATETIME2);
SET #Flag = 1
END
ELSE IF(#Flag = 1)
BEGIN
SET #Start = (SELECT TOP 1 EndDate FROM (SELECT EndDate FROM [AccSync] ORDER BY ActionDate DESC OFFSET 0 ROW) AS TT);
END
DECLARE #RealRowCount INT = (SELECT DISTINCT SUM(##ROWCOUNT) FROM [GetReport] (#Start, #End));
DECLARE #TransactionCount INT = (SELECT DISTINCT TransactionCount FROM [GetReport] (#Start, #End));
----------------------------------------------------------------------------------------------
WHILE (#RealRowCount <> #TransactionCount)
BEGIN
DECLARE #DiffDate INT = (SELECT DATEDIFF(SECOND, #Start, #End))
SET #End = DATEADD(SECOND, (#DiffDate/2), #Start)
SET #RealRowCount = (SELECT DISTINCT SUM(##ROWCOUNT) FROM [GetReport] (#Start, #End))
SET #TransactionCount = (SELECT DISTINCT TransactionCount FROM [GetReport] (#Start, #End))
END
----------------------------------------------------------------------------------------------
INSERT INTO [AccTransaction]
SELECT *
FROM [GetReport](#Start, #End)
----------------------------------------------------------------------------------------------
INSERT INTO [AccSync]
VALUES(NEWID(), GETDATE(), #Start, #End, ISNULL(#TransactionCount,0), DATEDIFF(SECOND, #Start, #End))
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.
After reading on this topic and being advised to use DateDiff. I wrote a function that doesn't provide the answer I want. The client wants to now how long it took to complete a checklist. I have a CreationDate and CompletionDate. I need to know how many years, months, weeks and days it took. If it is 2 days then '2 days' without the years. The function deducts the number of years and then attempt to check the number of months, then the number of weeks and then the number of days. Only results are given if available. It seems DateDiff is the problem... or I am the problem not understanding DateDiff. It even returns a week for a 4 day difference in dates which doesn't make sense. It should return the number of weeks within the two dates, not caring when it starts.
This is the code
ALTER FUNCTION [dbo].[DateRangeText]
(#FromDate DATETIME, #ToDate DATETIME)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #Result AS VARCHAR(MAX);
SET #Result = '';
DECLARE #TmpS AS VARCHAR(MAX);
SET #TmpS = '';
DECLARE #Years AS INT;
SET #Years = DATEDIFF(year, #FromDate, #ToDate);
IF (#Years > 0)
BEGIN
IF (#Years = 1)
SET #TmpS = ' Year ';
ELSE
SET #TmpS = ' Years ';
SET #Result = #Result + CAST(#Years AS VARCHAR) + #TmpS;
SET #ToDate = DATEADD(YEAR, -1 * #Years, #ToDate);
END;
DECLARE #Months AS INT;
SET #Months = DATEDIFF(month, #FromDate, #ToDate);
IF (#Months > 0)
BEGIN
IF (#Months = 1)
SET #TmpS = ' Month ';
ELSE
SET #TmpS = ' Months ';
SET #Result = #Result + CAST(#Months AS VARCHAR) + #TmpS;
SET #ToDate = DATEADD(MONTH, -1 * #Months, #ToDate);
END;
DECLARE #Weeks AS INT;
SET #Weeks = DATEDIFF(week, #FromDate, #ToDate);
IF (#Weeks > 0)
BEGIN
IF (#Weeks = 1)
SET #TmpS = ' Week ';
ELSE
SET #TmpS = ' Weeks ';
SET #Result = #Result + CAST(#Weeks AS VARCHAR) + #TmpS;
SET #ToDate = DATEADD(WEEK, -1 * #Weeks, #ToDate);
END;
DECLARE #Days AS INT;
SET #Days = DATEDIFF(day, #FromDate, #ToDate);
IF (#Days > 0)
BEGIN
IF (#Days = 1)
SET #TmpS = ' Day ';
ELSE
SET #TmpS = ' Days ';
SET #Result = #Result + CAST(#Days AS VARCHAR) + #TmpS;
SET #ToDate = DATEADD(WEEK, -1 * #Days, #ToDate);
END;
IF (#Result = '')
SET #Result = 'Same day';
RETURN Rtrim(COALESCE(#Result,''));
END;
Since you are using a function, consider the following Table-Valued Function. Easy to use a stand-alone or included as a CROSS APPLY.
Performant and Accurate without having to worry about all the misc date calculations.
Example
Select * from [dbo].[tvf-Date-Elapsed] ('1991-09-12 21:00:00.000',GetDate())
Returns
Years Months Days Hours Minutes Seconds
26 7 5 13 47 11
The TVF if interested
CREATE FUNCTION [dbo].[tvf-Date-Elapsed] (#D1 DateTime,#D2 DateTime)
Returns Table
Return (
with cteBN(N) as (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cteRN(R) as (Select Row_Number() Over (Order By (Select NULL))-1 From cteBN a,cteBN b,cteBN c),
cteYY(N,D) as (Select Max(R),Max(DateAdd(YY,R,#D1))From cteRN R Where DateAdd(YY,R,#D1)<=#D2),
cteMM(N,D) as (Select Max(R),Max(DateAdd(MM,R,D)) From (Select Top 12 R From cteRN Order By 1) R, cteYY P Where DateAdd(MM,R,D)<=#D2),
cteDD(N,D) as (Select Max(R),Max(DateAdd(DD,R,D)) From (Select Top 31 R From cteRN Order By 1) R, cteMM P Where DateAdd(DD,R,D)<=#D2),
cteHH(N,D) as (Select Max(R),Max(DateAdd(HH,R,D)) From (Select Top 24 R From cteRN Order By 1) R, cteDD P Where DateAdd(HH,R,D)<=#D2),
cteMI(N,D) as (Select Max(R),Max(DateAdd(MI,R,D)) From (Select Top 60 R From cteRN Order By 1) R, cteHH P Where DateAdd(MI,R,D)<=#D2),
cteSS(N,D) as (Select Max(R),Max(DateAdd(SS,R,D)) From (Select Top 60 R From cteRN Order By 1) R, cteMI P Where DateAdd(SS,R,D)<=#D2)
Select [Years] = cteYY.N
,[Months] = cteMM.N
,[Days] = cteDD.N
,[Hours] = cteHH.N
,[Minutes] = cteMI.N
,[Seconds] = cteSS.N
--,[Elapsed] = Format(cteYY.N,'0000')+':'+Format(cteMM.N,'00')+':'+Format(cteDD.N,'00')+' '+Format(cteHH.N,'00')+':'+Format(cteMI.N,'00')+':'+Format(cteSS.N,'00')
From cteYY,cteMM,cteDD,cteHH,cteMI,cteSS
)
--Max 1000 years
--Select * from [dbo].[tvf-Date-Elapsed] ('1991-09-12 21:00:00.000',GetDate())
--Select * from [dbo].[tvf-Date-Elapsed] ('2017-01-01 20:30:15','2018-02-05 22:58:35')
I'll leave weeks and leap years to you to figure out unless I have time to come back to this later. However this will get you the years, months, and days you are looking for:
DECLARE #FromDate DateTime
DECLARE #ToDate DateTime
DECLARE #years INT
DECLARE #months INT
DECLARE #days INT
-- just some sample dates for testing
Set #FromDate = '01-01-2014'
Set #ToDate = GetDate()
SET #years = DATEDIFF(mm, #FromDate, #ToDate)/12
SET #months = DATEDIFF(mm, #FromDate, #ToDate)%12 - 1
SET #days = ABS(DATEDIFF(dd, DATEADD(mm,#months , DATEADD(yy, #years, #FromDate)), #ToDate))
DECLARE #YearsStr VarChar(20)
DECLARE #MonthsStr VarChar(20)
DECLARE #DaysStr VarChar(20)
SET #YearsStr = Case When #years > 0 Then Convert(varchar(10),#years) + ' Years, ' Else '' End
SET #MonthsStr = Case When #months > 0 Then Convert(varchar(10),#months) + ' Months, ' Else '' End
SET #DaysStr = Convert(varchar(10),#days) + ' Days '
SELECT #YearsStr + #MonthsStr + #DaysStr
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;
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.