Can we write a sub function or procedure inside another stored procedure - sql

I want to check if SQL Server(2000/05/08) has the ability to write a nested stored procedure, what I meant is - WRITING a Sub Function/procedure inside another stored procedure. NOT calling another SP.
Why I was thinking about it is- One of my SP is having a repeated lines of code and that is specific to only this SP.So if we have this nested SP feature then I can declare another sub/local procedure inside my main SP and put all the repeating lines in that. and I can call that local sp in my main SP. I remember such feature is available in Oracle SPs.
If SQL server is also having this feature, can someone please explain some more details about it or provide a link where I can find documentation.
Thanks in advance
Sai

I don't recommend doing this as each time it is created a new execution plan must be calculated, but YES, it definitely can be done (Everything is possible, but not always recommended).
Here is an example:
CREATE PROC [dbo].[sp_helloworld]
AS
BEGIN
SELECT 'Hello World'
DECLARE #sSQL VARCHAR(1000)
SET #sSQL = 'CREATE PROC [dbo].[sp_helloworld2]
AS
BEGIN
SELECT ''Hello World 2''
END'
EXEC (#sSQL)
EXEC [sp_helloworld2];
DROP PROC [sp_helloworld2];
END
You will get the warning
The module 'sp_helloworld' depends on the missing object 'sp_helloworld2'.
The module will still be created; however, it cannot run successfully until
the object exists.
You can bypass this warning by using EXEC('sp_helloworld2') above.
But if you call EXEC [sp_helloworld] you will get the results
Hello World
Hello World 2

It does not have that feature. It is hard to see what real benefit such a feature would provide, apart from stopping the code in the nested SPROC from being called from elsewhere.

Oracle's PL/SQL is something of a special case, being a language heavily based on Ada, rather than simple DML with some procedural constructs bolted on. Whether or not you think this is a good idea probably depends on your appetite for procedural code in your DBMS and your liking for learning complex new languages.
The idea of a subroutine, to reduce duplication or otherwise, is largely foreign to other database platforms in my experience (Oracle, MS SQL, Sybase, MySQL, SQLite in the main).
While the SQL-building proc would work, I think John's right in suggesting that you don't use his otherwise-correct answer!
You don't say what form your repeated lines take, so I'll offer three potential alternatives, starting with the simplest:
Do nothing. Accept that procedural
SQL is a primitive language lacking
so many "essential" constructs that
you wouldn't use it at all if it
wasn't the only option.
Move your procedural operations outside of the DBMS and execute them in code written in a more sophisticated language. Consider ways in which your architecture could be adjusted to extract business logic from your data storage platform (hey, why not redesign the whole thing!)
If the repetition is happening in DML, SELECTs in particular, consider introducing views to slim down the queries.
Write code to generate, as part of your build process, the stored procedures. That way if the repeated lines ever need to change, you can change them in one place and automatically generate the repetition.
That's four. I thought of another one as I was typing; consider it a bonus.

CREATE TABLE #t1 (digit INT, name NVARCHAR(10));
GO
CREATE PROCEDURE #insert_to_t1
(
#digit INT
, #name NVARCHAR(10)
)
AS
BEGIN
merge #t1 AS tgt
using (SELECT #digit, #name) AS src (digit,name)
ON (tgt.digit = src.digit)
WHEN matched THEN
UPDATE SET name = src.name
WHEN NOT matched THEN
INSERT (digit,name) VALUES (src.digit,src.name);
END;
GO
EXEC #insert_to_t1 1,'One';
EXEC #insert_to_t1 2,'Two';
EXEC #insert_to_t1 3,'Three';
EXEC #insert_to_t1 4,'Not Four';
EXEC #insert_to_t1 4,'Four'; --update previous record!
SELECT * FROM #t1;
What we're doing here is creating a procedure that lives for the life of the connection and which is then later used to insert some data into a table.

John's sp_helloworld does work, but here's the reason why you don't see this done more often.
There is a very large performance impact when a stored procedure is compiled. There's a Microsoft article on troubleshooting performance problems caused by a large number of recompiles, because this really slows your system down quite a bit:
http://support.microsoft.com/kb/243586
Instead of creating the stored procedure, you're better off just creating a string variable with the T-SQL you want to call, and then repeatedly executing that string variable.
Don't get me wrong - that's a pretty bad performance idea too, but it's better than creating stored procedures on the fly. If you can persist this code in a permanent stored procedure or function and eliminate the recompile delays, SQL Server can build a single execution plan for the code once and then reuse that plan very quickly.

I just had a similar situation in a SQL Trigger (similar to SQL procedure) where I basically had same insert statement to be executed maximum 13 times for 13 possible key values that resulted of 1 event. I used a counter, looped it 13 times using DO WHILE and used CASE for each of the key values processing, while kept a flag to figure out when I need to insert and when to skip.

it would be very nice if MS develops GOSUB besides GOTO, an easy thing to do!
Creating procedures or functions for "internal routines" polute objects structure.
I "implement" it like this
BODY1:
goto HEADER HEADER_RET1:
insert into body ...
goto BODY1_RET
BODY2:
goto HEADER HEADER_RET2:
INSERT INTO body....
goto BODY2_RET
HEADER:
insert into header
if #fork=1 goto HEADER_RET1
if #fork=2 goto HEADER_RET2
select 1/0 --flow check!

I too had need of this. I had two functions that brought back case counts to a stored procedure, which was pulling a list of all users, and their case counts.
Along the lines of
select name, userID, fnCaseCount(userID), fnRefCount(UserID)
from table1 t1
left join table2 t2
on t1.userID = t2.UserID
For a relatively tiny set (400 users), it was calling each of the two functions one time. In total, that's 800 calls out from the stored procedure. Not pretty, but one wouldn't expect a sql server to have a problem with that few calls.
This was taking over 4 minutes to complete.
Individually, the function call was nearly instantaneous. Even 800 near instantaneous calls should be nearly instantaneous.
All indexes were in place, and SSMS suggested no new indexes when the execution plan was analyzed for both the stored procedure and the functions.
I copied the code from the function, and put it into the SQL query in the stored procedure. But it appears the transition between sp and function is what ate up the time.
Execution time is still too high at 18 seconds, but allows the query to complete within our 30 second time out window.
If I could have had a sub procedure it would have made the code prettier, but still may have added overhead.
I may next try to move the same functionality into a view I can use in a join.
select t1.UserID, t2.name, v1.CaseCount, v2.RefCount
from table1 t1
left join table2 t2
on t1.userID = t2.UserID
left join vwCaseCount v1
on v1.UserID = t1.UserID
left join vwRefCount v2
on v2.UserID = t1.UserID
Okay, I just created views from the functions, so my execution time went from over 4 minutes, to 18 seconds, to 8 seconds. I'll keep playing with it.

I agree with andynormancx that there doesn't seem to be much point in doing this.
If you really want the shared code to be contained inside the SP then you could probably cobble something together with GOTO or dynamic SQL, but doing it properly with a separate SP or UDF would be better in almost every way.

For whatever it is worth, here is a working example of a GOTO-based internal subroutine. I went that way in order to have a re-useable script without side effects, external dependencies, and duplicated code:
DECLARE #n INT
-- Subroutine input parameters:
DECLARE #m_mi INT -- return selector
-- Subroutine output parameters:
DECLARE #m_use INT -- instance memory usage
DECLARE #m_low BIT -- low memory flag
DECLARE #r_msg NVARCHAR(max) -- low memory description
-- Subroutine internal variables:
DECLARE #v_low BIT, -- low virtual memory
#p_low BIT -- low physical memory
DECLARE #m_gra INT
/* ---------------------- Main script ----------------------- */
-- 1. First subroutine invocation:
SET #m_mi = 1 GOTO MemInfo
MI1: -- return here after invocation
IF #m_low = 1 PRINT '1:Low memory'
ELSE PRINT '1:Memory OK'
SET #n = 2
WHILE #n > 0
BEGIN
-- 2. Second subroutine invocation:
SET #m_mi = 2 GOTO MemInfo
MI2: -- return here after invocation
IF #m_low = 1 PRINT '2:Low memory'
ELSE PRINT '2:Memory OK'
SET #n = #n - 1
END
GOTO EndOfScript
MemInfo:
/* ------------------- Subroutine MemInfo ------------------- */
-- IN : #m_mi: return point: 1 for label MI1 and 2 for label MI2
-- OUT: #m_use: RAM used by isntance,
-- #m_low: low memory condition
-- #r_msg: low memory message
SET #m_low = 1
SELECT #m_use = physical_memory_in_use_kb/1024,
#p_low = process_physical_memory_low ,
#v_low = process_virtual_memory_low
FROM sys.dm_os_process_memory
IF #p_low = 1 OR #v_low = 1 BEGIN
SET #r_msg = 'Low memory.' GOTO LOWMEM END
SELECT #m_gra = cntr_value
FROM sys.dm_os_performance_counters
WHERE counter_name = N'Memory Grants Pending'
IF #m_gra > 0 BEGIN
SET #r_msg = 'Memory grants pending' GOTO LOWMEM END
SET #m_low = 0
LOWMEM:
IF #m_mi = 1 GOTO MI1
IF #m_mi = 2 GOTO MI2
EndOfScript:

Thank you all for your replies!
I'm better off then creating one more SP with the repeating code and call that, which is the best way interms of performance and look wise.

Related

Conditionally inserting records into a table in multithreaded environment based on a count

I am writing a T-SQL stored procedure that conditionally adds a record to a table only if the number of similar records is below a certain threshold, 10 in the example below. The problem is this will be run from a web application, so it will run on multiple threads, and I need to ensure that the table never has more than 10 similar records.
The basic gist of the procedure is:
BEGIN
DECLARE #c INT
SELECT #c = count(*)
FROM foo
WHERE bar = #a_param
IF #c < 10 THEN
INSERT INTO foo
(bar)
VALUES (#a_param)
END IF
END
I think I could solve any potential concurrency problems by replacing the select statement with:
SELECT #c = count(*) WITH (TABLOCKX, HOLDLOCK)
But I am curious if there any methods other than lock hints for managing concurrency problems in T-SQL
One option would be to use the sp_getapplock system stored procedure. You can place your critical section logic in a transaction and use the built in locking of sql server to ensure synchronized access.
Example:
CREATE PROC MyCriticalWork(#MyParam INT)
AS
DECLARE #LockRequestResult INT
SET #LockRequestResult=0
DECLARE #MyTimeoutMiliseconds INT
SET #MyTimeoutMiliseconds=5000--Wait only five seconds max then timeouit
BEGIN TRAN
EXEC #LockRequestResult=SP_GETAPPLOCK 'MyCriticalWork','Exclusive','Transaction',#MyTimeoutMiliseconds
IF(#LockRequestResult>=0)BEGIN
/*
DO YOUR CRITICAL READS AND WRITES HERE
*/
--Release the lock
COMMIT TRAN
END ELSE
ROLLBACK TRAN
Use SERIALIZABLE. By definition it provides you the illusion that your transaction is the only transaction running. Be aware that this might result in blocking and deadlocking. In fact this SQL code is a classic candidate for deadlocking: Two transactions might first read a set of rows, then both will try to modify that set of rows. Locking hints are the classic way of solving that problem. Retry also works.
As stated in the comment. Why are you trying to insert on multiple threads? You cannot write to a table faster on multiple threads.
But you don't need a declare
insert into [Table_1] (ID, fname, lname)
select 3, 'fname', 'lname'
from [Table_1]
where ID = 3
having COUNT(*) <= 10
If you need to take a lock then do so
The data is not 3NF
Should start any design with a proper data model
Why rule out table lock?
That could very well be the best approach
Really, what are the chances?
Even without a lock you would have to have two at a count of 9 submit at exactly the same time. Even then it would stop at 11. Is the 10 an absolute hard number?

Semantics of sql NOCOUNT

This is a minor question regarding the usage and semantics of the NOCOUNT statement. I've seen it used a couple different ways and I want to know what is actually required or not.
I've seen it listed on MSDN with the trailing semicolon and GO statement like such:
SET NOCOUNT ON;
GO
and I've seen in without the trailing semicolon:
SET NOCOUNT ON
GO
and I've seen it without the GO statement
SET NOCOUNT ON
I realize that the GO simply signals the end of a batch, but should this be called in order for the NOCOUNT to take effect?
And what is the point of the semicolon?
A semicolon ends the current SQL statement.
To the best of my knowledge, it isn't needed after SET NOCOUNT ON.
You should not need 'GO' to have NOCOUNT take effect, though I'm less certain of that.
A ';' is a memory terminator I always though and a GO statement is a batch terminator.
So if you do DDL creation such as creating a proc, view, function, or other object you can do a bunch like:
Create proc blah as ....
GO
create proc blah2 as ....
GO
And then you can have a single nice creation script. If you did not have the GO's it would break as it would say something like: "Create (thing) must be the first statement in a creation ...." This means SQL thought you were doing a single operation for both. 'GO' says: "NEW SCOPE, NEW OBJECT". So it gets around that. If you look at the creation scripts for pubs and Northwind ( the old MS test databases) I believe they all are using batch terminators for a single '*.sql' file. It makes a bunch of creation possible in a single file.
A ; will just terminate the memory up to a statement. Most of the time it will be fine to omit them but a big place some of you SQL experts will know you cannot get away from this is..... CTE's!
Yes a CTE will yell at you right away because it begins with a 'with' but you can also use (nolock) hints with 'with' so it needs to differentiate between the two transactions and THUS you should use a ';'.
EG:
Select * from table -- standard SQL no biggie
Or
Select * from table
Select * from table2 -- these are fine stacked and will run
But...
Select * from table
with a as (select * from table2) select * from a
will break immediately because it did not know that 'with' s context was changed to a new statement. Proper SQL if you are being meticulous should be like:
Set NoCount ON; -- No thank you engine I don't need to see counts
Set Transaction Level Isolation Level Read Uncommitted; -- Set me to dirty reads as default
Select
*
from table
;
Select
*
from table2
;
SQL's Engine see this as:
Set NoRead ON;-- No thank you engine I don't need to see counts\nSet Transaction Level Isolation Level Read Uncommitted;\n-- Set me to dirty reads as default\n\nSelect\n*\nfrom table\n;\n\nSelect\n*\nfrom table2\n;
So it needs a little help from the person telling it where the white space TERMINATES. Or else it is not human and does not know where one statement stopped and another one began.
Whatever you do if you were writing it for others and under well defined guidelines I was always told to do the ';' terminator to make it official ending sequence.
A GO is a batch terminator but you can change contexts with it, which make it useful for switching databases like:
Use Database1
GO
Select * from TableOnDatabase1;
Use Database2
GO
Select * from TableOnDatabase2;
Also to save space I did a single line but really you should be doing your main sql syntaxes on a separate line and also sub syntax like:
Select
ColumnA
, ColumnB
, count(ColumnC) as cnt
From table
Where thing happens
Group by
ColumnA
, ColumnB
Having Count(ColumnC) > 1
Order by ColumnA
EDIT for common real world example:
set nocount on;
declare #Table table ( ints int);
declare #CursorInt int = 1;
while #CursorInt <= 100
begin
insert into #Table values (#CursorInt)
set #CursorInt += 1
End
-- wait a second engine you did not tell me what happened in the 'Messages' section?!
-- aw come on I want to see each transaction!
Set nocount off;
while #CursorInt <= 200
begin
insert into #Table values (#CursorInt)
set #CursorInt += 1
End
-- okay that is annoying I did not have to see 100: "(1 row(s) affected)"
You can turn on and off 'nocount' with memory terminators as much as you want in the scope of a procedure. I do it all the time when I want to see some inserts and ignore others in my procs. And in some if I want to pass them out I then set an output variable or a simple select of a final rowcount for a return.

Why does my stored procedure execute all selects regardless of condition logic?

I'm using SQL Server 2008.
I have an interesting scenario where a stored procedure (written by a "power user") has an okay runtime of (around 4 seconds) if there's data in the primary table. If the search value doesn't exist, the run time averages out at about 3 minutes. Because of how the process works, and the web application that uses the procedure, it requires an empty result set in the case of no data.
I've tested the logic below with values that have data and values that don't and the flow seems to work; however, when I put my actual query in the else statement, it seems like that part is always being evaluated despite my knowing that logic branch shouldn't execute.
DECLARE #spId int
SELECT #spId = td.mainId
FROM dbo.PRIMARYTABLE
WHERE td.longId = #searchVal
IF #spId < 1 OR #spId IS NULL
BEGIN
select 'RETURN EMPTY RESULT SET' as test
END
ELSE
BEGIN
SELECT 'DO ACTUAL QUERY' as test
END
When I test this with a dummy value, such as 1111, the select 'RETURN EMPTY RESULT SET' as test is returned. When I use a value that I know exists, the SELECT 'DO ACTUAL QUERY' as test is returned. When I replace "SELECT 'DO ACTUAL QUERY' as test" with the actual heavy duty query and use the same non-existent dummy value, it still looks like the ELSE clause is reached.
What am I missing here?
Perhaps you are not showing everything. There is an counter-intuitive thing about assignment in select where no rows are returned - the value of variable will not be cleared. Paste this in SSMS:
declare #searchVal as int
set #searchVal=111
DECLARE #spId int
set #spId = 2134
SELECT #spId = td.mainId
FROM (select 839 as mainId, 0 as longid) td
where td.longId = #searchVal
print #spid
#spid will be 2134. This is why you should always test using ##rowcount, in you case
IF ##rowcount = 0 or #spId < 1 or #spId is null
BEGIN
select 'RETURN EMPTY RESULT SET' as test
END
ELSE
BEGIN
SELECT 'DO ACTUAL QUERY' as test
END
There is also a possibility of duplicated data by longId, returning random mainid from rows that satisfy #searchval condition.
Other than that, I would not know.
Thank you all for your suggestions. I apologize for the lack of posting the entire stored procedure, but I'm not allowed to share that exact code. The snippet I began with was psuedo code (well, real code with tables and fields renamed).
I think Nikola Markovinović may be onto something with his answer and article link. This entire ordeal has been sort of maddening. I googled, debugged, and did the whole thing again, then search on stack overflow. After a few changes from your suggestions, the procedure magically started responding with the run time I thought it should. I don't think some of the initial changes took or maybe they weren't be cached by sql server correctly; I've got nothing but guesses.
It's very strange because, for a good hour or more, it was running as if it had never been changed (performance wise)...then it just kicked into gear. I wonder if this isn't my fault and maybe I didn't alter the one on staging like I did the one on Test...that seems the most feasible explanation.
Anyhow, thank you for your suggestions. I've learned a few things so that's always good.

Determine caller within stored proc or trigger

I am working with an insert trigger within a Sybase database. I know I can access the ##nestlevel to determine whether I am being called directly or as a result of another trigger or procedure.
Is there any way to determine, when the nesting level is deeper than 1, who performed the action causing the trigger to fire?
For example, was the table inserted to directly, was it inserted into by another trigger and if so, which one.
As far as I know, this is not possible. Your best bet is to include it as a parameter to your stored procedure(s). As explained here, this will also make your code more portable since any method used would likely rely on some database-specific call. The link there was specific for SQL Server 2005, not Sybase, but I think you're pretty much in the same boat.
I've not tested this myself, but assuming you are using Sybase ASE 15.03 or later, have your monitoring tables monProcessStatement and monSysStatement enabled, and appropriate permissions set to allow them to be accessed from your trigger you could try...
declare #parent_proc_id int
if ##nestlevel > 1
begin
create table #temp_parent_proc (
procId int,
nestLevel int,
contextId int
)
insert into #temp_parent_proc
select mss.ProcedureID,
mss.ProcNestLevel,
mss.ContextID
from monSysStatement mss
join monProcessStatement mps
on mss.KPID = mps.KPID
and mss.BatchID = mps.BatchID
and mss.SPID = mps.SPID
where mps.ProcedureID =##procid
and mps.SPID = ##spid
select #parent_proc_id = (select tpp.procId
from #temp_parent_proc tpp,
#temp_parent_proc2 tpp2
where tpp.nestLevel = tpp2.nestLevel-1
and tpp.contextId < tpp2.contextId
and tpp2.procId = ##procid
and tpp2.nestLevel = ##nestlevel
group by tpp.procId, tpp.contextId
having tpp.contextId = max(tpp.contextId ))
drop table #temp_parent_proc
end
The temp table is required because of the nature of monProcessStatement and monSysStatement.
monProcessStatement is transient and so if you reference it more than once, it may no longer hold the same rows.
monSysStatement is a historic table and is guaranteed to only return an individual rown once to any process accessing it.
if you do not have or want to set permissions to access the monitoring tables, you could put this into a stored procedure you pass ##procid, ##spid, and ##nestlevel to as parameters.
If this also isn't an option, since you cannot pass parameters into triggers, another possible work around would be to use a temporary table.
in each proc that might trigger this...
create table #trigger_parent (proc_id int)
insert into #trigger_parent ##procid
then in your trigger the temp table will be available...
if object_id('#trigger_parent') is not null
set #parent_proc = select l proc_id from #trigger_parent
you will know it was triggered from within another proc.
The trouble with this is it doesn't 'just work'. You have to enforce temp table setup.
You could do further checking to find cases where there is no #trigger_parent but the nesting level > 1 and combine a similar query to the monitoring tables as above to find potential candidates that would need to be updated.

Having TRANSACTION In All Queries

Do you think always having a transaction around the SQL statements in a stored procedure is a good practice? I'm just about to optimize this legacy application in my company, and one thing I found is that every stored procedure has BEGIN TRANSACTION. Even a procedure with a single select or update statement has one. I thought it would be nice to have BEGIN TRANSACTION if performing multiple actions, but not just one action. I may be wrong, which is why I need someone else to advise me. Thanks for your time, guys.
It is entirely unnecessary as each SQL statement executes atomically, ie. as if it were already running in its own transaction. In fact, opening unnecessary transactions can lead to increased locking, even deadlocks. Forgetting to match COMMITs with BEGINs can leave a transaction open for as long as the connection to the database is open and interfere with other transactions in the same connection.
Such coding almost certainly means that whoever wrote the code was not very experienced in database programming and is a sure smell that there may be other problems as well.
The only possible reason I could see for this is if you have the possibility of needing to roll-back the transaction for a reason other than a SQL failure.
However, if the code is literally
begin transaction
statement
commit
Then I see absolutely no reason to use an explicit transaction, and it's probably being done because it's always been done that way.
I don't know of any benefit of not just using auto commit transactions for these statements.
Possible disadvantages of using explicit transactions everywhere might be that it just adds clutter to the code and so makes it less easy to see when an explicit transaction is being used to ensure correctness over multiple statements.
Additionally it increases the risk that a transaction is left open holding locks unless care is taken (e.g. with SET XACT_ABORT ON).
Also there is a minor performance implication as shown in #8kb's answer. This illustrates it another way using the visual studio profiler.
Setup
(Testing against an empty table)
CREATE TABLE T (X INT)
Explicit
SET NOCOUNT ON
DECLARE #X INT
WHILE ( 1 = 1 )
BEGIN
BEGIN TRAN
SELECT #X = X
FROM T
COMMIT
END
Auto Commit
SET NOCOUNT ON
DECLARE #X INT
WHILE ( 1 = 1 )
BEGIN
SELECT #X = X
FROM T
END
Both of them end up spending time in CMsqlXactImp::Begin and CMsqlXactImp::Commit but for the explicit transactions case it spends a significantly greater proportion of the execution time in these methods and hence less time doing useful work.
+--------------------------------+----------+----------+
| | Auto | Explicit |
+--------------------------------+----------+----------+
| CXStmtQuery::ErsqExecuteQuery | 35.16% | 25.06% |
| CXStmtQuery::XretSchemaChanged | 20.71% | 14.89% |
| CMsqlXactImp::Begin | 5.06% | 13% |
| CMsqlXactImp::Commit | 12.41% | 24.03% |
+--------------------------------+----------+----------+
When performing multiple insert/update/delete, it is better to have a transaction to insure atomicity on operation and it insure that all the tasks of operation are executed or none.
For single insert/update/delete statement, it depends upon what kind of operation (from business layer perspective) you are performing and how important it is. If you are performing some calculation before single insert/update/delete, then better use transaction, may be some data changed after you retrieve data for insert/update/delete.
One plus point is you can add another INSERT (for example) and it's already safe.
Then again, you also have the problem of nested transactions if a stored procedure calls another one. An inner rollback will cause error 266.
If every call is simple CRUD with no nesting then it's pointless: but if you nest or have multiple writes pre TXN then it's good to have a consistent template.
You mentioned that you'll be optimizing this legacy app.
One of the first, and easiest, things you can do to improve performance is remove all the BEGIN TRAN and COMMIT TRAN for the stored procedures that only do SELECTs.
Here is a simple test to demonstrate:
/* Compare basic SELECT times with and without a transaction */
DECLARE #date DATETIME2
DECLARE #noTran INT
DECLARE #withTran INT
SET #noTran = 0
SET #withTran = 0
DECLARE #t TABLE (ColA INT)
INSERT #t VALUES (1)
DECLARE
#count INT,
#value INT
SET #count = 1
WHILE #count < 1000000
BEGIN
SET #date = GETDATE()
SELECT #value = ColA FROM #t WHERE ColA = 1
SET #noTran = #noTran + DATEDIFF(MICROSECOND, #date, GETDATE())
SET #date = GETDATE()
BEGIN TRAN
SELECT #value = ColA FROM #t WHERE ColA = 1
COMMIT TRAN
SET #withTran = #withTran + DATEDIFF(MICROSECOND, #date, GETDATE())
SET #count = #count + 1
END
SELECT
#noTran / 1000000. AS Seconds_NoTransaction,
#withTran / 1000000. AS Seconds_WithTransaction
/** Results **/
Seconds_NoTransaction Seconds_WithTransaction
--------------------------------------- ---------------------------------------
14.23600000 18.08300000
You can see there is a definite overhead associated with the transactions.
Note: this is assuming your these stored procedures are not using any special isolation levels or locking hints (for something like handling pessimistic concurrency). In that case, obvously you would want to keep them.
So to answer the question, I would only leave in the transactions where you are actually attempting to preserve the integrity of the data modifications in case of an error in the code, SQL Server, or the hardware.
I can only say that placing a transaction block like this to every stored procedure might be a novice's work.
A transaction should be placed only in a block that has more than one insert/update statements, other than that, there is no need to place a transaction block in the stored procedure.
BEGIN TRANSACTION / COMMIT syntax shouldn't be used in every stored procedure by default unless you are trying to cover the following scenarios:
You include the WITH MARK option because you want to support restoring the database from a backup to a specific point in time.
You intend to port the code from SQL Server to another database platform like Oracle. Oracle does not commit transactions by default.