how to get minutes difference? - tsql

with t-sql, I try to get time difference for running a sql statement:
declare #t1 datetime
declare #t2 datetime
declare #msg varchar(12)
set #t1 = getdate()
--run sql statements
....
set #t2 = getdate()
set #msg = Convert(varchar(12), Datediff(minute, #t1, #t2))
Print 'Processing time is %1!' , #msg
sql statement take time more than 1 munite, but output from print said time is 0.
How to get time difference? or how can get time difference with minutes + seconds?

Datediff returns the number of "boundaries" crossed between two datetimes (i.e. it returns an integer). If your start and end time are greater than 1 minute apart but less than 2 minutes apart, Datediff(minute, start, end) will return 1 since there isn't a full 2 minutes between them. What you need to do is get the number of seconds between the two datetimes, and then compute the number of minutes.
declare #t1 datetime
declare #t2 datetime
declare #seconds int
set #t1 = getdate()
--run sql statements
--....
set #t2 = getdate()
set #seconds = Datediff(S, #t1, #t2)
Print 'Processing time is ' +
convert(varchar, #seconds/60) + ':' +
convert(varchar, #seconds % 60)
I'm using #seconds/60 to get the number of whole minutes, and #seconds % 60 to get the remaining number of seconds. If you need more precision, you can do the same sort of thing with milliseconds.

Related

Run SQL command in loop until reach time condition

I need to solve following problem (in TSQL):
DECLARE
,#CurrentTime AS DATETIME = GETDATE()
,#KillTime AS DATETIME
,#ProcessToKill AS VARCHAR(100)
,#KillProcessCommand AS VARCHAR(100)
SET #KillTime = comming from concrete select
SET #ProcessToKill = process ID comming from concrete select
SET #KillProcessCommand = kill command
e.g.:
CurrentTime = 2022-06-01 16:00:00.830 ; KillTime = 2022-06-01 15:55:00.000
My loop should go in circle, until CurrentTime >= KillTime - if fulfilled, then kill the process, otherwise repeat each 5 minutes.
Could someone help?
Thanks
You don't need to run a loop to wait for a time, you can just use the WAITFOR command, e.g.
PRINT 'Time start: ' + CONVERT(VARCHAR(8), GETDATE(), 8);
DECLARE #Time VARCHAR(8) = CONVERT(VARCHAR(8), DATEADD(SECOND, 5, GETDATE()), 8);
WAITFOR TIME #Time;
PRINT 'Time End: ' + CONVERT(VARCHAR(8), GETDATE(), 8);
Example on db<>fiddle
If you don't want to wait until a certain time, and want to keep periodically checking a condition, then you can use a WHILE loop with WAITFOR DELAY within it then run your kill command at the end (if the process still exists):
DECLARE #ProcessToKill INT = ##SPID,
#KillTime DATETIME = DATEADD(MINUTE, 10, GETDATE());
WHILE EXISTS (SELECT * FROM sys.sysprocesses WHERE spid = #ProcessToKill) AND #KillTime > GETDATE()
BEGIN
WAITFOR DELAY '05:00';
END
IF EXISTS (SELECT * FROM sys.sysprocesses WHERE spid = #ProcessToKill)
BEGIN
--Kill your procesws
END

Function Getting the right week number of year

I want to create a function to get the right week number of year.
I already posted here to find a 'native' solution, but apparently there is not.
I tryed to create funcrtion based on this mysql example
Here is the code translated to postgresql:
CREATE OR REPLACE FUNCTION week_num_year(_date date)
RETURNS integer AS
$BODY$declare
_year integer;
begin
select date_part('year',_date) into _year;
return ceil((to_char(_date,'DDD')::integer+(to_char(('01-01-'||_year)::date,'D')::integer%7-7))/7);
end;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
But it gives wrong result, can someone help me ?
My config: PostgreSQL 9.2
If you want proper week numbers use:
select extract(week from '2012-01-01'::date);
This will produce the result 52, which is correct if you look on a calendar.
Now, if you actually want to define week numbers as "Every 7 days starting with the first day of the year" that's fine, though it doesn't match the week numbers anyone else uses and has some odd quirks:
select floor((extract(doy from '2011-01-01'::date)-1)/7)+1;
By the way, parsing date strings and hacking them up with string functions is almost always a really bad idea.
create or replace function week_num_year(_date date)
returns integer as
$body$
declare
_year date;
_week_number integer;
begin
select date_trunc('year', _date)::date into _year
;
with first_friday as (
select extract(doy from a::date) ff
from generate_series(_year, _year + 6, '1 day') s(a)
where extract(dow from a) = 5
)
select floor(
(extract(doy from _date) - (select ff from first_friday) - 1) / 7
) + 2 into _week_number
;
return _week_number
;
end;
$body$
language plpgsql immutable
You can retrieve the day of the week and also the week of the year by running:
select id,extract(DOW from test_date),extract(week from test_date), testdate,name from yourtable
What about the inbuild extract function?
SELECT extract (week from current_timestamp) FROM A_TABLE_FROM_YOUR_DB;

PostgreSQL checking a previous record's element

I need to check the previous record's element to make sure the date I query doesn't fall within a specific range between ending date and 7 days before starting date. I have the following code:
create or replace function eight (date) returns text as $$
declare
r record;
checkDate alias for $1;
begin
for r in
select * from periods
order by startDate
loop
if (checkDate between r.startDate and r.endDate) then
return q3(r.id);
elsif (checkDate between (r.startDate - interval '7 days') and r.startDate) then
return q3(r.id);
elsif (checkDate between (lag(r.endDate) over (order by r.startDate)) and (r.startDate - interval '8 days')) then
return q3(r.id);
end if;
end loop;
return null;
end;
$$ language plpgsql;
So basically, I need to check for the following:
If the query date is between the starting and ending dates
If the query date is 7 days before the start of the starting date
If the query date is between ending date and the starting date
and return the id that is associated with that date.
My function seems to work fine in most cases, but there are cases that seem to give me 0 results (when there should always be 1 result) is there something missing in my function? I'm iffy about the last if statement. That is, trying to check from previous records ending date to current records starting date (with the 7 day gap)
EDIT: no dates overlap.
Edit: Removed the part about RETURN NEXT - I had misread the question there.
Doesn't work the way you have it. A window function cannot be called like that. Your record variable r is like a built-in cursor in a FOR loop. Only the current row of the result is visible inside the loop. You would have to integrate the window function lag() it into the initial SELECT.
But since you are looping through the rows in a matching order anyway, you can do it another way.
Consider this largely rewritten example. Returns at the first violating row:
CREATE OR REPLACE FUNCTION q8(_day date)
RETURNS text AS
$BODY$
DECLARE
r record;
last_enddate date;
BEGIN
FOR r IN
SELECT *
-- ,lag(r.endDate) OVER (ORDER BY startDate) AS last_enddate
-- commented, because I supply an alternative solution
FROM periods
ORDER BY startDate
LOOP
IF _day BETWEEN r.startDate AND r.endDate THEN
RETURN 'Violates condition 1'; -- I return differing results
ELSIF _day BETWEEN (r.startDate - 7) AND r.startDate THEN
RETURN 'Violates condition 2';
ELSIF _day BETWEEN last_enddate AND (r.startDate) THEN
-- removed "- 7 ", that is covered above
RETURN 'Violates condition 3';
END IF;
last_enddate := r.enddate; -- remember for next iteration
END LOOP;
RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;
More hints
Why the alias for $1? You named it _day in the declaration already. Stick to it.
Be sure to know how PostgreSQL handles case in identifiers. ( I only use lower case.)
You can just add / subtract integers (for days) from a date.
Are you sure that lag() will return you something? I'm pretty sure that this is out of context here. Given that rows from periods are selected in order, you can store the current startDate in a variable, and use it in the if statement of the next cycle.
SET search_path='tmp';
DROP table period;
CREATE table period
( start_date DATE NOT NULL
, end_date DATE
);
INSERT INTO period(start_date ,end_date) VALUES
( '2012-01-01' , '2012-02-01' )
, ( '2012-02-01' , '2012-02-07' )
, ( '2012-03-01' , '2012-03-15' )
, ( '2012-04-01' , NULL )
, ( '2012-04-17' , '2012-04-21' )
;
DROP FUNCTION valid_date(DATE) ;
CREATE FUNCTION valid_date(DATE) RETURNS boolean
AS $body$
declare
found boolean ;
zdate ALIAS FOR $1;
begin
found = false;
SELECT true INTO found
WHERE EXISTS (
SELECT * FROM period p
WHERE (p.start_date > zdate
AND p.start_date < zdate + interval '7 day' )
OR ( p.start_date < zdate AND p.end_date > zdate )
OR ( p.start_date < zdate AND p.end_date IS NULL
AND p.start_date >= zdate - interval '7 day' )
)
;
if (found = true) then
return false;
else
return true;
end if;
end;
$body$ LANGUAGE plpgsql;
\echo 2011-01-01:true
SELECT valid_date('2011-01-01' );
\echo 2012-04-08:false
SELECT valid_date('2012-04-08' );
\echo 2012-04-30:true
SELECT valid_date('2012-04-30' );
BTW: I really think that the required functionality should be implemented as a table constraint, imposed by a trigger function (that might be based on the above function).

SQL Scalar UDF to get number of days in Year

I writing code to determine how many days in a year. I am trying to keep it really simple.
I found code that I think is very clean to determine a leap year. I am passing the inputted date using DATEPART(Y,#Year) to the leap year program and some how am not getting the correct results so I has to be in my SQL code to process the input date as the correct bit is returned.
Here is the code for the Leap Year:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[FN_Is_Leap_Year]
(
-- the parameters for the function here
#year int
)
RETURNS BIT
AS
BEGIN
RETURN (select case datepart(mm, dateadd(dd, 1, cast((cast(#year as varchar(4)) + '0228') as datetime)))
WHEN 2 THEN 1
ELSE 0 END)
END
Here is the code I wrote to process the input date & get the # days in a year:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[FN_Get_Days_In_Year]
(
#InputDT varchar(10)
)
RETURNS int
AS
BEGIN
DECLARE #Result int,
#Year int
Set #Result =
CASE
WHEN dbo.FN_Is_Leap_Year(Datepart(yyyy,#Year)) = 0 Then 365
WHEN dbo.FN_Is_Leap_Year(Datepart(yyyy,#Year)) = 1 Then 366
END
RETURN #Result
END
Got it working!!
GO
ALTER FUNCTION [dbo].[FN_Get_Days_In_Year]
(
#InputDT int
)
RETURNS varchar(3)
AS
BEGIN
Declare #Year int,
#RetVal bit,
#Result varchar(3)
Set #Year = datepart(yy, #InputDT)
Set #RetVal = dbo.FN_Is_Leap_Year(Datepart(yy,'2012'))
Set #Result = CASE #RetVal
WHEN 1 THEN 366
ELSE 365
End
Return #Result
END
Modified version of the above answer :
DECLARE #year INT,
#DaysInYear INT
SET #year = 2011
SELECT #DaysInYear = CASE DATEPART(mm, DATEADD(dd, 1, CAST((CAST(#year AS VARCHAR(4)) + '0228') AS DATETIME)))
WHEN 2 THEN 366 ELSE 365 END
SELECT #DaysInYear 'DaysInYear'

How to convert DATETIME to FILETIME value in T-SQL?

I need to convert a SQL Server DATETIME value to FILETIME in a T-SQL SELECT statement (on SQL Server 2000). Is there a built-in function to do this? If not, can someone help me figure out how to implement this conversion routine as a UDF (or just plain Transact-SQL)? Here is what I know:
FILETIME is 64-bit value representing the number of 100-nanosecond intervals since
January 1, 1601 (UTC) (per MSDN: FILETIME Structure).
SQL Server base time starts on 1900-01-01 00:00:00 (per SELECT CAST(0 as DATETIME)).
I found several examples showing how to convert FILETIME values to T-SQL DATETIME (I'm not 100% sure they are accurate, though), but could not find anything about reverse conversion. Even the general idea (or algorithm) would help.
Okay, I think I was able to implement this myself. Here is the function:
IF EXISTS
(
SELECT 1
FROM sysobjects
WHERE id = OBJECT_ID('[dbo].[fnDateTimeToFileTime]')
AND type = 'FN'
)
BEGIN
DROP FUNCTION [dbo].[fnDateTimeToFileTime]
END
GO
-- Create function.
CREATE FUNCTION [dbo].[fnDateTimeToFileTime]
(
#DateTime AS DATETIME
)
RETURNS
BIGINT
BEGIN
IF #DateTime IS NULL
RETURN NULL
DECLARE #MsecBetween1601And1970 BIGINT
DECLARE #MsecBetween1970AndDate BIGINT
SET #MsecBetween1601And1970 = 11644473600000
SET #MsecBetween1970AndDate =
DATEDIFF(ss, CAST('1970-01-01 00:00:00' as DATETIME), #DateTime) *
CAST(1000 AS BIGINT)
RETURN (#MsecBetween1601And1970 + #MsecBetween1970AndDate) * CAST(10000 AS BIGINT)
END
GO
IF ##ERROR = 0
GRANT EXECUTE ON [dbo].[fnDateTimeToFileTime] TO Public
GO
It seems to be accurate up to 1 second, which is okay with me (I could not make it more accurate due to data overflow). I used the TimeAndDate web tool to calculate the durations between dates.
What do you think?
2 SQL Server time era starts on
1900-01-01 00:00:00 (per SELECT CAST(0
as DATETIME).
No, that is the base date, datetime starts at 1753
run this
select cast('17800122' as datetime)
output
1780-01-22 00:00:00.000
But this is still less than filetime so you need to add that...however remember the gregorian and Julian calendars (also the reason that datetime starts at 1753)
The accepted answer work well, but will crash for date above 19 January 2038. Either use
DATEDIFF_BIG instead of DATEDIFF if you are on SQL Server 2016 or above, or use the following correction
CREATE FUNCTION [dbo].[fnDateTimeToFileTime]
(
#DateTime AS DATETIME
)
RETURNS
BIGINT
BEGIN
IF #DateTime IS NULL
RETURN NULL
DECLARE #MsecBetween1601And1970 BIGINT
DECLARE #MsecBetween1970AndDate BIGINT
DECLARE #MaxNumberDayBeforeOverflowDateDiff int;
SET #MaxNumberDayBeforeOverflowDateDiff = 24855; --SELECT DATEDIFF(day, CAST('1970-01-01 00:00:00' as DATETIME), CAST('2038-01-19 00:00:00' as DATETIME))
DECLARE #nbMaxDaysBetween1970AndDate int;
SET #nbMaxDaysBetween1970AndDate = DATEDIFF(day, CAST('1970-01-01 00:00:00' as DATETIME), #DateTime) / #MaxNumberDayBeforeOverflowDateDiff;
DECLARE #moduloResteDay int
SET #moduloResteDay = DATEDIFF(day, CAST('1970-01-01 00:00:00' as DATETIME), #DateTime) % #MaxNumberDayBeforeOverflowDateDiff;
DECLARE #nbSecondBefore19700101And20380119 bigint = 2147472000;
SET #MsecBetween1601And1970 = 11644473600000;
DECLARE #DateTimeModulo datetime;
SET #DateTimeModulo = DATEADD(day, -#nbMaxDaysBetween1970AndDate * #MaxNumberDayBeforeOverflowDateDiff, #DateTime)
SET #MsecBetween1970AndDate = CAST(CAST(#nbMaxDaysBetween1970AndDate as bigint) * #nbSecondBefore19700101And20380119 +
DATEDIFF(ss, CAST('1970-01-01 00:00:00' as DATETIME), #DateTimeModulo) as bigint)*
CAST(1000 AS BIGINT)
RETURN (#MsecBetween1601And1970 + #MsecBetween1970AndDate) * CAST(10000 AS BIGINT)
END