Auto increment even and odd for two databases for synchronization without affecting auto increment property - sql

Need quick help. I am having my database with bigint auto increment property. I have database on two location's which need to synchronized. As bigint is not good choice for synchronization because of possibility primary key replica on different sites. I can not move on with GUID because for that i need to change my code as well as database which is not possible for me.
Right now i have two location only for database, so i think if can make my primary key auto increment to be always even at one location and to be odd at other location. It can solve my issue in quick manner.
How can i do it using computed column specification or by any other way. For synchronization i am using Microsoft sycn framework.
If i use identity(1,2) A server or identity(2,2) B server after synchronization it disturbs next increment value. For example if at A server max id 3 and at B server current id is 4. After sync max id on A server will be now 4. I want new id on A server should be 5 only but in reality it inserts 6. How can i solve this issue

Here is a very simple solution, but it will work only for two servers. It can't be easily extended for more servers.
The good thing about it is that it doesn't use CHECKIDENT to reseed the tables and you don't need to worry about simultaneously running transactions to get the accurate MAX ID to feed into CHECKIDENT.
Also, MSDN warns that identity property on a column does not guarantee the following:
Consecutive values after server restart or other failures – SQL Server
might cache identity values for performance reasons and some of the
assigned values can be lost during a database failure or server
restart. This can result in gaps in the identity value upon insert. If
gaps are not acceptable then the application should use its own
mechanism to generate key values. Using a sequence generator with the
NOCACHE option can limit the gaps to transactions that are never
committed.
If you choose a solution that is based on reseeding identity using CHECKIDENT you'd better double check that it works correctly in such cases.
Also, to run CHECKIDENT you may need specific permissions:
Caller must own the table, or be a member of the sysadmin fixed server
role, the db_owner fixed database role, or the db_ddladmin fixed
database role.
Solution
My main idea is that on the first server you use IDENTITY(1,1) and on the second server you use IDENTITY(-1,-1). Rather than trying to make IDs odd and even they will be positive and negative.
Here is a script that proves that it works as intended without any extra work.
-- Sample data
CREATE TABLE #T1 (ID bigint IDENTITY(1,1), V1 int);
CREATE TABLE #T2 (ID bigint IDENTITY(-1,-1), V2 int);
INSERT INTO #T1 VALUES (11);
INSERT INTO #T1 VALUES (12);
INSERT INTO #T1 VALUES (13);
INSERT INTO #T1 VALUES (14);
INSERT INTO #T2 VALUES (21);
INSERT INTO #T2 VALUES (22);
INSERT INTO #T2 VALUES (23);
SELECT * FROM #T1;
SELECT * FROM #T2;
We start with this sample data in our tables:
#T1
ID V1
1 11
2 12
3 13
4 14
#T2
ID V2
-1 21
-2 22
-3 23
Perform the sync
-- Insert into T1 new values from T2
SET IDENTITY_INSERT #T1 ON;
MERGE INTO #T1 AS Dst
USING
(
SELECT ID, V2
FROM #T2
) AS Src
ON Dst.ID = Src.ID
WHEN NOT MATCHED BY TARGET
THEN INSERT (ID, V1)
VALUES (Src.ID, Src.V2);
SET IDENTITY_INSERT #T1 OFF;
-- Insert into T2 new values from T1
SET IDENTITY_INSERT #T2 ON;
MERGE INTO #T2 AS Dst
USING
(
SELECT ID, V1
FROM #T1
) AS Src
ON Dst.ID = Src.ID
WHEN NOT MATCHED BY TARGET
THEN INSERT (ID, V2)
VALUES (Src.ID, Src.V1);
SET IDENTITY_INSERT #T2 OFF;
SELECT * FROM #T1;
SELECT * FROM #T2;
Result of the sync - two identical tables
#T1
ID V1
1 11
2 12
3 13
4 14
-1 21
-2 22
-3 23
#T2
ID V2
-1 21
-2 22
-3 23
1 11
2 12
3 13
4 14
Insert more data to check how identity works after the sync
-- Insert more data into T1 and T2
INSERT INTO #T1 VALUES (15);
INSERT INTO #T1 VALUES (16);
INSERT INTO #T2 VALUES (24);
INSERT INTO #T2 VALUES (25);
INSERT INTO #T2 VALUES (26);
SELECT * FROM #T1;
SELECT * FROM #T2;
-- Clean up
DROP TABLE #T1;
DROP TABLE #T2;
Generated identities after the sync
#T1
ID V1
1 11
2 12
3 13
4 14
-1 21
-2 22
-3 23
5 15
6 16
#T2
ID V2
-1 21
-2 22
-3 23
1 11
2 12
3 13
4 14
-4 24
-5 25
-6 26
You can see that new identities in T1 continue to be positive and new identities in T2 continue to be negative.

Big Edit: (Much better) Option 1:
(Additional note: #VladimirBaranov mentioned this in the comments, and I missed it, but here's a fleshing out of how to use a SEQUENCE in this situation anyway)
My original idea is further down this answer, and would still be potentially viable, but I think this newer option will fit the bill exactly for however many servers you need. It was bothering me that I knew there was a right way to do this in TSQL, and I could not remember what it was. My brain finally dredged it up today: SEQUENCE. SQL Server 2012 and 2014 allow you to define a sequence to generate a series of numbers for use in your tables:
CREATE SEQUENCE oddNums
START WITH 1
INCREMENT BY 2;
GO
CREATE SEQUENCE evenNums
START WITH 0
INCREMENT BY 2;
GO
Then instead of AUTO INCREMENTing your PK's, give them a DEFAULT value from the SEQUENCE (these are the tables from the fiddle linked below):
CREATE TABLE oddMirror (
[id] int PRIMARY KEY DEFAULT NEXT VALUE FOR oddNums,
[data] varchar(7)
);
CREATE TABLE evenMirror (
[id] int PRIMARY KEY DEFAULT NEXT VALUE FOR evenNums,
[data] varchar(7)
);
These sequences are totally unaffected by merges, and will continue to generate odd or even numbers forever no matter what the latest PK in the table is.
Here is a SQLFiddle of this in action.
Note that you can't define the column as IDENTITY if you do this (because of the DEFAULT clause), so you will have to be careful about inserting into your id column, but otherwise this should be about as straightforward as it gets.
This could be done with as many servers as you want, just adjust how much each SEQUENCE increments by and where it starts from, but you would have a hard (not impossible) time adding additional servers to the mix once your SEQUENCEs were defined.
Also, here is an MSDN blog that discusses alternative strategies for simulating a SEQUENCE on prior versions of SQL Server.
(Not as good) Option 2:
(Note: this is my original answer) I have been playing with this some this evening, and depending on how you have things set up, I think you could get away with reseeding the table on each server after the sync is done, based on the current highest id in the table. You would just have to do it slightly differently for each server to keep new ids odd on one and even on the other.
So you have:
CREATE TABLE oddMirror
(id INT NOT NULL IDENTITY(1,2),
data NVARCHAR(10))
GO
and
CREATE TABLE evenMirror
(id INT NOT NULL IDENTITY(2,2),
data NVARCHAR(10)
GO
After you sync the two tables, you don't know if the current identity seed is odd or even, so you need to reset it on each table to the correct "next" value for the server. So, on oddMirror:
DECLARE #maxId INT
DECLARE #newSeed INT
SET #maxId = (SELECT MAX(id) FROM oddMirror)
SET #newSeed = (SELECT CASE WHEN #maxId % 2 = 1 THEN #maxId ELSE #maxId -1 END)
DBCC CHECKIDENT('dbo.oddMirror', RESEED, #newSeed)
GO
And an almost identical process on evenMirror:
DECLARE #maxId INT
DECLARE #newSeed INT
SET #maxId = (SELECT MAX(id) FROM evenMirror)
SET #newSeed = (SELECT CASE WHEN #maxId % 2 = 0 THEN #maxId ELSE #maxId -1 END)
DBCC CHECKIDENT('dbo.evenMirror', RESEED, #newSeed)
GO
So basically, on oddMirror we are saying, "Get the current max id. if it's odd, don't change it, but if it's even, back it up by one."
Then do the same thing on 'evenMirror', except check whether the max id is even instead of odd.
So as an example, take this data:
oddMirror
1,"one"
3,"three"
5,"five"
and
evenMirror
2,"two"
4,"four"
6,"six"
8,"eight"
(notice that evenMirror has more rows)
After a sync, each table would look like this:
oddMirror
1,"one"
2,"two"
3,"three"
4,"four"
5,"five"
6,"six"
8,"eight"
--evenMirror looks the same as this now
Running things through the above queries:
MAX(id) on oddMirror is 8. 8 % 2 = 0, so set #newSeed = 8 - 1 = 7, meaning the next row in oddMirror will get id = 9.
MAX(id) on evenMirror is also 8, but the query is slightly different. 8 % x = 0 so set #newSeed = 8, meaning the next row in 'evenMirrorwill getid = 10`
id = 7 will be skipped in this scenario, but I'm guessing that's not a tremendous concern.
If you then queried:
INSERT INTO oddMirror (data) VALUE ("data")
GO
INSERT INTO evenMirror (data) VALUE ("otherData")
GO
Tables would look like this:
oddMirror
1,"one"
2,"two"
3,"three"
4,"four"
5,"five"
6,"six"
8,"eight"
9,"data"
and
evenMirror
1,"one"
2,"two"
3,"three"
4,"four"
5,"five"
6,"six"
8,"eight"
10,"otherData"
This could theoretically be expanded out to accommodate more number of servers by changing what modulo you take and adding additional WHENs to the CASE statement for each possibility, although that would certainly get cumbersome to maintain. But, we already knew that the right solution (GUIDs) is not available here, and if you're reading this far, the next best solution (SEQUENCEs) may not be available, so whatever we come up with is inevitably going to be cumbersome.
The biggest drawback to this approach is that the tables must be locked until the sync process is complete. If a write comes in before sync is complete and ids are reseeded, there will almost certainly be a collision. If the tables are not written to very often, or you are already locking them for syncing, or if you have a significant "dead" spot in your daily cycle (like 3-4 a.m. or something) where this could be done without serious disruption, it may not be that big of a deal, but only you will know how viable this is.
So, your setup may or may not make this possible, but I have been playing with this quite a bit tonight in my sandbox db, and it seems to work well to make sure that new id's are always odd in one db and always even in the other.

An option, have one server set with identity(1,1), and the other set with identity(-1,-1). Identities will not overlap, and copying data from one server to the other would not affect the "next" id or reseeding.
Doesn't work for more than two servers, of course.

You have two source table for inserting to one destination table :
So I suggest you to do these :
[pkId]: Have an identity field in destination table as PK.
[src]: Add an integer -or any other as you want- field and update it from 1st. source data by1and for the 2nd one by2`.
[Id]: You have also a field that comes from sources.
[nId]: Add a bigint field that is null.
Now use this query to have your enumerated Id in nId field:
Update <table>
Set nId = isnull((select count(ti.*) from <table> as ti where ti.pkId < <table>.pkId), 0) + 1
For running this query after any inserts you can use triggers.
I think with this solution you have all data you need.
Edit
some results:
pkId | src | Id | nId before query | nId after query
-----+-----+----+------------------+--------------------
1 | 1 | 1 | null | 1
2 | 2 | 1 | null | 2
3 | 1 | 2 | null | 3
5 | 2 | 2 | null | 4
6 | 2 | 3 | null | 5
8 | 1 | 3 | null | 6

I think that the even/odd approach is making this very hard. Also, as you add rows to each node, you will have page-split issues, especially if your PK is the clustered index.
Are these tables replicated using peer-to-peer replication or are they manually synchronized? The page split issues would come into play if there is replication involved.
Why not use ranges of numbers for each node? 1-X for node 1, and X+1-Y for Node#2? Estimate your row volume and set the ranges so large that overlap won't occur.
Example for BIGINT:
Node 1: 1-200000000000 (200 billion rows)
Node 2: 200000000001-600000000000 (400 billion rows)
Leaving 600000000001 and up for future use. Caveat, the Identity does not have a maximum value, you need to police that manually.
To set the identity value to the correct number use DBCC CHECKIDENT with the RESEED option. This would also work if you're married to the even/odd scenario.
This also have the advantage of not page splitting once per insert, especially if the activity per node is not evenly balanced.

Related

Reset identity seed after in SQL Server in table filled with records

I have a Table called Person, this table is non-empty and it's filled with records ( more than 1000 rows )
How can I reset the identity seed of this table?
Id
PersonName
154
Alice
155
John
The query was executed, but the table still has the identity
use [MyDatabase]
DBCC CHECKIDENT ('dbo.Person', RESEED, 0)
GO
Expected result of the table after executing the previous query :
Id
PersonName
1
Alice
2
John
The problem is your understanding; the code is very likely working exactly as it is supposed to, and as I demonstrated.. RESEED resets the value of the next IDENTITY generated, it doesn't change any of the existing values.
Take SQL similar to what I gave in the comments:
CREATE TABLE dbo.Person (ID int IDENTITY(1,1), AbligatoryColumn char(1));
GO
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT N1.N
FROM N N1, N N2, N N3)
INSERT INTO dbo.Person (AbligatoryColumn)
SELECT 'a'
FROM Tally;
GO
SELECT TOP (5) *
FROM dbo.Person
ORDER BY ID ASC;
If you run this you get the follow results:
ID AbligatoryColumn
----------- ----------------
1 a
2 a
3 a
4 a
5 a
Now, let's RESEED the table, and INSERT another row:
DBCC CHECKIDENT ('dbo.Person', RESEED, 0)
GO
INSERT INTO dbo.Person
DEFAULT VALUES;
GO
SELECT TOP (5) *
FROM dbo.Person
ORDER BY ID ASC;
This gives the following data set:
ID AbligatoryColumn
----------- ----------------
1 a
1 NULL
2 a
3 a
4 a
Notice that there are 2 rows where ID has a value of 1. This is because the new row we inserted has used thenew seed, so the next value generated was 1 (as when you RESEED you are defining the last value used, not the next value to be).
Note that you can't UPDATE the value of an IDENTITY, so if we tried the following you would get an error:
UPDATE dbo.Person
SET ID = ID + 1000
WHERE AbligatoryColumn = 'a';
Cannot update identity column 'ID'.
The real question why do you want to change the value? An IDENTITY is just an arbitrary value, it's value doesn't matter.
If you "must" (and I would suggest you don't need to) you would need to CREATE a new table, INSERT the data from your existing table into it (likely with IDENTITY_INSERT enabled) and then DROP your old table, and rename the new one. If you have any foreign key constraints pointing to your current table, you'll need to DROP all of these, and I really hope you have any data referencing the existing ID values, as you'll then need to update all the foreign key values before you recreate the foreign key constraints. As a result your new table would (albeit likely temporarily) need to have both the old and new PK values in separate columns.
So, in truth, leave it as it is; the value of an IDENTITY literally doesn't matter. It's an arbitrary value and it whether the first value starts at 1, 17, or -167 or if there are numbers "missing" is irrelevant to functionality of what IDENTITY is there to achieve; an always ascending value.

Is there a way to make another value inside ID Primary key? [duplicate]

This question already has answers here:
Reset identity seed after deleting records in SQL Server
(25 answers)
Closed 2 years ago.
i have an primary key who generated itself when i tried to insert some data, but the problem is it will be generated the last one who i deleted before
I wan to make an input when i input the new data the generated id will be 17 not 72 and so on, i'm using SQL SERVER 2008, help :{
...but the problem is it will be generated the last one i deleted before
Do not expose your primary key. It's a bad idea.
Primary keys are internal unique row identifiers. They are not supposed to be sexy or good looking. If you are caring about its value, then it means you somehow want to expose its value to the external world, and that's a really bad idea; if you do this you'll be converting this key into a natural key somewhere else in a different application and that can become an expensive dependency to maintain in the short, medium, and long term.
Solution? If you really care about the format, numbering, and you want to expose the PK, then create a secondary key [column] on the table. This one can be generated automatically, with any value or format you prefer. And you can feel free to expose it to any other application, or even to the end user.
You can set IDENTITY_INSERT ON on the table and insert 17 and corresponding values. Read Identity_insert on MSDN. Also make sure that there should not any conflict occuring in future due to existing identity setup.
In that, you would have to reseed the identity.
You cannot update the identity column. You have to insert it fresh.
Sample code below
create table #table (id int identity(1,1), name varchar(30))
insert into #table
values('A'),('B')
SELECT * FROM #TABLE
SET IDENTITY_INSERT #table ON
insert into #table(id,name) values(8,'C')
SET IDENTITY_INSERT #table OFF
SELECT * FROM #table
+----+------+
| id | name |
+----+------+
| 1 | A |
| 2 | B |
| 8 | C |
+----+------+

AutoIncrement Identity Problem

Using SQL Server.
Table1
RecordNo ID
---------------
2 001
3 002
4 003
....
RecordNo column is the Identity column for Table1
Problem
The RecordNo column sometimes starting from 0, some times starting from 2. How to avoid this problem -- RecordNo Column should always start from 1. It should not start from other numbers.
RecordNo ID
--------------
1 001
2 002
3 003
....
Need query Help
The auto-incremented identity number increments whether the record is ultimately committed or not. It looks to me like possibly the initial insert is failing or is inside a transaction which is not being committed.
The identity column type is meant to be a surrogate identifier, and it's not recommended to use that number for anything else. It is possible to build your own autoincrement functionality, but that's generally a bad idea because of performance and concurrency problems.
Also, it is possible to reseed the identity column back to 1 thusly:
dbcc checkident (table1, reseed, 1)
Edit: I assume you have your table definition set properly with Seed set to 1 and increment set to 1 as well.
You can set the start value when you create the table:
Create Table Table1
(
RecordNo int not null Identity(1,1)
, Id char(3) ...
)
IDENTITY [ (seed ,increment ) ]
If you need to reseed a given table then see DBCC CHECKIDENT.

Getting the next ID without inserting a row

Is it possible in SQL (SQL Server) to retrieve the next ID (integer) from an identity column in a table before, and without actually, inserting a row? This is not necessarily the highest ID plus 1 if the most recent row was deleted.
I ask this because we occassionally have to update a live DB with new rows. The ID of the row is used in our code (e.g. Switch (ID){ Case ID: } and must be the same. If our development DB and live DB get out of sync, it would be nice to predict a row ID in advance before deployment.
I could of course SET IDENTITY OFF SET INSERT_IDENTITY ON or run a transaction (does this roll back the ID?) etc but wondered if there was a function that returned the next ID (without incrementing it).
try IDENT_CURRENT:
Select IDENT_CURRENT('yourtablename')
This works even if you haven't inserted any rows in the current session:
Returns the last identity value generated for a specified table or view. The last identity value generated can be for any session and any scope.
Edit:
After spending a number of hours comparing entire page dumps, I realised there is an easier way and I should of stayed on the DMVs.
The value survives a backup / restore, which is a clear indication that it is stored - I dumped all the pages in the DB and couldn't find the location / alteration for when
a record was added. Comparing 200k line dumps of pages isn't fun.
I had used the dedicated admin console I took a dump of every single internal table exposed inserted a row and then took a further dump of the system tables. Both of the dumps were identical, which indicates that whilst it survived, and therefore must be stored, it is not exposed even at that level.
So after going around in a circle I realised the DMV did have the answer.
create table foo (MyID int identity not null, MyField char(10))
insert into foo values ('test')
go 10
-- Inserted 10 rows
select Convert(varchar(8),increment_value) as IncrementValue,
Convert(varchar(8),last_value) as LastValue
from sys.identity_columns where name ='myid'
-- insert another row
insert into foo values ('test')
-- check the values again
select Convert(varchar(8),increment_value) as IncrementValue,
Convert(varchar(8),last_value) as LastValue
from sys.identity_columns where name ='myid'
-- delete the rows
delete from foo
-- check the DMV again
select Convert(varchar(8),increment_value) as IncrementValue,
Convert(varchar(8),last_value) as LastValue
from sys.identity_columns where name ='myid'
-- value is currently 11 and increment is 1, so the next insert gets 12
insert into foo values ('test')
select * from foo
Result:
MyID MyField
----------- ----------
12 test
(1 row(s) affected)
Just because the rows got removed, the last value was not reset, so the last value + increment should be the right answer.
Also going to write up the episode on my blog.
Oh, and the short cut to it all:
select ident_current('foo') + ident_incr('foo')
So it actually turns out to be easy - but this all assumes no one else has used your ID whilst you got it back. Fine for investigation, but I wouldn't want to use it in code.
This is a little bit strange but it will work:
If you want to know the next value, start by getting the greatest value plus one:
SELECT max(id) FROM yourtable
To make this work, you'll need to reset the identity on insert:
DECLARE #value INTEGER
SELECT #value = max(id) + 1 FROM yourtable
DBCC CHECKIDENT (yourtable, reseed, #value)
INSERT INTO yourtable ...
Not exactly an elegant solution but I haven't had my coffee yet ;-)
(This also assumes that there is nothing done to the table by your process or any other process between the first and second blocks of code).
You can pretty easily determine that the last value used is:
SELECT
last_value
FROM
sys.identity_columns
WHERE
object_id = OBJECT_ID('yourtablename')
Usually, the next ID will be last_value + 1 - but there's no guarantee for that.
Marc
Rather than using an IDENTITY column, you could use a UNIQUEIDENTIFIER (Guid) column as the unique row identifer and insert known values.
The other option (which I use) is SET IDENTITY_INSERT ON, where the row IDs are managed in a source controlled single 'document'.

Swap unique indexed column values in database

I have a database table and one of the fields (not the primary key) is having a unique index on it. Now I want to swap values under this column for two rows. How could this be done? Two hacks I know are:
Delete both rows and re-insert them.
Update rows with some other value
and swap and then update to actual value.
But I don't want to go for these as they do not seem to be the appropriate solution to the problem.
Could anyone help me out?
The magic word is DEFERRABLE here:
DROP TABLE ztable CASCADE;
CREATE TABLE ztable
( id integer NOT NULL PRIMARY KEY
, payload varchar
);
INSERT INTO ztable(id,payload) VALUES (1,'one' ), (2,'two' ), (3,'three' );
SELECT * FROM ztable;
-- This works, because there is no constraint
UPDATE ztable t1
SET payload=t2.payload
FROM ztable t2
WHERE t1.id IN (2,3)
AND t2.id IN (2,3)
AND t1.id <> t2.id
;
SELECT * FROM ztable;
ALTER TABLE ztable ADD CONSTRAINT OMG_WTF UNIQUE (payload)
DEFERRABLE INITIALLY DEFERRED
;
-- This should also work, because the constraint
-- is deferred until "commit time"
UPDATE ztable t1
SET payload=t2.payload
FROM ztable t2
WHERE t1.id IN (2,3)
AND t2.id IN (2,3)
AND t1.id <> t2.id
;
SELECT * FROM ztable;
RESULT:
DROP TABLE
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "ztable_pkey" for table "ztable"
CREATE TABLE
INSERT 0 3
id | payload
----+---------
1 | one
2 | two
3 | three
(3 rows)
UPDATE 2
id | payload
----+---------
1 | one
2 | three
3 | two
(3 rows)
NOTICE: ALTER TABLE / ADD UNIQUE will create implicit index "omg_wtf" for table "ztable"
ALTER TABLE
UPDATE 2
id | payload
----+---------
1 | one
2 | two
3 | three
(3 rows)
I think you should go for solution 2. There is no 'swap' function in any SQL variant I know of.
If you need to do this regularly, I suggest solution 1, depending on how other parts of the software are using this data. You can have locking issues if you're not careful.
But in short: there is no other solution than the ones you provided.
Further to Andy Irving's answer
this worked for me (on SQL Server 2005) in a similar situation
where I have a composite key and I need to swap a field which is part of the unique constraint.
key: pID, LNUM
rec1: 10, 0
rec2: 10, 1
rec3: 10, 2
and I need to swap LNUM so that the result is
key: pID, LNUM
rec1: 10, 1
rec2: 10, 2
rec3: 10, 0
the SQL needed:
UPDATE DOCDATA
SET LNUM = CASE LNUM
WHEN 0 THEN 1
WHEN 1 THEN 2
WHEN 2 THEN 0
END
WHERE (pID = 10)
AND (LNUM IN (0, 1, 2))
There is another approach that works with SQL Server: use a temp table join to it in your UPDATE statement.
The problem is caused by having two rows with the same value at the same time, but if you update both rows at once (to their new, unique values), there is no constraint violation.
Pseudo-code:
-- setup initial data values:
insert into data_table(id, name) values(1, 'A')
insert into data_table(id, name) values(2, 'B')
-- create temp table that matches live table
select top 0 * into #tmp_data_table from data_table
-- insert records to be swapped
insert into #tmp_data_table(id, name) values(1, 'B')
insert into #tmp_data_table(id, name) values(2, 'A')
-- update both rows at once! No index violations!
update data_table set name = #tmp_data_table.name
from data_table join #tmp_data_table on (data_table.id = #tmp_data_table.id)
Thanks to Rich H for this technique.
- Mark
Assuming you know the PK of the two rows you want to update... This works in SQL Server, can't speak for other products. SQL is (supposed to be) atomic at the statement level:
CREATE TABLE testing
(
cola int NOT NULL,
colb CHAR(1) NOT NULL
);
CREATE UNIQUE INDEX UIX_testing_a ON testing(colb);
INSERT INTO testing VALUES (1, 'b');
INSERT INTO testing VALUES (2, 'a');
SELECT * FROM testing;
UPDATE testing
SET colb = CASE cola WHEN 1 THEN 'a'
WHEN 2 THEN 'b'
END
WHERE cola IN (1,2);
SELECT * FROM testing;
so you will go from:
cola colb
------------
1 b
2 a
to:
cola colb
------------
1 a
2 b
I also think that #2 is the best bet, though I would be sure to wrap it in a transaction in case something goes wrong mid-update.
An alternative (since you asked) to updating the Unique Index values with different values would be to update all of the other values in the rows to that of the other row. Doing this means that you could leave the Unique Index values alone, and in the end, you end up with the data that you want. Be careful though, in case some other table references this table in a Foreign Key relationship, that all of the relationships in the DB remain intact.
I have the same problem. Here's my proposed approach in PostgreSQL. In my case, my unique index is a sequence value, defining an explicit user-order on my rows. The user will shuffle rows around in a web-app, then submit the changes.
I'm planning to add a "before" trigger. In that trigger, whenever my unique index value is updated, I will look to see if any other row already holds my new value. If so, I will give them my old value, and effectively steal the value off them.
I'm hoping that PostgreSQL will allow me to do this shuffle in the before trigger.
I'll post back and let you know my mileage.
In SQL Server, the MERGE statement can update rows that would normally break a UNIQUE KEY/INDEX. (Just tested this because I was curious.)
However, you'd have to use a temp table/variable to supply MERGE w/ the necessary rows.
For Oracle there is an option, DEFERRED, but you have to add it to your constraint.
SET CONSTRAINT emp_no_fk_par DEFERRED;
To defer ALL constraints that are deferrable during the entire session, you can use the ALTER SESSION SET constraints=DEFERRED statement.
Source
I usually think of a value that absolutely no index in my table could have. Usually - for unique column values - it's really easy. For example, for values of column 'position' (information about the order of several elements) it's 0.
Then you can copy value A to a variable, update it with value B and then set value B from your variable. Two queries, I know no better solution though.
Oracle has deferred integrity checking which solves exactly this, but it is not available in either SQL Server or MySQL.
1) switch the ids for name
id student
1 Abbot
2 Doris
3 Emerson
4 Green
5 Jeames
For the sample input, the output is:
id student
1 Doris
2 Abbot
3 Green
4 Emerson
5 Jeames
"in case n number of rows how will manage......"