How can I pass xml data from one sproc to another? - sql

I have 2 sprocs for an assignment, what I'm trying to do is pass the xml output from one sproc to another and put it in a variable, I know ex1.xml_sp1 is returning an int while calling it with EXEC and obviously when trying to select this it returns null because #x is xml data type.
What I want to do is retrieve and store the xml data from sproc 1 in to #x in sproc 2.
Is there any way to do this?
sproc 1:
ALTER PROC [ex1].[xml_sp1]
#careteamid int
as
select CareTeams.CareTeamID, Doctors.DoctorID, Doctors.DoctorName,
CareTeamDoctors.DateJoined, CareTeamDoctors.CurrentMember
from dbo.CareTeamTbl as CareTeams
inner join dbo.CareTeamDoctorTbl as CareTeamDoctors on
CareTeams.CareTeamID = CareTeamDoctors.CareTeamID
inner join dbo.DoctorTbl as Doctors on
CareTeamDoctors.DoctorID=CareTeamDoctors.DoctorID
where CareTeamDoctors.CareTeamID = #careteamid
and CareTeamDoctors.DoctorID = Doctors.DoctorID
for xml auto, root('thedata')
sproc 2:
ALTER PROC [ex1].[xml_sp2]
#careteamid int
as
declare #x xml
exec #x = ex1.xml_sp1
#careteamid = #careteamid
select #x as XMLData

I want to do is retrieve and store the xml data from sproc 1 in to #x in sproc 2.
You could achieve it very easily using OUTPUT parameters:
CREATE PROCEDURE [xml_sp1]
#careteamid INT,
#xml_output XML OUTPUT
AS
BEGIN
SET #xml_output = (SELECT * FROM ... FOR XML AUTO, root('thedata'));
END;
GO
CREATE PROCEDURE [xml_sp2]
#careteamid INT
AS
BEGIN
DECLARE #x XML;
EXEC [xml_sp1]
#careteamid,
#x OUTPUT;
SELECT #x AS XMLData;
END;
GO
And final call:
EXEC [xml_sp2] #careteamid = 1;
LiveDemo
Consider using BEGIN/END block and end each statement with ; to avoid possible nasty problems.
The full list of possible sharing data methods How to Share Data between Stored Procedures by Erland Sommarskog

For the return value (i.e. EXEC #ReturnValue = StoredProcName...;), INT is the only datatype allowed. If this needs to really stay as a Stored Procedure then you can either use an OUTPUT variable or create a temp table or table variable in the second Stored Procedure and do INSERT INTO ... EXEC StoredProc1;.
However, given that the first Stored Procedure is only doing a simple SELECT statement, you would be far better off converting this to be an Inline Table-Valued Function (iTVF) as follows:
CREATE FUNCTION dbo.GetData (#CareTeamID INT)
RETURNS TABLE
AS RETURN
SELECT tab.col AS [CareData]
FROM (
SELECT CareTeams.CareTeamID, Doctors.DoctorID, Doctors.DoctorName,
CareTeamDoctors.DateJoined, CareTeamDoctors.CurrentMember
FROM dbo.CareTeamTbl as CareTeams
INNER JOIN dbo.CareTeamDoctorTbl as CareTeamDoctors
ON CareTeams.CareTeamID = CareTeamDoctors.CareTeamID
INNER JOIN dbo.DoctorTbl as Doctors
ON CareTeamDoctors.DoctorID=CareTeamDoctors.DoctorID
WHERE CareTeamDoctors.CareTeamID = #CareTeamID
AND CareTeamDoctors.DoctorID = Doctors.DoctorID
FOR XML AUTO, ROOT('thedata'), TYPE -- "TYPE" causes result to be XML and not NVARCHAR
) tab(col);
Then just change the second stored procedure to be:
SELECT care.CareData AS [XMLData]
FROM dbo.GetData(#CareTeamID);
Or, if you actually need to make use of that XML data in the second Stored Procedure, do the following:
DECLARE #TempData XML;
SELECT #TempData = care.CareData
FROM dbo.GetData(#CareTeamID);
Finally, if you need the XML data to actually be of the XML datatype coming out of that iTVF (or even if you keep it as a Stored Procedure), then you need to add the TYPE option to the FOR XML clause, else what is returned is a string / NVARCHAR(MAX):
FOR XML AUTO, ROOT ('thedata'), TYPE

Why not try this instead of assigning output of sp1 directly
declare #t table
(
val xml
)
insert into #t
exec ex1.xml_sp1
#careteamid = #careteamid
select * from #t

Will this work?
declare #xml xml
set #xml = (select *
from tableABC
for xml auto, elements)
And then pass that variable to another sproc?
I go the answer from here
How to insert FOR AUTO XML result into table?.
Seems to me the idea is similar, or should work similar.

Related

Creating a stored procedure using variables

Is there any good way to do this, or am I just heading in the wrong direction? I would like to create a stored procedure inside an SQL script. I would like to have variables declared at the beginning of the script so that I can create the SPROCs to use in different contexts/servers.Here is what I would like to do (I know this obviously doesn't work, but I'm looking for any ideas of an alternative)..
DECLARE #golbalValue = 'SomeValue'
GO
CREATE PROCEDURE [dbo].[MyStoredProcedure](
AS
BEGIN
SELECT * FROM Mytable WHERE MyCol = #globalValue
END
GO
What you could do is use a scalar function for the variable
create function f ()
returns varchar(20)
as
begin
return 'some value'
end
go
then use it in your procedure
create proc p ()
as
begin
select *
from my_table
where col = f()
end
go
another possibility which is perhaps more appropriate is to use sqlcmd here's an example.
From what I understand, you need to create stored procedures with set value from your parameters. You don't want input parameters in the stored Procedures though. Second, you want to switch database contexts. So I think you'll need a tempTable for your parameters and some dynamic SQL. Try this out:
IF OBJECT_ID('tempdb..#globalParam') IS NOT NULL
DROP TABLE #globalParam;
IF OBJECT_ID('AdventureWorks2012.dbo.myTable') IS NOT NULL
DROP TABLE AdventureWorks2012.dbo.myTable
IF OBJECT_ID('Master..myTable') IS NOT NULL
DROP TABLE Master..mytable
--Create your data tables
SELECT 'SomeValue' AS col1 INTO AdventureWorks2012.dbo.myTable;
SELECT 1000 AS col1 INTO master.dbo.myTable;
CREATE TABLE #globalParam(
ParamName VARCHAR(100),
val SQL_VARIANT --SQL_Variant is designed to hold all data types.
);
--Here are your globalParams
DECLARE #globalParam1 VARCHAR(100) = 'SomeValue';
DECLARE #globalParam2 INT = 1000;
--Load your parameters into a table. Might have to cast some of your parameters to SQL_Variant
INSERT INTO #globalParam
VALUES ('globalParam1',#globalParam1),
('globalParam2',CAST(#globalParam2 AS sql_variant));
GO
--Switch database context
USE AdventureWorks2012
GO
--Variable to hold CREATE PROC
DECLARE #sql VARCHAR(MAX);
--Set #SQL with parameter value from #globalParam
SELECT #sql =
'CREATE PROCEDURE dbo.myStoredProc AS
BEGIN
SELECT * FROM myTable WHERE col1 = ''' + CAST(val AS VARCHAR(100)) + '''
END'
FROM #globalParam
WHERE ParamName = 'globalParam1'
--Execute to create the stored procedure
EXEC(#sql)
--Execute it to see if it works
EXEC dbo.myStoredProc
--Switch context. Repeat same steps
USE master
GO
DECLARE #sql VARCHAR(MAX);
SELECT #sql =
'CREATE PROCEDURE dbo.myStoredProc AS
BEGIN
SELECT * FROM myTable WHERE col1 = ''' + CAST(val AS VARCHAR(100)) + '''
END'
FROM #globalParam
WHERE ParamName = 'globalParam2'
EXEC(#sql)
EXEC dbo.myStoredProc
--Cleanup
DROP PROCEDURE dbo.myStoredProc;
USE AdventureWorks2012
GO
DROP PROCEDURE dbo.myStoredProc;
You cannot do what you want. T-SQL doesn't have the concept of global variables. One method is to store values in a "global" table and then reference them as needed. Something like:
create table GlobalParams (
name varchar(255) not null primary key,
value varchar(255) not null
);
create procedure . . .
begin
. . .
declare #value varchar(255);
select #value = value from Globalparams where name = 'name';
select *
from Mytable
where MyCol = #value;
. . .
end;
Note: this is a simplistic example that only allows variables whose type is a string.
You can also wrap the logic in a user-defined function, so the call looks like:
select *
from Mytable
where MyCol = udf_GlobalLookup('name');
It is rather rare to need global parameters that are shared among different stored procedures. Such a global context can be useful, at times, for complex systems. It is unlikely that you need all this machinery for a simple application. An alternative method, such as just passing the parameters in as arguments, is probably sufficient.

Iterate through XML variable in SQL Server

I have a XML variable in a stored procedure (SQL Server 2008), its sample value is
<parent_node>
<category>Low</category>
<category>Medium</category>
<category>High</category>
</parent_node>
I have to take each category and insert into table as a separate record. How to iterate in XML and take individual node value?
If I want to call a stored procedure and send each category as input parameter, how we can do that? The stored procedure is legacy one, which accept only one category at at time. I am trying to do invoke procedure in this way.
loop fetch single category from xml variable.
invoke stored procedure with current category.
move to next category.
loop until list contain value.
Any help will be appreciated.
Something like this?
DECLARE #XmlVariable XML = '<parent_node>
<category>Low</category>
<category>Medium</category>
<category>High</category>
</parent_node>'
INSERT INTO dbo.YourTargetTable(CategoryColumn)
SELECT
XTbl.Cats.value('.', 'varchar(50)')
FROM
#XmlVariable.nodes('/parent_node/category') AS XTbl(Cats)
Update: if you must use the old legacy stored procedure and cannot change it (that would be my preferred way of doing this), then you would have to do the row-by-agonizing-row (RBAR) looping yourself, e.g. by using a table variable:
-- declare temporary work table
DECLARE #RbarTable TABLE (CategoryName VARCHAR(50))
-- insert values into temporary work table
INSERT INTO #RbarTable(CategoryName)
SELECT
XTbl.Cats.value('.', 'varchar(50)')
FROM
#XmlVariable.nodes('/parent_node/category') AS XTbl(Cats)
-- declare a single category
DECLARE #CategoryNameToBeInserted VARCHAR(50)
-- get the first category
SELECT TOP 1 #CategoryNameToBeInserted = CategoryName FROM #RbarTable
-- as long as we have data
WHILE #CategoryNameToBeInserted IS NOT NULL
BEGIN
-- execute your stored procedure here.....
EXEC sp_executesql N'dbo.YourStoredProcedure #CategoryName',
N'#CategoryName VARCHAR(50)',
#CategoryName = #CategoryNameToBeInserted
-- delete the category we just inserted from the temporary work table
DELETE FROM #RbarTable WHERE CategoryName = #CategoryNameToBeInserted
-- see if we still have more categories to insert
SET #CategoryNameToBeInserted = NULL
SELECT TOP 1 #CategoryNameToBeInserted = CategoryName FROM #RbarTable ORDER BY CategoryName
END
With XML in SQL Server there's always more than one way to do it. Depending on the size of your XML doc and the number of times you're querying it, you could be best off using sp_xml_preparedocument which parses the document, gives you a handle to reference it, and then you can query it as many times and ways as you want to. Here's how you do that:
declare #xml xml = '
<parent_node>
<category>Low</category>
<category>Medium</category>
<category>High</category>
</parent_node>'
declare #xml_handle int
exec sp_xml_preparedocument #xml_handle output, #xml
select value from openxml(#xml_handle, '/parent_node/category', 2) with (value varchar(100) 'text()') x
exec sp_xml_removedocument #xml_handle

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

TSQL Statement IN

I am having a small problem with the IN SQL statement. I was just wondering if anyone could help me?
#Ids = "1,2,3,4,5"
SELECT * FROM Nav WHERE CONVERT(VARCHAR,NavigationID) IN (CONVERT(VARCHAR,#Ids))
This is coming back with the error below, I am sure this is pretty simple!
Conversion failed when converting the varchar value '1,' to data type int.
The SQL IN clause does not accept a single variable to represent a list of values -- no database does, without using dynamic SQL. Otherwise, you could use a Table Valued Function (SQL Server 2000+) to pull the values out of the list & return them as a table that you can join against.
Dynamic SQL example:
EXEC('SELECT *
FROM Nav
WHERE NavigationID IN ('+ #Ids +')')
I recommend reading The curse and blessings of dynamic SQL before using dynamic SQL on SQL Server.
Jason:
First create a function like this
Create FUNCTION [dbo].[ftDelimitedAsTable](#dlm char, #string varchar(8000))
RETURNS
--------------------------------------------------------------------------*/
/*------------------------------------------------------------------------
declare #dlm char, #string varchar(1000)
set #dlm=','; set #string='t1,t2,t3';
-- tHIS FUNCION RETUNRS IN THE ASCENDING ORDER
-- 19TH Apr 06
------------------------------------------------------------------------*/
--declare
#table_var TABLE
(id int identity(1,1),
r varchar(1000)
)
AS
BEGIN
declare #n int,#i int
set #n=dbo.fnCountChars(#dlm,#string)+1
SET #I =1
while #I <= #N
begin
insert #table_var
select dbo.fsDelimitedString(#dlm,#string,#i)
set #I= #I+1
end
if #n =1 insert #TABLE_VAR VALUES(#STRING)
delete from #table_var where r=''
return
END
And then
set quoted_identifier off
declare #ids varchar(max)
select #Ids = "1,2,3,4,5"
declare #nav table ( navigationid int identity(1,1),theother bigint)
insert #nav(theother) select 10 union select 11 union select 15
SELECT * FROM #Nav WHERE CONVERT(VARCHAR,NavigationID) IN (select id from dbo.ftDelimitedAsTable(',',#Ids))
select * from dbo.ftDelimitedAsTable(',',#Ids)
What you're doing is not possible with the SQL IN statement. You cannot pass a string to it and expect that string to be parsed. IN is for specific, hard-coded values.
There are two ways to do what you want to do here.
One is to create a 'dynamic sql' query and execute it, after substituting in your IN list.
DECLARE #query varchar(max);
SET #query = 'SELECT * FROM Nav WHERE CONVERT(VARCHAR,NavigationID) IN (' + #Ids + ')'
exec (#query)
This can have performance impacts and other complications. Generally I'd try to avoid it.
The other method is to use a User Defined Function (UDF) to split the string into its component parts and then query against that.
There's a post detailing how to create that function here
Once the function exists, it's trivial to join onto it
SELECT * FROM Nav
CROSS APPLY dbo.StringSplit(#Ids) a
WHERE a.s = CONVERT(varchar, Nav.NavigationId)
NB- the 'a.s' field reference is based on the linked function, which stores the split value in a column named 's'. This may differ based on the implementation of your string split function
This is nice because it uses a set based approach to the query rather than an IN subquery, but a CROSS JOIN may be a little complex for the moment, so if you want to maintain the IN syntax then the following should work:
SELECT * FROM Nav
WHERE Nav.NavigationId IN
(SELECT CONVERT(int, a.s) AS Value
FROM dbo.StringSplit(#Ids) a

Calling stored procedure and passing parameter from another stored procedure

This is my stored procedure that deletes rows according to array that was passed to it as XML parameter:
BEGIN
DECLARE #docHandle int
EXEC sp_xml_preparedocument #docHandle OUTPUT, #XMLDoc
DELETE Comments WHERE
ID IN (SELECT * FROM OPENXML(#docHandle, '/values/value1', 2) WITH (value1 INT '.'))
EXEC sp_xml_removedocument #docHandle
END
Now instead of DELETE i want to call to another stored procedure (that executes hierarchical delete, it receives ID parameter)
How can I do that?
UPDATE: this is my second SP:
(
#ID int
)
AS
IF #ID<>1
BEGIN
CREATE TABLE #nodesC (id int primary key)
INSERT INTO #nodesC (id) VALUES (#ID )
WHILE ##rowcount > 0
INSERT INTO #nodesC
SELECT child.ID
FROM Comments child
INNER JOIN #nodesC parent ON child.ParentId = parent.id
WHERE child.ID NOT IN (SELECT id FROM #nodesC)
DELETE
FROM Comments
WHERE ID IN (SELECT id FROM #nodesC)
END
You can't pass an array as a parameter directly. So:
Pass in XML and parse in 2nd stored proc
Use table datatype (SQL Server 2008)
Multiple calls to 2nd stored proc per ID in parsed XML (bad, here for completeness)
Otherwise, the authoritative article on how do deal with this is Arrays and Lists in SQL Server 2005 by Erland Sommarskog
You will need a cursor.
DECLARE c CURSOR LOCAL FORWARD_ONLY FOR SELECT ... FROM OPENXML(...) WITH(...)
OPEN c
WHILE 1 <> 0 BEGIN
FETCH c INTO ...
IF ##fetchstatus <> 0 BREAK
EXEC ...
END
CLOSE c
DEALLOCATE c