Brief synopsis: a SQL Server database was migrated from SQL Server 2012 on Windows Server 2012 R2 STD to SQL Server 2017 on Windows Server 2016 STD. Still running in 110 compatibility mode and no SQL Server changes.
The application was repointed and using ODBC to connect. The following error is seen whenever a user attempts data entry/changes via the application. I have attached the stored procedure code which caused the error. The table definition in SSMS shows an id column NOT NULL so nothing wrong with the DDL. I have run sp_blitz and it highlighted 'Objects created with dangerous SET Options' for the offending database. The sp_blitz URL takes me to https://www.brentozar.com/go/badset which highlights incorrect settings for QUOTED IDENTIFIERS. This might be a red herring but I am at a loss as to the underlying problem and would welcome some input.
Errors:
Ah it seems I'm not permitted to insert images so its a little difficult to share the problem. The error message says
Executing <sp_name> procedure SQLSTATS=42000 Microsoft SQL Server Native Client 10.0 Must declare the scalar variable
The stored procedure script with name masked:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
CREATE PROCEDURE [dbo].[xxxxxxxx]
#sTable varchar(40),
#lnew_id integer OUTPUT
AS
BEGIN
SET #lnew_id = -1;
SET #sTable = UPPER(#sTable);
SELECT #lnew_id = id_lastused
FROM idnext WITH (UPDLOCK, ROWLOCK)
WHERE id_table = #sTable;
IF ##error = 0
BEGIN
IF (#lnew_id IS NULL)
BEGIN
SET #lnew_id = -1;
END
IF (#lnew_id >= 0)
BEGIN
BEGIN TRANSACTION tu
SET #lnew_id = #lnew_id + 1
UPDATE idnext
SET id_lastused = #lnew_id
WHERE id_table = #sTable;
IF (##error = 0)
BEGIN
COMMIT TRANSACTION tu;
END
ELSE
BEGIN
ROLLBACK TRANSACTION tu;
SET #lnew_id = -999;
END
END
ELSE
BEGIN
SET #lnew_id = 1;
BEGIN TRANSACTION ti
INSERT INTO idnext (id_table, id_lastused)
VALUES (#sTable, #lnew_id);
IF (##error = 0)
BEGIN
COMMIT TRANSACTION ti;
END
ELSE
BEGIN
ROLLBACK TRANSACTION ti;
SET #lnew_id = -998;
END
END
END
ELSE
BEGIN
SET #lnew_id = -997;
END
RETURN (#lnew_id);
END
Related
I am working with a Old tool and the database that was connected to this tool in long gone. I am new at this and need some help understanding this. I need help with writing the code i tried and error-ed out every time.
public void UpdateUser(string NewUser, string OldUser)
{
using (SqlConnection con = HSDatabaseConnection())
{
using (SqlCommand cmd = new SqlCommand("UpdateNames", con))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add("#LogonName", System.Data.SqlDbType.VarChar).Value = NewUser;
cmd.Parameters.Add("#OldLogonName", System.Data.SqlDbType.VarChar).Value = OldUser;
cmd.ExecuteNonQuery();
}
con.Close();
}
}
I have 4 tables the only thing each table has in common is the column name "AN". I need to update "AN" if a agent changes there name legally
so far I have come up with this procedure
USE [HSDB]
GO
/****** Object: StoredProcedure [dbo].[UpdateNames] Script Date: 9/16/2016 12:32:33 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[UpdateNames]
--Add the parameters for the stored procedure here
#AN varchar(MAX)
AS
BEGIN
--SET NOCOUNT ON added to prevent extra result sets from
--interfering with SELECT statements.
SET NOCOUNT ON;
--Insert statements for procedure here
UPDATE Att
SET AN = #AN
WHERE (AN = #AN)
UPDATE MS
SET AN = #AN
WHERE (AN = #AN)
UPDATE Lost
SET AN = #AN
WHERE (AN = #AN)
UPDATE WeeklyCharges
SET AN = #AN
WHERE (AN = #AN)
END
Can some one please tell me what i am doing wrong. Thank you
Issue 1:
your c# code is passing 2 paramaters #LogonName and #OldLogonName but your stored procedure is only accepting 1 parameter #AN. So that should error out.
Issue 2:
all of your update statements are setting AN = to itself because you are saying when AN = #AN then set AN = #AN....
Without having more detail about the error and your data structures, which you should include in the future, it is a little difficult to say. However, you probably could do something like this in SQL-Server 2012 +:
USE [HSDB]
GO
/****** Object: StoredProcedure [dbo].[UpdateNames] Script Date: 9/16/2016 12:32:33 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[UpdateNames]
--Add the parameters for the stored procedure here
#LogonName varchar(100)
,#OldLogonName varchar(100)
AS
BEGIN
--SET NOCOUNT ON added to prevent extra result sets from
--interfering with SELECT statements.
SET NOCOUNT ON;
--Insert statements for procedure here
BEGIN TRY
IF NOT EXISTS (SELECT * FROM LogOnOrUserTable WHERE LogonName = #OldLogonName)
BEGIN
;THROW 51000, 'Old Logon Name Does Not Exists', 1
END
BEGIN TRANSACTION
UPDATE Att
SET AN = #LogonName
WHERE (AN = #OldLogonName)
UPDATE MS
SET AN = #LogonName
WHERE (AN = #OldLogonName)
UPDATE Lost
SET AN = #LogonName
WHERE (AN = #OldLogonName)
UPDATE WeeklyCharges
SET AN = #LogonName
WHERE (AN = #OldLogonName)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK TRANSACTION
;THROW
END CATCH
END
What it does:
first tests to see if the old user name exists and if it doesn't throws an error.
then updates all of the tables where AN = old logon name and sets it to the new logon name
if any one of those fails it rolls back the transaction and throws an error so that you don't have a change half made
I am doing some work on a remote sql server database which take some time and i need to block any other connection to it so no data get lost
i believe i should use single user mode to do this
i need to get it back to multi user mode after i finish my work but
my connection to the remote sever is not reliable and many times will get disconnected before finish and usually just roll back automatically and do it later
the problem is when i try to perform it within transaction i get this error :
ALTER DATABASE statement not allowed within multi-statement transaction
how can i perform
ALTER DATABASE dbName
SET SINGLE_USER WITH ROLLBACK IMMEDIATE
in a transaction and make sure it will roll back to Multi user mode if got disconnected ?
So, we're trying to arrange for a database to be returned to multi_user mode if our connection drops. Here's one way that works, but is as ugly as sin.
First, we set things up appropriately:
create database RevertTest
go
use master
go
create table RevertLock (L int not null)
go
declare #rc int
declare #job_id uniqueidentifier
exec #rc = msdb..sp_add_job #job_name='RevertSingleUser',
#description='Revert the RevertTest database to multi_user mode',
#delete_level=3,
#job_id = #job_id OUTPUT
if #rc != 0 goto Failed
exec #rc = msdb..sp_add_jobstep #job_id = #job_id,
#step_name = 'Wait to revert',
#command = '
WHILE EXISTS (SELECT * FROM RevertLock)
WAITFOR DELAY ''00:00:01''
ALTER DATABASE RevertTest set multi_user
DROP TABLE RevertLock'
if #rc != 0 goto Failed
declare #nowish datetime
declare #StartDate int
declare #StartTime int
set #nowish = DATEADD(minute,30,GETDATE())
select #StartDate = DATEPART(year,#nowish) * 10000 + DATEPART(month,#nowish) * 100 + DATEPART(day,#nowish),
#StartTime = DATEPART(hour,#nowish) * 10000 + DATEPART(minute,#nowish) * 100 + DATEPART(second,#nowish)
exec #rc = msdb..sp_add_jobschedule #job_id = #job_id,
#name='Failsafe',
#freq_type=1,
#active_start_date = #StartDate,
#active_start_time = #StartTime
if #rc != 0 goto Failed
exec #rc = msdb..sp_add_jobserver #job_id = #job_id
if #rc != 0 goto Failed
print 'Good to go!'
goto Fin
Failed:
print 'No good - couldn''t establish rollback plan'
Fin:
Basically, we create a job that tidies up after us. We schedule the job to start running in half an hours time, but that's just to protect us from a small race.
We now run our actual script to do the work that we want it to:
use RevertTest
go
alter database RevertTest set single_user with rollback immediate
go
begin transaction
go
insert into master..RevertLock(L) values (1)
go
exec msdb..sp_start_job #job_name='RevertSingleUser'
go
WAITFOR DELAY '01:00:00'
If you run this script, you'll be able to observe that the database has entered single-user mode - the WAITFOR DELAY at the end is just to simulate us "doing work" - whatever it is that you want to do within the database whilst it's in single-user mode. If you stop this query running and disconnect this query window, within a second you should see that the database has returned to multi_user mode.
To finish your script successfully, just make the last task (before COMMIT) to be to delete from the RevertLock table. Just as with the disconnection, the revert job1 will take care of switching the DB back into multi_user and then cleaning up after itself.
1The job is actually slightly deceptive. It won't actually sit looping and checking the table in master - since your transaction has an exclusive lock on it due to the INSERT. It instead sits and patiently waits to acquire a suitable lock, which only happens when your transaction commits or rolls back.
You cannot include the ALTER statement within your transaction. But you could top and tail your transaction, like so:
ALTER DATABASE TEST SET SINGLE_USER
GO
BEGIN TRANSACTION
-- Generate an error.
SELECT 1/0
ROLLBACK TRANSACTION
GO
ALTER DATABASE TEST SET MULTI_USER
This script sets the db to single user mode. Then encounters an error, before returning to multi user mode.
I'm experiencing an issue with an access form. I use access 2013 forms as frontend, and sql server 2014 as backend. The form has a button that should delete a record, through use of a stored procedure. Though, when I select the record that should be deleted, and when I press the button, it seems to have worked. The stored procedure has to delete 1 record in 2 different tables, which are linked together with 'articlenr'.
I can debug through the entire process (except for the stored procedure). I'm guessing there's something wrong with my stored procedure:
ALTER PROCEDURE [dbo].[spArticleDelete]
(
#articlenr int
)
AS
BEGIN TRANSACTION
IF (
SELECT COUNT(*)
FROM Article
WHERE articlenr = #articlenr
) <> 0
BEGIN
DELETE FROM Articleprice WHERE articlenr = #articlenr
DELETE FROM Article WHERE articlenr = #articlenr
END
If ##ERROR <> 0
BEGIN
COMMIT TRANSACTION
Raiserror('The article has been deleted!', 16, 1)
END
ELSE
ROLLBACK
Raiserror('The article has not been deleted!', 16,1)
Hope you guys can help me out here..
You may want to change your code to somethis like this:
begin try
BEGIN TRANSACTION
DELETE FROM x2 where i1 = 5
DELETE FROM x2 where i1 = 6
commit
Raiserror('The article has been deleted!', 16, 1)
end try
begin catch
if ##TRANCOUNT > 0
begin
ROLLBACK
end
Raiserror('The article has not been deleted!', 16,1)
end catch
In one StoredProcedure, I am using a linked server to do some inserts/updates in another DB. Have wrapped the code in a transaction.
CREATE PROCEDURE [mySchema].[mySP] (paramList)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT OFF;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
DECLARE #TranStarted AS BIT = 0;
BEGIN TRY
IF ##TRANCOUNT = 0
BEGIN
BEGIN TRANSACTION;
SET #TranStarted = 1;
END
-- Do some stuff with linked server (inserts and updates on another server)
IF #TranStarted = 1
BEGIN
COMMIT TRANSACTION;
END
END TRY
BEGIN CATCH
IF #TranStarted = 1
BEGIN
ROLLBACK TRANSACTION;
END
PRINT 'ERROR_NUMBER: ' + CAST(ERROR_NUMBER() AS NVARCHAR(10));
PRINT 'ERROR_MESSAGE: ' + ERROR_MESSAGE();
PRINT 'ERROR_SEVERITY: ' + CAST(ERROR_SEVERITY() AS NVARCHAR(2));
PRINT 'ERROR_STATE: ' + CAST(ERROR_STATE() AS NVARCHAR(3));
PRINT 'ERROR_PROCEDURE: ' + ERROR_PROCEDURE();
PRINT 'ERROR_LINE: ' + CAST(ERROR_LINE() AS NVARCHAR(5));
END CATCH
END
And here is the result:
ERROR_NUMBER: 3910
ERROR_MESSAGE: Transaction context in use by another session.
ERROR_SEVERITY: 16
ERROR_STATE: 2
ERROR_LINE: 1
I've seen this:
Related track but I think I don't have any loopback linked servers
The StoredProcedure works fine without transaction block.
Any help would be greatly appreciated.
This might not fit your scenario, but just in case it does fit your's or someone else's...
I encountered the "Transaction context is in use..." error when updating a table with a trigger referencing a linked server that is on the same SQL server and the connection string included ";MultipleActiveResultSets=true". The application I was adding script to needed that functionality.
If, however, I use a connection string without MultipleActiveResultSets, the application was able to successfully update the table. In our PROD environment, the linked server is connecting to a different server, but in our DEV environment, it links back to the same server. With MultipleActiveResultSets set to true, SQL server would attempt to create a distributed transaction and fail because it is a loopback (which many posts have indicated is not supported by DTC).
With MultipleActiveResultSets set to false (or left out of the connection string), SQL server does not have a problem with the loopback.
I am in the process of creating a stored procedure. This stored procedure runs local as well as external stored procedures. For simplicity, I'll call the local server [LOCAL] and the remote server [REMOTE].
Here's a simple topology:
The procedure
USE [LOCAL]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[monthlyRollUp]
AS
SET NOCOUNT, XACT_ABORT ON
BEGIN TRY
EXEC [REOMTE].[DB].[table].[sp]
--This transaction should only begin if the remote procedure does not fail
BEGIN TRAN
EXEC [LOCAL].[DB].[table].[sp1]
COMMIT
BEGIN TRAN
EXEC [LOCAL].[DB].[table].[sp2]
COMMIT
BEGIN TRAN
EXEC [LOCAL].[DB].[table].[sp3]
COMMIT
BEGIN TRAN
EXEC [LOCAL].[DB].[table].[sp4]
COMMIT
END TRY
BEGIN CATCH
-- Insert error into log table
INSERT INTO [dbo].[log_table] (stamp, errorNumber,
errorSeverity, errorState, errorProcedure, errorLine, errorMessage)
SELECT GETDATE(), ERROR_NUMBER(), ERROR_SEVERITY(), ERROR_STATE(), ERROR_PROCEDURE(),
ERROR_LINE(), ERROR_MESSAGE()
END CATCH
GO
When using a transaction on the remote procedure, it throws this error:
OLE DB provider ... returned message "The partner transaction manager has disabled its support for remote/network transactions.".
I get that I'm unable to run a transaction locally for a remote procedure.
How can I ensure that the this procedure will exit and rollback if any part of the procedure fails?
Notes
With regards to combining the simple procedures, some of them are used individually.
IMO easiest way is to
Add Return value to remote proc.
Wrap remote proc into transaction and try catch (inside remote proc). If error happened return false.
On local stored proc if false, simply do not continue.
I also fail to understand the reason behind multiple BEGIN TRANS / COMMIT in the local proc. I mean if this is month end rollup, shuldn't this be one big transaction rather than a bunch of small? Otherwise your trans 1 and 2 may commit successfully, but 3 will fail and that's that.
Names are made up ofc:
CREATE PROC [remote].db.REMOTE_PROC (
#return_value int output
)
AS
BEGIN
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANS
... do stuff ...
set #return_value = 1;
COMMIT;
END TRY
BEGIN CATCH
set #return_value = 0;
END CATCH
END
and the local proc
CREATE PROC [local].db.[monthlyRollUp] AS
BEGIN
SET XACT_ABORT ON;
declare #ret int;
EXECUTE [remote].dbo.REMOTE_PROC #return_value = #ret OUTPUT;
IF #ret = 0
PRINT 'ERROR :('
RETURN
END IF
BEGIN TRANS
-- one big transaction here
EXEC [LOCAL].[DB].[table].[sp1];
EXEC [LOCAL].[DB].[table].[sp2];
EXEC [LOCAL].[DB].[table].[sp3];
EXEC [LOCAL].[DB].[table].[sp4];
COMMIT;
END;
afair [remote].dbo.REMOTE_PROC runs its own transaction space, and returns 1 if successful. Local proc, checks the return value and decides whether to proceed or not.
sp1 sp2 sp3 and sp4 are all running in one single transactions, as having multiple transactions for each of them does not really make much sense to me.
You can try to execute both stored procedure into seperate TRY CATCH block and check for corresponding ERROR_NUMBER in CATCH block. If ERROR_NUMBER is same as error you are getting you can simply return or raiseerror as per your requirement.
Is it causing a fatal error. Please check what error severity is in the exception.
I might be a little unclear on what you want. If you need the entire monthlyRollUp SP to rollback on a failure of either the remote or local procedures, then you will need a distributed transaction coordinator. This will allow the servers to communicate the information about the transaction and coordinate the commits. I.e., both servers have to indicate that all necessary locks were gained and then coordinate commits on both servers so that the operation is automic. Here is one example of setting up a DTC:
http://social.msdn.microsoft.com/forums/en-US/adodotnetdataproviders/thread/7172223f-acbe-4472-8cdf-feec80fd2e64/
If you don't want the remote procedures to participate/affect the transaction, you can try setting:
SET REMOTE_PROC_TRANSACTIONS OFF;
http://msdn.microsoft.com/en-us/library/ms178549%28SQL.90%29.aspx
I haven't used that setting before though so I'm not sure if it will accomplish what you need.
If you can't or don't want to use DTC, and don't want to use CLR, then then you need to call the remote sp last, as you won't be able to rollback the remote sp call.
SET NOCOUNT, XACT_ABORT ON
SET REMOTE_PROC_TRANSACTIONS OFF;
BEGIN TRY
DECLARE #ret INT
BEGIN TRAN
--Perform these in a transaction, so they all rollback together
EXEC [LOCAL].[DB].[table].[sp1]
EXEC [LOCAL].[DB].[table].[sp2]
EXEC [LOCAL].[DB].[table].[sp3]
EXEC [LOCAL].[DB].[table].[sp4]
--We call remote sp last so that if it fails we rollback the above transactions
--We'll have to assume that remote sp takes care of itself on error.
EXEC [REMOTE].[DB].[table].[sp]
COMMIT
END TRY
BEGIN CATCH
--We rollback
ROLLBACK
-- Insert error into log table
INSERT INTO [dbo].[log_table] (stamp, errorNumber,
errorSeverity, errorState, errorProcedure, errorLine, errorMessage)
SELECT GETDATE(), ERROR_NUMBER(), ERROR_SEVERITY(), ERROR_STATE(),ERROR_PROCEDURE(),
ERROR_LINE(), ERROR_MESSAGE()
END CATCH
If the local sp's depend on results from the remote stored procedure, then you can use a CLR sp (will need EXTERNAL_ACCESS permissions) and manage the transactions explicitly (basically, a roll your own DTC, but no two-phase commit. You're effectively delaying the remote commit.)
//C# fragment to roll your own "DTC" This is not true two-phase commit, but
//may be sufficient to meet your needs. The edge case is that if you get an error
//while trying to commit the remote transaction, you cannot roll back the local tran.
using(SqlConnection cnRemote = new SqlConnection("<cnstring to remote>"))
{
try {
cnRemote.Open();
//Start remote transaction and call remote stored proc
SqlTransaction trnRemote = cnRemote.BeginTransaction("RemoteTran");
SqlCommand cmdRemote = cnRemote.CreateCommand();
cmdRemote.Connection = cnRemote;
cmdRemote.Transaction = trnRemote;
cmdRemote.CommandType = CommandType.StoredProcedure;
cmdRemote.CommandText = '[dbo].[sp1]';
cmdRemote.ExecuteNonQuery();
using(SqlConnection cnLocal = new SqlConnection("context connection=true"))
{
cnLocal.Open();
SqlTransaction trnLocal = cnLocal.BeginTransaction("LocalTran");
SqlCommand cmdLocal = cnLocal.CreateCommand();
cmdLocal.Connection = cnLocal;
cmdLocal.Transaction = trnLocal;
cmdLocal.CommandType = CommandType.StoredProcedure;
cmdLocal.CommandText = '[dbo].[sp1]';
cmdLocal.ExecuteNonQuery();
cmdLocal.CommandText = '[dbo].[sp2]';
cmdLocal.ExecuteNonQuery();
cmdLocal.CommandText = '[dbo].[sp3]';
cmdLocal.ExecuteNonQuery();
cmdLocal.CommandText = '[dbo].[sp4]';
cmdLocal.ExecuteNonQuery();
//Commit local transaction
trnLocal.Commit();
}
//Commit remote transction
trnRemote.Commit();
} // try
catch (Exception ex)
{
//Cleanup stuff goes here. rollback remote tran if needed, log error, etc.
}
}