Is it possible to do an Insert Into stored procedure? - sql

Basically I have a table of data and I would like to take the values of each row and feed it to the stored procedure.
In my perfect world, I'd do this
insert into StoredProcA #var1 #var2
select testdesc, testoption
from tableA
where testoption = 1
Well, I don't think that's going to work. So how, if possible, can I take the all data from a table/query and pass it to the stored procedure?
EDIT: The stored procedure already exists and does quite a bit of processing to the incoming data. The amount of data from the source table itself is only 300 rows.

You will need to do a couple of things to get this going, since your parameter is getting multiple values you need to create a Table Type and make your store procedure accept a parameter of that type.
Since you are passing a TABLE as a parameter you will need to create a TABLE TYPE something as follows
TABLE TYPE
CREATE TYPE dbo.Prco_Table AS TABLE
(
[Val1] Data Type
[Val2] Data Type
)
GO
Stored Procedure to Accept That Type Param
CREATE PROCEDURE mainValues
#TableParam Prco_Table READONLY --<-- Accepts a parameter of that type
AS -- Note it is ReadOnly
BEGIN
SET NOCOUNT ON;
/* do your insert from this parameter or other cool stuff */
INSERT INTO Target_Table (Col1, Col2)
SELECT [Val1] , [Val2]
FROM #TableParam --<-- Table Type variable
END
EXECUTE PROC
Declare a variable of that type and populate it with your values.
DECLARE #Table ClaimData( --<-- Declare a variable of your type
[Val1] Data Type
[Val2] Data Type
);
-- Populate the variable
INSERT INTO #Table ([Val1],[Val2])
SELECT testdesc, testoption
FROM tableA
WHERE testoption = 1
EXECUTE mainValues #Table --<-- Pass this variable of Table Type

I ended up using #logixologist suggestion and used a cursor.
It worked quite well.
declare testCursor cursor
for
select testdesc, testoption
from tableA
where testoption = 1
open testCursor
declare #p1 varchar(max), #p2 varchar(8)
fetch next from testCursor into #p1, #p2
while (##fetch_status <> -1)
begin
if (#fetch_status <> -2)
exec db1.dbo.usr_storedproc #p1, #p2
fetch next from testCursor into #p1, #p2
end
close testCursor
deallocate testCursor
go
If anyone has any improvements, better way, or different way of doing this, please post solution as an answer.

Related

Get/Use the Select value from a Stored Procedure

I have to use a Stored Procedure - that I cannot change/modify. While it is a bit complicated, it can be simplified to be a SELECT statement i.e. with no RETURN or OUTPUT parameter. For the purpose of this discussion assume it to be something like:
SELECT [URL] as imgPath
FROM [mydatasource].[dbo].[DigitalContent]
I need to execute this Stored Procedure passing in the Row ID (SKU) of each row in a Table.
I use a cursor for this as below:
DECLARE #sku varchar(100)
DECLARE #imgPath varchar(500)
DECLARE c CURSOR FOR
SELECT [SKU]
FROM [mydatasource].[dbo].[PROD_TABLE]
OPEN c
FETCH NEXT FROM c INTO #sku
WHILE ##FETCH_STATUS = 0 BEGIN
EXEC #imgPath = [mydatasource].[dbo].[getImage] #sku
--UPDATE PROD_TABLE SET ImgPath=#imgPath WHERE SKU=#sku
SELECT #imgPath AS ImgPath
FETCH NEXT FROM c INTO #sku
END
CLOSE c
DEALLOCATE c
Unfortunately, the return value #imgPath comes back as 0 i.e. success. This results in 0s being inserted into my PROD_TABLE or dumped on the Console. However, as the getImage Stored Procedure executes, it dumps the correct values of imgPath to the console.
How do I get this correct value (i.e. the result of the SELECT in the Stored Procedure) in the Loop above, so that I can correctly update my PROD_TABLE?
Answer
Thanks to RBarryYoung suggestion, my final code looks like:
DECLARE #sku varchar(100)
DECLARE #imgPath varchar(500)
DECLARE c CURSOR FOR
SELECT [SKU]
FROM [mydatasource].[dbo].[PROD_TABLE]
OPEN c
FETCH NEXT FROM c INTO #sku
WHILE ##FETCH_STATUS = 0 BEGIN
CREATE TABLE #OutData ( imgPath varchar(500) )
INSERT INTO #OutData EXEC [mydatasource].[dbo].[getImage] #sku
--UPDATE PROD_TABLE SET ImgPath=(SELECT * FROM #OutData) WHERE SKU=#sku
SELECT * FROM #OutData
DROP TABLE #OutData
FETCH NEXT FROM c INTO #sku
END
CLOSE c
DEALLOCATE c
The performance may not be the best, but at least it works :-).
First, create a temp table (#OutData) whose definition matches the output dataset being returned by getImage.
Then, change your EXEC .. statement to this:
INSERT INTO #OutData EXEC [mydatasource].[dbo].[getImage] #sku
Response to the question: "Is it possible to insert the Key/Row ID into the Temp Table, that way I will not have to TRUNCATE it after each loop iteration?"
First, as a general rule you shouldn't use TRUNCATE on #temp tables as there are some obscure locking problems with that. If you need to do that, just DROP and CREATE them again (they're optimized for that anyway).
Secondly, you cannot modify the dataset returned by a stored procedure in any way. Of course once its in the #temp table you can do what you want with it. So you could add a KeyId column to #OutData. Then inside the loop make a second #temp table (#TmpData), and use INSERT..EXEC to dump into that table instead. Then INSERT..SELECT into #OutData by selecting from #TmpData, adding your KeyID column. Finally, DROP TABLE #TmpData as the last statement in your loop.
This should perform fairly well.
Sometimes executing code entirely inside SQL Server can be more difficult than doing so directly client-side, sending multiple queries calling the SProc (ideally batched in a single round-trip) and processing the results there directly.
Otherwise, the INSERT-EXEC method seems the easier if you absolutely can't modify the called procedure. There are a few alternative methods, all with some additional problems, shown here: http://www.sommarskog.se/share_data.html

Scope of table variable within SQL cursor

If I run the below in MS SQL 2008 R2 I get an unexpected result.
create table #DataTable (someID varchar(5))
insert into #DataTable
values ('ID1'),('ID2'),('ID3'),('ID4'),('ID5')
declare #data varchar(8);
declare myCursor cursor for
select someID from #DataTable
open myCursor
FETCH NEXT FROM myCursor INTO
#data
WHILE(##Fetch_Status >=0)
BEGIN
declare #tempTable table (someValue varchar(10))
insert into #tempTable select #data + '_ASDF'
select * from #tempTable
FETCH NEXT FROM myCursor INTO
#data
END
close myCursor
deallocate myCursor
drop table #DataTable
Result of the last iteration:
someValue
ID1_ASDF
ID2_ASDF
ID3_ASDF
ID4_ASDF
ID5_ASDF
I haved expected only to see
someValue
ID5_ASDF
It seems that the table variable #tempTable is kept in scope between cursor iterations - but how is it then possible to re-declare the variable in each iteration ? Makes no sense to me.
I solved it by
delete #tempTable
in each iteration - which also backs up my assumption about it still being in scope.
Can anyone explain this behavior ?
Yes, it does - the scope isn't defined by the begin / end statements, but by the end of a stored procedure, or a go
The scope of a variable is the range of Transact-SQL statements that
can reference the variable. The scope of a variable lasts from the
point it is declared until the end of the batch or stored procedure in
which it is declared.
http://msdn.microsoft.com/en-us/library/ms187953(v=sql.105).aspx
Variable declarations in T-SQL are a bit of an odd beast - variable declarations ignore control flow.
This produces an error:
set #a = 2
This runs without issue, and doesn't print "Never":
if 1=0
begin
print 'Never'
declare #a int
end
set #a = 2
The lifetime of a variable is from the point of declaration until the batch completes.

Execute Stored Procedure for List of Parameters in SQL

I have an Stored Procedure that have an argument named Id:
CREATE PROCEDURE [TargetSp](
#Id [bigint]
)
AS
BEGIN
Update [ATable]
SET [AColumn] =
(
Select [ACalculatedValue] From [AnotherTable]
)
Where [ATable].[Member_Id] = #Id
END
So I need to use it for a list of Id's not for one Id like :
Exec [TargetSp]
#Id IN (Select [M].[Id] From [Member] AS [M] Where [M].[Title] = 'Example');
First: How can I Execute it for a list?
Second: Is there any Performance difference between I execute the sp many times or rewrite it in target script?
You could use a table-valued parameter (see http://msdn.microsoft.com/en-us/library/bb510489.aspx). Generally, if you send only one request to the server instead of a list of requests you will see a shorter execution time.
I normally pass in the information like that as XML, then you can use it just like it's a table... selecting, inserting, updating as necessary
DECLARE #IDS NVARCHAR(MAX), #IDOC INT
SET #IDS = N'<ROOT><ID>1</ID><ID>2<ID></ROOT>'
EXEC sp_xml_preparedocument #IDOC OUTPUT, #IDS
SELECT [ID] FROM OPENXML (#IDOC, '/ROOT/ID', 2) WITH ([ID] INT '.') AS XMLDOC
EXEC sp_xml_removedocument #IDOC
Similar to freefaller's example, but using xml type instead and inserting into a table variable #ParsedIds
DECLARE #IdXml XML = N'<root><id value="1"/><id value="2"/></root>'
DECLARE #ParsedIds TABLE (parsedId int not null)
INSERT INTO #ParsedIds (parsedId)
SELECT v.parsedId.value('#value', 'int')
FROM #IdXml.nodes('/root/id') as v(parsedId)
SELECT * FROM #ParsedIds
Interestingly I've worked on an large scale system with 1000's of users and we found that using this method out performed the table-valued parameter approach for small lists of id's (no more than say 5 id's). The table-valued parameter approach was faster for larger lists of Id's.
EDIT following edited question:
Looking at your example it looks like you want to update ATable based on the Title parameter. If you can you'd benefit from rewriting your stored procedure to instead except the title parameter.
create procedure [TargetSP](
#title varchar(50)
)
as
begin
update [ATable]
set [AColumn] =
(
select [ACalculatedValue] from [AnotherTable]
)
where [ATable].[Member_Id] in (select [M].[Id] from [Member] as [M] where [M].[Title] = #title);
end
Since you only care about all the rows with a title of 'Example', you shouldn't need to determine the list first and then tell SQL Server the list you want to update, since you can already identify those with a query. So why not do this instead (I'm guessing at some data types here):
ALTER PROCEDURE dbo.TargetSP
#title VARCHAR(255)
AS
BEGIN
SET NOCOUNT ON;
-- only do this once instead of as a subquery:
DECLARE #v VARCHAR(255) = (SELECT [ACalculatedValue] From [AnotherTable]);
UPDATE a
SET AColumn = #v
FROM dbo.ATable AS a
INNER JOIN dbo.Member AS m
ON a.Member_Id = m.Id
WHERE m.Title = #title;
END
GO
Now call it as:
EXEC dbo.TargetSP #title = 'Example';
DECLARE #VId BIGINT;
DECLARE [My_Cursor] CURSOR FAST_FORWARD READ_ONLY FOR
Select [M].[Id] From [Member] AS [M] Where [M].[Title] = 'Example'
OPEN [My_Cursor]
FETCH NEXT FROM [My_Cursor] INTO #VId
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC [TargetSp]
#Id = #VId
FETCH NEXT FROM [My_Cursor] INTO #VId
END
CLOSE [My_Cursor]
DEALLOCATE [My_Cursor];
GO
if the parameter is integer, you can only pass one value at a time.
Your options are:
call the proc several times, one for each parameter
Change the proc to accept a structure where you can pass more than
one id like a varchar where you pass a coma separated list of values
(not so good) or a table-value parameter
About the performance question, it would be faster to re-write the proc to iterate through a list of ids than call it several times, once per id, BUT unless you are dealing with a HUGE list of ids, I dont think you will see much of a difference

SQL INSERT INTO calling Stored Procedure instead

I would like to do something like this in SQL
Insert Into storedproc2
SELECT column1, column2 from Tablename
My goal is to have each row of data in tablename processed using the storedproc2 stored procedure, which itself handles any insertion necessary in the logic flow.
Well as others said, you can't do that on a single statement. (that's just the way things work)
If what you wan't is to call a proc with the results you can first select and then call the proc using a cursor.
The cursor would perform a row by row code and you would be able to call the proc passing the correct values. But beware cursors are slow use Flags like FAST_FORWARD.
The other way would be to change your proc to accept a whole table, as a table valued parameter, if that is possiblel, that would perform really better.
Hope this helps.
DECLARE CallingProcCursor CURSOR
FAST_FORWARD
FOR
SELECT database_id,name from sys.databases
DECLARE #database_id int, #name sysname
OPEN CallingProcCursor
FETCH NEXT FROM CallingProcCursor INTO #database_id, #name
WHILE (##fetch_status <> -1)
BEGIN
IF (##fetch_status <> -2)
BEGIN
EXEC PROCX #database_id, #name
END
FETCH NEXT FROM CallingProcCursor INTO #database_id, #name
END
CLOSE CallingProcCursor
DEALLOCATE CallingProcCursor
GO
You can't insert into a stored procedure. You can only insert into a table (and in some cases a view, depending on the DB platform and whether the view is updateable.)
You can use a stored procedure to insert data as shown here: http://www.codeproject.com/KB/cs/tariqaziz.aspx
This is NOT meant to be insulting, but rather helpful...
It sounds like you need to go read up on stored procedures, since your question shows that you don't get the basics.
http://databases.about.com/od/sqlserver/a/storedprocedure.htm
As David said, you can't do this the way you want.
What you CAN do is feed the stored proc a table as a parameter, and have it perform it's logic on each row in that table. It will involve some Dynamic SQL but it is doable.
you can put insert statement inside stored proc and pass the values as parameters e.g.
following is just an example, on each row you can call your stored proc and it should be fine
CREATE PROC BDD_AddMessageLogItem(
#BusinessDevelopmentItemId INT,
#MessageLog NVARCHAR(MAX),
#PostedBy SMALLINT,
#PostedOfficeId TINYINT,
#PostedDepartmentId TINYINT,
#PostedMessageLogType TINYINT)
AS
BEGIN
DECLARE #BusinessDevelopmentMessageLogId SMALLINT
INSERT INTO dbo.BusinessDevelopmentItemMessageLogs
( BusinessDevelopmentItemId ,
MessageLog ,
DatePosted,
PostedBy,
PostedOfficeId,
PostedDepartmentId,
PostedMessageLogType,
BusinessDevelopmentMessageLogId
)
VALUES ( #BusinessDevelopmentItemId , -- BusinessDevelopmentItemId - int
#MessageLog , -- Message - nvarchar(100)
GETDATE(),
#PostedBy,
#PostedOfficeId,
#PostedDepartmentId,
#PostedMessageLogType,
#BusinessDevelopmentMessageLogId
)
END
You can't pass data sets to stored procedures, only parameters. You could:
Pass the table name to the stored procedure and then construct the select.
Pass the whole query as a parameter.
And then execute it with sp_executesql. If you use this method you should read The Curse and Blessings of Dynamic SQL.

How do I execute a stored procedure once for each row returned by query?

I have a stored procedure that alters user data in a certain way. I pass it user_id and it does it's thing. I want to run a query on a table and then for each user_id I find run the stored procedure once on that user_id
How would I write query for this?
use a cursor
ADDENDUM: [MS SQL cursor example]
declare #field1 int
declare #field2 int
declare cur CURSOR LOCAL for
select field1, field2 from sometable where someotherfield is null
open cur
fetch next from cur into #field1, #field2
while ##FETCH_STATUS = 0 BEGIN
--execute your sproc on each row
exec uspYourSproc #field1, #field2
fetch next from cur into #field1, #field2
END
close cur
deallocate cur
in MS SQL, here's an example article
note that cursors are slower than set-based operations, but faster than manual while-loops; more details in this SO question
ADDENDUM 2: if you will be processing more than just a few records, pull them into a temp table first and run the cursor over the temp table; this will prevent SQL from escalating into table-locks and speed up operation
ADDENDUM 3: and of course, if you can inline whatever your stored procedure is doing to each user ID and run the whole thing as a single SQL update statement, that would be optimal
try to change your method if you need to loop!
within the parent stored procedure, create a #temp table that contains the data that you need to process. Call the child stored procedure, the #temp table will be visible and you can process it, hopefully working with the entire set of data and without a cursor or loop.
this really depends on what this child stored procedure is doing. If you are UPDATE-ing, you can "update from" joining in the #temp table and do all the work in one statement without a loop. The same can be done for INSERT and DELETEs. If you need to do multiple updates with IFs you can convert those to multiple UPDATE FROM with the #temp table and use CASE statements or WHERE conditions.
When working in a database try to lose the mindset of looping, it is a real performance drain, will cause locking/blocking and slow down the processing. If you loop everywhere, your system will not scale very well, and will be very hard to speed up when users start complaining about slow refreshes.
Post the content of this procedure you want call in a loop, and I'll bet 9 out of 10 times, you could write it to work on a set of rows.
You can do it with a dynamic query.
declare #cadena varchar(max) = ''
select #cadena = #cadena + 'exec spAPI ' + ltrim(id) + ';'
from sysobjects;
exec(#cadena);
Something like this substitutions will be needed for your tables and field names.
Declare #TableUsers Table (User_ID, MyRowCount Int Identity(1,1)
Declare #i Int, #MaxI Int, #UserID nVarchar(50)
Insert into #TableUser
Select User_ID
From Users
Where (My Criteria)
Select #MaxI = ##RowCount, #i = 1
While #i <= #MaxI
Begin
Select #UserID = UserID from #TableUsers Where MyRowCount = #i
Exec prMyStoredProc #UserID
Select
#i = #i + 1, #UserID = null
End
Use a table variable or a temporary table.
As has been mentioned before, a cursor is a last resort. Mostly because it uses lots of resources, issues locks and might be a sign you're just not understanding how to use SQL properly.
Side note: I once came across a solution that used cursors to update
rows in a table. After some scrutiny, it turned out the whole thing
could be replaced with a single UPDATE command. However, in this case,
where a stored procedure should be executed, a single SQL-command
won't work.
Create a table variable like this (if you're working with lots of data or are short on memory, use a temporary table instead):
DECLARE #menus AS TABLE (
id INT IDENTITY(1,1),
parent NVARCHAR(128),
child NVARCHAR(128));
The id is important.
Replace parent and child with some good data, e.g. relevant identifiers or the whole set of data to be operated on.
Insert data in the table, e.g.:
INSERT INTO #menus (parent, child)
VALUES ('Some name', 'Child name');
...
INSERT INTO #menus (parent,child)
VALUES ('Some other name', 'Some other child name');
Declare some variables:
DECLARE #id INT = 1;
DECLARE #parentName NVARCHAR(128);
DECLARE #childName NVARCHAR(128);
And finally, create a while loop over the data in the table:
WHILE #id IS NOT NULL
BEGIN
SELECT #parentName = parent,
#childName = child
FROM #menus WHERE id = #id;
EXEC myProcedure #parent=#parentName, #child=#childName;
SELECT #id = MIN(id) FROM #menus WHERE id > #id;
END
The first select fetches data from the temporary table. The second select updates the #id. MIN returns null if no rows were selected.
An alternative approach is to loop while the table has rows, SELECT TOP 1 and remove the selected row from the temp table:
WHILE EXISTS(SELECT 1 FROM #menuIDs)
BEGIN
SELECT TOP 1 #menuID = menuID FROM #menuIDs;
EXEC myProcedure #menuID=#menuID;
DELETE FROM #menuIDs WHERE menuID = #menuID;
END;
Can this not be done with a user-defined function to replicate whatever your stored procedure is doing?
SELECT udfMyFunction(user_id), someOtherField, etc FROM MyTable WHERE WhateverCondition
where udfMyFunction is a function you make that takes in the user ID and does whatever you need to do with it.
See http://www.sqlteam.com/article/user-defined-functions for a bit more background
I agree that cursors really ought to be avoided where possible. And it usually is possible!
(of course, my answer presupposes that you're only interested in getting the output from the SP and that you're not changing the actual data. I find "alters user data in a certain way" a little ambiguous from the original question, so thought I'd offer this as a possible solution. Utterly depends on what you're doing!)
I like the dynamic query way of Dave Rincon as it does not use cursors and is small and easy. Thank you Dave for sharing.
But for my needs on Azure SQL and with a "distinct" in the query, i had to modify the code like this:
Declare #SQL nvarchar(max);
-- Set SQL Variable
-- Prepare exec command for each distinctive tenantid found in Machines
SELECT #SQL = (Select distinct 'exec dbo.sp_S2_Laser_to_cache ' +
convert(varchar(8),tenantid) + ';'
from Dim_Machine
where iscurrent = 1
FOR XML PATH(''))
--for debugging print the sql
print #SQL;
--execute the generated sql script
exec sp_executesql #SQL;
I hope this helps someone...