Stored Procedure Does Not Fire Last Command - sql-server

On our SQL Server (Version 10.0.1600), I have a stored procedure that I wrote.
It is not throwing any errors, and it is returning the correct values after making the insert in the database.
However, the last command spSendEventNotificationEmail (which sends out email notifications) is not being run.
I can run the spSendEventNotificationEmail script manually using the same data, and the notifications show up, so I know it works.
Is there something wrong with how I call it in my stored procedure?
[dbo].[spUpdateRequest](#packetID int, #statusID int output, #empID int, #mtf nVarChar(50)) AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #id int
SET #id=-1
-- Insert statements for procedure here
SELECT A.ID, PacketID, StatusID
INTO #act FROM Action A JOIN Request R ON (R.ID=A.RequestID)
WHERE (PacketID=#packetID) AND (StatusID=#statusID)
IF ((SELECT COUNT(ID) FROM #act)=0) BEGIN -- this statusID has not been entered. Continue
SELECT ID, MTF
INTO #req FROM Request
WHERE PacketID=#packetID
WHILE (0 < (SELECT COUNT(ID) FROM #req)) BEGIN
SELECT TOP 1 #id=ID FROM #req
INSERT INTO Action (RequestID, StatusID, EmpID, DateStamp)
VALUES (#id, #statusID, #empID, GETDATE())
IF ((#mtf IS NOT NULL) AND (0 < LEN(RTRIM(#mtf)))) BEGIN
UPDATE Request SET MTF=#mtf WHERE ID=#id
END
DELETE #req WHERE ID=#id
END
DROP TABLE #req
SELECT #id=##IDENTITY, #statusID=StatusID FROM Action
SELECT TOP 1 #statusID=ID FROM Status
WHERE (#statusID<ID) AND (-1 < Sequence)
EXEC spSendEventNotificationEmail #packetID, #statusID, 'http:\\cpweb:8100\NextStep.aspx'
END ELSE BEGIN
SET #statusID = -1
END
DROP TABLE #act
END
Idea of how the data tables are connected:

From your comments I get you do mainly C# development. A basic test is to make sure the sproc is called with the exact same arguments you expect
PRINT '#packetID: ' + #packetID
PRINT '#statusID: ' + #statusID
EXEC spSendEventNotificationEmail #packetID, #statusID, 'http:\\cpweb:8100\NextStep.aspx'
This way you 1. know that the exec statement is reached 2. the exact values
If this all works than I very good candidate is that you have permission to run the sproc and your (C#?) code that calls it doesn't. I would expect that an error is thrown tough.
A quick test to see if the EXEC is executed fine is to do an insert in a dummy table after it.
Update 1
I suggested to add PRINT statements but indeed as you say you cannot (easily) catch them from C#. What you could do is insert the 2 variables in a log table that you newly create. This way you know the exact values that flow from the C# execution.
As to the why it now works if you add permissions I can't give you a ready answer. SQL security is not transparent to me either. But its good to research yourself a but further. Do you have to add both guest and public?
It would also help to see what's going inside spSendEventNotificationEmail. Chances are good that sproc is using a resource where it didn't have permission before. This could be an object like a table or maybe another sproc. Security is heavily dependent on context/settings and not an easy problem to tackle with a Q/A site like SO.

Related

SQL Server stored procedures reading data before insert completed

I'm new to SQL Server and stored procedures and could do with a couple of pointers regarding transaction handling on a bug I've inherited.
I have two stored procedures, one inserts a record passed into it, then it calls another one where the first thing it does is read what was inserted.
But sometimes it completes successfully without processing the data. My suspicion is that the selects are happening before the insert has 'hit' the table and retrieve no records, and the stored procedure doesn't handle that.
I don't have time to re-engineer just yet, but the transaction handling looks suspect. Below is a rough outline of what the stored procedures do.
procedure sp1
(#id, #pbody)
as
begin
begin try
set nocount on;
begin
insert into tbl1 (id, tbody)
values (#id, #pbody)
exec sp2 #id
end
end try
begin catch
execute sperror
end catch
end
go
procedure sp2 (#id)
as
begin
begin try
set nocount on;
declare #vbody varchar(max)
select #vbody = tbody -- I don't believe this step always retrieves the row inserted by sp1
from tbl1 with (nolock)
where id = #id
create table #tmp1 (id, msg)
insert into #tmp1
select id, msg
from openjson........
while exists(select top 1 * from #tmp1) -- this looks similar to above, not sure the insert has finished before the read
begin
** do some stuff **
end
end try
begin catch
execute sperror
end catch
end
go
sp2 is using the WITH (NOLOCK) query hint, which can have unintended side-effects. Missing rows is just one of them.
Using NOLOCK? Here's How You'll Get the Wrong Query Results. - Brent Ozar UnlimitedĀ®
I'd strongly recommend removing that hint unless you really understand what it does and have a very good reason for using it.

What is the correct way to capture OUTPUT deleted.x from EXEC stored_procedure?

I am using a table as a FIFO queue under high concurrency conditions, and dequeueing atomically like this:
CREATE PROCEDURE [jobs].[DequeueJob]
AS
BEGIN
DELETE TOP(1)
FROM jobs.JobQueue WITH (ROWLOCK, READPAST)
OUTPUT deleted.JobID;
END
I call this stored procedure from another to capture and use the deleted.JobID, so I have this:
CREATE PROCEDURE [jobs].[GetJob]
AS
BEGIN
DECLARE #Job TABLE (JobID int)
INSERT #Job EXEC jobs.DequeueJob
DECLARE #JobID int
SET #JobID = (SELECT JobID from #Job)
UPDATE jobs.Jobs
SET IsInQueue = 0
WHERE JobID = #JobID
SELECT *
FROM jobs.Jobs
WHERE JobID = #JobID
END
This works fine, but it smells: to get the value (JobID) that I want to use, I have to create a table variable with one column, INSERT a row into it, and then immediately SELECT that same row back out. Is there a better way (from either a readability or optimization perspective) to capture deleted.JobID when I call EXEC jobs.DequeueJob that will not break its atomicity?
EXEC #JobID = jobs.DequeueJob is valid syntax and looked promising, but it captures the return value, not the OUTPUT value. This answer has a good discussion of several result-passing options, but I can't find a way to fit my situation to any of them.
I'm using SQL Server 2012 SP3, but I'm prepared to upgrade if improved syntax for this is available in a newer version.
I agree with Shakeer Mirza's comment that inserting the OUTPUT clause result into a table is unavoidable since that's the only way the value can be used in T-SQL. Unless, you have a specific reason for separate procs, you could refactor like the example below, which is slightly less smelly, IMHO.
CREATE PROCEDURE [jobs].[GetNextJob]
AS
DECLARE #Job TABLE ( JobID int PRIMARY KEY );
DELETE TOP(1) FROM jobs.JobQueue WITH (ROWLOCK, READPAST)
OUTPUT deleted.JobID INTO #Job(JobID);
UPDATE jobs.Jobs set IsInQueue = 0
OUTPUT inserted.*
WHERE JobID = (SELECT JobID from #Job);
GO
Note that there is no defined order with DELETE TOP so, if you require FIFO, consider using a subquery with ORDER BY in the DELETE.

openquery apears to be rolled back when done

I'm using the following query.
select * from OPENQUERY(EXITWEB,N'SET NOCOUNT ON;
declare #result table (id int);
insert into [system_files] ([is_public], [file_name], [file_size], [content_type], [disk_name], [updated_at], [created_at])
output inserted.id into #result(id)
values (N''1'',N''7349.jpg'',N''146921'',N''image/jpeg'',N''5799dcc8a1eb1413195192.jpg'',N''2016-07-28 10:22:00.000'',N''2016-07-28 10:22:00.000'')
declare #id int = (select top 1 id from #result)
select * from system_files where id = #id
insert into linkToExternal (id, id_ext) values(#id, 47)
--select #id
')
when I perform a select from within the query it works just fine:
But when I go to check my database when the call has finished, the record is no longer there.
So I'm suspecting a transaction is rolled back. My question is: why. What can I do to prevent the transaction to be rolled back if that's the case.
Well, as always, after days of struggling and me post a question on stackoverflow I find the solution: http://www.sqlservercentral.com/Forums/Topic1128997-391-1.aspx#bm1288825
I was having the same problem as you and almost gave up on it but have
finally found an answer to the problem. Reading an article about
sharing data between stored procedures I discovered that OPENQUERY
issues an Implicit Transaction and that it was Rolling back my insert.
So I had to add an explicit Commit to my stored procedures, in
additional I discovered that if I use it in a query that has a Union
it has to be Commited twice. Since I'm doing my insert inside a BEGIN
TRY I can always just commit twice and not worry about whether it is
being used in a UNION. I'm returning different values if there is an
error but that was just apart of my debugging.
SELECT TOP 5 *
FROM mm
JOIN OPENQUERY([LOCALSERVER], 'EXEC cms60.dbo.sp_RecordReportLastRun ''LPS'', ''Test''') RptStats ON 1=1
ALTER PROCEDURE [dbo].[sp_RecordReportLastRun]
-- Add the parameters for the stored procedure here
#LibraryName varchar(50),
#ReportName varchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
BEGIN TRY
INSERT INTO cms60.dbo.ReportStatistics (LibraryName, ReportName, RunDate) VALUES (#LibraryName, #ReportName, GETDATE())
--
COMMIT; --Needed because OPENQUERY starts an Implicit Transaction but doesn't commit it.
COMMIT; --Need second Commit when used in a UNION and although it throws an error when not used in a UNION doesn't cause a problem.
END TRY
BEGIN CATCH
SELECT 2 Test
END CATCH
SELECT 1 Test
END
In my case, adding a ;COMMIT; after the inserts solved it, and made sure it got written into the database.

SQL procedure from Excel run from connection properties with user login as parameter

I want to run a SQL procedure from Excel. I want to do it by setting connection properties with command type as SQL. Here is a good example:
http://datapigtechnologies.com/blog/index.php/running-a-sql-stored-procedure-from-excel-with-dynamic-parameters/
How can I get user login and insert it to a specially prepared dbo.login_history_table? How should be written command text of Connection Properties.
I am looking for something like:
EXECUTE dbo.procedure_who_gets_here ORIGINAL_LOGIN()
where ORIGINAL_LOGIN() is a parameter.
Update. Here is the code of a stored procedure.
create procedure procedure_who_gets_here
(
#UserLogin as varchar(255)
)
as
begin
-- part one, get user data
insert into dbo.LoginsHistory
select
getdate(),
ORIGINAL_LOGIN(), -- if ORIGINAL_LOGIN() worked, the #UserLogin would be superfluous
#UserLogin
-- part two, show user what he has to see
select * from dbo.MyTable
end
All credits for hints to Sean Lange for this answer. I had to change the order of the two parts. First, SELECT then INSERT. Everything works now. Thank you, Sean.
create procedure procedure_who_gets_here
as
begin
-- part two, show user what he has to see
select * from dbo.MyTable
-- part one, get user data
insert into dbo.LoginsHistory
select
getdate(),
ORIGINAL_LOGIN()
end

How can I get number of deleted records?

I have a stored procedure which deletes certain records. I need to get the number of deleted records.
I tried to do it like this:
DELETE FROM OperationsV1.dbo.Files WHERE FileID = #FileID
SELECT ##ROWCOUNT AS DELETED;
But DELETED is shown as 0, though the appropriate records are deleted. I tried SET NOCOUNT OFF; without success. Could you please help?
Thanks.
That should work fine. The setting of NOCOUNT is irrelevant. This only affects the n rows affected information sent back to the client and has no effect on the workings of ##ROWCOUNT.
Do you have any statements between the two that you have shown? ##ROWCOUNT is reset after every statement so you must retrieve the value immediately with no intervening statements.
I use this code snippet when debugging stored procedures to verify counts after operations, such as a delete:
DECLARE #Msg varchar(30)
...
SELECT #Msg = CAST(##ROWCOUNT AS VARCHAR(10)) + ' rows affected'
RAISERROR (#Msg, 0, 1) WITH NOWAIT
START TRANSACTION;
SELECT #before:=(SELECT count(*) FROM OperationsV1.dbo.Files);
DELETE FROM OperationsV1.dbo.Files WHERE FileID = #FileID;
SELECT #after:=(SELECT count(*) FROM OperationsV1.dbo.Files);
COMMIT;
SELECT #before-#after AS DELETED;
I don't know about SQL server, but in MySQL SELECT count(*) FROM ... is an extremely cheap operation.

Resources