I'm doing some DB schema re-structuring.
I have a script that looks broadly like this:
BEGIN TRAN LabelledTransaction
--Remove FKs
ALTER TABLE myOtherTable1 DROP CONSTRAINT <constraintStuff>
ALTER TABLE myOtherTable2 DROP CONSTRAINT <constraintStuff>
--Remove PK
ALTER TABLE myTable DROP CONSTRAINT PK_for_myTable
--Add replacement id column with new type and IDENTITY
ALTER TABLE myTable ADD id_new int Identity(1, 1) NOT NULL
GO
ALTER TABLE myTable ADD CONSTRAINT PK_for_myTable PRIMARY KEY CLUSTERED (id_new)
GO
SELECT * FROM myTable
--Change referencing table types
ALTER TABLE myOtherTable1 ALTER COLUMN col_id int NULL
ALTER TABLE myOtherTable2 ALTER COLUMN col_id int NOT NULL
--Change referencing table values
UPDATE myOtherTable1 SET consignment_id = Target.id_new FROM myOtherTable1 AS Source JOIN <on key table>
UPDATE myOtherTable2 SET consignment_id = Target.id_new FROM myOtherTable2 AS Source JOIN <on key table>
--Replace old column with new column
ALTER TABLE myTable DROP COLUMN col_id
GO
EXEC sp_rename 'myTable.id_new', 'col_id', 'Column'
GO
--Reinstate any OTHER PKs disabled
ALTER TABLE myTable ADD CONSTRAINT <PK defn>
--Reinstate FKs
ALTER TABLE myOtherTable1 WITH CHECK ADD CONSTRAINT <constraintStuff>
ALTER TABLE myOtherTable2 WITH CHECK ADD CONSTRAINT <constraintStuff>
SELECT * FROM myTable
-- Reload out-of-date views
EXEC sp_refreshview 'someView'
-- Remove obsolete sequence
DROP SEQUENCE mySeq
ROLLBACK TRAN LabelledTransaction
Obviously that's all somewhat redacted, but the fine detail isn't the important thing in here.
Naturally, it's quite hard to locate all the things that need to be turned off/editted before the core change (even with some meta-queries to help me), so I don't always get the script correct first time.
But I put in the ROLLBACK in order to ensure that the failed attempts left the DB unchanged.
But what I actually see is that the ROLLBACK doesn't occur if there were errors in the TRAN. I think I get errors about "no matching TRAN for the rollback"?
My first instinct was that it was about the GO statements, but https://stackoverflow.com/a/11121382/1662268 suggests that labeling the TRAN should have fixed that?
What's happening? Why don't the changes get rolled back properly if there are errors.
How can I write and test these scripts in such a way that I don't have to manually revert any partial changes if the script isn't perfect first time?
EDIT:
Additional comments based on the first answer.
If the linked answer is not applicable to this query, could you expand on why that is, and why it's different from the example that they had given in their answer?
I can't (or rather, I believe that I can't) remove the GOs, because the script above requires the GOs in order to compile. If I remove the GOs then later statements that depend on the newly added/renamed columns don't compile. and the query can't run.
Is there any way to work around this, to remove the GOs?
If you have any error which automatically causes the transaction to be rolled back then the transaction will roll back as part of the current batch.
Then, control will return back to the client tool which will then send the next batch to the server and this next batch (and subsequent ones) will not be wrapped in any transaction.
Finally, when the final batch is executed that tries to run the rollback then you'll get the error message you received.
So, you need to protect each batch from running when its not protected by a transaction.
One way to do it would be to insert our old fried GOTO:
GO
IF ##TRANCOUNT=0 GOTO NBATCH
...Rest of Code
NBATCH:
GO
or SET FMTONLY:
GO
IF ##TRANCOUNT=0 BEGIN
SET FMTONLY ON
END
...Rest of Code
GO
Of course, this won't address all issues - some statements need to be the first or only statement in a batch. To resolve these, we have to combine one of the above techniques with an EXEC of some form:
GO
IF ##TRANCOUNT=0 BEGIN
SET FMTONLY ON
END
EXEC sp_executesql N'/*Code that needs to be in its own batch*/'
GO
(You'll also have to employ this technique if a batch of code relies on work a previous batch has performed which introduces new database objects (tables, columns, etc), since if that previous batch never executed, the new object will not exist)
I've also just discovered the existence of the -b option for the sqlcmd tool. The following script generates two errors when run through SSMS:
begin transaction
go
set xact_abort on
go
create table T(ID int not null,constraint CK_ID check (ID=4))
go
insert into T(ID) values (3)
go
rollback
Errors:
Msg 547, Level 16, State 0, Line 7
The INSERT statement conflicted with the CHECK constraint "CK_ID". The conflict occurred in database "TestDB", table "dbo.T", column 'ID'.
Msg 3903, Level 16, State 1, Line 9
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
However, the same script saved as Abortable.sql and run with the following commandline:
sqlcmd -b -E -i Abortable.sql -S .\SQL2014 -d TestDB
Generates the single error:
Msg 547, Level 16, State 1, Server .\SQL2014, Line 1
The INSERT statement conflicted with the CHECK constraint "CK_ID". The conflict
occurred in database "TestDB", table "dbo.T", column 'ID'.
So, it looks like running your scripts from the commandline and using the -b option may be another approach to take. I've just scoured the SSMS options/properties to see if I can find something equivalent to -b but I've not found it.
Remove the 'GO', that finishes the transaction
Only ROLLBACK if completes - just use TRY/CATCH:
BEGIN TRANSACTION;
BEGIN TRY
--Remove FKs
ALTER TABLE myOtherTable1 DROP CONSTRAINT <constraintStuff>
ALTER TABLE myOtherTable2 DROP CONSTRAINT <constraintStuff>
--Remove PK
ALTER TABLE myTable DROP CONSTRAINT PK_for_myTable
--Add replacement id column with new type and IDENTITY
ALTER TABLE myTable ADD id_new int Identity(1, 1) NOT NULL
ALTER TABLE myTable ADD CONSTRAINT PK_for_myTable PRIMARY KEY CLUSTERED (id_new)
SELECT * FROM myTable
--Change referencing table types
ALTER TABLE myOtherTable1 ALTER COLUMN col_id int NULL
ALTER TABLE myOtherTable2 ALTER COLUMN col_id int NOT NULL
--Change referencing table values
UPDATE myOtherTable1 SET consignment_id = Target.id_new FROM myOtherTable1 AS Source JOIN <on key table>
UPDATE myOtherTable2 SET consignment_id = Target.id_new FROM myOtherTable2 AS Source JOIN <on key table>
--Replace old column with new column
ALTER TABLE myTable DROP COLUMN col_id
EXEC sp_rename 'myTable.id_new', 'col_id', 'Column'
--Reinstate any OTHER PKs disabled
ALTER TABLE myTable ADD CONSTRAINT <PK defn>
--Reinstate FKs
ALTER TABLE myOtherTable1 WITH CHECK ADD CONSTRAINT <constraintStuff>
ALTER TABLE myOtherTable2 WITH CHECK ADD CONSTRAINT <constraintStuff>
SELECT * FROM myTable
-- Reload out-of-date views
EXEC sp_refreshview 'someView'
-- Remove obsolete sequence
DROP SEQUENCE mySeq
ROLLBACK TRANSACTION
END TRY
BEGIN CATCH
print 'Error caught'
select ERROR_NUMBER() AS ErrorNumber, ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
Related
I have a migration script written in Transact-SQL which is using transactions in order to have a proper rollback if something goes wrong during the execution.
Unfortunately, this rollback behaviour is not working as expected when I'm using some GO utility statements in my script.
The issue can be reproduced with a simple script:
BEGIN TRANSACTION
-- Create a table with two nullable columns
CREATE TABLE [dbo].[t1](
[id] [nvarchar](36) NULL,
[name] [nvarchar](36) NULL
)
-- add one row having one NULL column
INSERT INTO [dbo].[t1] VALUES(NEWID(), NULL)
-- set one column as NOT NULLABLE
-- this fails because of the previous insert
ALTER TABLE [dbo].[t1] ALTER COLUMN [name] [nvarchar](36) NOT NULL
GO
-- create a table as next action, so that we can test whether the rollback happened properly
CREATE TABLE [dbo].[t2](
[id] [nvarchar](36) NOT NULL
)
GO
COMMIT TRANSACTION
When I execute this script, I get the following output:
(1 row affected)
Msg 515, Level 16, State 2, Line 23
Cannot insert the value NULL into column 'name', table 'test-transaction.dbo.t1'; column does not allow nulls. UPDATE fails.
The statement has been terminated.
Msg 3902, Level 16, State 1, Line 31
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
As expected, it is complaining that the column 'name' contains a NULL value but only the corresponding GO batch fails. The next batch is executed and the table t2 is successfully created.
My understanding of the GO documentation is that it should not impact the T-SQL transactions but this is not the case in my example.
How can I make the whole transaction be rolled back if any of the GO batch fails?
ps: if I remove the GO statements, the transaction rollback is working as expected. But I do need those GO statements, in order to ensure that some parts of the script are executed before others.
Some errors roll back the transaction. Don't bother figuring out which ones, because there's no simple rule.
A multi-batch script should have a single error handler scope that rolls back the transaction on error, and commits at the end. In TSQL you can do this with dynamic sql, eg
BEGIN TRANSACTION
BEGIN TRY
EXEC('
-- Create a table with two nullable columns
CREATE TABLE [dbo].[t1](
[id] [nvarchar](36) NULL,
[name] [nvarchar](36) NULL
)
')
EXEC('
-- add one row having one NULL column
INSERT INTO [dbo].[t1] VALUES(NEWID(), NULL)
')
-- set one column as NOT NULLABLE
-- this fails because of the previous insert
EXEC('
ALTER TABLE [dbo].[t1] ALTER COLUMN [name] [nvarchar](36) NOT NULL
')
EXEC('
-- create a table as next action, so that we can test whether the rollback happened properly
CREATE TABLE [dbo].[t2](
[id] [nvarchar](36) NOT NULL
)
')
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK;
THROW;
END CATCH
With SQLCMD you can use the -b option to abort the script on error.
I am attempting to migrate some data from one database to another using Microsoft SQL Server. Both databases have a "Properties/Locations" type of table that is referenced by a foreign key.
Unfortunately, even though the entities referenced in the two tables are the same, the primary keys are not. As such, in order to migrate the data, I am trying to temporarily disable the foreign key constraint, insert and update the data appropriately, and then re-enable the constraint.
However, I am receiving the following message:
The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "FK__TwelveCri__Store__114A936A". The conflict occurred in database "API", table "dbo.Properties", column 'ID'.
While, I understand the general reason why the error is being thrown (it is not finding a match between the column StoreID in the Reports table and the ID columns in Properties), I do not understand why it is doing so in this specific case.
BEGIN TRAN
USE API;
EXEC sp_msforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT ALL";
SET IDENTITY_INSERT Midamcorp.TwelveCriticalsReports ON;
INSERT INTO Midamcorp.TwelveCriticalsReports (ID, StoreID, InspectorName, ReportTime, ReportDate, PointsPoss, PointsReceived)
SELECT
id, storeID, inspectorName, reportTIme, reportDate, pointsPoss, pointsReceived
FROM
midAmCorp.dbo.criticalReports;
SET IDENTITY_INSERT Midamcorp.TwelveCriticalsReports OFF;
UPDATE API.Midamcorp.TwelveCriticalsReports
SET StoreID = 1
WHERE StoreID = 4;
!--- MORE UPDATE STATEMENTS HERE ---!
USE API
SET IDENTITY_INSERT Midamcorp.SecretShopperReportSummary ON;
INSERT INTO Midamcorp.SecretShopperReportSummary(ID, StoreID, PointsPoss, PointsReceived, DriveTime, CompletedBy, DateOfVisit)
SELECT
id, storeID, pointsPoss, pointsReceived, driveTime, completedBy, dateOfVisit
FROM
midamCorp.dbo.secretShopperReportSummary;
SET IDENTITY_INSERT Midamcorp.SecretShopperReportSummary OFF;
!--- MORE UPDATE STATEMENTS HERE ---!
USE API
SET IDENTITY_INSERT Midamcorp.SecretShopperReportDetails ON;
INSERT INTO Midamcorp.SecretShopperReportDetails(ID, ReportID, QuestionID)
SELECT
id, reportID, questionID
FROM
midAmCorp.dbo.secretShopperReportDetails;
SET IDENTITY_INSERT Midamcorp.SecretShopperReportDetails OFF;
SELECT *
FROM Midamcorp.TwelveCriticalsReports
WHERE StoreID NOT IN (SELECT StoreID FROM dbo.Properties);
EXEC sp_msforeachtable "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT ALL";
COMMIT TRAN;
The SELECT statement at near the end returns no results, which is what I would expect if the relationships were properly updated. However, I am still receiving the error message noted above, presumably from the EXEC sp_msforeachtable "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT ALL"; statement.
Any advice would be appreciated.
Are there any negative implications to running an alter table alter column statement more than once in SQL Server?
Say I alter a column's datatype and nullability like this:
--create table
create table Table1
(
Column1 varchar(50) not null
)
go
--insert some records
insert into Table1 values('a')
insert into Table1 values('b')
go
--alter once
alter table Table1
alter column Column1 nvarchar(250) not null
go
--alter twice
alter table Table1
alter column Column1 nvarchar(250) not null
go
The above set of sql all works and I have tested these. I could also test for the properties in the alter statements. The question is that is there any advantage to say checking if the column is not already nullable before altering.
After the first alter, does SQL Server figure out that the table has already been altered and hence the 2nd alter essentially does nothing?
Are there any differences across different versions of SQL Server about how this is handled?
Thanks,
Ilias
This is a metadata only operation.
It doesn't have to read or write any of the data pages belonging to Table1. It isn't quite a no-op though.
It will still start up a transaction, acquire a schema modification lock on the table and update the modified column in the row for this table in sys.sysschobjs (exposed to us through the modified_date column in sys.objects).
Moreover because the table has been modified any execution plans referencing the table will need to be recompiled on next usage.
This question already has answers here:
How to add identity to the column in SQL Server?
(4 answers)
Closed 8 years ago.
I have a table and primary key is already set to that table and now I want that column to be autoincrement. Table has many records. Is it possible? or which one is fastest way to do that?
I think you have to make some effort for this as you cannot create identity column on existing column. However you may have a workaround for this like first try this to add a new column having identity field:
ALTER TABLE dbo.Table_name
ADD ID INT IDENTITY
and then make your ID as primary key like this:
ALTER TABLE dbo.Table_name
ADD CONSTRAINT PK_YourTable
PRIMARY KEY(ID)
And yes you have to remove the old dependencies before performing the above steps like this:
ALTER TABLE Table_name
DROP CONSTRAINT PK_Table1_Col1
EDIT:-
From the source:
We can use ALTER TABLE...SWITCH to work around this by only modifying metadata. See Books Online for restrictions on using the SWITCH method presented below. The process is practically instant even for the largest tables.
USE tempdb;
GO
-- A table with an identity column
CREATE TABLE dbo.Source (row_id INTEGER IDENTITY PRIMARY KEY NOT NULL, data SQL_VARIANT NULL);
GO
-- Some sample data
INSERT dbo.Source (data)
VALUES (CONVERT(SQL_VARIANT, 4)),
(CONVERT(SQL_VARIANT, 'X')),
(CONVERT(SQL_VARIANT, {d '2009-11-07'})),
(CONVERT(SQL_VARIANT, N'áéíóú'));
GO
-- Remove the identity property
BEGIN TRY;
-- All or nothing
BEGIN TRANSACTION;
-- A table with the same structure as the one with the identity column,
-- but without the identity property
CREATE TABLE dbo.Destination (row_id INTEGER PRIMARY KEY NOT NULL, data SQL_VARIANT NULL);
-- Metadata switch
ALTER TABLE dbo.Source SWITCH TO dbo.Destination;
-- Drop the old object, which now contains no data
DROP TABLE dbo.Source;
-- Rename the new object to make it look like the old one
EXECUTE sp_rename N'dbo.Destination', N'Source', 'OBJECT';
-- Success
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Bugger!
IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
PRINT ERROR_MESSAGE();
END CATCH;
GO
-- Test the the identity property has indeed gone
INSERT dbo.Source (row_id, data)
VALUES (5, CONVERT(SQL_VARIANT, N'This works!'))
SELECT row_id,
data
FROM dbo.Source;
GO
-- Tidy up
DROP TABLE dbo.Source;
How can I delete a row from a table if it has a foreign key?
I have this stored procedure but when I execute it it gives me this error :The DELETE statement conflicted with the REFERENCE constraint "FK__Pilot_Fli__pilot__4E88ABD4". The conflict occurred in database "Airline Reservation", table "dbo.Pilot_Flight", column 'pilot_id'.
create procedure DeletePilot
(#id INTEGER,#result varchar(70) output)
as
If NOT Exists ( Select * From Pilot
Where pilot_id=#id)
Begin
Set #result='There is no record with that ID'
RETURN
END
Delete from Pilot
where pilot_id=#id
set #result='Record was deleted'
RETURN
GO
select * from Pilot
Declare #res varchar(70)
EXEC DeletePilot 7,#res OUTPUT
print(#res)
Can anyone help me please!
You'd have to either run a statement like this (if it's nullable):
UPDATE Pilot_Flight
SET pilot_id = NULL
WHERE pilot_id = #id
or do this:
DELETE Pilot_Flight WHERE pilot_id = #id
Either way you have to do one or the other before the DELETE from Pilot.
There are records in dbo.Pilot_Flight that reference records in dbo.Pilot.
You could delete the records in Pilot_Flight before deleting the records in Pilot, enable (cascade delete which would delete records in Pilot_Flight when Pilot records are deleted (bad), or disable the foreign key reference... (worse).