What does a try catch change in a while statement that uses ##ROWCOUNT? - tsql

I want to add a try catch to a while loop. The loop used while on ##ROWCOUNT > 0. In the while I have an update top (100) statement that works well without a try catch around it. When I add the try, the while ends in the first loop. What impact does the try have on ##ROWCOUNT that makes the while loop end even tough the update touched 100 records?
--do we have anything to process?
select top 1 * from SomeTable where processedFlag is null
WHILE(##ROWCOUNT > 0)
BEGIN
begin try
-- here I have an udpate top (100) statement that processes records with null flag in small batches
end try
begin catch
-- update ##ROWCOUNT so the while continues?
select top 1 * from SomeTable where processedFlag is null
end catch
END

I believe it because of
Statements such as USE, SET , DEALLOCATE CURSOR, CLOSE CURSOR,
BEGIN TRANSACTION or COMMIT TRANSACTION reset the ROWCOUNT value to 0.
May be END TRY is among them, but MSDN doesn't list all possible statements.
This will fix the problem:
DECLARE #i INT
SELECT #i = COUNT(*) FROM SomeTable WHERE processedFlag IS NULL
WHILE(#i > 0)
BEGIN
BEGIN TRY
UPDATE...
SET #i = ##ROWCOUNT
END TRY
BEGIN CATCH
SELECT #i = COUNT(*) FROM SomeTable WHERE processedFlag IS NULL
END CATCH
END

Related

UPDATE/SET/OUTPUT/FROM with output into variable

Consider the following stored procedure
CREATE PROCEDURE AssignCodeToCustomer (#customerId int)
AS
BEGIN
DECLARE #code NVARCHAR(255)
BEGIN TRY
BEGIN TRANSACTION
SELECT #code = (
UPDATE
Codes
SET
CustomerId = #customerId
OUTPUT
INSERTED.Code
FROM (
SELECT TOP 1
Code
FROM
Codes
) AS c
WHERE
c.Code = Codes.Code
-- Other stuff
COMMIT TRANSACTION
END TRY
BEGIN CATCH
BEGIN
ROLLBACK TRANSACTION
EXEC spLogSQLError
END
END CATCH
END
GO
I get an error 'Incorrect syntax near the keyword UPDATE' on line 10 (which holds the keyword UPDATE). I could also first select a code and then assign it, but with concurrency in mind I want only one query. The query works if I don't try to set the output value into the variable. How can I fix this error or should I use another approach?

how to execute the next T-SQL when a before T-SQL throw an error?

I am working with SQlite and I have many T-SQL that I want to execute all of them in this way:
T-SQL1; T-SQL2, ... T-SQLN
My T-SQL are:
insert into myRelationTable(IDTable1, IDTabl2) VALUES (1,1);
insert into myRelationTable(IDTable1, IDTabl2) VALUES (1,2);
insert into myRelationTable(IDTable1, IDTabl2) VALUES (1,3);
...
With this T.SQLs I want to related records from the table1 with the table2. If any of the relations exist, there are no problem all the T-SQL is execute, but if for exameple there are a problem with the second, the first is execute but the third and the next T-SQL are not executed.
My quiestion it's if there are any way to continue execute the T-SQL and don't take care if some of the T-SQL throw an error, because what I want it's to have the relation, if some relation exists it's because other user created it, so at the end it's what I want, that the relation exists, so I would like to continue with the next T-SQL.
Is it possible?
However, if I try to delete a record that does not exists, the next T-SQL are executed, so SQLite does not take care about the error and continue with the following. Why when I try to add a new record it does not work in the same way?
Thanks.
I would strongly recommend checking if it is OK to perform the T-SQL rather than ignoring errors.
You can do this by:
DECLARE #count int
SET #count = (SELECT COUNT(1) FROM myRelationTable WHERE IDTable1 =1 AND IDTabl2 = 1)
IF #count = 0 OR #count IS NULL
BEGIN
INSERT INTO myRelationTable(IDTable1, IDTabl2) VALUES (1,1)
END
SET #count = (SELECT COUNT(1) FROM myRelationTable WHERE IDTable1 =1 AND IDTabl2 = 2)
IF #count = 0 OR #count IS NULL
BEGIN
INSERT INTO myRelationTable(IDTable1, IDTabl2) VALUES (1,2)
END
SET #count = (SELECT COUNT(1) FROM myRelationTable WHERE IDTable1 =1 AND IDTabl2 = 3)
IF #count = 0 OR #count IS NULL
BEGIN
INSERT INTO myRelationTable(IDTable1, IDTabl2) VALUES (1,3)
END
Which can very easily be wrapped within a stored procedure.
As to your question the answer is:
Sure, easily:
BEGIN TRY
insert into myRelationTable(IDTable1, IDTabl2) VALUES (1,1);
END TRY
BEGIN CATCH
--Do nothing
END CATCH
BEGIN TRY
insert into myRelationTable(IDTable1, IDTabl2) VALUES (1,2);
END TRY
BEGIN CATCH
--Do nothing
END CATCH
BEGIN TRY
insert into myRelationTable(IDTable1, IDTabl2) VALUES (1,3);
END TRY
BEGIN CATCH
--Do nothing
END CATCH
Really my problem is that I am using SQLite Expert to execute the T-SQL, but this program detect as error syntax the Begin try line.
Other way to solve the problem is to use the igonre keyword in this way:
insert or ignore into my table...
This makes that if exists an error, the ignore it and execute the next statement. But again, the "ignore" keyword is detected as an syntax error by SQLite Expert.
The "ignore" keyword belongs to the on conflict clouse. There are more information in this link.
Thanks.

TSQL break loop when ##ROWCOUNT = 0

I have insert statements (simplified) in a SPROC like the following
SET ROWCOUNT 100
WHILE(1=1)
BEGIN
INSERT INTO table1
SELECT *
FROM table2
WHERE some_condition
-- EDIT: Realized forgot to include this following vital line that is causing issue
SET #var = #var + ##ROWCOUNT
-- ##ROWCOUNT now takes on a value of 1, which will cause the following IF check to fail even when no lines are inserted
IF(##ROWCOUNT = 0)
BEGIN
BREAK
END
END
But the issue is, after any operation even when no more rows fit my some_condition, ##ROWCOUNT is equal to 1, not 0.
How can I break that loop when there are 0 rows returned matching my some_condition?
The "set" statement creates a row count of 1. What you should do is immediately save ##ROWCOUNT into a #rowCount variable and use that var later on.
declare #rowCount int
WHILE(1=1)
BEGIN
INSERT INTO table1
SELECT *
FROM table2
WHERE some_condition
-- EDIT: Realized forgot to include this following vital line that is causing issue
SET #rowCount = ##ROWCOUNT
SET #var = #var + #rowCount
-- ##ROWCOUNT now takes on a value of 1, which will cause the following IF check to fail even when no lines are inserted
IF(#rowCount = 0)
BEGIN
BREAK
END
END
Also, you can simplify by setting #rowCount to -1 initially and changing the WHILE condition to #rowCount <> 0. The conditional BREAK will no longer be needed.
An alternative solution. This checks each iteration to see if the ID of the last inserted record has changed or not. If it hasn't changed, it indicates that no records were added that iteration.
SET ROWCOUNT 100
declare #id int;
WHILE(1=1)
INSERT INTO table1
SELECT *
FROM table2
WHERE some_condition
IF(#id= ##identity)
BEGIN
BREAK
END
set #id = ##identity;
END
Try this solutions:
1st solution
Using ##ROWCOUNT in loop's condition.
SET ROWCOUNT 100
INSERT INTO table1
SELECT *
FROM table2
WHERE some_condition
WHILE(##ROWCOUNT > 0)
BEGIN
INSERT INTO table1
SELECT *
FROM table2
WHERE some_condition
END
2nd solition
Using goto.
SET ROWCOUNT 100
WHILE(1=1)
BEGIN
INSERT INTO table1
SELECT *
FROM table2
WHERE some_condition
IF(##ROWCOUNT = 0)
BEGIN
goto label
END
END
label1:
print 'After lopp'
I think you should use select to get the ##rowcount into a variable. try this:
declare #number_of_rows int
SET ROWCOUNT 100
WHILE(1=1)
BEGIN
INSERT INTO table1
SELECT *
FROM table2
WHERE some_condition
SELECT #number_of_rows=##ROWCOUNT
IF (#number_of_rows = 0)
BEGIN
BREAK
END
END
Implemented solution similar to Moho, but used SELECT instead of SET to store ##ROWCOUNT.

TSQL Cursor Infinite Loop without TOP xxx in SELECT Statement

I have a curious problem with an infinite loop in a TSQL cursor. The cursor loops infinitely when I do not add TOP 300 to the defining select statement of the cursor. The following is an example of the code: Any assistance to this issue is much appreciated.
DECLARE #Done BIT
SET #Done = 0
DECLARE cursOut CURSOR LOCAL FAST_FORWARD
FOR
SELECT
--TOP 300
FirstName FirstName
,LastName LastName
,MiddleName MiddleName
,Email Email
,Address1 Address1
,Address2 Address2
,City City
,[State] [State]
FROM StagedUsers
OPEN cursOut;
WHILE (#Done = 0)
BEGIN
--Fetch next row
FETCH NEXT
FROM cursOut
INTO ,#v_FirstName
,#v_LastName
,#v_MiddleName
,#v_Email
,#v_Address1
,#v_Address2
,#v_City
,#v_State
IF (##FETCH_STATUS <> 0)
BEGIN
SET #Done = 1
BREAK
END
--if #batch = 0
BEGIN TRANSACTION
--process statements
--updates or insert statements
--Commit transaction
COMMIT TRANSACTION
--End While
END
--CleanUp:
CLOSE cursOut
DEALLOCATE cursOut
Thanks,
Renegrin
First I think you do not need transaction here, I presume it is only one statement executed, so remove transaction from code.
Second do it without #done flag, it is confusing (it is probably not problem here).
DECLARE #v_FirstName VARCHAR(500),#v_LastName VARCHAR(500),#v_MiddleName VARCHAR(500),#v_Email VARCHAR(500),#v_Address1 VARCHAR(500),#v_Address2 VARCHAR(500),#v_City VARCHAR(500),#v_State VARCHAR(500)
DECLARE cursOut CURSOR FAST_FORWARD FOR
SELECT FirstName FirstName,LastName LastName,MiddleName MiddleName,Email Email,Address1 Address1,Address2 Address2,City City,[State] [State]
FROM StagedUsers
OPEN cursOut;
FETCH NEXT FROM cursOut INTO #v_FirstName,#v_LastName,#v_MiddleName,#v_Email,#v_Address1,#v_Address2,#v_City,#v_State
declare #i int = 1
WHILE ##FETCH_STATUS = 0
BEGIN
print cast(#i as varchar(10))
--> statement here
FETCH NEXT FROM cursOut INTO #v_FirstName,#v_LastName,#v_MiddleName,#v_Email,#v_Address1,#v_Address2,#v_City,#v_State
set #i = #i + 1
END
CLOSE cursOut
DEALLOCATE cursOut
Do you want to update current row where cursor point? If so, there is way to do it.
Is there any indexes on table? Can you post an update query?

How to select into a variable in PL/SQL when the result might be null?

Is there a way in to just run a query once to select into a variable, considering that the query might return nothing, then in that case the variable should be null.
Currently, I can't do a select into a variable directly, since if the query returns nothing, the PL/SQL would complain variable not getting set. I can only run the query twice, with the first one do the count and if the count is zero, set the variable to null, and if the count is 1, select into the variable.
So the code would be like:
v_column my_table.column%TYPE;
v_counter number;
select count(column) into v_counter from my_table where ...;
if (v_counter = 0) then
v_column := null;
elsif (v_counter = 1) then
select column into v_column from my_table where ...;
end if;
thanks.
Update:
The reason I didn't use exception is I still have some following logic after assigning the v_column, and I have to use goto in the exception section to jump back to the following code. I'm kind of hesitate of goto lines.
You can simply handle the NO_DATA_FOUND exception by setting your variable to NULL. This way, only one query is required.
v_column my_table.column%TYPE;
BEGIN
BEGIN
select column into v_column from my_table where ...;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_column := NULL;
END;
... use v_column here
END;
I know it's an old thread, but I still think it's worth to answer it.
select (
SELECT COLUMN FROM MY_TABLE WHERE ....
) into v_column
from dual;
Example of use:
declare v_column VARCHAR2(100);
begin
select (SELECT TABLE_NAME FROM ALL_TABLES WHERE TABLE_NAME = 'DOES NOT EXIST')
into v_column
from dual;
DBMS_OUTPUT.PUT_LINE('v_column=' || v_column);
end;
What about using MAX?
That way if no data is found the variable is set to NULL, otherwise the maximum value.
Since you expect either 0 or 1 value, MAX should be OK to use.
v_column my_table.column%TYPE;
select MAX(column) into v_column from my_table where ...;
Using an Cursor FOR LOOP Statement is my favourite way to do this.
It is safer than using an explicit cursor, because you don't need to remember to close it, so you can't "leak" cursors.
You don't need "into" variables, you don't need to "FETCH", you don't need to catch and handle "NO DATA FOUND" exceptions.
Try it, you'll never go back.
v_column my_table.column%TYPE;
v_column := null;
FOR rMyTable IN (SELECT COLUMN FROM MY_TABLE WHERE ....) LOOP
v_column := rMyTable.COLUMN;
EXIT; -- Exit the loop if you only want the first result.
END LOOP;
From all the answers above, Björn's answer seems to be the most elegant and short. I personally used this approach many times. MAX or MIN function will do the job equally well. Complete PL/SQL follows, just the where clause should be specified.
declare v_column my_table.column%TYPE;
begin
select MIN(column) into v_column from my_table where ...;
DBMS_OUTPUT.PUT_LINE('v_column=' || v_column);
end;
I would recommend using a cursor. A cursor fetch is always a single row (unless you use a bulk collection), and cursors do not automatically throw no_data_found or too_many_rows exceptions; although you may inspect the cursor attribute once opened to determine if you have a row and how many.
declare
v_column my_table.column%type;
l_count pls_integer;
cursor my_cursor is
select count(*) from my_table where ...;
begin
open my_cursor;
fetch my_cursor into l_count;
close my_cursor;
if l_count = 1 then
select whse_code into v_column from my_table where ...;
else
v_column := null;
end if;
end;
Or, even more simple:
declare
v_column my_table.column%type;
cursor my_cursor is
select column from my_table where ...;
begin
open my_cursor;
fetch my_cursor into v_column;
-- Optional IF .. THEN based on FOUND or NOTFOUND
-- Not really needed if v_column is not set
if my_cursor%notfound then
v_column := null;
end if;
close my_cursor;
end;
I use this syntax for flexibility and speed -
begin
--
with KLUJ as
( select 0 ROES from dual
union
select count(*) from MY_TABLE where rownum = 1
) select max(ROES) into has_rows from KLUJ;
--
end;
Dual returns 1 row, rownum adds 0 or 1 rows, and max() groups to exactly 1. This gives 0 for no rows in a table and 1 for any other number of rows.
I extend the where clause to count rows by condition, remove rownum to count rows meeting a condition, and increase rownum to count rows meeting the condition up to a limit.
COALESCE will always return the first non-null result. By doing this, you will get the count that you want or 0:
select coalesce(count(column) ,0) into v_counter from my_table where ...;