Determine caller within stored proc or trigger - sql

I am working with an insert trigger within a Sybase database. I know I can access the ##nestlevel to determine whether I am being called directly or as a result of another trigger or procedure.
Is there any way to determine, when the nesting level is deeper than 1, who performed the action causing the trigger to fire?
For example, was the table inserted to directly, was it inserted into by another trigger and if so, which one.

As far as I know, this is not possible. Your best bet is to include it as a parameter to your stored procedure(s). As explained here, this will also make your code more portable since any method used would likely rely on some database-specific call. The link there was specific for SQL Server 2005, not Sybase, but I think you're pretty much in the same boat.

I've not tested this myself, but assuming you are using Sybase ASE 15.03 or later, have your monitoring tables monProcessStatement and monSysStatement enabled, and appropriate permissions set to allow them to be accessed from your trigger you could try...
declare #parent_proc_id int
if ##nestlevel > 1
begin
create table #temp_parent_proc (
procId int,
nestLevel int,
contextId int
)
insert into #temp_parent_proc
select mss.ProcedureID,
mss.ProcNestLevel,
mss.ContextID
from monSysStatement mss
join monProcessStatement mps
on mss.KPID = mps.KPID
and mss.BatchID = mps.BatchID
and mss.SPID = mps.SPID
where mps.ProcedureID =##procid
and mps.SPID = ##spid
select #parent_proc_id = (select tpp.procId
from #temp_parent_proc tpp,
#temp_parent_proc2 tpp2
where tpp.nestLevel = tpp2.nestLevel-1
and tpp.contextId < tpp2.contextId
and tpp2.procId = ##procid
and tpp2.nestLevel = ##nestlevel
group by tpp.procId, tpp.contextId
having tpp.contextId = max(tpp.contextId ))
drop table #temp_parent_proc
end
The temp table is required because of the nature of monProcessStatement and monSysStatement.
monProcessStatement is transient and so if you reference it more than once, it may no longer hold the same rows.
monSysStatement is a historic table and is guaranteed to only return an individual rown once to any process accessing it.
if you do not have or want to set permissions to access the monitoring tables, you could put this into a stored procedure you pass ##procid, ##spid, and ##nestlevel to as parameters.
If this also isn't an option, since you cannot pass parameters into triggers, another possible work around would be to use a temporary table.
in each proc that might trigger this...
create table #trigger_parent (proc_id int)
insert into #trigger_parent ##procid
then in your trigger the temp table will be available...
if object_id('#trigger_parent') is not null
set #parent_proc = select l proc_id from #trigger_parent
you will know it was triggered from within another proc.
The trouble with this is it doesn't 'just work'. You have to enforce temp table setup.
You could do further checking to find cases where there is no #trigger_parent but the nesting level > 1 and combine a similar query to the monitoring tables as above to find potential candidates that would need to be updated.

Related

Using results of one stored procedure in another stored procedure - SQL Server

Is there any way to use the results of one stored procedure in another stored procedure without using a table variable or temp table? The logic is as follows:
IF (#example = 1)
BEGIN
DECLARE #temp TABLE (Id INT);
INSERT INTO #temp
EXEC [Procedure1] #ItemId = #StockId
set #Cost = (select top 1 id from #temp)
Ideally i would like to know if there is a way to do this without having to use a temp table. Looked around online but can't find anything that works. Any advice would be great.
In general, if you want to use user-defined code in a SELECT, then it is better to phrase the code as a user-defined function rather than a user-defined procedure.
That is, procedures should be used for their side effects and functions should be used for their return values.
That said, you can use openquery (documented here) to run an exec on a linked server. The linked server can be the server you are running on.

StoredProc manipulating Temporary table throws 'Invalid column name' on execution

I have a a number of sp's that create a temporary table #TempData with various fields. Within these sp's I call some processing sp that operates on #TempData. Temp data processing depends on sp input parameters. SP code is:
CREATE PROCEDURE [dbo].[tempdata_proc]
#ID int,
#NeedAvg tinyint = 0
AS
BEGIN
SET NOCOUNT ON;
if #NeedAvg = 1
Update #TempData set AvgValue = 1
Update #TempData set Value = -1;
END
Then, this sp is called in outer sp with the following code:
USE [BN]
--GO
--DBCC FREEPROCCACHE;
GO
Create table #TempData
(
tele_time datetime
, Value float
--, AvgValue float
)
Create clustered index IXTemp on #TempData(tele_time);
insert into #TempData(tele_time, Value ) values( GETDATE(), 50 ); --sample data
declare
#ID int,
#UpdAvg int;
select
#ID = 1000,
#UpdAvg = 1
;
Exec dbo.tempdata_proc #ID, #UpdAvg ;
select * from #TempData;
drop table #TempData
This code throws an error: Msg 207, Level 16, State 1, Procedure tempdata_proc, Line 8: Invalid column name "AvgValue".
But if only I uncomment declaration AvgValue float - everything works OK.
The question: is there any workaround letting the stored proc code remain the same and providing a tip to the optimizer - skip this because AvgValue column will not be used by the sp due to params passed.
Dynamic SQL is not a welcomed solution BTW. Using alternative to #TempData tablename is undesireable solution according to existing tsql code (huge modifications necessary for that).
Tried SET FMTONLY, tempdb.tempdb.sys.columns, try-catch wrapping without any success.
The way that stored procedures are processed is split into two parts - one part, checking for syntactical correctness, is performed at the time that the stored procedure is created or altered. The remaining part of compilation is deferred until the point in time at which the store procedure is executed. This is referred to as Deferred Name Resolution and allows a stored procedure to include references to tables (not just limited to temp tables) that do not exist at the point in time that the procedure is created.
Unfortunately, when it comes to the point in time that the procedure is executed, it needs to be able to compile all of the individual statements, and it's at this time that it will discover that the table exists but that the column doesn't - and so at this time, it will generate an error and refuse to run the procedure.
The T-SQL language is unfortunately a very simplistic compiler, and doesn't take runtime control flow into account when attempting to perform the compilation. It doesn't analyse the control flow or attempt to defer the compilation in conditional paths - it just fails the compilation because the column doesn't (at this time) exist.
Unfortunately, there aren't any mechanisms built in to SQL Server to control this behaviour - this is the behaviour you get, and anything that addresses it is going to be perceived as a workaround - as evidenced already by the (valid) suggestions in the comments - the two main ways to deal with it are to use dynamic SQL or to ensure that the temp table always contains all columns required.
One way to workaround your concerns about maintenance if you go down the "all uses of the temp table should have all columns" is to move the column definitions into a separate stored procedure, that can then augment the temporary table with all of the required columns - something like:
create procedure S_TT_Init
as
alter table #TT add Column1 int not null
alter table #TT add Column2 varchar(9) null
go
create procedure S_TT_Consumer
as
insert into #TT(Column1,Column2) values (9,'abc')
go
create procedure S_TT_User
as
create table #TT (tmp int null)
exec S_TT_Init
insert into #TT(Column1) values (8)
exec S_TT_Consumer
select Column1 from #TT
go
exec S_TT_User
Which produces the output 8 and 9. You'd put your temp table definition in S_TT_Init, S_TT_Consumer is the inner query that multiple stored procedures call, and S_TT_User is an example of one such stored procedure.
Create the table with the column initially. If you're populating the TEMP table with SPROC output just make it an IDENTITY INT (1,1) so the columns line up with your output.
Then drop the column and re-add it as the appropriate data type later on in the SPROC.
The only (or maybe best) way i can thing off beyond dynamic SQL is using checks for database structure.
if exists (Select 1 From tempdb.sys.columns Where object_id=OBJECT_ID('tempdb.dbo.#TTT') and name = 'AvgValue')
begin
--do something AvgValue related
end
maybe create a simple function that takes table name and column or only column if its always #TempTable and retursn 1/0 if the column exists, would be useful in the long run i think
if dbo.TempTableHasField('AvgValue')=1
begin
-- do something AvgValue related
end
EDIT1: Dang, you are right, sorry about that, i was sure i had ... this.... :( let me thing a bit more

Stored procedure and trigger

I had a task -- to create update trigger, that works on real table data change (not just update with the same values). For that purpose I had created copy table then began to compare updated rows with the old copied ones. When trigger completes, it's neccessary to actualize the copy:
UPDATE CopyTable SET
id = s.id,
-- many, many fields
FROM MainTable s WHERE s.id IN (SELECT [id] FROM INSERTED)
AND CopyTable.id = s.id;
I don't like to have this ugly code in the trigger anymore, so I have extracted it to a stored procedure:
CREATE PROCEDURE UpdateCopy AS
BEGIN
UPDATE CopyTable SET
id = s.id,
-- many, many fields
FROM MainTable s WHERE s.id IN (SELECT [id] FROM INSERTED)
AND CopyTable.id = s.id;
END
The result is -- Invalid object name 'INSERTED'. How can I workaround this?
Regards,
Leave the code in the trigger. INSERTED is a pseudo-table only available in the trigger code. Do not try to pass around this pseudo-table values, it may contain a very large number of entries.
This is T-SQL, a declarative data access language. It is not your run-of-the-mill procedural programming language. Common wisdom like 'code reuse' does not apply in SQL and it will only cause you performance issues. Leave the code in the trigger, where it belongs. For ease of re-factoring, generate triggers through some code generation tool so you can easily refactor the triggers.
The problem is that INSERTED is only available during the trigger
-- Trigger changes to build list of id's
DECLARE #idStack VARCHAR(max)
SET #idStack=','
SELECT #idStack=#idStack+ltrim(str(id))+',' FROM INSERTED
-- Trigger changes to call stored proc
EXEC updateCopy(#idStack)
-- Procedure to take a comma separated list of id's
CREATE PROCEDURE UpdateCopy(#IDLIST VARCHAR(max)) AS
BEGIN
UPDATE CopyTable SET
id = s.id,
-- many, many fields
FROM MainTable s WHERE charindex(','+ltrim(str(s.id))+',',#idList) > 0
AND CopyTable.id = s.id;
END
Performance will not be great, but it should allow you to do what you want.
Just typed in on the fly, but should run OK
The real question is "How to pass array of GUIDs in a stored procedure?" or, more wide, "How to pass an array in a stored procedure?".
Here is the answers:
http://www.sommarskog.se/arrays-in-sql-2005.html
http://www.sommarskog.se/arrays-in-sql-2008.html

Stored Procedures and Triggers in data base

what do Stored Procedures and Triggers in data base mean ?
how can i create Stored Procedures ?
how can i crest Triggers ?
if you have simple examples for each of these .please help :)
what i know is only about trigger which is activated if an action of(insert or delete or update ) violates the constrains specified but i don't know how to create ,so again if any have example please
Think of a Stored Procedure as a method in your code. It runs a specific set of instructions.
Stored Procedures are created to, for example, manage complex sets of data that would normally be a pain to handle along in your code.
You can create a Stored Procedure with the following instructions:
Oracle
CREATE OR REPLACE PROCEDURE P_PROCEDURE_NAME (
pParameter1 NUMBER
, pParameter2 VARCHAR2(100 Bytes)
) AS
BEGIN
-- Procedure code here...
END;
SQL Server
CREATE PROCEDURE cspProcedureName
#parameter1 int
, #parameter2 nvarchar(100)
AS
-- Procedure code here...
Oracle
As for the Triggers, they are sets of code called upon an action occuring to the related table. For instance, in Oracle, there are no INDENTITY columns such as SQL Server offers. Instead, Sequences are used along with Triggers to simulate the same. Hence, you will need to create an Oracle SEQUENCE, then the TRIGGER to update the ID field of your table.
CREATE SEQUENCE SEQ_CUSTOMERS
MINVALUE 1
MAXVALUE 65535
START WITH 1
INCREMENT BY 1;
CREATE OR REPLACE TRIGGER TRG_CUSTOMERS_INSERT
BEFORE INSERT
ON TBL_CUSTOMERS
FOR EACH ROW
BEGIN
:NEW.CUST_ID := SEQ_CUSTOMERS.NEXTVAL;
END;
SQL Server
A trigger example in SQL Server would be updating automatically the update datetime of a record. Consider the following:
CREATE TABLE Customers (
CustId int NOT NULL IDENTITY(1, 1) PRIMARY KEY
, CustName nvarchar(100) NOT NULL
, CreatedOn datetime DEFAULT GETDATE()
, LastUpdate datetime NOT NULL
)
GO
CREATE TRIGGER trgCustomersUpdt
AFTER UPDATE
ON Customers
AS
update Customers
set LastUpdate = GETDATE()
where CustId = inserted.Custid
GO
DISCLAIMER
This code has not been tested and may require minor changes for it to work properly against its respective RDBMS.
To sum it up, Triggers are mainly used to as illustrated here, despite there are many other possible use, such as building up an history of table changes that occured throught time, keeping all records of transactions into an history table or the like. The Stored Procedures are mainly used to perform complex database tasks where this would get too complex to do in code.

SQL Server Delete on read

Is there an easy way in SQLServer touse data as READ_ONCE? What I mean is, can I set it to delete a row after it has selected it?
Off the top of my head, the only way I can think of would be to restrict all logins to prohiibit any Select access, and only allow access through a stored procedure "FetchMyWhateverData" and then delete the rows as second SQL statement inside the stored proc.
CreateProcedure FetchMyWhateverData
#MyEntityId Integer,
As
Set NoCount On
Select * From TableName
Where Id = #MyEntityId
Delete TableName
Where Id = #MyEntityId
Return 0
-- and adding in the other appropriate infrastructure code of course.
If you read it with DELETE ... OUTPUT .... This is how queues work.
You could do this easily if the data is accessed through a stored procedure. You can select the data into a temp table, delete the data and return the temp. All wrapped in a transaction of course.