views:

72

answers:

4

I have a table regionkey:

areaid  -- primary key, int
region  -- char(4)
locale  -- char(4)

The entire rest of the database is foreign-keyed to areaid. In this table there is an index on (region, locale) with a unique constraint.

The problem is that I have two records:

101   MICH   DETR
102   ILLI   CHIC

And I need to swap the (region,locale) fields between them, so that I wind up with:

101   ILLI   CHIC
102   MICH   DETR

The naive approach won't work because it violates the unique index on region and locale:

update regionkey
     set region='ILLI', locale='CHIC' where areaid = 101; -- FAILS
update regionkey
     set region='MICH', locale='DETR' where areaid = 102;

How can I do this? Is there an atomic way to make the swap? Suggestions?

+1  A: 

BEst bet is to make three updates. Update the first record to a temp set of values, update the second record and then reupdate the first record to the values you want.

HLGEM
+5  A: 

You can't defer constraint checks in SQL Server over multiple statements (unless you DISABLE) so you have to avoid the conflict or do it in one statement

update
    regionkey 
set
    region= CASE areaid WHEN 101 THEN 'ILLI' ELSE 'MICH' END, 
    locale= CASE areaid WHEN 101 THEN 'CHIC' ELSE 'DETR' END
where
    areaid IN (101, 102);

or, more conventionally (in a transaction this one)

update regionkey 
     set region='AAAA', locale='BBBB' where areaid = 101;
update regionkey 
     set region='MICH', locale='DETR' where areaid = 102;
update regionkey 
     set region='ILLI', locale='CHIC' where areaid = 101;

Edit: Why not swap keys not values? It usually achieves the sane result unless areaid has some meaning

update
    regionkey 
set
    areaid = 203 - areaid 
where
    areaid IN (101, 102);
gbn
This may just be crazy enough to work. Do you think he should wrap it in a transaction?
Broam
a single insert is a single implicit transaction anyway
gbn
Just a bit quicker than I, @gbn ;-)
KG
The first gives a syntax error near the first comma. I considered the second option, but since there might be multiple occurances of this going on at the same time I can't rely on "AAAA" and "BBBB" being unique without a boatload of hackery.
clintp
@clintp: I forgot END on each CASE statement, a common mistake I make...
gbn
The first solution (with the "END") works great. You beat me to the edit by seconds.
clintp
@gbn: There's a lot more going on in these records than I let on. Thanks for the answers.
clintp
A: 

Have you tried the simple act of wrapping it in a transaction?

I understand you can set up constraints to allow it to only enforce the constraint at the end of a transaction but I am unsure if your constraints are set up that way.

Broam
There is no "defer constraint" in SQL Server, unless you explicitly use DISABLE which is a DDL statement
gbn
I am enlightened, thank you.
Broam
A: 

One suggestion, which may not be the safest for large record sets, would be to set both records to ' ' for both region & locale, and then execute two update statements, one for each record, like so:

UPDATE
    regionkey
SET
   region = '    ',
   locale = '    '
WHERE
    areaid in (101,102)

UPDATE
    regionkey
SET
    region = 'ILLI',
    locale = 'CHIC'
WHERE
    areaid = 101

UPDATE
    regionkey
SET
    region = 'MICH',
    locale = 'DETR'
WHERE
    areaid = 102

Like I said, this is probably not the safest way to go, but for a small data set it should be OK.

UPDATE: Larry correctly pointed out that the first UPDATE statement will violate the UNIQUE constraint. Use this instead for the first UPDATE:

UPDATE
    regionkey
SET
    region = areaid,
    locale = areaid
WHERE
    areaid in (101,102)

This way each intermediate region and locale is (or should be) unique.

KG
Same problem — the initial UPDATE will violate UNIQUEness on region, locale.
Larry Lustig
You're RIGHT!!! I've updated my answer to reflect this. Good catch, @Larry!
KG