I came across an interesting problem today. I was altering a stored procedure and put a select statement at the very end. It was meant to be temporary and just for working with the data. I was surprised to find out later that the statement got saved and was executing whenever the SP ran.
SET ANSI_NULLS ON
GO
-- Comments usually go here and are saved as part of the SP
ALTER PROCEDURE [dbo].[MySP]
#param INT
AS
BEGIN
--Your normal SQL statements here
END
--You can also add SQL statements here
select * from LargeTable
--You have access to the params
select #param
It makes sense that everything is saved, not just what is inside BEGIN/END, otherwise the comments and SET ANSI_NULLS, etc. would disappear. I'm a little confused with what starts where, so I have a few questions:
SET ANSI_NULLS gets saved as part of the SP. I have confirmed that each SP has its own value. How does SQL Server know to save this as part of the SP since it's not referenced before? Does it do a full scan of the current environment state, then when ALTER PROCEDURE runs it saves the state (possibly only non-default values)?
Apparently the BEGIN/END are optional and have no intrinsic meaning. Why are they even included then? They give a false sense of scope that doesn't exist. It seems to me no BEGIN/END and a GO at the end would make the most sense.
ANSI NULLS and QUOTED IDENTIFIERS are stored as metadata attributes of the stored procedure code. You can review these settings via
select * from sys.sql_modules
When a procedure is saved, these attributes are set to whatever they are for the connection through which the procedure is being saved. This can lead to irritating inconsistancies, so be wary.
As for BEGIN/END, it's exactly as #bobs says -- they denote code blocks, they do not denote the start and end of stored procedure code. (Functions, yes, procedures, no.) As you say, no BEGIN/END and a GO at the end would make the most sense is the way I've been doing it for years.
Technically, SQL will (attempt to) save everything in a batch as part of the stored procedure -- that is, all the text you submit, as broken up by GO statements (if any). If you stuck a RETURN statement right before your ad hoc queries, they'd be included in the code but never run.
The BEGIN...END defines a block of code. It doesn't define the beginning and ending of a script or procedure. But, I agree it can be confusing.
The SET QUOTED_IDENTIFIER and SET ANSI_NULLS settings are saved but not the other settings. Check out Interoperability here for more information.
Encloses a series of Transact-SQL
statements so that a group of
Transact-SQL statements can be
executed. BEGIN and END are
control-of-flow language keywords.
When SET ANSI_NULLS is ON, a SELECT statement that uses WHERE column_name = NULL returns zero rows even if there are null values in column_name. A SELECT statement that uses WHERE column_name <> NULL returns zero rows even if there are nonnull values in column_name.
When SET ANSI_NULLS is OFF, the Equals (=) and Not Equal To (<>) comparison operators do not follow the ISO standard. A SELECT statement that uses WHERE column_name = NULL returns the rows that have null values in column_name. A SELECT statement that uses WHERE column_name <> NULL returns the rows that have nonnull values in the column. Also, a SELECT statement that uses WHERE column_name <> XYZ_value returns all rows that are not XYZ_value and that are not NULL.
When SET ANSI_NULLS is ON, all comparisons against a null value evaluate to UNKNOWN. When SET ANSI_NULLS is OFF, comparisons of all data against a null value evaluate to TRUE if the data value is NULL. If SET ANSI_NULLS is not specified, the setting of the ANSI_NULLS option of the current database applies. For more information about the ANSI_NULLS database option, see ALTER DATABASE (Transact-SQL) and Setting Database Options.
SET ANSI_NULLS ON affects a comparison only if one of the operands of the comparison is either a variable that is NULL or a literal NULL. If both sides of the comparison are columns or compound expressions, the setting does not affect the comparison.
For a script to work as intended, regardless of the ANSI_NULLS database option or the setting of SET ANSI_NULLS, use IS NULL and IS NOT NULL in comparisons that might contain null values.
SET ANSI_NULLS should be set to ON for executing distributed queries.
SET ANSI_NULLS must also be ON when you are creating or changing indexes on computed columns or indexed views. If SET ANSI_NULLS is OFF, any CREATE, UPDATE, INSERT, and DELETE statements on tables with indexes on computed columns or indexed views will fail. SQL Server will return an error that lists all SET options that violate the required values. Also, when you execute a SELECT statement, if SET ANSI_NULLS is OFF, SQL Server will ignore the index values on computed columns or views and resolve the select operation as if there were no such indexes on the tables or views.
Related
I have SQL procedure which should return a bit different result if it is called from one specific procedure. Is it possible for the SQL procedure to detect that it is called from one particular other SQL procedure?
Maybe monitoring mon$... table data can give the answer?
Question applied to Firebird 2.1
E.g. there is mon$call_stack table, but for mostly mon$... tables are empty for Firebird 2.1, they fill up for later versions of Firebird.
Hidden data dependencies are bad idea. There is a reason why programmers see "pure function" as a good thing to pursue. Perhaps not in all situations and not at all costs, but when other factors are not affected it better be so.
https://en.wikipedia.org/wiki/Pure_function
So, Mark is correct that if there is something that affects your procedure logic - then it better be explicitly documented by becoming an explicit function parameter. Unless your explicit goal was exactly to create a hidden backdoor.
This, however, mean that all the "clients" of that procedure, all the places where it can be called from, should be changed as well, and this should be done in concert, both during development and during upgrades at client deployment sites. Which can be complicated.
So I rather would propose creating a new procedure and moving all the actual logic into it.
https://en.wikipedia.org/wiki/Adapter_pattern
Assuming you have some
create procedure old_proc(param1 type1, param2 type2, param3 type3) as
begin
....some real work and logic here....
end;
transform it into something like
create procedure new_proc(param1 type1, param2 type2, param3 type3,
new_param smallint not null = 0) as
begin
....some real work and logic here....
....using new parameter for behavior fine-tuning...
end;
create procedure old_proc(param1 type1, param2 type2, param3 type3) as
begin
execute procedure new_proc(param1, param2, param3)
end;
...and then you explicitly make "one specific procedure" call new_proc(...., 1). Then gradually, one place after another, you would move ALL you programs from calling old_proc to calling new_proc and eventually you would retire the old_proc when all dependencies are moved to new API.
https://www.firebirdsql.org/rlsnotesh/rnfbtwo-psql.html#psql-default-args
There is one more option to pass "hidden backdoor parameter" - that is context variables, introduced in Firebird 2.0
https://www.firebirdsql.org/rlsnotesh/rlsnotes20.html#dml-dsql-context
and then your callee would check like that
.....normal execution
if ( rdb$get_context('USER_TRANSACTION','my_caller') is not null) THEN BEGIN
....new behavior...
end;
However, you would have to make that "one specific procedure" to properly set this variable before calling (which is tedious but not hard) AND properly delete it after the call (and this should be properly framed to properly happen even in case of any errors/exceptions, and this also is tedious and is not easy).
I'm not aware of any such option. If your procedure should exhibit special behaviour when called from a specific procedure, I'd recommend that you make it explicit by adding an extra parameter specifying the type of behaviour, or separating this into two different procedures.
That way, you can also test the behaviour directly.
Although I agree that the best way would probably be to add a parameter to the procedure to help identify where it is being called from, sometimes we don't have the luxury for that. Consider the scenario where the procedure signature can't change because it is in a legacy system and called in many places. In this scenario I would consider the following example;
The stored procedure that needs to know who called it will be called SPROC_A in this example.
First we create a Global Temp Table
CREATE GLOBAL TEMPORARY TABLE GTT_CALLING_PROC
( PKEY INTEGER primary key,
CALLING_PROC VARCHAR(31))
ON COMMIT DELETE ROWS;
Next we create another Stored procedure called SPROC_A_WRAPPER that will wrap the calling to SPROC_A
CREATE OR ALTER PROCEDURE SPROC_A_WRAPPER
AS
DECLARE CALLING_SPROC VARCHAR(31);
BEGIN
DELETE FROM GTT_CALLING_PROC
WHERE GTT_CALLING_PROC.PKEY = 1;
INSERT INTO GTT_CALLING_PROC (
PKEY,
CALLING_PROC)
VALUES (
1,
'SPROC_A_WRAPPPER');
EXECUTE PROCEDURE SPROC_A;
DELETE FROM GTT_CALLING_PROC
WHERE GTT_CALLING_PROC.PKEY = 1;
END
and finally we have SPROC_A
CREATE OR ALTER PROCEDURE SPROC_A
AS
DECLARE CALLING_SPROC VARCHAR(31);
BEGIN
SELECT FIRST 1 CALLING_PROC
FROM GTT_CALLING_PROC
WHERE GTT_CALLING_PROC.PKEY = 1
INTO :CALLING_SPROC;
IF (:CALLING_SPROC = 'SPROC_A_WRAPPER') THEN
BEGIN
/* Do Something */
END
ELSE
BEGIN
/* Do Something Else */
END
END
The SPROC_A_WRAPPER will populate the Temp table, call that SPROC_A and then delete the row from the Temp Table, in case SPROC_A is called from someplace else within the same transaction, it won't think SPROC_A_WRAPPER called it.
Although somewhat crude, I believe this would satisfy your need.
I have one select statement in proc which is query the integer value from one table and then i increment that integer value by one , i have one update statement which is updating the increment value in table .
I want make this atomic while calling the procedure for getting the updated integer value in each request.
please help to make this atomic .
i was trying to use only update command with inline assignment variable like
Update table SET col=col+1, #variable = col+1 where ?
but it is working in sybase db but not wrking in db2 .
Consider using the following syntax:
select columnName from final table ( update yourTable set columnName = ColumnName + 1 where ... )
This removes the need for two separate statements, better for concurrency.
For best results ensure that the WHERE clause is fully indexed, so you should examine the access plan to confirm this.
Choose the correct isolation level (at connection level, or statement level) to match the other statements (if any) in the transaction with the business requirements.
I have the following table
create table LIST_PIPE_TABLE
(
ID INT,
ITEM VARCHAR(4000),
IS_FOLDER VARCHAR(10)
)
with 3 rows of data
insert into LIST_PIPE_TABLE values(1,'Victorias Secret','true')
insert into LIST_PIPE_TABLE values(2,'Porsche','true')
insert into LIST_PIPE_TABLE values(3,'Babbolat','false')
And a stored procedure that should return resultset
CREATE or alter PROCEDURE LIST_PIPE
RETURNS
( col1 varchar(4000),
col2 varchar(10)
)
AS
begin
FOR SELECT ITEM AS ITEM
,IS_FOLDER AS IS_FOLDER
FROM LIST_PIPE_TABLE
into :col1, :col2
do begin
suspend;
end
end
When I try to execute it with the following statement
execute procedure LIST_PIPE
the only one top row is returned
COL1 COL2
Victorias Secret true
Please advise what is wrong about it. How should I execute it to see all 3 rows it is designed to return?
When you have suspend in stored procedure, it is called "selectable stored sprocedure", and as the name says you select from it, so:
select * from LIST_PIPE
As ain already answered, you need to use SELECT * FROM <your procedure> for a selectable procedure (that is: it contains a SUSPEND).
The Interbase 6 Embedded SQL Guide (see under InterBase 6.0 Manuals) says:
There are two types of procedures that can be called from an application:
Select procedures that an application can use in place of a table or view in a SELECT statement. A select procedure must return one or more values, or an error results.
Executable procedures that an application can call directly, with the EXECUTE PROCEDURE statement. An executable procedure may or may not return values to the calling program.
Both kinds of procedures are defined with CREATE PROCEDURE and have the same syntax. The difference is in how the procedure is written and how it is intended to be used. Select procedures always return zero or more rows, so that to the calling program they appear as a table or view. Executable procedures are simply routines invoked by the calling program that can return only a single set of values.
In fact, a single procedure conceivably can be used as a select procedure or an executable procedure, but this is not recommended. In general a procedure is written specifically to be used in a SELECT statement (a select procedure) or to be used in an EXECUTE PROCEDURE statement (an executable procedure).
On a protocol level, an EXECUTE PROCEDURE statement will always produce a single row of results (which might be empty), where as a SELECT * FROM <procedure> will behave the same as a select from a table or view. This means that if a selectable procedure is called with EXECUTE PROCEDURE, that Firebird itself will fetch only one row from the stored procedure, and then end execution of the procedure.
It is unfortunate that it is possible to use EXECUTE PROCEDURE with a selectable procedure. The Interbase 6 Language Reference on SUSPEND explicitly mentions "SUSPEND should not be used in an executable procedure." (the phrasing is weird, because the presence of SUSPEND is what makes it selectable, though here they mean calling it with EXECUTE PROCEDURE is not advisable).
I am doing a select in a stored procedure with a cursor, I would to know If I could do the same select without using a cursor.
PROCEDURE "DOUGLAS"."tmp.douglas.yahoo::testando" ( )
LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER
DEFAULT SCHEMA DOUGLAS
AS
BEGIN
/*****************************
procedure logic
*****************************/
declare R varchar(50);
declare cursor users FOR select * from USERS WHERE CREATE_TIME between ADD_SECONDS (CURRENT_TIMESTAMP , -7200 ) and CURRENT_TIMESTAMP;
FOR R AS users DO
CALL _SYS_REPO.GRANT_ACTIVATED_ROLE('dux.health.model.roles::finalUser',R.USER_NAME);
END FOR;
END;
Technically you could convert the result set into an ARRAY and then loop over the array - but for what?
The main problem is that you want to automatically grant permissions on any users that match your time based WHERE condition.
This is not a good idea in most scenarios.
The point of avoiding cursors is to allow the DBMS to optimize SQL commands. Telling the DBMS what data you want, not how to produce it.
In this example it really wouldn't make any difference performance wise.
A much more important factor is that you run SELECT * even though you only need the USER_NAME and that your R variable is declared as VARCHAR(50) (which is wrong if you wanted to store the USER_NAME in it) but never actually used.
The R variable in the FOR loop exists in a different validity context and actually contains the current row of the cursor.
We have an Oracle application that uses a standard pattern to populate surrogate keys. We have a series of extrinsic rows (that have specific values for the surrogate keys) and other rows that have intrinsic values.
We use the following Oracle trigger snippet to determine what to do with the Surrogate key on insert:
IF :NEW.SurrogateKey IS NULL THEN
SELECT SurrogateKey_SEQ.NEXTVAL INTO :NEW.SurrogateKey FROM DUAL;
END IF;
If the supplied surrogate key is null then get a value from the nominated sequence, else pass the supplied surrogate key through to the row.
I can't seem to find an easy way to do this is T-SQL. There are all sorts of approaches, but none of which use the notion of a sequence generator like Oracle and other SQL-92 compliant DBs do.
Anybody know of a really efficient way to do this in SQL Server T-SQL? By the way, we're using SQL Server 2008 if that's any help.
You may want to look at IDENTITY. This gives you a column for which the value will be determined when you insert the row.
This may mean that you have to insert the row, and determine the value afterwards, using SCOPE_IDENTITY().
There is also an article on simulating Oracle Sequences in SQL Server here: http://www.sqlmag.com/Articles/ArticleID/46900/46900.html?Ad=1
Identity is one approach, although it will generate unique identifiers at a per table level.
Another approach is to use unique identifiers, in particualr using NewSequantialID() that ensues the generated id is always bigger than the last. The problem with this approach is you are no longer dealing with integers.
The closest way to emulate the oracle method is to have a separate table with a counter field, and then write a user defined function that queries this field, increments it, and returns the value.
Here is a way to do it using a table to store your last sequence number. The stored proc is very simple, most of the stuff in there is because I'm lazy and don't like surprises should I forget something so...here it is:
----- Create the sequence value table.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[SequenceTbl]
(
[CurrentValue] [bigint]
) ON [PRIMARY]
GO
-----------------Create the stored procedure
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE procedure [dbo].[sp_NextInSequence](#SkipCount BigInt = 1)
AS
BEGIN
BEGIN TRANSACTION
DECLARE #NextInSequence BigInt;
IF NOT EXISTS
(
SELECT
CurrentValue
FROM
SequenceTbl
)
INSERT INTO SequenceTbl (CurrentValue) VALUES (0);
SELECT TOP 1
#NextInSequence = ISNULL(CurrentValue, 0) + 1
FROM
SequenceTbl WITH (HoldLock);
UPDATE SequenceTbl WITH (UPDLOCK)
SET CurrentValue = #NextInSequence + (#SkipCount - 1);
COMMIT TRANSACTION
RETURN #NextInSequence
END;
GO
--------Use the stored procedure in Sql Manager to retrive a test value.
declare #NextInSequence BigInt
exec #NextInSequence = sp_NextInSequence;
--exec #NextInSequence = sp_NextInSequence <skipcount>;
select NextInSequence = #NextInSequence;
-----Show the current table value.
select * from SequenceTbl;
The astute will notice that there is a parameter (optional) for the stored proc. This is to allow the caller to reserve a block of ID's in the instance that the caller has more than one record that needs a unique id - using the SkipCount, the caller need make only a single call for however many IDs are needed.
The entire "IF EXISTS...INSERT INTO..." block can be removed if you remember to insert a record when the table is created. If you also remember to insert that record with a value (your seed value - a number which will never be used as an ID), you can also remove the ISNULL(...) portion of the select and just use CurrentValue + 1.
Now, before anyone makes a comment, please note that I am a software engineer, not a dba! So, any constructive criticism concerning the use of "Top 1", "With (HoldLock)" and "With (UPDLock)" is welcome. I don't know how well this will scale but this works OK for me so far...