I have been using a stored procedure for more than 1.5 years. But I've never considered how data is retrieved from the UI or within another stored procedure.
When I write a simple stored procedure.
eg.
CREATE PROCEDURE sp_test
AS
BEGIN
SELECT * FROM tblTest --Considering table has 3 columns.
END
How does C# gets this result into DataTable.
Whenever I have to use the result of this procedure in another procedure, I think we have to create a table valued parameter using the table datatype and assign its result to a table variable. I've never tried it.
CREATE PROCEDURE sp_testcall
AS
BEGIN
#temp = exec sp_test -- I think this would be the way, never tried
END
If the above sample code is true, then what is the difference between using the above method and a query to insert records into a temporary table?
CREATE PROCEDURE sp_test
AS
BEGIN
SELECT * INTO #tmp FROM tblTest --Considering table has 3 columns.
END
It would seem that copying the result into a temporary table requires another effort by sql server.
But what would be going on behind the scenes? Would it directly assign references of the result into a table valued parameter or does it use the same process as a temporary table?
My question might not be clear. But I will try to improve.
For an beginer to intermediate level you should always consider #temp tables and #table variables two faces of the same coin. While there are some differences between them, for all practical purposes they cost the same and behave nearly identical. The sole major difference is that #table variables are not transacted and hence not affected by rollbacks.
If you drill down into details, #temp tables are slightly more expensive to process (since they are transacted) but on the other hand #table variables have only the lifetime of a variable scope.
As to other issues raised by your question:
table value parameters are always read only and you cannot modify them (insert/update/delete into them)
tacking the result set of a procedure into a table (real table, #temp table or #tabel variable, doesn't matter) can only be done by using INSERT INTO <table> EXEC sp_test
as a rule of thumb a procedure that produces a result that is needed in another procedure is likely to be better of as a User Defined Function
The topic of sharing data between procedures was analyzed at length by Erland Sommarskog, see How to Share Data Between Stored Procedures.
A select means "return data to client". C# is a client, therefore it gets the data.
Then again, it's not exactly C# that does it, it's ADO.NET. There's a data provider that knows how to use a network/memory/some other protocol to talk to the SQL server and read data streams it generates. This particular client (ADO.NET) uses the received data to construct certain classes, such as DataTable, other providers can do something completely different.
All that is irrelevant at SQL Server level, because as far as the server is concerned, the data has been sent out using the protocol with which the connection was established, that's it.
From inside, it doesn't make much sense to have a stored procedure return simply selected data to anything else.
When you need to do that, you have the means to explicitly tell SQL Server what you want, such as inserting the data into a temporary table available to both involved SPs, inserting data into a table-valued parameter passed to the procedure, or rewriting your stored procedure as a function that returns a table.
Then again, it's not exacly clear to me what you were asking about.
Related
I have some stored procedures in which multiple queries are being executed. To get last identity of insert I am using IDENT_CURRENT which is causuing problem.
My question is can I have lock statements like C# in T-SQL so that it can be thread safe?
EDIT: Code I am using
INSERT INTO activities
(creator
,title
,description)
VALUES
(#Creator
,#Tile
,#Description)
SET #ActivityId = IDENT_CURRENT('dbo.activities');
INSERT INTO [dbo].activity_cfs
([activity_id],
[cf_id],
[cf_field_name],
[field_key],
[value])
SELECT
#ActivityId,
cf_id,
cf_field_name,
field_key,
value
FROM #ActivityCustomFields
#ActivityCustomFields is my temp table.
It is quite likely that you should use SCOPE_IDENTITY instead of IDENT_CURRENT. There are many explanations how they differ, for example: What is the difference between Scope_Identity(), Identity(), ##Identity, and Ident_Current?
But, if you really need to guarantee that certain part of the code is not being run by several threads at the same time, you can use sp_getapplock.
Based on the code that you added to the question I'm now pretty sure that you should simply use SCOPE_IDENTITY, like this:
INSERT INTO activities
(creator
,title
,description)
VALUES
(#Creator
,#Tile
,#Description);
SET #ActivityId = SCOPE_IDENTITY();
INSERT INTO [dbo].activity_cfs
([activity_id],
[cf_id],
[cf_field_name],
[field_key],
[value])
SELECT
#ActivityId,
cf_id,
cf_field_name,
field_key,
value
FROM #ActivityCustomFields;
The SCOPE_IDENTITY function returns the last identity created in the same session and the same scope.
The IDENT_CURRENT returns the last identity created for a specific table or view in any session. So, if you have several threads that run this procedure simultaneously IDENT_CURRENT will see identities generated in other threads, which you don't want.
SCOPE_IDENTITY would be the way to go in my understanding. Please follow following link that in SQL Server when using SCOPE_IDENTITY will always be threadsafe:
http://www.vbforums.com/showthread.php?727047-RESOLVED-Is-MSSQL-s-SCOPE_IDENTITY()-thread-safe
I got stored procedure returning 2 tables. I use the information from both in my c# in Visual Studio 2010 without any issue.
Now my problem is, after doing the work at C# side, I want to update all the fields in the results with new date, using a brand new update stored procedure. For that, the easiest theoretical way would be to call the same stored procedure and use the returning tables in my update statement.
Unfortunately, that's where my problem starts. In the front end, all I gotta do call ds.Table[0] and ds.Table[1] (assuming results were stored in ds), to access each returned table, but I don't know how to access the multiple tables returned from the stored procedure, in another stored procedure.
For example (really simplified version), this was used in first stored procedure:
Select distinct
CTE.SSN, CTE.LastName, CTE.FirstName, CTE.MiddleI
From (Table 1)
Select distinct
CTE.SSN, CTE.AddressID, CTE.AddressLine
From (Table 2)
So 2 different tables were returned, both OF WHICH I can easily accessed in front end to do my work. However, now I want to update using the SSN in both tables which are returned by my previous stored-procedure. So all I gotta do, is use the results from the previous stored-procedure in my new update stored procedure (I do not want to send the ssn from c# to sql). However, I do not know how to access multiple tables returned from stored procedure! If my stored procedure was returning only 1 table, it would be peace of cake, but that's not what is happening!
Like to thank you for reading such long post :)
Here is my requirement:
I want to delete multiple rows of a table
I have the list of all ids that needs to be deleted in the client app.
All I want is to make just a single call from my C# client app to delete all these records.
What I had earlier:
foreach (var id in idList)
{
//Call a Stored Procedure which takes the id
//as parameter and deletes the record
}
This is a problem because there is a database hit for each item in loop.
Then I started using the SQL Server 2008 newly introduced Table Types and Table Valued Parameters for stored procedures for this.
First have a Table Type created in the database which has a single column (of type of Id)
Create a new Stored Procedure which accepts this new table type (Table Valued Parameter)
In the Client code, create a list/Datatable with all the ids to be deleted.
Make a single call to the database to make use of the new stored Procedure.
It is pretty straight forward in the client code, which goes something like this:
SqlParameter parameter = new SqlParameter();
parameter.SqlDbType = System.Data.SqlDbType.Structured;
OK - Though I achieved what I set for, this definitely does not work for SQL server versions before 2008.
So what I did was to introduce a fall back mechanism:
I introduced a Stored Procedure that takes comma separated id values
My client code will create such comma separated ids, and then simply pass it to the new stored proc.
The Stored Proc will then split all those ids, and then call delete one by one.
So in a way, I have achieved what I had set for even on SQL Server version prior to 2008: delete multiple rows with a single database hit.
But I am not that convinced with the last approach I took - A quick search on the internet did not reveal anything much.
So can somebody tell me if I am doing the right thing with comma separated ids and all.
What is the usual best practice to delete multiple records from the client app with a single database hit, provided the ids are known.
Thanks in advance.
hi you just concatenate the all values in the list with delemeter as follows 1,2,3,4,5
here is the sample store procdure for deleteing multiple rows
create PROCEDURE deleteusers --deleteusers '1,2,3,4'
-- Add the parameters for the stored procedure here
#UsersIds varchar(max)
AS
BEGIN
declare #abc varchar(max)
set #abc='delete from users where userid in('+#UsersIds+')'
exec(#abc)
END
GO
It will delete the users with userid 1,2,3,4
You can send a string containing comma separated or some other character(to be used for split) to send ids from C# end.
Create Procedure deleteusers
(
#ids nvarchar(MAX)
)
as
begin
DECLARE #pos bigint,#count int,#id varchar(max),#I int,#Spos int
set #Spos=1
set #I=1
set #count= len(#ids)-len(REPLACE(#ids,',',''))
while(#I<=#count)
begin
SET #pos = CHARINDEX(',', #ids, 1)
set #id = substring(#ids,#Spos,#pos-1)
delete from TableName where id=#id
set #ids= stuff(#ids, #Spos, #pos,'')
set #I=#I+1
end
end
The Dynamic SQL option listed here is probably your best bet but just to give you one more option.
CREATE PROCEDURE deleteusers --deleteusers '1,2,3,4'
-- Add the parameters for the stored procedure here
#UsersIds varchar(max)
CREATE TABLE #DeleteUsers (UserId Int)
-- Split #UserIds and populate into the #DeleteUsers temp table
DELETE FROM Users WHERE UserId IN (SELECT UserId FROM #DeleteUsers)
If you have a column delimited list of values, say #list, you can do what you want with this delete statement:
delete from t
where ','+#list+',' like '%,'+cast(t.id as varchar(255))+',%'
This is emulating the in expression with string operations.
The downside is that this requires a full-table scan. It will not use an index on id.
Rather than build SQL strings in code, I'd suggest looking at an ORM (object-relational-mapper), such as the Entity Framework or NHibernate.
I am attempting to create a stored procedure/ADO.NET mapping mechanism where a stored procedure with parameters becomes
object MyStoredProcedure.Execute(out returnValue, param1, param2, ...)
The problem comes when trying to generate the actual data retrieval method. I can easily obtain most of the schema information I need from the Information Schema views, but I can't reliably find what type of return (output param vs. SELECT/SqlDataReader vs. both) should come from the procedure and whether to call ExecuteNonQuery or ExecuteReader.
Worst-case, I can probably parse the procedure's text, but there are all kinds of funky things that could go wrong there.
The reason for the code generation is that the application database contains many hundreds of stored procedures. And we inherited this project, so there is no way to wrap our heads around that many procs that we didn't create.
I actually have two main goals for the ADO.NET generation:
1) Remove all string literals (stored proc names in SqlCommand creation, parameter names in SqlParameter creation, etc.) from the application. This way, when a procedure or database schema changes, we can regenerate the ADO.NET wrappers, and any errors resulting from those changes will be caught at compile time.
2) Remove the need to dig through a proc to determine params, returns types, etc. So basically, the database itself becomes an API, with all of the internal stored procedure details abstracted away.
Yup; that isn't easy. For simple cases you can try running the sp (awooga!) passing nulls for all the parameters, and using SET FMTONLY ON - a bit risky (extended sprocs are stll executed, for example) and not robust since the TSQL could branch on the input. But an option.
The "out" ets should be available via metadata; the "old" way would be syscolumns (there is probably an info-schema alternative for doing it the right way).
Just as an update; if you want the database to describe itself as an API, perhaps consider UDFs for the selects; advantages:
the metadata for the return values is rigid and easy to query
it is composable at the caller
Or; just use an ORM. LINQ-to-SQL will work happily with this type of setup (including composability); Entity Framework will certainly do all that hard work for you for stored procedures. Or any of the others; NHibernate, LLBLGen Pro, etc. They have all tackled this exact problem. It isn't trivial; why re-invent it?
I don't think you should be worried about it.
Wouldn't it be better to provide overloads & let the user decide which method to call?
Let me know, if I have not understood the question correctly.
Assuming that all of your stored procedures are consistent, that is, each returns at most one result-set, with the same column-set irrespective of it's parameter values or the data-state of the database, and all OUTPUT parameters are always written to, ..
And also assuming that this is a development or at latest a build-time tool (and not a run-time tool) and that you have some control over Srored Procedure's content, .. THEN here is what I would try:
Institute a retroactive Stored Procedure standard that requires all sProcs to have a comment of the following form, that must work correctly in the test (or development?) database:
'TEST: EXEC spThisProc [, ...]
Write your tool to extract the list of stored procedures, their parameters, data types and output parameters from the system catalogs (you can use the INFORMATION_SCHEMA tables ROUTINES and PARAMETERS for this).
Also retrieve the script for all sProcs (from the ROUTINE_DEFINITON column of the INFORMATION_SCHEMA.ROUTINES table, for one place), then extract the text of the "'TEST:" command (above) and ExecuteReader that command against the Test database. Then test the Resultset returned to see if it contains any datasets (or is it "Tables"?). If not, then mark this sProc as needing ExecuteNonQuery, otherwise, extract the column definitions of the returned dataset and use than to generate the corresponding ExecuteReader code for your Class definitions.
hope this could help retrieving the type of params
select s.id, s.name, t.name as [type], t.length,s.isoutparam
from syscolumns s inner join systypes t
on s.xtype = t.xtype
where id = (select id from sysobjects where name = 'SP NAME')
And check out this excellent link - get the list of columns returned from the SQL select
this is a sample query
USE tempdb
GO
IF OBJECT_ID('dbo.TestSP') IS NOT NULL
DROP PROCEDURE dbo.TestSP;
GO
IF NOT EXISTS (SELECT * FROM sys.servers WHERE name = 'Loopback')
BEGIN
EXEC sp_addlinkedserver #server = 'Loopback', #srvproduct = '',
#provider = 'SQLOLEDB', #datasrc = ##servername
END
GO
CREATE PROC dbo.TestSP
AS
SELECT 1 as [ID], 'Name' as [Name], 'Boston' as [City]
GO
DECLARE #MyXML XML
SELECT #MyXML = (SELECT * FROM
(SELECT *
FROM OPENQUERY(Loopback,'SET FMTONLY ON;
EXEC tempdb.dbo.TestSP;
SET FMTONLY OFF')) vw FOR XML AUTO, XMLSCHEMA)
SELECT #MyXML
SET FMTONLY--> It will allow you to run a SP, but only get back metadata
Also, you can try looking at SqlServer SMO http://msdn.microsoft.com/en-us/library/ms162169.aspx for querying information on your database without having to write queries against the information schema directly.
I've created a stored procedure similar to the one below (I'm using this cut down version to try and figure our the problem).
CREATE PROCEDURE bsp_testStoredProc
AS
BEGIN
CREATE TABLE #tmpFiles
(
AuthorName NVARCHAR(50),
PercentageHigh INT
)
-- Insert data into temp table
SELECT AuthorName, PercentageHigh FROM #tmpFiles
ORDER BY PercentageHigh DESC
DROP TABLE #tmpFiles
RETURN 0
END
From my C# code in VS2008, I'm trying to use the Query component with the Use Existing Stored Procedure option to connect this up to a DataTable / DataGridView to display the results.
However, because I'm selecting from a temporary table, in the Query component properties Visual Studio does not display any columns being returned from the stored procedure. I assume that it has trouble determining the data types being used since the SP is not based on a real schema.
Connecting to different stored procedures that select from real tables do show the columns correctly.
Does anyone know away around this? Is there some sort of hint I can add somewhere to explicitly state what sort of data will be returned?
Thanks in advance.
For info, you might consider using a "table variable" rather than a temporary table (i.e. #FOO rather than #FOO) - this might help a little, and it certainly helps a few tempdb issues.
With temporary tables - no there is no way of explicitly declaring the SPs schema. I would perhaps suggest using a simplified version of the SP while you generate your wrapper classes - i.e. have it do a trivial SELECT of the correct shape.
Alternatively, I would use LINQ to consume a UDF, which does have explicit schema.