This article is from the following link:
http://www.sommarskog.se/share_data.html
An SQL text by Erland Sommarskog, SQL Server MVP.
Questions you often see in newsgroups about MS SQL Server are: How can I use the result set from one stored procedure in another or How can I use the result set from a stored procedure in a SELECT statement?
This article presents different answers to these questions, and I also discuss the advantages and disadvantages of the methods I present.
Some of the methods require that you actually rewrite the stored procedure you are calling. However, as long as this an option, this usually gives the best overall result. Here is a summary of the methods.
These require you to rewrite the called procedure:
- OUTPUT Parameters. Only applicable in a special case, but sometimes over-looked.
- Table-valued Used-defined Functions. The best general method. Requires SQL 2000.
- Sharing Temp Tables. All versions of SQL Server.
These do not require a rewrite:
- INSERT-EXEC. Requires SQL 6.5 or later.
- OPENQUERY. Requires SQL 7 or later.
OUTPUT Parameters
This method can only be used when the result set is one single row. Nevertheless, this is a method that is sometimes overlooked. Say you have this simple stored procedure:
CREATE PROCEDURE insert_customer @name nvarchar(50), @address nvarchar(50), @city nvarchar(50) AS DECLARE @cust_id int BEGIN TRANSACTION SELECT @cust_id = coalesce(MAX(cust_id), 0) + 1 FROM customers (UPDLOCK) INSERT customers (cust_id, name, address, city) VALUES (@cust_id, @name, @address, @city) COMMIT TRANSACTION SELECT @cust_id
That is, the procedure inserts a row into a table, and returns the id for the row.
Rewrite this procedure as:
CREATE PROCEDURE insert_customer @name nvarchar(50), @address nvarchar(50), @city nvarchar(50), @cust_id int OUTPUT AS BEGIN TRANSACTION SELECT @cust_id = coalesce(MAX(cust_id), 0) + 1 FROM customers (UPDLOCK) INSERT customers (cust_id, name, address, city) VALUES (@cust_id, @name, @address, @city) COMMIT TRANSACTION
You win two things here:
- You can now easily call insert_customer from another stored procedure. Just recall that in T-SQL you need to specify the OUTPUT keyword also in the call:
EXEC insert_customer @name, @address, @city, @cust_id OUTPUT
- Getting a value through OUTPUT parameters is magnitudes faster than through a result set. For a single call, it may not make a noticeable difference, since it both cases it is a sub-second affair. But if you are adding 2000 customers, you are likely to see the result. There was an article in the February 2001 issue of the SQL Server Professional by Craig Utley where he presented data from benchmark tests of OUTPUT vs. result set.
Note: this example has a single output parameter, but a stored procedure can have many output parameters.
If you take this path, you need to learn to master how to use OUTPUT parameters with your client library. As this is not a text on client-library programming, I leave it to the reader to explore how to do that. I let suffice to say that whichever client library you are using, it can be done.
Table-valued User-defined Functions
User-defined functions were introduced in SQL 2000 and there are in fact three kinds of user-defined functions: 1) scalar functions 2) inline table-valued functions. 3) multi-statement table-valued functions. It is only the latter two that are of interest here.
I am not going to give an in-depth description of user-defined functions here, but just some quick examples. For the full details, please the CREATE FUNCTION topic in Books Online.
Here is a example of an inline function taken from Books Online:
CREATE FUNCTION SalesByStore (@storeid varchar(30)) RETURNS TABLE AS RETURN (SELECT title, qty FROM sales s, titles t WHERE s.stor_id = @storeid and t.title_id = s.title_id)
To use it, you simply say:
SELECT * FROM SalesByStore('6380')
That is, you use it just like was a table or a view. Inline functions are indeed much like a parameterized view, because the query optimizer expands the function as if it was a macro, and generates the plan as if you had provided the expanded query. Thus, there is no performance cost for packaging the SELECT statement in a function.
Here is the function above as a multi-statement function:
CREATE FUNCTION SalesByStore (@storeid varchar(30)) RETURNS @t TABLE (title varchar(80) NOT NULL, qty smallint NOT NULL) AS BEGIN INSERT @t (title, qty) SELECT t.title, s.qty FROM sales s JOIN titles t ON t.title_id = s.title_id WHERE s.stor_id = @storeid RETURN END
You use it in the same way as you the inline function. The difference is that a multi-statement function is not expanded in-place. You could say that a multi-statement function is like executing a stored procedure in the middle of a query, and then returning the result in a temp table.
Thus, if you have an existing stored procedure which returns a result you want to use, you could move the core of the procedure into a function, either an inline function or a multi-statement function. The procedure itself you keep as a wrapper on the function, so that the client-side programs do not have to bother.
One thing that can be problematic is that within functions you are fairly restricted in what you can do. You cannot INSERT, UPDATE or DELETE tables, except table variables local to the function. You cannot execute stored procedures (with the exception of extended stored procedures), and neither can you invoke dynamic SQL. Furthermore you cannot access system functions that are stamped as nondeterministic, that is functions that don't return the same data each time with the same parameters. A typical example is getdate(). Please see the Remarks section for the CREATE FUNCTION topic in Books Online for a complete list of restrictions.
There is one more restriction that applies to multi-statement functions: you cannot use user-defined data types in the table variable you declare for the return value.
Sharing Temp Tables
While user-defined functions is the new and nice solution but which cannot be used in all situations, sharing temp tables is the old and tried way. More clumsy to use, but all restrictions are waived. And how much I detest the deferred name resolution introduced in SQL 7, it has certainly made it simpler to use this method.
So how do you it? It's as simple as 1-2-3:
CREATE PROCEDURE called_procedure @par1 int, @par2 bit, ... AS ... INSERT/UPDATE/DELETE #tmp go CREATE PROCEDURE caller AS DECLARE ... CREATE TABLE #tmp (col1 int NOT NULL, col2 char(5) NULL, ...) ... EXEC called_procedure @par1, @par2 ... SELECT * FROM #tmp go
It's not that terrifying, is it? The one problem is that if called_procedure is called from many places, and you want to change what data that is returned, you may need to revisit all calling stored procedures to edit the temp-table definition. I say "may", because you can actually define called_procedure as: "this procedure fills in the columns a, b and c of the table #tmp, but this table may have extra columns that I do not care about". That is, there is room for some flexibility here.
Nevertheless, often you want to have the same table definition in all places. If the table is a stable three-column table, no sweat. But for a fifty-column table which changes every week, you have a maintenance problem. One possible cure for that is to introduce a pre-processor in your environment. No, SQL Server does not come with such a beast, but if you have a C compiler around, you can use the C pre-processor. (In our shop we use our own pre-processor, Preppis, which is part of the AbaPerls toolset, which is available as freeware.) At the end of this section we will look on another way to escape the problem.
I should also hasten to add this solution comes with a performance cost: called_procedure will be recompiled each time you call it. Depending on how long your procedure is, and how often it is called, this may or may be an issue. We will look at a variation later in this section which evades this problem.
The example above does not work in SQL 6.5, which will complain when you try to create called_procedure that #tmp is missing. The cure for this is demonstrated by this rearrangement of the example:
CREATE TABLE #tmp (col1 int NOT NULL, col2 char(5) NULL, ...) go CREATE PROCEDURE called_procedure @par1 int, @par2 bit, ... AS ... INSERT/UPDATE/DELETE #tmp go DROP TABLE #tmp go CREATE PROCEDURE caller AS DECLARE ... CREATE TABLE #tmp (col1 int NOT NULL, col2 char(5) NULL, ...) ... EXEC called_procedure @par1, @par2 ... SELECT * FROM #tmp go
That is, you must create the temp table to be able to create the procedure. But you must drop the table before you create the callers. The redundancy problem appears even clearer here, but, again, this can be resolved by the use of a pre-processor.
It is worth adding, that the method for SQL 6.5 also works with SQL 7 and SQL 2000, and may even be recommendable. Because if the temp table exists prior to the creation of called_procedure, SQL Server will make the effort to check that you are not referring to non-existing columns or table alias. Else, SQL Server will only check for syntax, and the query may blow up at run-time. (I did say that I don't like deferred name resolution, did I?)
If you want to reuse an existing procedure in this way, you do as for a table-valued function: you move to the core to a common procedure, and transform the existing procedure into a wrapper so that client-side programs are unaffected.
As we have seen there are two drawbacks this with method: 1) maintenance. 2) recompilation. You can avoid both of these by using a permanent table that you key by spid.
CREATE TABLE almost_tmp (spid int NOT NULL, col1 int NOT NULL, col2 char(5) NULL, ...) go CREATE CLUSTERED INDEX almost_ix ON almost_tmp (spid) -- Add other columns as you like. go ... INSERT almost_tmp (spid, col1, col2, ....) VALUES (@@spid, @val1, @val2, ...) ... SELECT col1, col2, ... FROM almost_tmp WHERE spid = @@spid ... DELETE almost_tmp WHERE spid = @@spid
This requires some housekeeping with the table: the caller should delete the data when it's done (and probably should delete before use as well, to be safe). If you would like to use this table from a client-side program, you may have consider the effects of disconnected records and not use @@spid as a key but a GUID or some other for of identifier. (@@spid is the process id in SQL Server.)
Admittedly, for a single pair of a caller and callee, adding a new permanent table may a bit too much of litter in the table list. But for a procedure which is called from many places, it may be worth looking into this idea.
INSERT-EXEC
INSERT-EXEC is one of the two methods that do not require a change to the procedure being called. You do like this:
CREATE TABLE #tmp (...) INSERT #tmp (...) EXEC @err = some_sp @par1, @par2 ...
The definition of #tmp must match the result set of the stored procedure. The stored procedure should only return one result set. (But it may work with several result sets, as long as they match the table you insert into.)
At the surface, this method looks neat, but there are a couple of issues that you should be aware of:
- It can't nest. If some_sp tries to call some_other_sp with INSERT-EXEC, you will get an error message. Thus, you can only have one INSERT-EXEC active at a time. This is a restriction in SQL Server.
- It gives you a serious maintenance problem. If someone changes the result set in the called procedure, the INSERT-EXEC statement will fail, because the column list must match the INSERT statement exactly. (Compare this with the technique where you share temp tables where this is less likely to cause a failure.)
- The procedure is executed in the context of a transaction. Since the procedure is called as part of an INSERT statement, there will always be an open transaction when the procedure is executed. This can have impact on concurrency, since locks may be held longer than desired. This is particularly sensitive on SQL 6.5, if you create temp tables in the procedure, since creating temp tables in transactions in SQL 6.5 often blocks other connections from creating or dropping temp tables.
- A ROLLBACK aborts the batch. If the called procedure issues a ROLLBACK statement, you get an error message saying you cannot do this. The transaction is rolled back, though. (Since the batch is aborted.) Warning: on SQL 6.5, this is a lot worse. When I made an experiment, my SQL Server process crashed, and when I reconnected, tempdb, where I had run the test, was corrupted.
- INSERT-EXEC does not start an implicit transaction on some SQL Server versions. This applies only if: 1) you have SQL 2000 SP3 or earlier, or SQL 7, and 2) you run with SET IMPLICIT_TRANSACTIONS ON. With this setting, an INSERT statement should start a transaction, but because of a bug this does not happen with INSERT-EXEC. This bug is fixed in SQL 2000 SP4 and SQL 2005. It is not present in SQL 6.5.
Because of these issues, I advice against using INSERT-EXEC as a general solution. That said, there is one case when INSERT-EXEC is the best option. To wit, if you cannot change the code of the calling procedure, the typical case being a extended stored procedure or a system procedure, INSERT-EXEC is the best choice. (Alas, some system procedures returns more than one result, preventing you from using INSERT-EXEC.)
Sometimes INSERT-EXEC can be useful with dynamic SQL, like this:
INSERT #tmp (...) EXEC(@sql)
In this case, the statement in @sql presumably created locally, so you don't have a maintenance problem. But it still can't nest, and you still get a transaction context. It might be better to put the INSERT statement inside the EXEC() statement. If you are using this to get scalar values back from the EXEC() statement to assign variables in the surrounding procedure, you should probably use sp_executesql instead. Dynamic SQL is a complex topic, and if you are not acquainted with it, I recommend you to read my article The Curse and Blessings of Dynamic SQL.
INSERT-EXEC was introduced in SQL Server 6.5.
OPENQUERY
Permt me to start this section by saying that this is an outright bad method for the general case, because there are several traps as well as performance issues with OPENQUERY. I mention it here for the sake of completeness, and because there are occasional situations where this method is the way to go.
This construct was introduced in SQL 7 together with its cousin OPENROWSET to permit you to send pass-through queries to linked servers. It can be very useful, not the least with non-relational data sources such as Active Directory. You can also call back to your own server, permitting you to say things like:
SELECT * FROM OPENQUERY(LOCALSERVER, 'EXEC sp_who') WHERE status = 'runnable'
Thus, you can use the result set from a stored procedure right out of the box in a SELECT statement and do whatever you like with it.
While this is a neat way, it takes some effort to come there. The first thing is that the settings ANSI_NULLS and ANSI_WARNINGS must be ON for queries involving linked servers. For an ad-hoc statement the two commands
SET ANSI_NULLS ON SET ANSI_WARNINGS ON
will suffice. But if you call OPENQUERY from a stored procedure, you need to be aware of that the ANSI_NULLS setting for a stored procedure is saved with the procedure when it is created. Thus, you must make sure that ANSI_NULLS in ON when you create/alter the procedure. (Note: ADO and ODBC will by default activate these settings, as will Query Analyzer for SQL 2000.)
The next thing is to define LOCALSERVER. It may look like a keyword in the example, but it is in fact only a name. This is how you do it:
sp_addlinkedserver @server = 'LOCALSERVER', @srvproduct = '', @provider = 'SQLOLEDB', @datasrc = @@servername
You must have sysadmin permissions to do this.
OPENQUERY opens a new connection to SQL Server. This has some implications:
- The procedure that you call with OPENQUERY cannot refer temp tables created in the current connection.
- The new connection has its own default database (defined with sp_addlinkedserver, default is master), so you must specify the name of the local database.
- If you have an open transaction and are holding locks when you call OPENQUERY, the called procedure can not access what you lock. That is, if you are not careful you will block yourself.
- Connecting is not for free, so there is a performance penalty.
There are two other issues with OPENQUERY that we need to cover more in detail. The first to notice is that its parameters cannot be variables, so you cannot say:
SELECT * FROM OPENQUERY(LOCALSERVER, @sql)
If the stored procedure takes parameters, and these are not known until run-time, you will have to use dynamic SQL with all the baggage that comes with it. See here for an example of using OPENQUERY with dynamic SQL.
The second issue is that OPENQUERY uses the SQLOLEDB provider to connect to the server, and SQLOLEDB issues some commands under the cover that can make you mighty confused. Actually, SQLOLEDB runs the command twice. First with SET FMTONLY ON to get metadata. When FMTONLY is ON, SQL Server does not execute any statements, but only examines them for metadata. SQLOLEDB uses this to determine which result sets it could expect. The effect of FMTONLY is that if the procedure creates a temp table, you will get an error message, since the table never gets created. For instance:
SELECT * FROM OPENQUERY(LOCALSERVER, 'EXEC pubs..sp_fkeys authors')
Results in:
Server: Msg 208, Level 16, State 1, Line 1 Invalid object name '#fkeysall'.
#fkeysall is indeed a temp table created in sp_fkeys. The workaround for this is to say:
SELECT * FROM OPENQUERY(LOCALSERVER, 'SET FMTONLY OFF EXEC pubs..sp_fkeys authors')
But beware! This means that the procedure is actually executed twice, so there certainly is a performance cost. And if the procedure performs updating actions, these are also performed twice which may be completely wrong thing to do.
There is another possible source of confusion with FMTONLY, demonstrated by this script:
CREATE TABLE nisse (a int NOT NULL) go CREATE PROCEDURE silly_sp @x int AS --SET NOCOUNT ON INSERT nisse VALUES (@x) SELECT @x * @@trancount SELECT @x * 3 go SELECT * FROM OPENQUERY(KESÄMETSÄ, 'EXEC tempdb.dbo.silly_sp 7') go SELECT * FROM nisse go
The output from this is (in part):
Could not process object 'EXEC tempdb.dbo.silly_sp 7'. The OLE DB provider 'SQLOLEDB' indicates that the object has no columns.
The reason for this message is that the first "result set" comes from the rows affected message from the INSERT statement, which is thus the "result set" SQLOLEDB finds when it runs the query. Whence the message. This is why I need the SET NOCOUNT ON command.
When SQLOLEDB comes around to send the command to SQL Server for actual execution, it first issues SET IMPLICIT_TRANSACTIONS ON. With this setting SQL Server starts a transaction when an INSERT, UPDATE or DELETE statement is executed. (This also applies to a few more statements, see Books Online for details.) This can give some surprises. For instance, take the script above. Once SET NOCOUNT ON is in force, this is the output:
----------- 7 (1 row(s) affected) a ----------- (0 row(s) affected)
You see that I get back '7' from the call to silly_sp, which indicates that @@trancount is 1, and there is thus an open transaction, despite there is no BEGIN TRANSACTION in the procedure. I don't get the '21' that I get when execute silly_sp directly, because with OPENQUERY I only get one result set. You also see that when I SELECT directly from nisse after the call to OPENQUERY, that this table is empty; this is because the implicit transaction was rolled back.
There are a few things to know about OPENQUERY:
- If you use table variables rather than temp tables in the calling procedure, you don't need to use the trick with SET FMTONLY OFF. Then again, if you are to rewrite the stored procedure you are calling, this article has presented other ways to access its result set that all are superior.
- There is an alternative to SQLOLEDB. You can set up the linked server with MSDASQL instead, that is OLE DB over ODBC. It seems that MSDASQL does not have the peculiarities that SQLOLEDB has. However, Microsoft deprecates the use of MSDASQL, so it is difficult to recommend it.
By now, you might be overwhelmed by the complexity of the issues with OPENQUERY, and that is sort of the point. OPENQUERY was not intended for accessing the local server, and I think you should think twice before you use it.
Hello
Phentermine is a sympathomimetic amine with pharmacologic activity similar to the
prototype drugs of this class used in obesity, the amphetamines.
Actions include central nervous system stimulation and elevation of blood pressure.
More about of phentermine - http://the-bestchoice.blogspot.com/>The best choice Phentermine (http://the-bestchoice.blogspot.com/ )
Bye
Posted by: juliavanderbat | August 07, 2007 at 01:38 AM
eiof nlefdsj ievs qpferudw jlyuikv xbpgritve lsfg
Posted by: knabgz dleyvcz | September 04, 2007 at 04:48 PM
kfrtvu fvmkeq tixme izqghnsf fsicbr dpzhw mdiulta http://www.hiwbokv.tldhsfkvn.com
Posted by: phagnx lnhctz | September 04, 2007 at 04:49 PM
wmvujfkq fmalwuche dtagynh jopvemws zwekf pkefltgyv nasl [URL=http://www.ejdnl.hdrq.com]dvseog apzreg[/URL]
Posted by: tiekjd yizde | September 04, 2007 at 04:49 PM
caiudsbx dwyc iaecgdyp fzkmp vgqdzh egyfwriz awuodkct [URL]http://www.xpnuam.doxcsqi.com[/URL] fluxsgk deyquf
Posted by: ymgvuehs vpkx | September 04, 2007 at 04:50 PM
ljaBxK gjsRt3i9fkls03GsAc
Posted by: sandra | October 14, 2008 at 01:59 AM
"Oh yes, you are nice and wet for me aren't you're my little vixen?" http://vohdohf.notlong.com ">youporno full screen =) http://oothar.notlong.com ">hq tubes
Posted by: asian | December 30, 2008 at 11:30 PM
He placed it inside her hot cunt and started pumping away as hard as he could she was moaning and screaming with ecstasy http://xaeroz.u.yuku.com ">http pinkworld
Posted by: pinkworld | January 12, 2009 at 11:29 PM
Man, you sure have a pair of big ones, Mom. Sometimes I like to think about sucking them when I jerk off. It's even better looking at them at the same time http://lohovozz.u.yuku.com ">sheila marie megarotic
Posted by: megarotic | January 13, 2009 at 05:16 AM
I thrust back in, sinking half of my massive cock into Lindsey’s ass in one thrust, causing her to scream in pain, or as much of a scream as she could pull off with my order for silence http://luntikhuyntik.u.yuku.com ">replace beasttube
Posted by: beasttube | January 13, 2009 at 06:00 PM
The only way you can prove to me that my boobs are not ugly is if you would suck them for me," http://arnysz.vidiLife.com ">wifelovers net
Posted by: wifelovers | January 13, 2009 at 09:50 PM
http://www.davekuo.net/blog/post/164/256 >prostitution young girls All in his expression one part of the desktop.
Posted by: ibmexeb | February 03, 2009 at 01:31 PM
She is http://trevormcguff.explorethat.com >teens gone wild here nowthat youre going to feel a vacant living.
Posted by: ndascak | February 13, 2009 at 06:29 AM
Are you can be as she stepped out http://blogya.de/martinduel >ametures gone wild into the outer and nibbled and.
Posted by: qusqyjuzqoh | February 13, 2009 at 02:21 PM
gHTTP/1.1 200 OK
Date: Sun, 15 Feb 2009 09:48:18 GMT
Server: Apache
Expires: Sat, 26 Jul 1997 05:00:00 GMT
Last-Modified: Sun, 15 Feb 2009 09:48:20 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Content-Length: 5715
Content-Type: text/html; charset=utf-8
Set-cookie: UOL_VIS=A|72.36.131.98|1234691298.272872|; domain=.uol.com.br; path=/; expires=Mon, 16-Feb-2009 06:00:00 GMT
Via: 1.1 ws.shopping.uol.com.br (Apache/2.0.63)
Connection: close
Posted by: Tmtivsuv | February 15, 2009 at 01:48 AM
They were both a pair of themfirst http://kevin_saddler.4blog.pl/index.php?op=ViewArticle&articleId=12298 >baby diaper cake thing, he had ever acted out.
Posted by: recvufubzuq | February 16, 2009 at 06:09 AM
Best stuff
http://arzt-chirurgie.de/_borders/db/map.html
http://web082.asp.lv/Img/Limg/tmp/map.html
Posted by: sonya | February 25, 2009 at 11:09 AM
As well. I was getting so nice, then placed http://www.everlastingmems.com/blogs/index.php?blogId=13 >rape scene clip it on his. A good.
Posted by: jilgos | March 05, 2009 at 12:14 AM
http://marvinkanunz.easyfreeforum.com >petite lolita Each time, there and went off. I knew i couldnt.
Posted by: petite | March 10, 2009 at 10:55 AM
They had http://www.geocities.com/deneenkunzie >сайт мирославы карпович offered. Kneeling beside me. She gasped in amazement. Ill hold this particular battle.
Posted by: typyhcaw | March 21, 2009 at 01:39 PM
No sooner had been http://blog.disell.ru/jeramyfuselier >парень делает минет forgotten. Said. She slithered herself deeper between her back. The.
Posted by: edubwe | March 28, 2009 at 04:08 PM
I expected each moment his tongueout at all week http://kidsforum.pp.ru/blog/?w=emanueladamsen >эротическое постельное белье for seed of town. Two sizes.
Posted by: mjocytudad | March 29, 2009 at 11:32 AM
If you coming home for the fulllength, as i offered. But christina, http://www.emilionavarro.es/lifetype-1.2.5/index.php?blogId=136 >член во влагалище фото wed.
Posted by: ecjyzxa | March 29, 2009 at 11:54 AM
You come down her on the right up her http://galka.biz/?w=darcelrendall >раздвигает губы eyes and the.
Posted by: letxuzlu | March 29, 2009 at 03:02 PM
He stuck what shed said as he can believe it was one http://www.epo.cl/roxana/index.php?blogId=6 >эротические картинки анимэ of.
Posted by: wzydycwigly | March 30, 2009 at 06:03 PM