SQL Server 'Invalid column name' in transaction - sql-server

I have a script where I'm adding a column to the table, and immediately after I populate that column with data from another table. I'm getting 'Invalid column name' error on the column that I am adding.
The error, specifically, is Invalid column name 'tagID'.
The code between BEGIN TRANSACTION and COMMIT is actually an excerpt of a much larger script, but this is the relevant excerpt (and I need all of it to succeed or simply roll back):
BEGIN TRY
BEGIN TRANSACTION
ALTER TABLE [Items] ADD tagID [uniqueidentifier] NULL
MERGE INTO
Items AS target
USING
Tags AS t ON t.tag = target.tag
WHEN MATCHED THEN
UPDATE SET target.tagID = t.id;
COMMIT
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
END CATCH
GO

SQL Server tries to compile all statements in the batch. If the table doesn't exist compilation of the statement is deferred but there is no deferred compilation for missing columns.
You can use
BEGIN TRY
BEGIN TRANSACTION
ALTER TABLE [Items] ADD tagID [uniqueidentifier] NULL
EXEC('
MERGE INTO
Items AS target
USING
Tags AS t ON t.tag = target.tag
WHEN MATCHED THEN
UPDATE SET target.tagID = t.id;
')
COMMIT
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
END CATCH
GO
To push the usage of the column into a child batch compiled after the column is created. It still belongs to the same transaction opened in the parent scope.

Transaction scope are for DML operations and not for DDL operations. So not sure why you are having the ALTER statement in the same transaction block. If not wrong, you should be having that ALTER statement outside the transaction block.
ALTER TABLE [Items] ADD tagID [uniqueidentifier] NULL
BEGIN TRANSACTION
MERGE INTO
.....
Also, I would remove those [] from the datatype of the column from your ALTER statement
ALTER TABLE [Items] ADD tagID UniqueIdentifier;

Related

SQL temp table already exists even though I checked it with OBJECT_ID('tempdb..##myTable') IS NULL

I need to use existing global temp tables or create them and fill if they don't exist.
Example of one table
BEGIN TRANSACTION
if OBJECT_ID('tempdb..##myTable') IS NULL
begin
create table ##myTable (
--stuff
)
end
COMMIT TRANSACTION
BEGIN TRANSACTION
if (select count(*) from ##myTable) = 0
begin
insert into ##myTable
--select stuff
end
COMMIT TRANSACTION
Sometimes it works and sometimes error "Table ##myTable already exists" shows. I am the only one who uses those global temp tables
This is a classic race condition. Multiple sessions can execute the if OBJECT_ID('tempdb..##myTable') IS NULL query at the same time. If the table doesn't exist, both will attempt to create the table and only one will succeed.
One way to address the issue is with an application lock to serialize the code block among multiple sessions. For example:
SET XACT_ABORT ON; --best practice with explict T-SQL transactions
BEGIN TRANSACTION;
EXEC sp_getapplock
#Resource = 'create ##myTable'
,#LockMode = 'Exclusive'
,#LockOwner = 'Transaction';
if OBJECT_ID('tempdb..##myTable') IS NULL
begin
create table ##myTable (
--select stuff
);
end;
COMMIT TRANSACTION; --also releases app lock

SQL Large Insert Transaction log full Error

I am trying to insert almost 1,75,00,000 in 8 tables.
I have stored procedure for that. At the start and end of that procedure, I have written Transaction.
Error: The transaction log for database 'Database' is full due to 'ACTIVE_TRANSACTION'.
Note: I want to keep everything in the transaction, its automated process. This process will run on Database every month
CREATE OR ALTER PROCEDURE [dbo].[InsertInMainTbls]
AS
BEGIN
PRINT('STARTED [InsertInMainTbls]')
DECLARE #NoRows INT
DECLARE #maxLoop INT
DECLARE #isSuccess BIT=1
BEGIN TRY
BEGIN TRAN
--1st table
SET #NoRows = 1
SELECT #maxLoop=(MAX([col1])/1000)+1 FROM ProcessTbl
SELECT 'loop=='+ CAST(#maxLoop as Varchar)
WHILE (#NoRows <= #maxLoop)
BEGIN
INSERT INTO MainTbl WITH(TABLOCK)
( col1,col2,col3....col40)
SELECT
( val1,val2,val3....val40)FROM
ProcessTbl
WHERE [col1] BETWEEN (#NoRows*1000)-1000
AND (#NoRows*1000)-1
SET #NoRows = #NoRows+1;
END
--2nd table
.
.
.
--8th table
SET #isSuccess=1;
COMMIT TRAN
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE();
SELECT ERROR_MESSAGE() 'ErrorMsg' ;
SET #isSuccess=0;
ROLLBACK TRAN
END CATCH
Despite the fact that is a nonsense to have such a huge transaction, while you can do a manual rollback by tagging the rows with something like a timesptamp or a GUID, to do so, you need to have the necessary space in the transaction log file to store all the rows of all your inserts from the first one til the last one, plus all the transaction that other user swill do at the same time. Many solutions to solve your problem :
1) enlarge your transaction log file
2) add some complementary log files
3) remove or decrease the transaction scope

How to check if sp_rename is done successfully?

I am running the following query:
SELECT * INTO dbo.2015_10_2_cs FROM dbo.2015_10_2
IF NOT EXISTS
(SELECT type FROM sys.indexes WHERE object_id = object_id('dbo.2015_10_2_cs')
AND NAME ='cci' AND type = 5)
BEGIN
CREATE CLUSTERED COLUMNSTORE INDEX cci
ON dbo.2015_10_2_cs
DROP TABLE dbo.2015_10_2
EXEC sp_rename "dbo.2015_10_2_cs" , "dbo.2015_10_2"
END
and I want to make sure that the part where I am renaming the table dbo.2015_10_2_cs to dbo.2015_10_2 is done successfully (without losing any data).
The step inside the loop should be surrounded with SQL transaction to keep the process safe and reliable (in case if any step will fail).
Could anyone help with this? Thanks in advance.
EXEC sp_rename "dbo.2015_10_2_cs" , "dbo.2015_10_2"
This will not do what you expect. The new table will be named [dbo].[dbo.2015_10_2] if you specify the schema name in the new table name. Renamed tables are implicitly in the existing table's schema since one must use ALTER SCHEMA instead of sp_rename to move an object between schemas.
There are a number of other problems with your script. Because the table name starts with a number, it doesn't conform to regular identifier naming rules and must be enclosed in square brackets or double quotes. The literal parameters passed to sp_rename should be single quotes. You can also check to stored procedure return code to ascertain success or failure. The example below performs these tasks in a transaction with structured error handling.
DECLARE #rc int;
BEGIN TRY
BEGIN TRAN;
IF NOT EXISTS
(SELECT type FROM sys.indexes WHERE object_id = object_id(N'dbo.2015_10_2_cs')
AND NAME ='cci' AND type = 5)
BEGIN
CREATE CLUSTERED COLUMNSTORE INDEX cci
ON dbo.[2015_10_2_cs];
DROP TABLE dbo.[2015_10_2];
EXEC #rc = sp_rename 'dbo.[2015_10_2_cs]' , '2015_10_2';
IF #rc <> 0
BEGIN
RAISERROR('sp_rename returned return code %d',16,1);
END;
END;
COMMIT;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK;
THROW;
END CATCH;
You can use an EXISTS checking for the tablename and schema.
IF NOT EXISTS (SELECT 'table does not exist' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = N'2015_10_2'AND TABLE_SCHEMA = 'dbo')
BEGIN
RAISERROR('The table doesn''t exist!!!!', 16, 1)
END
sp_rename won't make you lose table contents, it will just change the table reference name and update all it's contraints and indexes references. It will also raise an error if the table to rename does not exist. Maybe what you want is to wrap your process in a transaction and rollback if something fails.
EDIT:
For basic transaction handling you can use the following. Please read the documentation for using transaction, it might take a while to know how it works correctly.
IF OBJECT_ID('tempdb..#Test') IS NOT NULL
DROP TABLE #Test
CREATE TABLE #Test (Number INT)
SELECT AmountRecords = COUNT(1) FROM #Test -- AmountRecords = 0
BEGIN TRY
BEGIN TRANSACTION
-- Do your statements here
INSERT INTO #Test (Number)
VALUES (1)
DECLARE #errorVariable INT = CONVERT(INT, 'NotAnInteger!!') -- Example of error: can't convert
COMMIT
END TRY
BEGIN CATCH -- If something goes wrong
IF ##TRANCOUNT > 0 -- ... and transaction is still open
ROLLBACK -- Revert statements from the BEGIN TRANSACTION onwards
END CATCH
SELECT AmountRecords = COUNT(1) FROM #Test -- AmountRecords = 0 (the transaction was rolled back and the INSERT reverted)
Basically you use BEGIN TRANSACTION to initiate a restore point to go back to if something fails. Then use a COMMIT once you know everything is OK (from that point onwards, other users will see the changes and modifications will be persisted). If something fails (you need TRY/CATCH block to handle errors) you can issue a ROLLBACK to revert your changes.

Raiserror severity 16 does not rollbacks trigger

I have a database running on SQL Server 2008.
I have a trigger in which I raise error with severity 16. When I test the trigger the error appears but the operation is not rolled back, i.e. I have additional row in the table. I can not understand why is that, because severity 16 results in rollback. We also used that convention in other triggers and it terminates trigger and causes rollback.
The table has also another trigger which does not allow deletion of rows.
Here is the trigger:
ALTER TRIGGER dbo.trg
ON dbo.tbl
AFTER INSERT, UPDATE
AS
BEGIN
IF (##ROWCOUNT > 0)
IF ((SELECT COUNT(SDS.ID) AS Count0 FROM dbo.tbl SDS WHERE SDS.IsIdleTimeReferred = 0) <> 1) OR
((SELECT MAX(SDS.CreatedDate) FROM dbo.tbl SDS WHERE SDS.IsIdleTimeReferred = 0) <
(SELECT MAX(SDS.CreatedDate) FROM dbo.tbl SDS WHERE SDS.IsIdleTimeReferred = 1))
BEGIN
--IF ##TRANCOUNT > 0 ROLLBACK
RAISERROR('Only one record with value IsIdleTimeReferred=0 must exist and it must be the last one', 16, 1);
END;
END;
When I uncomment ##TRANCOUNT the operation behaves correctly.
The table tbl consists of 3 columns:
[ID], [IsIdleTimeReferred], [CreatedDate]
I can not figure out where is the problem. For me code is designed correctly.
Any ideas?
EDIT:
Ok, I agree that Severity 16 does not rollback transaction in a trigger. So I implemented the following code (the trigger is AFTER INSERT, UPDATE):
BEGIN
IF ##TRANCOUNT > 0 ROLLBACK TRANSACTION;
RAISERROR('Does not allow modifications in the past', 16, 1);
RETURN;
END;
This code does not rollbacks transaction in a trigger. If I switch rows and first RAISERROR and then ROLLBACK the changes are not rollbacked.
Why this happening?
To be sure that transaction is roll back always try to use explicit 'ROLLBACK' (see comments of Damien_The_Unbeliever, JodyT and Andrew). All credits to them.
Kevin-Suchlicki is also right but in my case I don't want to use SET XACT_ABORT ON.
Instead of using RAISERROR, which does not respect XACT_ABORT, use THROW instead.
This is valid from SQL Server 2012 onwards, which by now everyone should have upgraded.
Because XACT_ABORT is on by default in triggers, the transaction is automatically rolled back. There is no need, nor should you, explicitly rollback.
CREATE OR ALTER TRIGGER dbo.trg
ON dbo.tbl
AFTER INSERT, UPDATE
AS
SET NOCOUNT ON;
IF NOT EXISTS (SELECT 1 FROM inserted)
RETURN;
IF EXISTS (SELECT 1
FROM dbo.tbl notIdle
WHERE notIdle.IsIdleTimeReferred = 0
HAVING COUNT(*) <= 1
AND MAX(idle.CreatedDate) > (
SELECT MAX(idle.CreatedDate)
FROM dbo.tbl idle
WHERE idle.IsIdleTimeReferred = 1
)
)
BEGIN
THROW 50001, 'Only one record with value IsIdleTimeReferred=0 must exist and it must be the last one', 1;
END;
Note also the simplification of the checks, and the use of SET NOCOUNT.
However, in this particular case, you can enforce part of this with a constraint. A conditional unique constraint can be enforced with a filtered index
CREATE UNIQUE NONCLUSTERED INDEX IX_OneIdle ON dbo.tbl (IsIdleTimeReferred) WHERE (IsIdleTimeReferred = 0);
This doesn't help you for your other constraint though.

Force unique varchar(255) column value in a SQL Server table with Entity Framework

I have database records for some users on a Windows server. One of the columns is the user's SID (unique string for that user).
When I get a message for a user, I want to first perform a query for a record with the SID value for the SID column. If no record is returned, then create a record with that SID value. However, between the query and the add, another thread can get a message with the same user/SID. So I could end up adding it twice.
Is there a way to create a transaction where no other record can be added to the table until the transaction completes?
Or is there a better way as locking an entire table, because no matter how fast the query/add, that's a choke point. (If this is the best way, I can query with no transaction, return if it exists, and only in the rare case it does not, then transaction, query again, add.)
Is there a way to tell SQL Server that the column is unique? The properties for setting that are disabled in SQL Server Management Studio, I assume because it's a varchar.
thanks - dave
You can Create a Unique Index on the column to enforce uniquenss of the values on the column and the you can use the following Code tweak it to your database specifications,
Warring Since I have set Transaction Isolation Level to SERIALIZABLE it can slow down the query if multipule users try to ADD the data since it does not allow other user to change or add data into the table when this Transaction Isolation Level is set to SERIALIZABLE...
CREATE Procedure usp_InsertSID
#SID varchar(225)
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
IF #SID IS NULL
RAISERROR('Validation Failed: SID is cannot be null', 16,1)
IF EXISTS (SELECT SID from tableName
WHERE SID= #SID)
RAISERROR('Validation Failed: SID already Exists',16,1)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION
INSERT INTO tableName(ColumnNames)
VALUES (#SID)
COMMIT TRANSACTION
SELECT #ReturnMessage = 'Sucess! New SID is added.'
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
ROLLBACK TRAN
SELECT #ReturnMessage = ERROR_MESSAGE()
SELECT #ReturnCode AS ReturnCode, #ReturnMessage AS ReturnMessage,
ERROR_LINE() AS ErrorLine,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState
END CATCH
END
GO

Resources