Multiple DATEADD functions in one query - TSQL - sql

I'm trying to make multiple edits on a random date to leave a full random datetime within a range of dates (3 months back, 3 months forward) but setting the Hours/Minutes/Seconds/Milliseconds to 0.
I'm doing this so I can then add a random amount of time to create activity start and end times that will always fit within office working hours. The script below sets a variable for the start time of the activity, then carries out 5 separate edits to this variable to zero the time element.
Is there an easier way to carry out multiple DATEADD edits, it seems clunky!
DECLARE #STARTTIME DATETIME
DECLARE #ENDTIME DATETIME
SET #STARTTIME = (SELECT DATEADD(DAY,ABS(CHECKSUM(NEWID()) % 180), (select dateadd(dd, -90, getdate()) )))
SET #STARTTIME = (SELECT DATEADD(HH, - (SELECT DATEPART(HH,#STARTTIME)),#STARTTIME))
SET #STARTTIME = (SELECT DATEADD(MI, - (SELECT DATEPART(MI,#STARTTIME)),#STARTTIME))
SET #STARTTIME = (SELECT DATEADD(SS, - (SELECT DATEPART(SS,#STARTTIME)),#STARTTIME))
SET #STARTTIME = (SELECT DATEADD(MS, - (SELECT DATEPART(MS,#STARTTIME)),#STARTTIME))
SET #STARTTIME = (SELECT DATEADD(hh,(SELECT FLOOR(RAND()*(15-08)+08)), #STARTTIME))
SET #ENDTIME = (SELECT DATEADD(hh,(SELECT FLOOR(RAND()*(3-1)+1)), #STARTTIME))
SELECT
#STARTTIME AS 'STARTTIME',
#ENDTIME AS 'ENDTIME'
Results
STARTIME 2017-04-02 13:00:00.000
ENDTIME 2017-04-02 15:00:00.000

There is not a need to use newid() in T-SQL code. It is only needed within a query to generate multiple random numbers.
So:
set #starttime = cast(datedd(day, floor(rand() * 180 - 90), getdate()) as date);
set #starttime = dateadd(hour, floor(rand()*(15-08)+08), #starttime);
set #enddtime = dateadd(hour, floor(rand()*(3-1)+1), #starttime);
Notes:
This uses cast(. . . as date) to remove the time component of the date.
There is no need to have nested select statements.
For T-SQL code, you can use rand(), rather than the newid() work-around (that is needed within a single query to generate multiple random values).
Don't use date part abbreviations such as "hh". Just spell out the date part. The code is much easier to write and maintain.

You convert it to a Date data type would set the time part to 00:00:000
DECLARE #STARTTIME DATETIME
DECLARE #ENDTIME DATETIME
SET #STARTTIME = cast((SELECT DATEADD(DAY,ABS(CHECKSUM(NEWID()) % 180), (select dateadd(dd, -90, getdate()) ))) as date)
SET #STARTTIME = (SELECT DATEADD(hh,(SELECT FLOOR(RAND()*(15-08)+08)), #STARTTIME))
SET #ENDTIME = (SELECT DATEADD(hh,(SELECT FLOOR(RAND()*(3-1)+1)), #STARTTIME))
SELECT
#STARTTIME AS 'STARTTIME',
#ENDTIME AS 'ENDTIME'

Related

Inserting rows per hour into another table

I have two tables, both have the same column structure. The first table has around 700 million rows. I want to copy these rows per hour into the second table.
To test my approach I've tried this query, which copies only one row from the first table into the second. The execution time for this query was around 4.5min
DECLARE #startDate DateTime2(0) = '2020-03-29 13:05:18'
DECLARE #endDate DateTime2(0) = '2020-03-29 13:05:18'
WHILE #startDate <= '2020-03-29 13:05:18'
BEGIN
SET #endDate = DATEADD(hh, 1, #startDate)
INSERT INTO "secondtable"
SELECT *
FROM "first table"
WHERE time_position >= #myCounter AND time_position <= '2020-03-29 13:05:18'
SET #startDate = DATEADD(hh, 1, #startDate)
END
For inserting rows per hour into the second table, I've tried this query but it's executing for 20+ minutes and the risk will probably run out of space and stop the query.
DECLARE #startTime DateTime2(0) = (select min(time_position) from "first table" where time_position = '2022-01-01')
DECLARE #endTime datetime2(0) = (select max(time_position) from "first table" where time_position = '2022-01-01')
WHILE #startTime <= #endTime
BEGIN
INSERT INTO "second table"
SELECT *
FROM "first table"
WHERE time_position >= #startTime AND time_position <= #endTime
SET #startTime = DATEADD(HH, 1, #startTime)
END
How can I copy these rows per hour into the second table in an efficient way?

Calculate difference between times in results SQL

I have a table with results:
C_EventTime L_TID
20130228162022 27200
20130228162059 27200
How would I calculate difference in seconds between the two?
In the end, I need to calculate all the differences to make a total for the month.
I've tried:
declare #startdt '20130228162022'
declare #enddt '20140101000001'
set #startdt = cast('20130101000001' as datetime)
set #enddt = cast('20140101000001' as datetime)
SELECT DATEDIFF(C_EventTime) FROM tTerminalStateLog
WHERE C_EventTime BETWEEN #startDate and #endDate
and L_TID = 27200
but suspect with my limited SQL knowledge I'm way off! Any help appreciated.
Tx
The strings you are using, don't convert nicely to a valid datetime format.
Credit to http://rdineshkumar.wordpress.com/tag/how-to-convert-yyyymmddhhmmss-to-datetimedatetime-in-sql-server/ for the formula here.
First off, DATEDIFF() takes 3 arguments. The return value, be it seconds, days, etc. Then the start/end dates. Ex DATEDIFF(SS,startdate,enddate) Doc.
Here is a sample on how to convert your two start/end values to a datetime, and calculate the difference between them.
declare #startdt datetime
declare #enddt datetime
select #startdt =
Convert(time,Dateadd(SECOND,
Right('20130228162022',2)/1,
Dateadd(MINUTE,
Right('20130228162022',4)/100,
Dateadd(hour,
Right('20130228162022',6)/10000,
'1900-01-01')))) +
convert(datetime,LEFT('20130228162022',8))
select #enddt =
Convert(time,Dateadd(SECOND,
Right('20140101000001',2)/1,
Dateadd(MINUTE,
Right('20140101000001',4)/100,
Dateadd(hour,
Right('20140101000001',6)/10000,
'1900-01-01')))) +
convert(datetime,LEFT('20140101000001',8))
select #startdt, #enddt
select DATEDIFF(ss, #startdt, #enddt)
However your table shows the values split into separate rows... This makes it slightly more complicated.
Assuming you have 2 results per L_Tid (no more, or this won't work) and you always want to compare the oldest-to-newest date (they'll never write backwards), you could do this:
declare #tTerminalStateLog table (C_EventTime varchar(15), L_Tid INT)
insert into #tTerminalStateLog
select '20130228162022',27200 union all
select '20130228162059',27200
declare #startdt varchar(15), #enddt varchar(15)
set #startdt = '20130228162022'
set #enddt = '20140101000001'
; with datesdata as
(
SELECT Convert(time,Dateadd(SECOND,
Right(C_EventTime,2)/1,
Dateadd(MINUTE,
Right(C_EventTime,4)/100,
Dateadd(hour,
Right(C_EventTime,6)/10000,
'1900-01-01')))) +
convert(datetime,LEFT(C_EventTime,8)) myDate,
L_Tid,
ROW_NUMBER() over(order by C_EventTime) as myID
FROM #tTerminalStateLog
WHERE C_EventTime BETWEEN #startdt and #enddt
and L_Tid=27200
)
select d1.myDate, d2.myDate, DATEDIFF(ss, d1.myDate, d2.myDate) [sec_diff]
from datesdata d1
left outer join datesdata d2
on d1.L_Tid=d2.L_Tid
and d2.myID=2
where d1.myID=1

Query of set time range relative to current date

I come in to work 6.30am, and need to audit what happened from 5pm when I left to 6.30am this morning. I have used code to search 13.5 hours back from any given time:
SELECT * FROM TRANSACTION_HISTORY
WHERE TRANSACTION_HISTORY.ACTIVITY_DATE_TIME > (SELECT DATEADD(hour,-13.5,(SELECT MAX (TRANSACTION_HISTORY.ACTIVITY_DATE_TIME) FROM TRANSACTION_HISTORY)))
Problem is if I run query later, I lose time from the start, eg. If I run query at 7am, I only get results from 5.30pm onwards. Rather than change criteria every day, I wanted to able to search from 6.30am of the current day, back to 5.30pm of the previous day. Can this be done?
Normally, I don't advise using between for datetime, because the boundary conditions can cause confusion. I don't think that is the case.
Here is one method:
SELECT *
FROM TRANSACTION_HISTORY th
WHERE th.ACTIVITY_DATE_TIME between cast(cast(getdate() -1 as date) as datetime) + 17.5/24.0
cast(cast(getdate() as date) as datetime) + 6.5/24.0;
The expression cast(cast(getdate() as date) as datetime) is truncating the datetime value to midnight. As a datetime, SQL Server lets you add a number which is understood as a fraction of a day. Hence, 17.5/24 represents "5:30". This addition doesn't work for the date data type.
You can do this.
DECLARE #dt datetime
DECLARE #dt1 datetime
DECLARE #dt2 datetime
SET #dt = CONVERT(datetime, CONVERT(VARCHAR(10), getdate(), 101 ), 101)
SET #dt1 = dateadd(mi, 30, dateadd(hh, 6, #dt))
SET #dt2 = dateadd(mi, -27*30, #dt1)
SELECT #dt1 as StartDate, #dt2 as EndDate
For additional details what this script does you may check these pages.
http://technet.microsoft.com/en-us/library/ms174450%28v=sql.105%29.aspx
http://technet.microsoft.com/en-us/library/ms186819%28v=sql.105%29.aspx

SQL Query based on a while loop

I am trying to construct an sql query using a while loop that increments a datetime by one minute each iteration and then generates a select statement based on the time:
declare #dt datetime
set #dt = '2011-7-21'
while #dt < '2011-7-22'
begin
select Count(*) From Actions Where Timestamp = #dt
set #dt = DATEADD(mi, 1, #dt)
end
The query works as intended except that every iteration of the while loop seems to produce a new query entirely, rather than simply a new row. Is there a way to construct this so that its one single query and each row is generated by the incrementation of the loop?
I believe this occurs because the select statement is inside the loop, but I'm not sure how to construct it a different way that works.
EDIT - Here is what I came up with using a temporary table, but it is slow. Maybe there is a faster way? If not thats fine, atleast this works:
create table #temp
(
[DT] datetime not null,
[Total] int not null
)
declare #dt datetime
declare #result int
set #dt = '2011-7-21'
while #dt < '2011-7-22'
begin
set #result = Count(*) From Actions Where Timestamp = #dt
insert #temp ([DT],[Total]) values (#dt, #result)
set #dt = DATEADD(mi, 1, #dt)
end
select * from #temp;
drop table #temp;
One way by using a table of numbers
declare #dt datetime
set #dt = '2011-07-21'
select DATEADD(mi, number, #dt)
from master..spt_values
where type = 'P'
and DATEADD(mi, number, #dt) < '2011-07-22'
If you have your own number table, use that
See here for more info http://wiki.lessthandot.com/index.php/Date_Ranges_Without_Loops
you full query would be like
DECLARE #dt DATETIME
SET #dt = '2011-07-21'
SELECT x.SomeTime,y.TheCount FROM
(SELECT DATEADD(mi, number, #dt) as SomeTime FROM master..spt_values
WHERE TYPE = 'P'
AND DATEADD(mi, number, #dt) < '2011-07-22') x
LEFT JOIN (
SELECT TIMESTAMP, COUNT(*) AS TheCount
FROM Actions
GROUP BY TIMESTAMP
) AS y
ON x.SomeTime = dateadd(mi, datediff(mi, 0, y.Timestamp)+0, 0)
If you have a numbers table (from 0 to a million or whatever), this is relatively simple:
SELECT *
FROM Numbers AS n
LEFT JOIN (
SELECT Timestamp, COUNT(*) AS Ct
FROM Actions
GROUP BY Timestamp
) AS ActionSummary
ON ActionSummary.Timestamp = DATEADD(mi, n.Number, '2011-07-21')
WHERE DATEADD(mi, n.Number, '2011-07-21') < '2011-07-22'
ORDER BY DATEADD(mi, n.Number, '2011-07-21')
No need for loops.
There's ways to optimize this, but that should be fairly understandable as it is.
Also note that the timestamps cannot have any seconds or fractions of a second for this to work (your original has this problem as well).

Set time portion of a datetime variable

I am working on a query that will be an automated job. It needs to find all the transactions between 8 PM and 8 PM for the last day. I was thinking of doing something like this
DECLARE #start_date DATETIME
DECLARE #end_date DATETIME
SET #start_date = DATEADD(DAY, -2, GETDATE())
SET #end_date = DATEADD(DAY, -1, GETDATE())
For an automated query this works good at figuring out the date portion. But the TIME portion of the variable is the current time that the query executes. Is there a quick simple way to hard code the time portion of both variables to be 8:00 PM?
DECLARE #start_date DATETIME
DECLARE #end_date DATETIME
SET #start_date = DATEADD(hour, 20, DATEDIFF(DAY, 2, GETDATE()))
SET #end_date = #start_date + 1
select #start_date, #end_date
This will also work:
DECLARE #start_date datetime
DECLARE #end_date datetime
SET #start_date = LEFT(CONVERT(nvarchar, DATEADD(DAY, -2, GETDATE()), 120), 11) + N'20:00:00'
SET #end_date = #start_date + 1
select #start_date, #end_date
Although cyberkiwi's answer is very clever! =)
I needed to pull a date from the database and append 3:00 Pm to it. I did it this way
select dateadd(hour, 15, datediff(day, 0, myDatabaseDate))
from dbo.myDatabaseTable
where myDatabaseId = 1
The result that it returned was 2017-10-01 15:00:00.000. The date in the database is 2017-10-01. The solution that I proposed was to keep my current date. I added 0 days to my existing date. I gave it 15:00 hours and it worked like a charm.
In case of just updating a particular part of the datetime you can use SMALLDATETIMEFROMPARTS like:
UPDATE MyTable
SET MyDate = SMALLDATETIMEFROMPARTS(YEAR(MyDate), MONTH(MyDate), DAY(MyDate), <HoursValue>, <MinutesValue>)
In other cases it may be required to copy parts of datetime to other or update only certain parts of the datetime:
UPDATE MyTable
SET MyDate = SMALLDATETIMEFROMPARTS(YEAR(MyDate), MONTH(MyDate), DAY(MyDate), DATEPART(hour, MyDate), DATEPART(minute, MyDate))
Refer SQL Server Date/Time related API references for more such functions
DECLARE #start_date DATETIME = DATEADD(HOUR, 20, DATEADD(MINUTE, 00, CONVERT(DATETIME, CONVERT(DATE, GETDATE())))) - 2
DECLARE #end_date DATETIME = DATEADD(HOUR, 20, DATEADD(MINUTE, 00, CONVERT(DATETIME, CONVERT(DATE, GETDATE())))) - 1
Notes:
GETDATE() + X is the equivalent of DATEADD(DAY, X, GETDATE()).
Converting a DATEIME to a DATE and then back to a DATETIME again sets the time to midnight i.e. 00:00:00.000.
Seperate SET and DECLARE statements are unnecessary, but just in case it helps later, variables may be set as part of a SELECT statement too.
I had to do something similar, create a procedure to run from a certain time the previous day to a certain time on the current day.
This is what I did to set the start date to 16:30 on the previous day, basically subtract the parts you don't want to get them back to 0 then add the value that you want it to be.
-- Set Start Date to previous day and set start time to 16:30.00.000
SET #StartDate = GetDate()
SET #StartDate = DateAdd(dd,- 1, #StartDate)
SET #StartDate = DateAdd(hh,- (DatePart(hh,#StartDate))+16, #StartDate)
SET #StartDate = DateAdd(mi,- (DatePart(mi,#StartDate))+30, #StartDate)
SET #StartDate = DateAdd(ss,- (DatePart(ss,#StartDate)), #StartDate)
SET #StartDate = DateAdd(ms,- (DatePart(ms,#StartDate)), #StartDate)
Hope this helps someone.