Issue when comparing tables with unique constraint
Bastiaan Molsbeck
Posts: 73
Hi,
I created an application using the Red-Gate SQL Comparison SDK 10 to compare two databases (which are identical by schema but differ in data) and synchronize them after the comparison.
Now I run into an issue for tables with a unique constraint. Let me explain it using an example.
The table definition looks something like this:
1001, 1, 12, "Role_1", 0
1002, 1, 13, "Role_1", 0
This table in the target databas contains (amongst others) these two rows:
1001, 1, 13, "Role_1", 0
1002, 1, 12, "Role_1", 0
When comparing these two tables, the following script is generated:
How can I address this issue?
Do I have to manually drop and recreate all unique contraints?
I created an application using the Red-Gate SQL Comparison SDK 10 to compare two databases (which are identical by schema but differ in data) and synchronize them after the comparison.
Now I run into an issue for tables with a unique constraint. Let me explain it using an example.
The table definition looks something like this:
CREATE TABLE tbdRole ( fldRoleID INT IDENTITY (1, 1) NOT NULL PRIMARY KEY, fldRoleType INT NOT NULL, fldTemplateID INT NOT NULL, fldName VARCHAR(255) NOT NULL, fldReadRole BIT NOT NULL ) -- make sure all roles for a template have a unique name ALTER TABLE tbdRole ADD CONSTRAINT UN_tbdRole UNIQUE NONCLUSTERED ( fldTemplateID, fldName )Now, this table in the source database contains (amongst others) these two rows:
1001, 1, 12, "Role_1", 0
1002, 1, 13, "Role_1", 0
This table in the target databas contains (amongst others) these two rows:
1001, 1, 13, "Role_1", 0
1002, 1, 12, "Role_1", 0
When comparing these two tables, the following script is generated:
SET NUMERIC_ROUNDABORT OFF GO SET ANSI_PADDING, ANSI_WARNINGS, CONCAT_NULL_YIELDS_NULL, ARITHABORT, QUOTED_IDENTIFIER, ANSI_NULLS, NOCOUNT ON GO SET DATEFORMAT YMD GO SET XACT_ABORT ON GO SET TRANSACTION ISOLATION LEVEL SERIALIZABLE GO BEGIN TRANSACTION -- Pointer used for text / image updates. This might not be needed, but is declared here just in case DECLARE @pv binary(16) -- Drop constraints from [dbo].[tbdRole] ALTER TABLE [dbo].[tbdRole] DROP CONSTRAINT [FK_tbdRole_tbdTemplate] -- Drop unused indexes from [dbo].[tbdRole] DROP INDEX [IX_tbdRole_fldTemplateID] ON [dbo].[tbdRole] -- Update 2 rows in [dbo].[tbdRole] UPDATE [dbo].[tbdRole] SET [fldTemplateID]=12 WHERE [fldRoleID]=1001 UPDATE [dbo].[tbdRole] SET [fldTemplateID]=13 WHERE [fldRoleID]=1002 -- Add indexes to [dbo].[tbdRole] CREATE CLUSTERED INDEX [IX_tbdRole_fldTemplateID] ON [dbo].[tbdRole] ([fldTemplateID]) ON [PRIMARY] -- Add constraints to [dbo].[tbdRole] ALTER TABLE [dbo].[tbdRole] WITH NOCHECK ADD CONSTRAINT [FK_tbdRole_tbdTemplate] FOREIGN KEY ([fldTemplateID]) REFERENCES [dbo].[tbdTemplate] ([fldTemplateID]) ALTER TABLE [dbo].[tbdRole] NOCHECK CONSTRAINT [FK_tbdRole_tbdTemplate] COMMIT TRANSACTION GOWhen running this script, the following error occurs when executing the first UPDATE statement, because of the existing unique constraint:
Violation of UNIQUE KEY constraint 'UN_tbdRole'. Cannot insert duplicate key in object 'dbo.tbdRole'.NOTE: I'm aware of the SqlOptions.DropConstraintsAndIndexes, but having set this or not does not matter. The exact same script is generated.
How can I address this issue?
Do I have to manually drop and recreate all unique contraints?
Comments
AFAIK what you want to happen is a DELETE or INSERT rather than an UPDATE, if the row identifiers are the columns that are part of the UN_tbdRole constraint.
I rather would like a DELETE/INSERT instead of an UPDATE, because of the unique constraint, or a way to ignore the unique constraint.
Do I achieve this with your posted code?
Sidenote: the comparison key is set to the primary key of the table. We have some tables that have more than one unique key constraint, therefore we always set the comparison key on the primary key.
The script I posted was generated WITH the DropConstraintsAndIndexes turned ON:
I'm using v10.0.1 of the SDK, if you might want to know this.
Any other ideas? Am I missing something in the code?
How will I receive updates on this issue? Do they also post in this topic?
Only the data differs.
Could this be the reason? Could you test this?
The reason why I use "SchemaMappings" is that the method "SQLDataCompare.Engine.DataCompareUserActions.ReplayUserActions" requires this object.
I cannot convert a "TableMappings" to a "SchemaMappings" object to pass to this method.
And I use the "ReplayUserActions" method, because some tables have to be excluded, and some tables have a WHERE clause to exclude some rows.
(Sidenote for this case: the table "tbdRole" is not excluded and does not have a WHERE clause).
"Holds mappings for schemas, users, or roles. Uses the database objects to create the mappings for the views and the tables from the two databases."
http://help.red-gate.com/help/SQLDataCompareAPIv6/1/en/html/N_RedGate_SQLDataCompare_Engine.htm
Anyway, all I want to achieve is that the SqlOption "DropConstraintsAndIndexes" actually does drop and recreate the unique constraint.
The online help page about this describes the following:
DropConstraintsAndIndexes - Drop and recreate primary keys, indexes, and unique constraints in the synchronization script. If the primary key, index, or unique constraint is the comparison key, it cannot be dropped.
http://help.red-gate.com/help/SQLDataCompareAPIv6/1/en/html/T_RedGate_SQLDataCompare_Engine_SqlOptions.htm
On the concerning table, the unique constraint is NOT used as the comparison key, so it should be dropped, but it isn't. Sounds like a bug to me.
What do you suggest that I should modify in my code?
I'm turning this support issue over to the development team.
I am using Redgate.SQLDataCompare.Engine v 10.0.1.101
I cannot comment about the documnentation, I have once again notified the product management about the problems with the documentation.
I would really like to get this working for you but I don't believe SchemaMappings are the way forward, but as you point out, I can't prove that with the documentation in the current state it is in.
I changed my code so that it is the same as yours:
- I used the "TableMappings" instead of the "SchemaMappings"
- I used the same "EngineDataCompareOptions" as you specfied, which includes the "DropConstraintsAndIndexes", and assigned it to the "TableMappings", "ComparisonSession" and "SqlProvider"
- I do not call the method "ReplayUserActions" anymore
- I added the "For...Next" loop to include all TableMappings after the "CreateMappings" call
But:
The unique constraint is NOT dropped!
The script generated is somewhat bigger (because more tables are included), but regarding the concerning table ("tbdRole") the script is exactly the same:
Could it be that the difference in version of the component "Redgate.SQLDataCompare.Engine" is the reason?
I currently use version 10.0.1.69.
Is it possible that I can obtain your version (build 101), to test this?
Apparently the unique contraint on the concerning table "tbdRole" was DISABLED.
Therefore, your component does not generate the DROP statement for this constraint.
This should be not a problem, because a disabled unique constraint would not fire when a duplicate value is inserted.
But because a CLUSTERED index was also present on the table, this index is dropped.
And dropping a clustered index results in a rebuild of ALL other indexes, including the unique constraint.
This means that after the DROP INDEX statement, the unique constraint was enabled again, and prevents the data from being modified!
The solution for me would be to enable all unique constraints first, because then your component does generate the DROP statements for them.
But I would like to know why your component doesn't generate a DROP statement for disabled unique constraints, because dropping indexes could enabled them.