Sorry, lots of code coming up..
I saw another question like this that used output parameters. I'm using the RETURN statement to return the value I want to use.
I have one stored procedure InsertMessage that looks like this:
ALTER PROCEDURE dbo.InsertNewMessage
(
#messageText text,
#dateTime DATETIME,
#byEmail bit,
#bySMS bit
)
AS
DECLARE #NewId int
BEGIN
BEGIN TRANSACTION
INSERT INTO MessageSet VALUES (#byEmail, #bySMS, #dateTime, #messageText)
SET #NewId = SCOPE_IDENTITY()
COMMIT
END
RETURN #NewId
which another stored procedure uses:
ALTER PROCEDURE dbo.InsertMessageFromUserToGroup
(
#userEmail nvarchar(256),
#groupId int,
#messageText text,
#bySMS bit,
#byEmail bit
)
AS
--Inserts a new message to a group
DECLARE #messageId int
DECLARE #dateTime DATETIME = GETDATE()
--First check if user is a part of the group
IF NOT EXISTS (SELECT userEmail FROM UserToGroupSet WHERE userEmail = #userEmail AND groupId = #groupId)
RETURN 'User not part of group'
ELSE --User is a part of the group, add message
BEGIN
BEGIN TRANSACTION
SET #messageId = [dbo].[InsertNewMessage](#messageText, #dateTime, #bySMS, #byEmail)
INSERT INTO MessageToUser VALUES(#userEmail, #messageId)
INSERT INTO MessageToGroup VALUES(#messageId, #groupId)
COMMIT
END
The row that causes the trouble and of which I'm unsure how to handle is this one:
SET #messageId = [dbo].[InsertNewMessage](#messageText, #dateTime, #bySMS, #byEmail)
The syntax seems ok because I can save it. When I run it I get the error message:
Running [dbo].[InsertMessageFromUserToGroup] ( #userEmail = test#test.com, #groupId = 5, #messageText = sdfsdf, #bySMS = false, #byEmail = true ).
Cannot find either column "dbo" or the user-defined function or aggregate "dbo.InsertNewMessage", or the name is ambiguous.
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
No rows affected.
(0 row(s) returned)
#RETURN_VALUE =
Finished running [dbo].[InsertMessageFromUserToGroup].
It seems as if the other stored procedure can't be found. I've tried different ways of calling the procedure but everything else fails as well. Any suggestions?
Try changing
SET #messageId = [dbo].[InsertNewMessage](#messageText, #dateTime, #bySMS,
#byEmail)
to
EXEC #messageId = [dbo].[InsertNewMessage] #messageText, #dateTime, #bySMS,
#byEmail
Notice that SET has been changed to EXEC, and the parentheses have been removed from the parameters.
See the example in the MSDN documenation at the end of the article for more information.
Related
I wrote a trigger after insert on a table, the number 5 in a specific column on the table, but after I do an insert, nothing happens. I'm wondering what mistakes I have in my code so that after an insert nothing happens
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[trigger_EstadoDoc]
ON [dbo].[EstadoDoc]
AFTER INSERT AS
BEGIN
DECLARE #Puntaje1 INT;
DECLARE #Puntaje2 INT;
SET #Puntaje1 = (SELECT 5
FROM EstadoDoc, inserted
WHERE inserted.IdDoc = EstadoDoc.IdDoc
AND inserted.Estado IN ('A','RyR')
AND EstadoDoc.Estado = 'S')
SET #Puntaje2 = (SELECT 10
FROM EstadoDoc, inserted
WHERE inserted.IdDoc = EstadoDoc.IdDoc
AND inserted.Estado IN ('A')
AND EstadoDoc.Estado = 'RyR')
IF #Puntaje1 != NULL
BEGIN
UPDATE EstadoDoc
SET PuntajePrevio = #puntaje1
FROM EstadoDoc, inserted
WHERE inserted.IdDoc = EstadoDoc.IdDoc
AND inserted.Secuencia = EstadoDoc.Secuencia
END
IF #Puntaje2 != NULL
BEGIN
UPDATE EstadoDoc
SET PuntajePrevio = #puntaje2
FROM EstadoDoc, inserted
WHERE inserted.IdDoc = EstadoDoc.IdDoc
AND inserted.Secuencia = EstadoDoc.Secuencia
END
See all my comments - you basically need to rewrite your trigger to use a proper set-based approach to handle multi-row inserts, and you need to get in the habit of using proper ANSI/ISO JOIN syntax (instead of the deprecated comma-separated table list in the FROM clause).
Try something like this - the trigger also looks a lot simpler that way!
CREATE OR ALTER TRIGGER [dbo].[trigger_EstadoDoc]
ON [dbo].[EstadoDoc]
AFTER INSERT AS
BEGIN
SET NOCOUNT ON;
-- as per charlieface's recommendation - check to see if any rows
-- have actually been inserted at all - if not, bow out
IF (NOT EXISTS (SELECT * FROM Inserted))
RETURN;
UPDATE e
SET PuntajePrevio = 5
FROM dbo.EstadoDoc e
INNER JOIN inserted i ON i.IdDoc = e.IdDoc
AND i.Secuencia = e.Secuencia
WHERE e.Estado = 'S'
AND i.Estado IN ('A', 'RyR');
UPDATE e
SET PuntajePrevio = 10
FROM dbo.EstadoDoc e
INNER JOIN inserted i ON i.IdDoc = e.IdDoc
AND i.Secuencia = e.Secuencia
WHERE i.Estado = 'A'
AND e.Estado = 'RyR';
END
I've different different tables to categorically store data and a log table where all the transactions log are recorded
e.g. 1) VoucherNO, Add, ...
2) VoucherNO, Delete, ..
After I backup the database and restore in another server for my Reporting Purpose. That time I want to ensure all the log data and transaction are available in TestDB if not then I remove log from 'AUD_USER_ACTIVITY'.
To find the transaction exist or not, I create a dynamic sql select statement and check whether record is exist or not.
Basis on #RecExist Value I do the action like if records is not available in TestDB the log will be remove, if record exist immediately break this loop and going for next procedure
But #RecExist variable is not updating in Dynamic SQL Execution. Please guide me
declare #MvDocNo varchar(50)
DECLARE #SCtr as DECIMAL(10,0)
declare #LocationCode varchar(4)
declare #UName Nvarchar(40)
declare #toe varchar(30)
declare #QryTxt as nvarchar(MAX);
Declare #RecExist as INT =0;
SET #RecExist=0
WHILE #RecExist=0
BEGIN
select top 1 #MvDocNo=DOCNO, #SCtr=SrlNo,#LocationCode =DMLTYPE,#UName=TABLENAME
FROM R_AUDDB..AUD_USER_ACTIVITY
WHERE DBNAME='TestDB' and DMLTYPE not in ('AD','D','PD') ORDER BY SRLNO DESC;
select top 1 #toe=docno from TestDB..M_TYPEOFENTRY where TBLNAME=#UName;
set #QryTxt='Select #RecExist=1 From R_TestDB..'+#UName+ ' Where '+#toe+'='''+#MvDocNo+''''
exec (#QryTxt)
IF #RecExist=0
BEGIN
DELETE R_AUDDB..AUD_USER_ACTIVITY WHERE SRLNO=#SCtr
END
END
The following code sample demonstrates how to check for a row in a table with a specific column and value using dynamic SQL. You ought to be able to change the values of the first three variables to reference a table and column in your database for testing.
Note that SQL injection is still possible: there is no validation of the table or column names.
-- Define the table to check and the target column name and value.
declare #TableName as SysName = 'Things';
declare #ColumnName as SysName = 'ThingName';
declare #TestValue as NVarChar(32) = 'Beth';
-- Create a SQL statement to check for a row in the target table with the specified column name and value.
declare #SQL as NVarChar(1024);
declare #Result as Bit;
-- Note that only object names are substituted into the statement at this point and QuoteName() is used to reduce problems.
set #SQL = N'select #iResult = case when exists ( select 42 from dbo.' + QuoteName( #TableName ) +
N' where ' + QuoteName( #ColumnName ) + N' = #iTestValue ) then 1 else 0 end;'
select #SQL as SQL;
-- Execute the SQL statement.
-- Note that parameters are used for all values, i.e. the target value and return value.
execute sp_executesql #stmt = #SQL,
#params = N'#iTestValue NVarChar(32), #iResult Bit output',
#iTestValue = #TestValue, #iResult = #Result output
-- Display the result.
select #Result as Result;
My problem is I reach the limit of the stack. And the message error says “You should increase max_stack_depth” and shows me the line that I use to update another column.
I encounter this error after an update request (code below).
I know my problem may look like others questions but none of them explain why I reach this error.
What I want to do is simple and I've done it many times, but here I'm missing something.
I want: if there is an update on the table support_fh pull a trigger. I expect this trigger to do:
if the new values of the update request are section= 'DISTRIBUTION' and modulo= '6' and fabricant = 'NEXANS' and capacite = 12 then set diametre = '12.5' (code below).
Of course it is the line of diametre from the same line than update request.
Futhermore I know I should use the character varying type instead of the integer type, but I was asked to so it like that.
My trigger function:
create or replace function maj_diam() returns trigger
as
$$
Declare fab_loc character varying;
Declare section_loc character varying;
Declare capa_loc character varying;
Declare modulo_loc character varying;
BEGIN
Select fabricant into fab_loc from support_fh where id = new.id;
Select section into section_loc from support_fh where id = new.id;
Select capcite into capa_loc from support_fh where id = new.id;
Select modulo into modulo_loc from support_fh where id = new.id;
if fab_loc = 'NEXANS' and section_loc = 'DISTRIBUTION'
and capa_loc = '12' and modulo_loc = '6' then
update support_fh set diametre = '12.2' where id = new.id;
endif;
return new;
end;
$$;
My trigger :
create trigger maj_diam
After update on support_fh
for each row
execute procedure maj_diam();
My update request to test my trigger :
update support_fh set fabricant = 'NEXANS', section = 'DISTRIBUTION', capacite = '12', modulo = '6'
where id = 11827;
I want to learn from this, so, if possible, explain to me what I'm doing wrong here, or if my approach is lacking insight.
You get that problem because the update in the trigger will launch the trigger again, causing an infinite loop. No value of max_stack_depth is big enough for that (and increasing that value too much is dangerous anyway).
Instead of what you are doing, you should create a BEFORE trigger and modify the NEW value that are about to be inserted:
IF NEW.fab_loc = 'NEXANS' AND NEW.section_loc = 'DISTRIBUTION'
AND NEW.capa_loc = '12' AND NEW.modulo_loc = '6'
THEN
NEW.diametre := '12.2';
END IF;
If you want to change columns in a row that is updated (or inserted), don't use UPDATE in the trigger function. Declare the trigger as BEFORE UPDATE, then simply assign the new values.
You also don't need four select statements to read four columns from the same table.
But as you are only accessing columns from the same row that was updated, you don't even need a SELECT at all.
So your trigger function can be simplified to:
create or replace function maj_diam() returns trigger
as
$$
BEGIN
if new.fabricant = 'NEXANS'
and new.section = 'DISTRIBUTION'
and new.capcite = '12'
and new.modulo = '6'
then
new.diametre := '12.2';
end if;
return new;
end;
$$;
Assuming that capcite, modulo and diametre are actually numbers, you shouldn't compare them with varchar values. So the above code should probably be: new.diametre := 12.2; or new.capcite = 12.
And the trigger definition needs to be changed to:
create trigger maj_diam
BEFORE update on support_fh
for each row
execute procedure maj_diam();
I wish to know whether it's feasible to have a TSQL stored procedure return both a result set and the output parameter like so.
create procedure uspReadMyXmlInformation(#myXmlDoc xml, #myProductNum varchar(18) output) as
begin
set nocount on;
declare #myXmlContent table(MyOrderId varchar(12) not null
,CreatedAt datetime not null)
insert into #myXmlContent
select x.c.value('MyOrderID[1]', 'varchar(12)')
x.c.value('CreatedAt[1]', 'datetime')
from #myXmlDoc.nodes('MyRootNodeName/MyChildNodeName') x(c)
set #myProductNum='MyProductNum'
select *
from #myXmlContent
return;
end
So, what happens here is that I can either obtain the result set, when I remove the output parameter, or I obtain the output parameter and the result set is always empty (0=count(*)).
Is there anyway I can obtain both with the same stored procedure or I'd better split them?
I think it's doable from this post in Oracle. I'd like to achieve the same in SQL Server, although constrained to the 2008 version.
Oracle stored procedure: return both result set and out parameters
What I like from doing it using the same SP is that both the result set and the output parameter represent information I read from the XML document. So, the name of the SP says it all, somehow.
EDIT
As some think it might be a duplicate of:
Possible to return an out parameter with a DataReader
I don't think it is as answers there are related as to how the DataReader behaves more than how it could be achieved with TSQL.
The fact is that I get the the value from the output parameter, but I don't get it from the result set at all, it's always returning null.
So, I'm on a SQL Server only project and I'd need that. Otherwise, I'll split it in two, if I can't achieve it in a timely fashion.
Here's how it's used:
declare #xmlInformationData table(MyOrderId varchar(12) not null
,CreatedAt datetime not null)
insert into #xmlInformationData
execute uspReadMyXmlInformation #myXmlDoc, #myProductNum output
while 0<(select count(*) from #xmlInformationData)
begin
-- This will never be executed because I have no rows in #xmlInformationData
-- And yet, before the loop, I have my out param value!
end
The following is a trivial demonstration of using both an output parameter and result set. Try running it a few times and the results should vary.
create procedure Arthur( #TheAnswer as Int Output ) as
begin
-- Set the value of the output parameter.
set #TheAnswer = 42;
-- Generate a single row most of the time.
select GetDate() as NextVogonPoetryReading
where DatePart( millisecond, GetDate() ) < 750;
end;
go 1
-- Declare the variables for the test.
declare #HHGTTU as Table ( HHGTTUId Int Identity, NextVogonPoetryReading DateTime );
declare #SixTimesNine as Int;
-- Execute the SP once so that the while loop might.
insert into #HHGTTU ( NextVogonPoetryReading )
execute Arthur #TheAnswer = #SixTimesNine Output;
-- See what happens.
while exists ( select Pi() from #HHGTTU )
begin
-- See where we are at.
select #SixTimesNine as SixTimesNine, Max( HHGTTUId ) as MaxHHGTTUId, Max( NextVogonPoetryReading ) as MaxNextVogonPoetryReading
from #HHGTTU;
-- Reset.
delete from #HHGTTU;
set #SixTimesNine = 54;
select #SixTimesNine as SixTimesNineAfterReset;
waitfor delay '00:00:00.100';
-- Execute the SP again.
insert into #HHGTTU ( NextVogonPoetryReading )
execute Arthur #TheAnswer = #SixTimesNine Output;
end;
Aside: My apologies for the trauma introduced into your life by my mention of a DataReader. I was merely attempting to pass on my experience in a C# application without getting into the weeds of exactly what sort of connection to the database you are using, which driver(s) might be involved, ... .
In my stored procedure I have multiple similar variables #V1, #V2 ... #V20 (let's say 20 of them) FETCHED from a record. How would I use dynamic SQL to make 20 calls to another stored procedure using those variables as parameters?
Of course #V[i] syntax is incorrect, but it expresses the intent
fetch next from maincursor into #status, #V1, #V2, ...
while #i<21
begin
-- ??? execute sp_executesql 'SecondSP', '#myParam int', #myParam=#V[i]
-- or
-- ??? execute SecondSP #V[i]
set #i = #i+1
end
As others have said, set up a temporary table, insert the values that you need into it. Then "iterate" through it executing the necessary SQL from those values. This will allow you to have 0 to MANY values to be executed, so you don't have to set up a variable for each.
The following is a complete sample of how you may go about doing that without cursors.
SET NOCOUNT ON
DECLARE #dict TABLE (
id INT IDENTITY(1,1), -- a unique identity column for reference later
value VARCHAR(50), -- your parameter value to be passed into the procedure
executed BIT -- BIT to mark a record as being executed later
)
-- INSERT YOUR VALUES INTO #dict HERE
-- Set executed to 0 (so that the execution process will pick it up later)
-- This may be a SELECT statement into another table in your database to load the values into #dict
INSERT #dict
SELECT 'V1Value', 0 UNION ALL
SELECT 'V2Value', 0
DECLARE #currentid INT
DECLARE #currentvalue VARCHAR(50)
WHILE EXISTS(SELECT * FROM #dict WHERE executed = 0)
BEGIN
-- Get the next record to execute
SELECT
TOP 1 #currentid = id
FROM #dict
WHERE executed = 0
-- Get the parameter value
SELECT #currentvalue = value
FROM #dict
WHERE id = #currentid
-- EXECUTE THE SQL HERE
--sp_executesql 'SecondSP', '#myParam int', #myParam =
PRINT 'SecondSP ' + '#myParam int ' + '#myParam = ' + #currentvalue
-- Mark record as having been executed
UPDATE d
SET executed = 1
FROM #dict d
WHERE id = #currentid
END
Use a #TempTable
if you are at SQL Server 2005 you can create a #TempTable in the parent stored procedure, and it is available in the child stored procedure that it calls.
CREATE TABLE #TempTable
(col1 datatype
,col2 datatype
,col3 datatype
)
INSERT INTO #TempTable
(col1, col2, col3)
SELECT
col1, col2, col3
FROM ...
EXEC #ReturnCode=YourOtherProcedure
within the other procedure, you have access to #TempTable to select, delete, etc...
make that child procedure work on a set of data not on one element at a time
remember, in SQL, loops suck performance away!
Why not just use the table variable instead, and then just loop through the table getting each value.
Basically treat each row in a table as your array cell, with a table that has one column.
Just a thought. :)
This seems like an odd request - will you always have a fixed set of variables? What if the number changes from 20 to 21, and so on, are you constantly going to have to be declaring new variables?
Is it possible, instead of retrieving the values into separate variables, to return them each as individual rows and just loop through them in a cursor?
If not, and you have to use the individual variables as explained, here's one solution:
declare #V1 nvarchar(100)
set #V1 = 'hi'
declare #V2 nvarchar(100)
set #V2 = 'bye'
declare #V3 nvarchar(100)
set #V3 = 'test3'
declare #V4 nvarchar(100)
set #V4 = 'test4'
declare #V5 nvarchar(100)
set #V5 = 'end'
declare aCursor cursor for
select #V1
union select #V2 union select #V3
union select #V4 union select #V5
open aCursor
declare #V nvarchar(100)
fetch next from aCursor into #V
while ##FETCH_STATUS = 0
begin
exec TestParam #V
fetch next from aCursor into #V
end
close aCursor
deallocate aCursor
I don't really like this solution, it seems messy and unscalable. Also, as a side note - the way you phrased your question seems to be asking if there are arrays in T-SQL. By default there aren't, although a quick search on google can point you in the direction of workarounds for this if you absolutely need them.