Is it possible to use a table as input for a stored procedure?
EXEC sp_Proc SELECT * FROM myTable
I've created a function to return a table consisting of a single record.
ALTER FUNCTION dbo.preEmail
(
#Num INT,
#WID INT
)
RETURNS
#Results TABLE
(
WID char(10),
Company nchar(50),
Tech nchar(25),
StartDate datetime,
Description varchar(max),
Address varchar(200),
Phone varchar(15),
Status varchar(35)
)
AS
BEGIN
INSERT INTO #Results
(WID, Company, Tech, StartDate, Description, Address, Phone, Status)
SELECT WID, company, tech, startDate, description, address, phone, status
FROM wo_tbl
WHERE Num = #Number AND wid = #WID
RETURN
END
GO
Next I have a stored procedure that sends an email to the tech that is scheduled in the above record.
EXEC sp_emailTech #WID, #Company, #Tech, #StartDate, #Description, #Address, #Phone, #Status.
but I'd rather do
EXEC sp_emailTech SELECT * FROM dbo.preEmail(1, 5746)
No, you cannot pass a table as a parameter like that.
You could however look at using a Use Table-Valued Parameters (Database Engine) (SQL Server 2008 up)
In your case however it seems that you might be looking at using a DECLARE CURSOR (Transact-SQL) rather.
Do be aware thought that cursor execution does have a performance hit over set-based queries.
Re #Aaron Bertrand comment
DECLARE #id INT,
#name varchar(5)
DECLARE Cur CURSOR FOR
SELECT *
FROM myTable
OPEN Cur
FETCH NEXT FROM Cur INTO #ID, #Name
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC sp_Proc #id, #Name
FETCH NEXT FROM Cur INTO #ID, #Name
END
CLOSE Cur
DEALLOCATE Cur
First, declare a table variable to hold the result. Then, execute the SP with the rigth parameters and hold the result in the previous declared table variable. Then, select the content of this table.
You should also give a look to this solved SO thread. Also, I would recommend you to have a look at OPENXML query (pass in table xml and using xpath access the respective field).
See these examples:
Example 1
Example 2
Related
I want to use a database cursor; first I need to understand what its use and syntax are, and in which scenario we can use this in stored procedures? Are there different syntaxes for different versions of SQL Server?
When is it necessary to use?
Cursors are a mechanism to explicitly enumerate through the rows of a result set, rather than retrieving it as such.
However, while they may be more comfortable to use for programmers accustomed to writing While Not RS.EOF Do ..., they are typically a thing to be avoided within SQL Server stored procedures if at all possible -- if you can write a query without the use of cursors, you give the optimizer a much better chance to find a fast way to implement it.
In all honesty, I've never found a realistic use case for a cursor that couldn't be avoided, with the exception of a few administrative tasks such as looping over all indexes in the catalog and rebuilding them. I suppose they might have some uses in report generation or mail merges, but it's probably more efficient to do the cursor-like work in an application that talks to the database, letting the database engine do what it does best -- set manipulation.
cursor are used because in sub query we can fetch record row by row
so we use cursor to fetch records
Example of cursor:
DECLARE #eName varchar(50), #job varchar(50)
DECLARE MynewCursor CURSOR -- Declare cursor name
FOR
Select eName, job FROM emp where deptno =10
OPEN MynewCursor -- open the cursor
FETCH NEXT FROM MynewCursor
INTO #eName, #job
PRINT #eName + ' ' + #job -- print the name
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM MynewCursor
INTO #ename, #job
PRINT #eName +' ' + #job -- print the name
END
CLOSE MynewCursor
DEALLOCATE MynewCursor
OUTPUT:
ROHIT PRG
jayesh PRG
Rocky prg
Rocky prg
Cursor might used for retrieving data row by row basis.its act like a looping statement(ie while or for loop).
To use cursors in SQL procedures, you need to do the following:
1.Declare a cursor that defines a result set.
2.Open the cursor to establish the result set.
3.Fetch the data into local variables as needed from the cursor, one row at a time.
4.Close the cursor when done.
for ex:
declare #tab table
(
Game varchar(15),
Rollno varchar(15)
)
insert into #tab values('Cricket','R11')
insert into #tab values('VollyBall','R12')
declare #game varchar(20)
declare #Rollno varchar(20)
declare cur2 cursor for select game,rollno from #tab
open cur2
fetch next from cur2 into #game,#rollno
WHILE ##FETCH_STATUS = 0
begin
print #game
print #rollno
FETCH NEXT FROM cur2 into #game,#rollno
end
close cur2
deallocate cur2
Cursor itself is an iterator (like WHILE). By saying iterator I mean a way to traverse the record set (aka a set of selected data rows) and do operations on it while traversing. Operations could be INSERT or DELETE for example. Hence you can use it for data retrieval for example. Cursor works with the rows of the result set sequentially - row by row. A cursor can be viewed as a pointer to one row in a set of rows and can only reference one row at a time, but can move to other rows of the result set as needed.
This link can has a clear explanation of its syntax and contains additional information plus examples.
Cursors can be used in Sprocs too. They are a shortcut that allow you to use one query to do a task instead of several queries. However, cursors recognize scope and are considered undefined out of the scope of the sproc and their operations execute within a single procedure. A stored procedure cannot open, fetch, or close a cursor that was not declared in the procedure.
I would argue you might want to use a cursor when you want to do comparisons of characteristics that are on different rows of the return set, or if you want to write a different output row format than a standard one in certain cases. Two examples come to mind:
One was in a college where each add and drop of a class had its own row in the table. It might have been bad design but you needed to compare across rows to know how many add and drop rows you had in order to determine whether the person was in the class or not. I can't think of a straight forward way to do that with only sql.
Another example is writing a journal total line for GL journals. You get an arbitrary number of debits and credits in your journal, you have many journals in your rowset return, and you want to write a journal total line every time you finish a journal to post it into a General Ledger. With a cursor you could tell when you left one journal and started another and have accumulators for your debits and credits and write a journal total line (or table insert) that was different than the debit/credit line.
CREATE PROCEDURE [dbo].[SP_Data_newUsingCursor]
(
#SCode NVARCHAR(MAX)=NULL,
#Month INT=NULL,
#Year INT=NULL,
#Msg NVARCHAR(MAX)=null OUTPUT
)
AS
BEGIN
DECLARE #SEPERATOR as VARCHAR(1)
DECLARE #SP INT
DECLARE #VALUE VARCHAR(MAX)
SET #SEPERATOR = ','
CREATE TABLE #TempSiteCode (id int NOT NULL)
WHILE PATINDEX('%' + #SEPERATOR + '%', #SCode ) <> 0
BEGIN
SELECT #SP = PATINDEX('%' + #SEPERATOR + '%' ,#SCode)
SELECT #VALUE = LEFT(#SCode , #SP - 1)
SELECT #SCode = STUFF(#SCode, 1, #SP, '')
INSERT INTO #TempSiteCode (id) VALUES (#VALUE)
END
DECLARE
#EmpCode bigint=null,
#EmpName nvarchar(50)=null
CREATE TABLE #TempEmpDetail
(
EmpCode bigint
)
CREATE TABLE #TempFinalDetail
(
EmpCode bigint,
EmpName nvarchar(500)
)
DECLARE #TempSCursor CURSOR
DECLARE #TempFinalCursor CURSOR
INSERT INTO #TempEmpDetail
(
EmpCode
)
(
SELECT DISTINCT EmpCode FRom tbl_Att_MSCode
WHERE tbl_Att_MSCode.SiteCode IN (SELECT id FROM #TempSiteCode)
AND fldMonth=#Month AND fldYear=#Year
)
SET #TempSiteFinalCursor=CURSOR FOR SELECT EmpCode FROM #TempEmpDetail
OPEN #TempSiteFinalCursor
FETCH NEXT FROM #TempSiteFinalCursor INTO #EmpCode,#SiteCode,#HrdCompanyId
WHILE ##FETCH_STATUS=0
BEGIN
SEt #EmpName=(SELECt EmpName FROm tbl_Employees WHERE EmpCode=#EmpCode)
INSERT INTO #TempFinalDetail
(
EmpCode,
EmpName
)
VALUES
(
#EmpCode,
#EmpName
)
FETCH NEXT FROM #TempSiteFinalCursor INTO #EmpCode
END
SELECT EmpCode,
EmpName
FROM #TempFinalDetail
DEALLOCATE #TempSiteFinalCursor
DROP TABLE #TempEmpDetail
DROP TABLE #TempFinalDetail
END
newbie here.
I am trying to do self-learning about SQL.
Right now, I am having a problem in combining procedure and cursor.
Hereby the case
The Case
Create procedure named ‘sp4’ that receive StaffName from user’s input
to display StaffName and StaffPosition for every staff which name
contains the word that has been inputted by user.
(create procedure, declare cursor, like)
Hereby the code that I have tried it
My Code
CREATE PROCEDURE sp4 (#name VARCHAR(100))
AS
DECLARE cur2 CURSOR
SCROLL
FOR
SELECT StaffName, StaffPosition
FROM MsStaff
OPEN cur2
DECLARE #pointer AS VARCHAR(100)
FETCH NEXT FROM cur2 INTO #pointer
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT StaffName, StaffPosition
FROM MsStaff
WHERE #pointer = #name
AND StaffName LIKE '%' + #pointer + '%'
FETCH NEXT FROM cur2 INTO #name
END
CLOSE cur2
DEALLOCATE cur2
Your SELECT statement for the cursor indicates SELECT StaffName, StaffPosition but your FETCH statements don't match that (or each other).
The FETCH is going to load the data from the cursor into your memory variables. So you need to supply one variable for each column indicated in the SELECT. For example, if you have
DECLARE #staffName VARCHAR(100)
DECLARE #staffPosition VARCHAR(100)
then you can replace both of your fetches with:
FETCH NEXT FROM cur2 INTO #staffName, #staffPosition
I just started SQL with Microsoft SQL Server 2008 R2 and I want to select a list of Ids and run each of them through a stored procedure but am not sure how to do it.
SELECT Id
FROM UserId
WHERE ProgramId = #ProgramId
Then, I have created a procedure called temp_sp_UpdateIds
Normally I can just run the stored procedure with
EXEC temp_sp_UpdateIds #ProgramId
but I am not sure how to run the stored procedure with the list of Ids returned from the select statement and place it under #ProgramId
Do I need to store the Ids in a local table or something?
Thanks.
You can use Table-Valued Parameters
Creating a Table Type and SP
CREATE TYPE dbo.ListOfIds AS TABLE(Id int PRIMARY KEY)
GO
CREATE PROCEDURE dbo.temp_sp_UpdateIds
(
#ListOfIds dbo.ListOfIds READONLY
)
AS
BEGIN
...body of procedure
END
GO
Calling a Procedure with a Table-Valued Parameter
DECLARE #ListOfIds dbo.ListOfIds
INSERT #ListOfIds
SELECT Id
FROM UserId
WHERE ProgramId = #ProgramId
EXEC dbo.temp_sp_UpdateIds #ListOfIds
See SQLFiddle
You're unfortunately looking a cursor. Concept is you will provide your query in the first block, declare the variable(s) you'll need to operate your proc and then iterate through them.
DECLARE CSR CURSOR
READ_ONLY
FOR SELECT ProgramId FROM UserId
DECLARE #programid int
OPEN CSR
FETCH NEXT FROM CSR INTO #programid
WHILE (##fetch_status <> -1)
BEGIN
IF (##fetch_status <> -2)
BEGIN
EXECUTE temp_sp_UpdateIds #programId
END
FETCH NEXT FROM CSR INTO #programId
END
CLOSE CSR
DEALLOCATE CSR
GO
The other option that comes to mind is to generate the EXEC calls in SQL, concatenate all of that together with a semicolon and then exec that.
If you can change the proc to a function (that returns a value - even just null) you can simply do this:
SELECT temp_sp_UpdateIds(id) FROM UserId WHERE ProgramId=#ProgramId
In normal case you may do something like this:
SELECT * FROM UserId WHERE ProgramId in (SELECT ProgramId FROM t WHERE ...)
If procedure fill #ParamId by somthing like select result with one column - it must work
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
I have a stored proc that does inserts of “people”. I have an xml document with a bunch of people I want to insert. I want to call my stored proc like below and expect the stored proc to be called for each person in the xml. It is telling me that the stored proc “expects a parameter of #Id” and is failing. #Id is the first param and it appears that my syntax is not allowed. Is there a way to do this without iterating over each person in a cursor? I am using SQL Server 2005.
EXEC Stored_Procedure_That_Inserts_People
SELECT Node.value('Id[1]', 'Int') AS Id
,Node.value('FirstName[1]', 'varchar(50)') AS FirstName
,Node.value('LastName[1]', 'varchar(50)') AS LastName
,Node.value('MI[1]', 'char(1)') AS MI
FROM #PeopleXML.nodes('/ArrayOfPeople/Person') TempXML (Node)
For anybody interested, this is how I implemented my solution based on Tom's answer below:
CREATE PROCEDURE [dbo].[ccIU_PersonBulkImport]
(
#PersonXML as xml
)
AS
BEGIN
SET NOCOUNT ON
DECLARE
#LastName AS varchar(50),
#FirstName AS varchar(50),
#MI AS char(1)
DECLARE People CURSOR FORWARD_ONLY STATIC READ_ONLY FOR
SELECT
Node.value('FirstName[1]', 'varchar(50)') AS FirstName
,Node.value('LastName[1]', 'varchar(50)') AS LastName
,Node.value('MI[1]', 'char(1)') AS MI
FROM #PersonXML.nodes('/ArrayOfPeople/Person') TempXML (Node)
OPEN People;
FETCH NEXT FROM People INTO #FirstName,#LastName,#MI
WHILE (##FETCH_STATUS = 0)
BEGIN
EXEC domIU_People #FirstName,#LastName,#MI -- second stored proc that inserts or updates the person
FETCH NEXT FROM People INTO #FirstName,#LastName,#MI;
END
CLOSE People;
DEALLOCATE People;
END
No.
You cant iterate a stored procedure like that generally they can only take objects as parameters the exception being a table object.
In this example SQL will try and call the SP and then run the select as separate events which is why you are getting the error about the missing parameter.
Your choices are to iterate through the XML and call the SP for each record, refactor the SP to either work using the XML as a separate parameter and break it down in the insert people procedure or refactor the code from the sp into the XML handling logic procedure.
If the stored procedure is a simple insert into the People table then you could create a new stored procedure such as:
CREATE PROCEDURE dbo.Insert_People_From_XML
#people_xml XML
AS
BEGIN
INSERT INTO dbo.People
(
id,
first_name,
last_name,
middle_initial
)
SELECT
Node.value('Id[1]', 'Int'),
Node.value('FirstName[1]', 'varchar(50)'),
Node.value('LastName[1]', 'varchar(50)'),
Node.value('MI[1]', 'char(1)')
FROM
#people_xml.nodes('/ArrayOfPeople/Person') TempXML (Node)
END
If you have business logic (or other logic that you don't want to duplicate) then you may want to reuse your insert stored procedure as it is. In that case, you will have to iterate through the XML nodes. As much as I try to avoid cursors, this would be a time to use one:
DECLARE
#id INT,
#first_name VARCHAR(50),
#last_name VARCHAR(50),
#middle_initial CHAR(1)
DECLARE people_cursor CURSOR FOR
SELECT
Node.value('Id[1]', 'Int'),
Node.value('FirstName[1]', 'varchar(50)'),
Node.value('LastName[1]', 'varchar(50)'),
Node.value('MI[1]', 'char(1)')
FROM
#people_xml.nodes('/ArrayOfPeople/Person') TempXML (Node)
OPEN people_cursor
FETCH NEXT FROM people_cursor INTO #id, #first_name, #last_name, #middle_initial
WHILE (##FETCH_STATUS = 0)
BEGIN
EXEC Your_Proc
#id = #id,
#first_name = #first_name,
#last_name = #last_name,
#middle_initial = #middle_initial
FETCH NEXT FROM people_cursor INTO #id, #first_name, #last_name, #middle_initial
END
CLOSE people_cursor
DEALLOCATE people_cursor
NOTE: This was all written off the top of my head. I don't use XML much, so syntax may need to be corrected, you'll want to add error-handling, etc.