I am trying to debug a stored procedure(lets say proc1) that populates a #table, lets say #reserves. The procedure has no insert statements for #reserves but still there is some data being populated into the table which is being sent to the output as a result set.
Proc1 internally calls Proc2 which as well has a #reserves declared and is populated with some data. My question is - is there a way that Proc2.#reserves would send data to Proc1.#reserves?
Below is the sample procedure
Create Proc1
As
Begin
Create Table #Reserves
(
id int not null,
value int not null
)
Exec Proc2
Select id,
value
from #Reserves
End
Create Proc2
AS
Begin
Create Table #Reserves
(
id int not null,
value int not null
)
Insert into #reserves
(id, value)
Select 1 as id,2 as value
End
Each proc is creating it's own version of #Reserves (this is doable in ASE; if, before you exit proc2, you query your tempdb's sysobjects table you should see 2 entries for name like '#Reserves%').
If you want proc2 to populate the #Reserves created by proc1, consider modifying proc2 to only create #Reserves if it doesn't already exist.
Related
I want to create a stored procedure or database function to delete some entries and return their IDs to the client. I started off with this FUNCTION:
CREATE FUNCTION dbo.FixHangingCarriers ()
RETURNS #returntable TABLE
(
ID_Plant INT NOT NULL,
ID_ChargeCarrier INT NOT NULL
)
AS
BEGIN;
-- Declare a temporary table
DECLARE #EntriesToDelete TABLE (
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NOT NULL,
StoredOn DATETIME2(2) NOT NULL
);
-- Select all the entries that should be deleted
INSERT INTO #EntriesToDelete
SELECT
ID_ChargeCarrier,
ID_Storage,
StoredOn
FROM dbo.CurrentStorage
WHERE ID_StorageType = 4
AND StoredOn < DATEADD(MINUTE, -30, GetDate());
-- Return immediately, if there is currently nothing to delete
IF (SELECT 1 FROM #EntriesToDelete) = 1
RETURN;
-- Delete hanging entries
DELETE ccs
FROM dbo.ChargeCarrier_Storage ccs
INNER JOIN #EntriesToDelete d ON
d.ID_ChargeCarrier = ccs.ID_ChargeCarrier AND
d.ID_Storage = ccs.ID_Storage AND
d.StoredOn = ccs.StoredOn;
-- Prepare return table
INSERT INTO #returntable
SELECT cs.ID
FROM #EntriesToDelete d
INNER JOIN dbo.ChargeCarriersCurrentlyInPlant cp ON
cp.ID_ChargeCarrier = d.ID_ChargeCarrier;
-- Return deleted entries to caller
RETURN;
END;
Obviously this doesn't work, because SQL functions cannot delete anything. I wanted to change it into a PROCEDURE, but then I saw, that procedures naturally seem not to be able to return a table. How can I solve this issue?
As the comments suggest, this seems like all you need is this:
CREATE PROC dbo.FixHangingCarriers
AS BEGIN
DELETE ccs
OUTPUT deleted.ID
FROM dbo.ChargeCarrier_Storage ccs
INNER JOIN CurrentStorage cs ON cs.ID_ChargeCarrier = ccs.ID_ChargeCarrier
AND cs.ID_Storage = ccs.ID_Storage
AND cs.StoredOn = ccs.StoredOn
WHERE cs.ID_StorageType = 4
AND cs.StoredOn < DATEADD(MINUTE, -30, GETDATE());
END;
You basically have two choices.
One is to return the rows as part of a SELECT statement as suggested. However, if you want to consume those in the next bit of T-SQL, you can't just SELECT from a table returned by a stored procedure.
Lots of people ask for a command like SELECT * FROM (EXEC someproc) but that doesn't exist.
What you can do instead is to define a table variable like this:
DECLARE #OutputIDs TABLE
(
OutputIDKey int IDENTITY(1,1) PRIMARY KEY,
OutputID int
);
Then you can use an INSERT EXEC to get the values:
INSERT #OutputIDs (OutputID)
EXEC dbo.MyStoredProcedure;
And then you have the IDs in the table variable to do what you want with. (Note: you could also do that with a temporary table but it isn't as good an option)
The other option is to use an OUTPUT parameter to the stored procedure. However, you'd need to pack all your IDs into a string (perhaps a comma-delimited list) before you return it. You then use a function to unpack the string back to a set of IDs. (If you're on SQL Server 2016 or later, you could use the STRING_SPLIT function).
The trick with using OUTPUT parameters is that you have to define it as OUTPUT when you are calling the procedure, and in the header of the procedure itself. Here's the skeleton code:
CREATE PROCEDURE dbo.DoSomething
#OtherParameter int,
#IDsToOutput nvarchar(max) OUTPUT,
#YetAnotherParameter int
AS
BEGIN
...
END;
And then when you call the procedure, you do this:
DECLARE #OutputIDs nvarchar(max);
EXEC dbo.DoSomething #OtherParameter = 1,
#IDsToOutput = #OutputIDs OUTPUT,
#YetAnotherParameter = 2;
SELECT * FROM STRING_SPLIT(#OutputIDs, ',');
Hope that helps.
The goal is to get one results out of those three stored procedures by grouping them by Id to avoid duplication!
I am having three stored procedure , they returns same tables with different results based on the logic, I have created. I am stuck on how to filter the results to avoid duplication, and return one table with all of them (filtered based on Id Col).
ALTER PROC [dbo].[Recommendation]
#SurveyInstanceId INT
AS
/*
DECLARE
#SurveyInstanceId INT = 74
EXECUTE [dbo].[recommendation]
#SurveyInstanceId
*/
EXECUTE [dbo].[Resources_Question10Answers]
#SurveyInstanceId
EXECUTE dbo.Resources_Question11Answers
#SurveyInstanceId
EXECUTE [dbo].[Resources_Question1Answers]
#SurveyInstanceId
You can INSERT the results of a stored procedure into a temp table or table variable, and then send the results of a query from that table to the client.
eg:
ALTER PROC [dbo].[Recommendation]
#SurveyInstanceId INT
AS
/*
DECLARE
#SurveyInstanceId INT = 74
EXECUTE [dbo].[recommendation]
#SurveyInstanceId
*/
begin
set nocount on;
declare #results table (id int, ...)
insert into #results
EXECUTE [dbo].[Resources_Question10Answers]
#SurveyInstanceId
insert into #results
EXECUTE dbo.Resources_Question11Answers
#SurveyInstanceId
insert into #results
EXECUTE [dbo].[Resources_Question1Answers]
#SurveyInstanceId
select id, ...
from #results
group by id
end
I am trying to trigger a stored procedure to take the inserted values into my stored procedure as parameters and it is not letting me.
My table flow goes like this: a patient's history information will be inserted (HISTORY_APPOINTMENTS) and if at the time the patient has a column value of HasSuicidalThoughts = 'Y' I want the trigger to send the inserted patients information into a table I created called SuicideWatchLog.
First I created the table:
/* Table Creation for SuicideWatch Log*/
CREATE TABLE SuicideWatchLog
(
logNum integer IDENTITY(1,1) NOT NULL PRIMARY KEY,
PatientStudy# integer FOREIGN KEY References Patients(PatientStudy#),
PatientName varchar(20),
[Date] date,
Dr# integer FOREIGN KEY References DOCTORS(Dr#),
DaysinStudy integer
)
Next I created the procedure:
CREATE PROCEDURE AddSuicideWatch
#PatientStudy# integer,
#PatientName varchar(20),
#Date date,
#Dr# integer,
#DaysinStudy integer
AS
BEGIN
INSERT INTO SuicideWatchLog(PatientStudy#, Date, Dr#)
(SELECT PatientStudy#, ApptDate, Dr#
FROM APPOINTMENTS
WHERE #PatientStudy# = PatientStudy#
AND #Date = ApptDate
AND #Dr# = Dr#)
INSERT INTO SuicideWatchLog(PatientName, DaysinStudy)
(SELECT PatientFName, datediff(day,StudyStartDate,getdate())
FROM PATIENTS
WHERE #PatientName = PatientFName
AND #DaysinStudy = datediff(day,StudyStartDate,getdate()))
END
Finally I created the trigger:
CREATE TRIGGER SuicidalPatient
ON HISTORY_APPOINTMENT
AFTER INSERT
AS
EXEC AddSuicideWatch(
SELECT (I.PatientStudy#, P.PatientFName, A.ApptDate,
FROM INSERTED I
JOIN APPOINTMENTS A ON I.Appt# = A.Appt#
JOIN PATIENTS P ON I.PatientStudy# = P.PatientStudy#)
I expected this to allow me to send the inserted values into the stored procedure to trigger the creation of the log, but instead I am getting an error that is telling me my parameters aren't being found.
Is this an issue with the select statement, or is it a problem with the procedure itself?
Is this an issue with the select statement, or is it a problem with the procedure itself?
Your stored procedure accepts scalar parameters. You can't pass a whole resultset to it. You can:
1) Integrate the INSERTs directly into the trigger body, eliminating the stored procedure.
2) Open a cursor over the query in the trigger, and loop through the rows, calling the stored procedure fore each one.
3) Declare a User-Defined Table Type matching the query result rows, declare and load an instance of the table type in the trigger body, and change the stored procedure to accept a Table-Valued Parameter.
you cant pass table to sp , but i know 2 ways for that :
1- use user defined type like that :
create type NewTable AS table (PatientStudy# int, PatientFName nvarchar(max), ApptDate date)
and the insert into NewTable Then call sp
declare #TempTable NewTable
insert into #TempTable(PatientStudy# , PatientFName , ApptDate)
select I.PatientStudy#, P.PatientFName, A.ApptDate,
FROM INSERTED I
JOIN APPOINTMENTS A ON I.Appt# = A.Appt#
JOIN PATIENTS P ON I.PatientStudy# = P.PatientStudy#
EXEC AddSuicideWatch( #TempTable)
and of course you should edit your SP :
CREATE PROCEDURE AddSuicideWatch
#Table NewTable
AS
BEGIN
INSERT INTO SuicideWatchLog(PatientStudy#, Date, Dr#)
SELECT PatientStudy#, ApptDate, Dr#
FROM APPOINTMENTS A join #Table T ON
A.PatientStudy# = T.PatientStudy#
A.Date = T.ApptDate
A.Dr# = D.Dr#
INSERT INTO SuicideWatchLog(PatientName, DaysinStudy)
(SELECT PatientFName, datediff(day,StudyStartDate,getdate())
FROM PATIENTS P join #Table T ON
T.PatientName = P.PatientFName
AND A.DaysinStudy = datediff(day,StudyStartDate,getdate()))
END
And the seccond way : just pass the primary key to sp and handle other things in sp
I am in a situation where I need to insert multiple records in one stored procedure.
I use "Table valued parameter", comes from c# code and passing it to stored procedure. (this TVP has a list of analise IDs)
So, I am trying to create a loop; to insert multiple records and to iterate until the max rowsize of the #TVP rows.
How can i get the row size of the TVP (Table Valued Parameters) passed in the SP and iterate in these TVP rows to take their tvp.id's one by one, for a multiple insert?
SP is like this:
create proc insertTable
(
#nID int,
#TVP Analises READONLY
)
as
declare #i int
BEGIN
While #i <--max rownumber of #TVP
BEGIN
--insert into tbl_insertAnalyses
--values(#nID,#tvp.analiseID[i]) -- >need to iterate here
END
END
Thanks.
A Table-Valued Paramater (TVP) is a table variable. You can just do a simple INSERT...SELECT:
CREATE PROCEDURE insertTable
(
#nID int,
#TVP Analises READONLY
)
AS
SET NOCOUNT ON;
INSERT INTO tbl_insertAnalyses (ID, AnalysisID)
SELECT #nID, t.analiseID
FROM #TVP t;
I'm having a stored procedure which returns two result sets based on the success or failure.
SP success result set: name, id ,error,desc
SP failure result sret: error,desc
I'm using the following query to get the result of the stored procedure. It returns 0 for success and -1 for failure.
declare #ret int
DECLARE #tmp TABLE (
name char(70),
id int,
error char(2),
desc varchar(30)
)
insert into #tmp
EXEC #ret = sptest '100','King'
select #ret
select * from #tmp
If the SP is success the four field gets inserted into the temp table since the column matches.
But in case of failure the sp result set has only error and desc which does not matchs with no of columns in the temp table...
.I can't change the Sp, so I need to do some thing (not sure) in temp table to handle both failure and success.
You can't return 2 different recordsets and load the same temp table.
Neither can try and fill 2 different tables.
There are 2 options.
Modify your stored proc
All 4 columns are returned in all conditions
1st pair (name, ID) columns are NULL on error
2nd pair (error, desc) are NULL on success
If you are using SQL Server 2005 then use the TRY/CATCH to separate your success and fail code paths. The code below relies on using the new error handling to pass back the error result set via exception/RAISERROR.
Example:
CREATE PROC sptest
AS
DECLARE #errmsg varchar(2000)
BEGIN TRY
do stuff
SELECT col1, col2, col3, col4 FROM table etc
--do more stuff
END TRY
BEGIN CATCH
SELECT #errmsg = ERROR_MESSAGE()
RAISERROR ('Oops! %s', 16, 1, #errmsg)
END CATCH
GO
DECLARE #tmp TABLE ( name CHAR(70), id INT, error char(2), desc varchar(30)
BEGIN TRY
insert into #tmp
EXEC sptest '100','King'
select * from #tmp
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE()
END CATCH
My fault!!
Was too quick in the answer.
You need only to relv on the return value, so building up the logic against it is much better.
If you still want to use the temp table, then calling the sptest twice could be a way to deal with it (not optimal though), one time to get the return value and based on it then have 2 different temp tables you are filling up (one would be with the 4 fields, the other only with 2 fields).
declare #ret int
DECLARE #tmp TABLE (name CHAR(70), id INT, error char(2), desc varchar(30))
DECLARE #tmperror TABLE (error char(2), desc varchar(30))
EXEC #ret = sptest '100','King'
IF #ret != 0
BEGIN
INSERT INTO #tmperror
EXEC sptest '100','King';
SELECT * FROM #tmperror;
END
ELSE
BEGIN
INSERT INTO #tmp
EXEC sptest '100','King';
SELECT * FROM #tmp;
END
Keep in mind that this solution is not optimal.
Try modifying your table definition so that the first two columns are nullable:
DECLARE #tmp TABLE (
name char(70) null,
id int null,
error char(2),
desc varchar(30)
)
Hope this helps,
Bill
You cannot do this with just one call. You will have to call it once, either getting the return status and then branching depending on the status to the INSERT..EXEC command that will work for the number of columns that will be returned or Call it once, assuming success, with TRY..CATCH, and then in the Catch call it again assuming that it will fail (which is how it got to the CATCH).
Even better, would be to either re-write the stored procedure so that it returns a consistent column set or to write you own stored procedure, table-valued function or query, by extracting the code from this stored procedure and adapting it to your use. This is the proper answer in SQL.