DECLARE statement make variables to be global? [duplicate] - tsql

I would like to declare a variable within an if/else statement in a SQL Server stored procedure. I understand that this is fairly impossible because SQL Server doesn't do memory management with respect to declaration of variables within procedures. Is there a way to have a variable scoped in an if/else statement, then redeclare a variable with the same name in another if/else statement? For example:
create procedure Foo
as
begin
if exists (x)
begin
declare #bob int
set bob = 1
end
else
begin
declare #bob int
set bob = 2
end
end

From books online:
The scope of a variable is the range of Transact-SQL statements that can reference the variable. The scope of a variable lasts from the point it is declared until the end of the batch or stored procedure in which it is declared.
However. Nothing keeps you from doing this:
create procedure Foo as begin
declare #bob int
if exists (x)
begin
set #bob = 1
end
else
begin
set #bob = 2
end
end

No, SQL is pretty funny/weird like that
Declare the variable before the if exists block of code
so
declare #bob int
set #bob = 2
if exists(x)
begin
set #bob = 1
end
Now, take a look at these examples and try to guess what happens
WHILE 1 = 2 --not true of course
BEGIN
DECLARE #VAR INT;
END
SET #VAR = 1;
SELECT #VAR;
This of course works, but it is not initialized every time
DECLARE #loop INT
SET #loop = 0
WHILE #loop <=6
BEGIN
DECLARE #VAR INT
SET #VAR = COALESCE(#VAR,0) + 1
SET #loop = #loop +1
END
SELECT #VAR

is there some reason why you can't do :
declare #bob int
if exists(x)
begin set #bob = 1 end
else
begin set #bob = 2 end

You could resort to using dynamic SQL:
if exists (x)
begin
exec sp_executesql N'
declare #bob int
set #bob = 1
';
end
else
begin
exec sp_executesql N'
declare #bob int
set #bob = 2
';
end

Related

Return result set using Weakly-typed cursor Variable

CREATE or replace PROCEDURE return_result_set ( )
LANGUAGE SQL
SPECIFIC return_result_set
DYNAMIC RESULT SETS 1
rrs: BEGIN
DECLARE rs_cur CURSOR WITH RETURN
FOR SELECT *
FROM dummytable;
OPEN rs_cur;
END rrs
I can return result set using cursor by using above stored procedure, but I want to use cursor variable (Weakly-typed) in my stored procedure as the select query and table are going to vary based on a condition. Sample code here seems to be for different use case..
How to return a result set using a Cursor Variable?
Try this as is:
--#SET TERMINATOR #
SET SERVEROUTPUT ON#
BEGIN
DECLARE V_C1 CURSOR;
DECLARE V_I INT;
DECLARE PROCEDURE L_PROC(OUT LP_C1 CURSOR)
BEGIN
-- Dynamic cursor
--DECLARE V_STMT VARCHAR(128) DEFAULT 'SELECT * FROM (VALUES 1) T(I)';
--PREPARE V_S1 FROM V_STMT;
--SET LP_C1 = CURSOR FOR V_S1;
-- Static cursor
SET LP_C1 = CURSOR FOR SELECT * FROM (VALUES 1) T(I);
OPEN LP_C1;
END;
CALL L_PROC(V_C1);
FETCH V_C1 INTO V_I;
CALL DBMS_OUTPUT.PUT_LINE('I: ' || V_I);
CLOSE V_C1;
END#
SET SERVEROUTPUT OFF#

What is wrong with this SQL WHILE LOOP?

This nested while loop is only successfully executing the lowest level loop. It refuses to add 1 to #wKey even though I tell it to SET #wKey = #wKey + 1.... What am I doing wrong here? Does SQL not allow nested loops?
DECLARE #fMinKey INT;
DECLARE #fMaxKey INT;
DECLARE #wMaxKey INT;
DECLARE #wKey INT;
DECLARE #wDate date;
DECLARE #fcStart DATE;
SET #fMinKey = (select min([fcKey]) from dbo.fact_Fc);
SET #fMaxKey = (select max([fcKey]) from dw.fact_Fc);
SET #wMaxKey = (select max([WellKey]) from dw.fact_Fc);
SET #wKey = 1;
SET #wDate =
(select min([fapDate]) from dbo.dim_W where [Key] = #wKey);
SET #fcStart =
(select min([Date]) from dw.fact_Fc where [wKey] = #wKey);
WHILE (#fMinKey <= #fMaxKey)
BEGIN
WHILE (#wKey <= #wMaxKey)
BEGIN
WHILE (#wDate < #fcStart)
BEGIN
INSERT INTO dw.fact_FcTemp2 ([wKey], [Date], [pAmount], [fcKey], [AddedDate])
VALUES (#wKey, #wDate, 0, #fMinKey, CURRENT_TIMESTAMP)
SET #wDate = dateadd(DAY, 1, #wDate)
END
SET #wKey = #wKey + 1
END
SET #fMinKey = #fMinKey + 1
END
The resulting table is only showing [wKey] = 1, but it should have rows for multiple different wKeys
Once when #wDate reach #fcStart looks like you never reset #wDate to initial state
So next loop run again
You need some how to reset #wDate for next loop
Also having 3 loops perhaps is miss of design, sql performances does not like loops at all.
Can you show us data structure and needed result maybe tehe is way to constuct sql whitout 3 nested loops

SQLCODE=-501 SQLSTATE=24501 with Cursor

I have written a stored procedure as
CREATE OR REPLACE PROC1()
DECLARE VAR1 INT;
DECLARE VAR2 INT;
DECLARE TEXT VARCHAR(1000);
DECLARE exitcode INTEGER DEFAULT 0;
DECLARE SQLCODE INTEGER DEFAULT 0;
DECLARE CUR1 CURSOR WITH HOLD for STMT1 ;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET exitcode = 1;
SET TEXT= (-----);
PREPARE STMT1 FROM TEXT;
open cur1;
fetch from cur1 into var1,var2;
while(sqlcode =0)
do
--
--
CALL SYSPROC.ADMIN_CMD('REORG TABLE emp1 ');
set exit code = 0;
fetch from cur1 into var1,var2;
IF (exitcode = 1)
THEN
LEAVE wloop;
END IF;
end while;
close cur1;
My question is even I declared my cursor as WITH HOLD option, after the first fetch the cursor is closing & throwing -501 error.If i remove the REORG statement from the loop. Then cursor is working normally,fetching all the rows. Can some one tel me the way to keep my cursor to be open even If i use the REORG statement inside the loop.
Thanks in advance

Stored procedure returns nullable int

In my procedure i am returning a int value.
ALTER PROCEDURE [dbo].[GetValue]
-- Add the parameters for the stored procedure here
#ID int,
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
DECLARE #isNew int
SET #isNew=0
DECLARE #returnedValue int
DECLARE #output int
SET #returnedValue=[dbo].fn_GetIsNewLecturer(#ID)
IF(#returnedValue=0)
BEGIN
PRINT 'new'
EXEC #output=[dbo].[GetNew] #ID
SELECT #output
END
ELSE
BEGIN
PRINT 'old'
EXEC #output=[dbo].[sp_GetOld] #ID
SELECT #output
END
RETURN #output
END
it return value should be int. But it returns Nullable int?. how to change it as int
Try this:
select [Output] = isnull(#output, 0)
Here's why it should work:
declare #i int
select ni = #i, nni = isnull(#i,0)
into #t
select is_nullable, *
from tempdb.sys.columns
where [object_id] = object_id(N'tempdb..#t')
drop table #t

What is the effect of having XACT_ABORT on/off in parent/child stored procedures respectively?

I'm trying to improve the error handling of a current system to produce more meaningful error messages. I have a "root" stored procedure that makes several calls to other nested stored procedures.
In the root sp, XACT_ABORT is set to ON but in the nested procedures, XACT_ABORT is set to OFF. I want to capture the specific errors from the lower level procedures rather than getting the root procedure's error.
I often see the error, uncommittable transaction is detected at the end of the batch, the transaction is being rolled back.
Is there any effect to having these "mixed" environments with the XACT_ABORTs?
Also, if you have any suggestions for advanced error handling, that would be much appreciated. I think I would like to use sp_executesql so I can pass parameters to get error output without having to modify all of the stored procedures and use RAISERROR to invoke the parent procedure's CATCH block.
As per Andomar's answer here and MSDN:
The setting of SET XACT_ABORT is set at execute or run time and not at
parse time
i.e. XACT_ABORT will not be 'copied' from the creation session to each procedure, so any PROC which doesn't explicitly set this option internally will inherit the setting from the ambient session at run time, which can be disastrous.
FWIW, as a general rule, we always ensure that XACT_ABORT is ON globally and do a lint check to ensure none of our PROCs have overridden this setting.
Note that XACT_ABORT isn't a silver bullet, however - e.g. errors that have been raised by your PROC with RAISERROR won't terminate the batch. However, it seems that this is improved with the THROW keyword in SQL 2012
As you've suggested, and as per Remus Rusanu's observation, structured exception handling (TRY / CATCH) is a much more clean and robust mechanism for handling of exceptions.
A way to keep XACT_ABORT on and get errors if any or commit if all is fine when calling SP that may call other SP: two sp and three tests as example
create PROCEDURE [dbo].[myTestProcCalled]
(
#testin int=0
)
as
begin
declare #InnerTrans int
set XACT_ABORT on;
set #InnerTrans = ##trancount;
PRINT '02_01_Trancount='+cast (#InnerTrans as varchar(2));
begin try
if (#InnerTrans = 0)
begin
PRINT '02_02_beginning trans';
begin transaction
end
declare #t2 int
set #t2=0
PRINT '02_03_doing division'
set #t2=10/#testin
PRINT '02_04_doing AfterStuff'
if (#InnerTrans = 0 and XACT_STATE()=1)
begin
PRINT '02_05_Committing'
commit transaction
end
PRINT '02_05B_selecting calledValue=' +cast(#t2 as varchar(20))
select #t2 as insidevalue
end try
begin catch
PRINT '02_06_Catching Errors from called'
declare #ErrorMessage nvarchar(4000);
declare #ErrorNumber int;
declare #ErrorSeverity int;
declare #ErrorState int;
select #ErrorMessage = error_message(), #ErrorNumber = error_number(), #ErrorSeverity = error_severity(), #ErrorState = error_state();
if (#InnerTrans = 0 and XACT_STATE()=-1)
begin
PRINT '02_07_Rolbacking'
rollback transaction
end
PRINT '02_08_Rising Error'
raiserror(#ErrorMessage, #ErrorSeverity, #ErrorState);
--use throw if in 2012 or above
-- else might add a "return" statement
end catch
end
go
create PROCEDURE [dbo].[myTestPCalling]
(
#test int=0
,#testinside int=0
)
as
begin
declare #InnerTrans int
set XACT_ABORT on;
set #InnerTrans = ##trancount;
PRINT '01_01_Trancount='+cast (#InnerTrans as varchar(2));
begin try
if (#InnerTrans = 0)
begin
PRINT '01_02_beginning trans';
begin transaction
end
declare #t2 int
set #t2=0
PRINT '01_03_doing division'
set #t2=10/#test
PRINT '01_04_calling inside sp'
execute [dbo].[myTestProcCalled]
#testin = #testinside
--
PRINT '01_05_doing AfterStuff'
if (#InnerTrans = 0 and XACT_STATE()=1)
begin
PRINT '01_06_Committing'
commit transaction
PRINT '01_06B_selecting callerValue=' +cast(#t2 as varchar(20))
select #t2 as outsidevalue
end
end try
begin catch
PRINT '01_07_Catching Errors from Caller'
declare #ErrorMessage nvarchar(4000);
declare #ErrorNumber int;
declare #ErrorSeverity int;
declare #ErrorState int;
select #ErrorMessage = error_message(), #ErrorNumber = error_number(), #ErrorSeverity = error_severity(), #ErrorState = error_state();
if (#InnerTrans = 0 and XACT_STATE()=-1)
begin
PRINT '01_08_Rolbacking'
rollback transaction
end
PRINT '01_09_Rising Error'
raiserror(#ErrorMessage, #ErrorSeverity, #ErrorState);
--use throw if in 2012 or above
-- else might add a "return" statement
end catch
end
----test 1 :result OK----
USE [PRO-CGWEB]
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[myTestPCalling]
#test =2
,#testinside = 2
SELECT 'Return Value' = #return_value
GO
----test2 :error in caller ----
USE [PRO-CGWEB]
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[myTestPCalling]
#test =0
,#testinside = 2
SELECT 'Return Value' = #return_value
GO
----test3 :error in calling ----
USE [PRO-CGWEB]
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[myTestPCalling]
#test =2
,#testinside = 0
SELECT 'Return Value' = #return_value
GO