For the application I work on... we're creating a custom logging system. The user can view logs and apply "Tags" to them (Just like how you can apply tags to questions here!)
In this example, I'm trying to get a list of all the Logs given a "Tag." I realize I can accomplish this by using joins... but this is also an exercise for me to learn Stored Procedures a little better :)
I have a stored procedure that looks something like this to select a log by the PK
ALTER PROCEDURE [dbo].[getLogByLogId]
-- Add the parameters for the stored procedure here
#ID int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT TOP 1
LOG_ID,
a.A,
a.B,
a.C
FROM dbo.LOG a
WHERE a.LOG_ID = #ID
Now I would like to call this Stored Procedure from another... something like this
ALTER PROCEDURE [dbo].[getLogsByTagName]
-- Add the parameters for the stored procedure here
#TAG nvarchar(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
SELECT TOP 1000
LOG_ID --somehow store this and execute the dbo.getLogByLogId procedure here
FROM dbo.LOG_TAG a
WHERE a.TAG = #TAG
Thanks
If you have complex logic in your logbyid SP which you are trying to avoid reproducing in multiple places in your system (choice of columns, derived columns, etc), I would recommend turning that into an inline table-valued function instead (potentially without taking the ID parameter, in which case, you can actually use an ordinary view).
Then you can either join to that ITVF/view in your other stored proc (or also make another udf) which does the search or use the OUTER APPLY functionality (not as efficient).
Inline table-valued functions are basically parameterized views and can be optimized fairly easily by the optimizer.
If you want to call another sproc from within a sproc just use:
CREATE PROCEDURE myTestProc
AS
BEGIN
--Do some work in this procedure
SELECT blah FROM foo
--now call another sproc
EXEC nameOfSecondSproc
END
The only way you can achive what you are attempting is by using a CURSOR.
If this is for your learning only, then by all means, give this a go, but I would not recomend this for production.
It would go something like this
DECLARE #Table TABLE(
ID INT
)
INSERT INTO #Table SELECT 1
INSERT INTO #Table SELECT 2
INSERT INTO #Table SELECT 3
INSERT INTO #Table SELECT 4
INSERT INTO #Table SELECT 5
INSERT INTO #Table SELECT 6
DECLARE Cur CURSOR FOR
SELECT ID
FROM #Table
OPEN Cur
DECLARE #ID INT
FETCH NEXT FROM Cur INTO #ID
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #ID
FETCH NEXT FROM Cur INTO #ID
END
CLOSE Cur
DEALLOCATE Cur
By using the #ID retrieved in the WHILE loop, you can then execute the sp you wish and insert the values into a table variable.
INSERT INTO #Table EXEC sp_MySP #ID
You can call a stored procedure from another using the following syntax:
ALTER PROCEDURE [dbo].[getLogsByTagName]
-- Add the parameters for the stored procedure here
#TAG nvarchar(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
SELECT TOP 1000
LOG_ID --somehow store this and execute the dbo.getLogByLogId procedure here
FROM dbo.LOG_TAG a
WHERE a.TAG = #TAG
-- Execute dbo.getLogByLogId stored procedure
DECLARE #logId INTEGER
SET #logId = <some value>
EXEC dbo.getLogByLogId #logId
END
However, the difficult part of your question is that your dbo.getLogByLogId procedure can only accept a single LogID parameter and therefore will only be able to return a single Log record. You need to return information for all Logs where the LogId has a corresponding record in the Tags table.
The correct way to do this would be to JOIN the Log and Tag tables together, like so:
SELECT *
FROM dbo.LOG_TAG a
INNER JOIN dbo.LOG b ON a.LOG_ID = b.LOG_ID
WHERE a.TAG = #TAG
If you are concerned about returning the same logId multiple times, you can use the DISTINCT keyword in the SELECT statement to filter out the duplicated logIds.
You may also be able to rewrite your dbo.getLogByLogId procedure as a user-defined function (UDF). UDFs can accept a table as a parameter and return a table result.
An introduction to user-defined functions can be found in this article.
Related
I have 2 procedures proc_Data, proc_FetchData.
I'm calling proc_Data from within proc_FetchData. proc_Data returns 2 tables. I want to insert only the first table into a temp table in my second procedure and use it further.
The problem is I cannot change proc_Data in any way as this is a very old procedure used in various parts of our application.
Sample code for reference
create procedure proc_Data
As
Begin
select 'Apples'
select 'Oranges','Grapes'
end
create procedure proc_FetchData
As
Begin
create table #temp(Data varchar(30))
insert into #temp
exec Test_proc
select * from #temp
end
I'm using SQL Server 2014 - is there any way to achieve this? Thanks in advance.
You cant do it that way, you can split your first SP into 2 and call it in your second SP, like,
create procedure proc_Data1
As
Begin
select 'Apples'
end
GO
create procedure proc_Data2
As
Begin
select 'Oranges','Grapes'
end
GO
ALTER procedure proc_FetchData
As
Begin
create table #temp(
Data varchar(30)
)
insert into #temp
exec proc_Data1
select * from #temp
end
To insert only the first table you can use OPENROWSET.
create procedure proc_FetchData
As
Begin
declare #userData TABLE(Data NVARCHAR(30))
INSERT INTO #userData
SELECT * FROMOPENROWSET('SQLNCLI','Server=[server];Trusted_Connection=yes;','EXEC [database name].dbo.proc_Data')
select * from #userData
end
If you need to access all resultsets you will need to use SQLCLR proc: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/da5328a7-5dab-44b3-b2b1-4a8d6d7798b2/insert-into-table-one-or-multiple-result-sets-from-stored-procedure?forum=transactsql
I suggest you add a new input parameter with a default value to your procedure and return different datasets depended on its value.
ALTER procedure proc_Data
#Mode INT = 0
As
Begin
IF #Mode = 0 BEGIN
select 'Apples'
select 'Oranges','Grapes'
END
ELSE BEGIN
select 'Apples'
END
end
go
ALTER procedure proc_FetchData
As
Begin
create table #temp(Data varchar(30))
insert into #temp
exec proc_Data #Mode = 1
select * from #temp
end
After doing so, you will be able to use your proc_Data in any old queries without changes, whereas its output will be different with different #Mode values if you need.
I have a transaction that calls a stored procedure which creates a temp table. I need to be able to access this temp table outside of the stored procedure after it has been ran. Note: for what I am trying to do, I cannot use global temp tables.
Example:
Here is an example of the stored procedure:
CREATE PROCEDURE [dbo].[GetChangeID]()
AS
BEGIN
IF OBJECT_ID('tempdb..#CurrentChangeID') IS NOT NULL
DROP TABLE #CurrentChangeID
SELECT '00000000-0000-0000-0000-000000000000' AS ChangeID INTO #CurrentChangeID
END
GO
Here is an example of the transaction:
BEGIN TRANSACTION
DECLARE #changeID uniqueidentifier
EXEC dbo.GetChangeID
DECLARE #test uniqueidentifier
SET #test = (SELECT ChangeID FROM #CurrentChangeID)
COMMIT TRANSACTION
GO
The issue is that it cannot find a table named #CurrentChangeID.
How can I make it to where it can see this table without declaring it as a global temp table such as ##CurrentChangeID?
------UPDATE------
So let me give more context to my question because that was just a simplified example. So what I am ultimately trying to do is this: 1. Begin Transaction 2. Call stored procedure that generates the GUID 3. Then update row in a given view that has a trigger. 4. Within that trigger get the GUID that was generated within the sp. 5. Commit.
First of all you can't get access to local temp table defined in SP outside stored procedure. It will always be out of scope.
Second you probalbly don't even need temp table. In your example:
SET #test = (SELECT ChangeID FROM #CurrentChangeID)
it looks like you want only one value.
I propose to use output parameter.
CREATE PROCEDURE [dbo].[GetChangeID](
#test UNIQUEIDENTIFIER OUTPUT
)
AS
BEGIN
-- ...
SET #test = '00000000-0000-0000-0000-000000000000';
END;
And call:
DECLARE #changeID uniqueidentifier
EXEC dbo.GetChangeID #chaneId OUTPUT;
SELECT #changeId;
Thank you lad2025 and Dan Guzman for your input. The way I was originally trying to do this was definitely incorrect.
I did, however, figure out a way to accomplish this task.
Modified Stored Procedure:
CREATE PROCEDURE [dbo].[GetChangeID]()
AS
BEGIN
DECLARE #ChangeID uniqueidentifier
...
Code that generates the uniqueidentifier, #ChangeID.
...
--This can be seen within the context of this batch.
SET CONTEXT_INFO #ChangeID
END
GO
Then anywhere within this transaction that you would like to access the changeID, you just have to use the following query:
SELECT CONTEXT_INFO as changeID
FROM sys.dm_exec_requests
WHERE session_id = ##SPID AND request_id = CURRENT_REQUEST_ID()
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.
I have a conceptual way I'd like to code a set of related functions and stored procedures. I'm hoping to get a little feedback on whether or not that way is doable.
In a stored procedure, I'd like to assign the values of a table-valued function to a temporary table, then pass that table to another stored procedure...
Can I do this without creating table types?
A quick sample of the #temp table solution:
CREATE PROCEDURE dbo.B
AS
BEGIN
SET NOCOUNT ON;
SELECT * FROM #foo;
END
GO
CREATE PROCEDURE dbo.A
AS
BEGIN
SET NOCOUNT ON;
SELECT TOP 1 * INTO #foo FROM sys.objects;
EXEC dbo.B;
DROP TABLE #foo;
END
GO
EXEC dbo.A;
DROP PROCEDURE dbo.A, dbo.B;
I have 2 stored procedures usp_SP1 and usp_SP2. Both of them make use of insert into #tt exec sp_somesp. I wanted to create a 3rd stored procedure which will decide which stored proc to call. Something like:
create proc usp_Decision
(
#value int
)
as
begin
if (#value = 1)
exec usp_SP1 -- this proc already has insert into #tt exec usp_somestoredproc
else
exec usp_SP2 -- this proc too has insert into #tt exec usp_somestoredproc
end
Later, I realized I needed some structure defined for the return value from usp_Decision so that I can populate the SSRS dataset field. So here is what I tried:
Within usp_Decision created a temp table and tried to do "insert into #tt exec usp_SP1". This didn't work out. error "insert exec cannot be nested"
Within usp_Decision tried passing table variable to each of the stored proc and update the table within the stored procs and do "select * from ". That didn't work out as well. Table variable passed as parameter cannot be modified within the stored proc.
Please suggest what can be done.
Can you modify usp_SP1 and usp_SP2?
If so, in usp_Decision, create a local temporary table with the proper schema to insert the results:
create table #results (....)
Then, in the called procedure, test for the existence of this temporary table. If it exists, insert into the temporary table. If not, return the result set as usual. This helps preserve existing behavior, if the nested procedures are called from elsewhere.
if object_id('tempdb..#results') is not null begin
insert #results (....)
select .....
end
else begin
select ....
end
When control returns to the calling procedure, #results will have been populated by the nested proc, whichever one was called.
If the result sets don't share the same schema, you may need to create two temporary tables in usp_Decision.
Have you had a look at table-valued user-defined functions (either inline or multi-statement)? Similar to HLGEM's suggestion, this will return a set which you may not have to insert any where.
Not a fan of global temp tables in any event (other processes can read these table and may interfere with the data in them).
Why not have each proc use a local temp table and select * from that table as the last step.
Then you can insert into a local temp table in the calling proc.
esimple example
create proc usp_mytest1
as
select top 1 id into #test1
from MYDATABASE..MYTABLE (nolock)
select * from #test1
go
--drop table #test
create proc usp_mytest2
as
select top 10 MYTABLE_id into #test2
from MYDATABASE..MYTABLE (nolock)
select * from #test2
go
create proc usp_mytest3 (#myvalue int)
as
create table #test3 (MYTABLE_id int)
if #myvalue = 1
Begin
insert #test3
exec ap2work..usp_mytest1
end
else
begin
insert #test3
exec ap2work..usp_mytest2
end
select * from #test3
go
exec ap2work..usp_mytest3 1
exec ap2work..usp_mytest3 0
See this blog article for one wortkaround (uses OPENROWSET to essentially create a loopback connection on which one of the INSERT EXEC calls happens)