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
Related
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.
Periodically I need to check the records in a table and update them, in particular check the records with "payment_pending" status and update them to "payment_pending_expired" status. But I'm not sure how to do it properly:
CREATE OR REPLACE FUNCTION cancel_pending_orders(user_id integer, max_day_amount_allowed integer)
RETURNS SETOF void AS
$BODY$
BEGIN
-- if the records exist at all...
IF EXISTS (
SELECT ph.id FROM payment_history as ph
INNER JOIN payment AS p
ON p.id = ph.payment_id
WHERE p.user_id = $1
AND ph.status = 'payment_pending'
AND ph.added_at + max_day_amount_allowed <= now()
) THEN
-- make all of them status = 'payment_pending_expired'
RETURN;
END IF;
The questions:
1) How do I add max_day_amount_allowed to ph.added_at? If it were a literal I could do this by:
....
AND (ph.added_at + interval '30d') <= now()
but it is not a literal, it is a variable.
2) How do I refer to the found records (in case, the exist)
....
) THEN
-- make all of them ph.status = 'payment_pending_expired'
-- but how do I refer to them?
RETURN;
P.S. ph.status has a type of varchar and not integer only for simplicity.
1) You need to cast the day count to an interval:
AND (ph.added_at + ($2 || ' days')::interval) <= now()
2) You can use CURSORs to do something with each row in a result-set.
But in your case (if you want to only update them) just use a single UPDATE command:
UPDATE payment_history AS ph
SET ph.status = 'payment_pending_expired'
FROM payment AS p
WHERE p.id = ph.payment_id
AND p.user_id = $1
AND ph.status = 'payment_pending'
AND (ph.added_at + ($2 || ' days')::interval) <= now()
How do I add max_day_amount_allowed to ph.added_at?
Assuming the type timestamp for added_at.
Don't convert to text, concatenate and convert back. Just multiply an interval:
ph.added_at + interval '1d' * max_day_amount_allowed <= now()
Or, if added_at is a date, you can just add integer to a date. The date is then coerced to timestamp automatically (according to local time) for the comparison to now():
ph.added_at + max_day_amount_allowed <= now()
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).
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'
Basically I want to use PRINT statement inside a user defined function to aide my debugging.
However I'm getting the following error;
Invalid use of side-effecting or time-dependent operator in 'PRINT'
within a function.
Can this not be done?
Anyway to aid my user defined function debugging?
Tip:
generate error.
declare #Day int, #Config_Node varchar(50)
set #Config_Node = 'value to trace'
set #Day = #Config_Node
You will get this message:
Conversion failed when converting the varchar value 'value to trace'
to data type int.
No, sorry. User-defined functions in SQL Server are really limited, because of a requirement that they be deterministic. No way round it, as far as I know.
Have you tried debugging the SQL code with Visual Studio?
I got around this by temporarily rewriting my function to something like this:
IF OBJECT_ID ('[dbo].[fx_dosomething]', 'TF') IS NOT NULL
drop function [dbo].[fx_dosomething];
GO
create FUNCTION dbo.fx_dosomething ( #x numeric )
returns #t table (debug varchar(100), x2 numeric)
as
begin
declare #debug varchar(100)
set #debug = 'printme';
declare #x2 numeric
set #x2 = 0.123456;
insert into #t values (#debug, #x2)
return
end
go
select * from fx_dosomething(0.1)
I have tended in the past to work on my functions in two stages. The first stage would be to treat them as fairly normal SQL queries and make sure that I am getting the right results out of it. After I am confident that it is performing as desired, then I would convert it into a UDF.
Use extended procedure xp_cmdshell to run a shell command. I used it to print output to a file:
exec xp_cmdshell 'echo "mytextoutput" >> c:\debuginfo.txt'
This creates the file debuginfo.txt if it does not exist. Then it adds the text "mytextoutput" (without quotation marks) to the file. Any call to the function will write an additional line.
You may need to enable this db-server property first (default = disabled), which I realize may not be to the liking of dba's for production environments though.
No, you can not.
You can call a function from a stored procedure and debug a stored procedure (this will step into the function)
On my opinion, whenever I want to print or debug a function. I will copy the content of it to run as a normal SQL script. For example
My function:
create or alter function func_do_something_with_string(#input nvarchar(max)) returns nvarchar(max)
as begin
-- some function logic content
declare #result nvarchar(max)
set #result = substring(#input , 1, 10)
-- or do something else
return #result
end
Then I just copy and run this out of the function to debug
declare #input nvarchar(max) = 'Some string'
-- some function logic content
declare #result nvarchar(max)
set #result = substring(#input , 1, 10)
-- this line is added to check while debugging
print #result
-- or do something else
-- print the final result
print #result
You can try returning the variable you wish to inspect.
E.g. I have this function:
--Contencates seperate date and time strings and converts to a datetime. Date should be in format 25.03.2012. Time as 9:18:25.
ALTER FUNCTION [dbo].[ufn_GetDateTime] (#date nvarchar(11), #time nvarchar(11))
RETURNS datetime
AS
BEGIN
--select dbo.ufn_GetDateTime('25.03.2012.', '9:18:25')
declare #datetime datetime
declare #day_part nvarchar(3)
declare #month_part nvarchar(3)
declare #year_part nvarchar(5)
declare #point_ix int
set #point_ix = charindex('.', #date)
set #day_part = substring(#date, 0, #point_ix)
set #date = substring(#date, #point_ix, len(#date) - #point_ix)
set #point_ix = charindex('.', #date)
set #month_part = substring(#date, 0, #point_ix)
set #date = substring(#date, #point_ix, len(#date) - #point_ix)
set #point_ix = charindex('.', #date)
set #year_part = substring(#date, 0, #point_ix)
set #datetime = #month_part + #day_part + #year_part + ' ' + #time
return #datetime
END
When I run it.. I get:
Msg 241, Level 16, State 1, Line 1
Conversion failed when converting date and/or time from character string.
Arghh!!
So, what do I do?
ALTER FUNCTION [dbo].[ufn_GetDateTime] (#date nvarchar(11), #time nvarchar(11))
RETURNS nvarchar(22)
AS
BEGIN
--select dbo.ufn_GetDateTime('25.03.2012.', '9:18:25')
declare #day_part nvarchar(3)
declare #point_ix int
set #point_ix = charindex('.', #date)
set #day_part = substring(#date, 0, #point_ix)
return #day_part
END
And I get '25'. So, I am off by one and so I change to..
set #day_part = substring(#date, 0, #point_ix + 1)
Voila! Now it works :)