Retrieving "output messages" from batch SQL command (SQL Server) - c#

I'm executing several discrete queries in a single batch against SQL Server. For example:
update tableX set colA = 'freedom';
select lastName from customers;
insert into tableY (a,b,c) values (x,y,z);
Now, I want to capture the result in a DataSet (from select statement) which is easy enough to do...but how do I also capture the "meta" response from that command similar to the way Query Analyzer/SQL Mgt Studio does when it displays the "Messages" tab and diplays something similar to:
(1 Row affected)
(2 Rows Updated)

look into SQL Connection events. I think that's what you're after:
http://msdn.microsoft.com/en-us/library/a0hee08w.aspx

My suggestion would be to use the ##rowcount variable for this. Usually, when I'm doing these kind of commands, if I want to trap both the potential error and the rows affected, I do
declare #rowsAffected int, #error int
select * from sometable
select #rowsAffected = ##rowcount, #error = ##error
if ##error <> 0 goto errorCleanup

Nick is right to suggest ##ROWCOUNT - in fact, as a matter of routine I always use SET NOCOUNT ON, which has a (small) performance benefit - but more importantly, this detail is an implementation detail - so you code shouldn't care...
If you want to return a value (such as number of rows updated), use any of:
return value
output parameter
SELECT statement
The last has the most overhead

Related

Calling a Stored Procedure from NHibernate and retrieving ##ROWCOUNT

After trying to work this out for a few hours, I'm beginning to think I can't solve this without your help. Situation:
My stored procudure in SQL Server Management Studio (basically):
SET NOCOUNT ON;
DELETE myTable
... -- Complex join query that deletes duplicates
RETURN ##ROWCOUNT -- The number of rows affected
This procedure works well, if I run it in SSMS the following query is run:
USE myDb
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[StoredProcedure]
#var1 = N'asd',
#var2 = N'fgh',
#var3 = N'jkl'
SELECT 'Return Value' = #return_value
GO
The result is shown as 1 row with name Return Value and value 8700 (this is correct, it's the number of rows that was deleted by the query).
My problems begin in my C#/NHibernate code. In my NHibernate mapping:
<sql-query name="MyStoredProcedureName">
exec dbo.StoredProcedure #var1=:var1, #var2=:var2, #var3=:var3
</sql-query>
In my method where I call the stored procedure:
var deletedEntities = session.GetNamedQuery("MyStoredProcedureName")
.SetString("var1", "asd")
.SetString("var2", "fgh")
.SetString("var3", "jkl")
.ExecuteUpdate();
This results in deletedEntities = -1. I would like it to be deletedEntities = 8700.
I'd like to point out that the following did return the number of affected rows:
var deletedEntities = session.CreateQuery("DELETE Class c WHERE c.var1 = :var1 AND c.var2 = :var2 AND c.var3 = :var3")
.SetString("var1", var1)
.SetString("var2", var2)
.SetString("var3", var3)
.ExecuteUpdate();
Where Class is the class that belongs to the NHibernate mapping.
So, to summarize:
ExecuteUpdate on a SIMPLE session.CreateQuery gives me back the number of affected rows (GOOD)
ExecuteUpdate on a COMPLEX session.GetNamedQuery gives me back -1 (BAD)
This same complex stored procedure gives me back the desired 8700 when I execute it in SSMS (GOOD)
Anyone has an idea of how to fix 2?
Using http://csharptechies.blogspot.co.uk/2013/04/how-to-get-return-value-from-stored.html I got my code working. In the procedure, replaced RETURN ##ROWCOUNT with SELECT ##ROWCOUNT
In the C# code, I replaced .ExecuteUpdate() with .UniqueResult(). UniqueResult returns an object, which will contain the ##ROWCOUNT value (need to convert to int as stated in the link above).
So the solution is a combination (I'd like to use the mapping):
Use SELECT instead of RETURN
Use .UniqueResult() instead of .ExecuteUpdate()
Edit: I got another problem, the query had a timeout whenever there was a large number of records to be removed. I fixed this with the help of other questions, such as SQL Server: Effects of using 'WITH RECOMPILE' in proc definition?.
Use WITH RECOMPILE in a complex query on large tables (my query involved a left outer join, min(id) and group by's, the number of records to be affected are often around 20.000 but can run up to 100.000+ and my table has over 350.000.000 records in it) to avoid timeouts.

Is "BEGIN TRAN" needed in order to get correct SCOPE_IDENTITY?

I'm using an SqlCommand like so:
command.CommandText = "INSERT INTO ... VALUES ...; SELECT SCOPE_IDENTITY();";
Is this enough, or do i need BEGIN TRAN etc.? (Mentioned here.)
I tried it first, of course, and it works fine. But will it work correctly even if there are two simultaneous inserts? (And I'm not sure how to test that.)
You don't need BEGIN TRAN. Scope_Identity() functions fine without it. Even if there are "simultaneous inserts". That is the whole point of the function--to return an answer for the current scope only.
Be aware that in less than SQL Server 2012, parallelism can break Scope_Identity(), so you must use the query hint WITH (MAXDOP 1) on your INSERT statement if you want it to work properly 100% of the time. You can read about this problem on Microsoft Connect. (It is theoretically fixed in Cumulative Update package 5 for SQL Server 2008 R2 Service Pack 1, but some people seem to think that may not be 100% true).
There is also the OUTPUT clause in SQL Server 2005 and up, which is another way to return data about your INSERT, either by sending a rowset to the client or by outputting to a table. Be aware that receiving the rowset does not actually prove the INSERT was properly committed... so you should probably use SET XACT_ABORT ON; in your stored procedure. here's an example of OUTPUT:
CREATE TABLE #AInsert(IDColumn);
INSERT dbo.TableA (OtherColumn) -- not the identity column
OUTPUT Inserted.IDColumn -- , Inserted.OtherColumn, Inserted.ColumnWithDefault
INTO #AInsert
SELECT 'abc';
-- Do something with #AInsert, which contains all the `IDColumn` values
-- that were inserted into the table. You can insert all columns, too,
-- as shown in the comments above
Not exactly the answer to your question, but if you are on SQL Server 2005 and above, consider using the OUTPUT clause, take a look this so answer for full sample, it's simple enough to implement
INSERT dbo.MyTable (col1, col2, col3)
OUTPUT INSERTED.idCol
VALUES ('a', 'b', 'c')
Scope_Identity and Begin Tran work independently, begin tran is used when you might want to rollback or commit a transaction at a given point within your query.

what is a reliable way to return number of records inserted from a stored procedure

I am using INSERT Trigger on that table. Once trigger is executed (it update the table if a condition is meet), that is where the problem is.
int records = sc.ExecuteNonQuery(); // works ok if trigger does not update the record
The above code always ruturns -1 if I leave SET NOCOUNT ON; in the stored procedure itself. If I remove it, I get correct result but if trigger does update the record, then wrong result. I sometime get 10 or a different number. My Trigger looks like this
UPDATE students
SET status = 'Inactive'
FROM Inserted i
INNER JOIN students T2
ON i.sname = T2.sname
AND i.id <> t2.id
That means it can return more than one record (esp in test cases). Can someone tell me what is the cure? I am open to use Functions if that solves the problem or any better approach.
Thanks
Adding Insert SP
CREATE PROCEDURE sp_InsertSudent
-- Add the parameters for the stored procedure here
#student_name varchar(25) = null,
#status varchar(20) = null,
#renew varchar(15) = null,
#edate datetime = null
AS
BEGIN
--SET NOCOUNT ON;
insert into students VALUES(#student_name,#status,#renew,#edate)
END
GO
Note: I am looking for an error because the fields are picked from Excel. if any field is in wrong format or empty, the Insert SP will produce error. I must convey that error to the user.
Adding Actual SP
So the whole problem is in the SP. If I remove it, everything works fine. Here is my actual SP
UPDATE CustomerTbl
SET [Account Status] = 'Inactive',
[End Date] = DateAdd(day,-1,[Supplier End Date]),
[EnrollmentStatus] = 'Waiting'
WHERE OET.[Account No] = (SELECT [Account No] FROM CustomerTbl WHERE id = ##identity)
AND id <> ##identity
The logic is the same as above but stated differently. The ExecuteNonQuery oupts the result of this trigger than than the actual storedprocedure, so what is he cure? Can suppress its output somehow.
I would add Try Catch blocks to the proc and have it return 1 if successful and 0 if not successful.
I also would adjust the trigger to be more efficient by only chaning the status of those where are active and meet the other criteria.
UPDATE students
SET status = 'Inactive'
FROM Inserted i
INNER JOIN students T2
ON i.sname = T2.sname
AND i.id <> t2.id
AND status <> 'inactive'
This could save you from updating 1000 rows when you only really need to update one active row.
My own answer (not complete yet). According to MSDN documentation for ExecuteNonQuery
When a trigger exists on a table being inserted or updated, the return
value includes the number of rows affected by both the insert or
update operation and the number of rows affected by the trigger or
triggers.
This means I need to modify the trigger itself to accommodate the logic, or even by the fact that when trigger is called, that proves that a record was successful. That means if I get anything greater than 0, that should be assumed as success. Although not solid logic but it will work. SET COUNT ON must be commented for this.
maybe I'm not understanding the question, but if your goal is to track the number of rows inserted, then you would maintain a count of how many times you're calling your stored procedure (assuming your sproc inserts one row at a time, of course).
alternately, if you are trying to maintain a count of the total # of records affected (by both inserts and updates), then you could incorporate the logic in the trigger into your sproc. the rows affected by both the insert statement (always 1) and by the update statement (##rowcount after update completes) ... you could then return that value to the caller.
UPDATE:
Sure, return 0 if there is an error. If using SQL2005 (or above), use TRY/CATCH mechanism.
Add an extra column that the stored proc doesnt populate (leaves null)
then when the trigger runs, simply update the Null values to a non-null value and use the RowCount from the Update to determine how many rows were updated.

T-SQL: how to output/print types of fields/columns returned by a simple SELECT statement?

While using SqlDataReader, it's necessary to know the types of the fields returned in order to call appropriate GetXXX method. So is it possible to output this info in Sql Management Studio?
SELECT ..INTO.. and examine the definition of the new tabke
The WHERE 1 = 0 bit will by shortcircuited here so it should be quick. Of course, you'll need to add your own conditions.
SELECT
...
INTO dbo.TempTable
FROM ...
WHERE 1 = 0
GO
SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TempTable'
GO
DROP TABLE dbo.TempTable
If you have a single table in the FROM:
SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'SourceTable'
Which method depends on complexity. For example, a calculation on decimal column changes precision and scale. Or varchar processing can change length or char to varchar.
You'd be running the SQL anyway to make sure it's OK before calling it the client code...

how to improve SQL query performance in my case

I have a table, schema is very simple, an ID column as unique primary key (uniqueidentifier type) and some other nvarchar columns. My current goal is, for 5000 inputs, I need to calculate what ones are already contained in the table and what are not. Tht inputs are string and I have a C# function which converts string into uniqueidentifier (GUID). My logic is, if there is an existing ID, then I treat the string as already contained in the table.
My question is, if I need to find out what ones from the 5000 input strings are already contained in DB, and what are not, what is the most efficient way?
BTW: My current implementation is, convert string to GUID using C# code, then invoke/implement a store procedure which query whether an ID exists in database and returns back to C# code.
My working environment: VSTS 2008 + SQL Server 2008 + C# 3.5.
My first instinct would be to pump your 5000 inputs into a single-column temporary table X, possibly index it, and then use:
SELECT X.thecol
FROM X
JOIN ExistingTable USING (thecol)
to get the ones that are present, and (if both sets are needed)
SELECT X.thecol
FROM X
LEFT JOIN ExistingTable USING (thecol)
WHERE ExistingTable.thecol IS NULL
to get the ones that are absent. Worth benchmarking, at least.
Edit: as requested, here are some good docs & tutorials on temp tables in SQL Server. Bill Graziano has a simple intro covering temp tables, table variables, and global temp tables. Randy Dyess and SQL Master discuss performance issue for and against them (but remember that if you're getting performance problems you do want to benchmark alternatives, not just go on theoretical considerations!-).
MSDN has articles on tempdb (where temp tables are kept) and optimizing its performance.
Step 1. Make sure you have a problem to solve. Five thousand inserts isn't a lot to insert one at a time in a lot of contexts.
Are you certain that the simplest way possible isn't sufficient? What performance issues have you measured so far?
What do you need to do with those entries that do or don't exist in your table??
Depending on what you need, maybe the new MERGE statement in SQL Server 2008 could fit your bill - update what's already there, insert new stuff, all wrapped neatly into a single SQL statement. Check it out!
http://blogs.conchango.com/davidportas/archive/2007/11/14/SQL-Server-2008-MERGE.aspx
http://www.sql-server-performance.com/articles/dba/SQL_Server_2008_MERGE_Statement_p1.aspx
http://blogs.msdn.com/brunoterkaly/archive/2008/11/12/sql-server-2008-merge-capability.aspx
Your statement would look something like this:
MERGE INTO
(your target table) AS t
USING
(your source table, e.g. a temporary table) AS s
ON t.ID = s.ID
WHEN NOT MATCHED THEN -- new rows does not exist in base table
....(do whatever you need to do)
WHEN MATCHED THEN -- row exists in base table
... (do whatever else you need to do)
;
To make this really fast, I would load the "new" records from e.g. a TXT or CSV file into a temporary table in SQL server using BULK INSERT:
BULK INSERT YourTemporaryTable
FROM 'c:\temp\yourimportfile.csv'
WITH
(
FIELDTERMINATOR =',',
ROWTERMINATOR =' |\n'
)
BULK INSERT combined with MERGE should give you the best performance you can get on this planet :-)
Marc
PS: here's a note from TechNet on MERGE performance and why it's faster than individual statements:
In SQL Server 2008, you can perform multiple data manipulation language (DML) operations in a single statement by using the MERGE statement. For example, you may need to synchronize two tables by inserting, updating, or deleting rows in one table based on differences found in the other table. Typically, this is done by executing a stored procedure or batch that contains individual INSERT, UPDATE, and DELETE statements. However, this means that the data in both the source and target tables are evaluated and processed multiple times; at least once for each statement.
By using the MERGE statement, you can replace the individual DML statements with a single statement. This can improve query performance because the operations are performed within a single statement, therefore, minimizing the number of times the data in the source and target tables are processed. However, performance gains depend on having correct indexes, joins, and other considerations in place. This topic provides best practice recommendations to help you achieve optimal performance when using the MERGE statement.
Try to ensure you end up running only one query - i.e. if your solution consists of running 5000 queries against the database, that'll probably be the biggest consumer of resources for the operation.
If you can insert the 5000 IDs into a temporary table, you could then write a single query to find the ones that don't exist in the database.
If you want simplicity, since 5000 records is not very many, then from C# just use a loop to generate an insert statement for each of the strings you want to add to the table. Wrap the insert in a TRY CATCH block. Send em all up to the server in one shot like this:
BEGIN TRY
INSERT INTO table (theCol, field2, field3)
SELECT theGuid, value2, value3
END TRY BEGIN CATCH END CATCH
BEGIN TRY
INSERT INTO table (theCol, field2, field3)
SELECT theGuid, value2, value3
END TRY BEGIN CATCH END CATCH
BEGIN TRY
INSERT INTO table (theCol, field2, field3)
SELECT theGuid, value2, value3
END TRY BEGIN CATCH END CATCH
if you have a unique index or primary key defined on your string GUID, then the duplicate inserts will fail. Checking ahead of time to see if the record does not exist just duplicates work that SQL is going to do anyway.
If performance is really important, then consider downloading the 5000 GUIDS to your local station and doing all the analysis localy. Reading 5000 GUIDS should take much less than 1 second. This is simpler than bulk importing to a temp table (which is the only way you will get performance from a temp table) and doing an update using a join to the temp table.
Since you are using Sql server 2008, you could use Table-valued parameters. It's a way to provide a table as a parameter to a stored procedure.
Using ADO.NET you could easily pre-populate a DataTable and pass it as a SqlParameter.
Steps you need to perform:
Create a custom Sql Type
CREATE TYPE MyType AS TABLE
(
UniqueId INT NOT NULL,
Column NVARCHAR(255) NOT NULL
)
Create a stored procedure which accepts the Type
CREATE PROCEDURE spInsertMyType
#Data MyType READONLY
AS
xxxx
Call using C#
SqlCommand insertCommand = new SqlCommand(
"spInsertMyType", connection);
insertCommand.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParam =
insertCommand.Parameters.AddWithValue(
"#Data", dataReader);
tvpParam.SqlDbType = SqlDbType.Structured;
Links: Table-valued Parameters in Sql 2008
Definitely do not do it one-by-one.
My preferred solution is to create a stored procedure with one parameter that can take and XML in the following format:
<ROOT>
<MyObject ID="60EAD98F-8A6C-4C22-AF75-000000000000">
<MyObject ID="60EAD98F-8A6C-4C22-AF75-000000000001">
....
</ROOT>
Then in the procedure with the argument of type NCHAR(MAX) you convert it to XML, after what you use it as a table with single column (lets call it #FilterTable). The store procedure looks like:
CREATE PROCEDURE dbo.sp_MultipleParams(#FilterXML NVARCHAR(MAX))
AS BEGIN
SET NOCOUNT ON
DECLARE #x XML
SELECT #x = CONVERT(XML, #FilterXML)
-- temporary table (must have it, because cannot join on XML statement)
DECLARE #FilterTable TABLE (
"ID" UNIQUEIDENTIFIER
)
-- insert into temporary table
-- #important: XML iS CaSe-SenSiTiv
INSERT #FilterTable
SELECT x.value('#ID', 'UNIQUEIDENTIFIER')
FROM #x.nodes('/ROOT/MyObject') AS R(x)
SELECT o.ID,
SIGN(SUM(CASE WHEN t.ID IS NULL THEN 0 ELSE 1 END)) AS FoundInDB
FROM #FilterTable o
LEFT JOIN dbo.MyTable t
ON o.ID = t.ID
GROUP BY o.ID
END
GO
You run it as:
EXEC sp_MultipleParams '<ROOT><MyObject ID="60EAD98F-8A6C-4C22-AF75-000000000000"/><MyObject ID="60EAD98F-8A6C-4C22-AF75-000000000002"/></ROOT>'
And your results look like:
ID FoundInDB
------------------------------------ -----------
60EAD98F-8A6C-4C22-AF75-000000000000 1
60EAD98F-8A6C-4C22-AF75-000000000002 0

Categories

Resources