简体   繁体   中英

Incrementing with one query a set of values in a field with UNIQUE constraint, Postgres

I have a table in which I have a numeric field A , which is set to be UNIQUE . This field is used to indicate an order in which some action has to be performed. I want to make an UPDATE of all the values that are greater, for example, than 3 . For example, I have

A     
1
2
3
4
5

Now, I want to add 1 to all values of A greater than 3 . So, the result would be

A     
1
2
3
5
6

The question is, whether it is possible to be done using only one query? Remember that I have a UNIQUE constraint on the column A .

Obviously, I tried

UPDATE my_table SET A = A + 1 WHERE A > 3;

but it did not work as I have the constraint on this field.

PostgreSQL 9.0 and later

PostgreSQL 9.0 added deferrable unique constraints , which is exactly the feature you seem to need. This way, uniqueness is checked at commit-time rather than update-time.

Create the UNIQUE constraint with the DEFERRABLE keyword:

ALTER TABLE foo ADD CONSTRAINT foo_uniq (foo_id) DEFERRABLE;

Later, before running the UPDATE statement, you run in the same transaction:

SET CONSTRAINTS foo_uniq DEFERRED;

Alternatively you can create the constraint with the INITIALLY DEFERRED keyword on the unique constraint itself -- so you don't have to run SET CONSTRAINTS -- but this might affect the performance of your other queries which don't need to defer the constraint.

PostgreSQL 8.4 and older

If you only want to use the unique constraint for guaranteeing uniqueness -- not as a target for a foreign key -- then this workaround might help:

First, add a boolean column such as is_temporary to the table that temporarily distinguishes updated and non-updated rows:

CREATE TABLE foo (value int not null, is_temporary bool not null default false);

Next create a partial unique index that only affects rows where is_temporary=false:

CREATE UNIQUE INDEX ON foo (value) WHERE is_temporary=false;

Now, every time do make the updates you described, you run them in two steps:

UPDATE foo SET is_temporary=true, value=value+1 WHERE value>3;
UPDATE foo SET is_temporary=false WHERE is_temporary=true;

As long as these statements occur in a single transaction, this will be totally safe -- other sessions will never see the temporary rows. The downside is that you'll be writing the rows twice.

Do note that this is merely a unique index, not a constraint , but in practice it shouldn't matter.

You can do it in 2 queries with a simple trick :

First, update your column with +1, but add a with ax(-1) factor :

update my_table set A=(A+1)*-1  where A > 3.

You will swtich from 4,5,6 to -5,-6,-7

Second, convert back the operation to restore positive :

update my_table set A=(A)*-1  where A < 0.

You will have : 5,6,7

You can do this with a loop. I don't like this solution, but it works:

CREATE TABLE update_unique (id INT NOT NULL);
CREATE UNIQUE INDEX ux_id ON update_unique (id);
INSERT INTO update_unique(id) SELECT a FROM generate_series(1,100) AS foo(a);

DO $$
DECLARE v INT;
BEGIN
  FOR v IN SELECT id FROM update_unique WHERE id > 3 ORDER BY id DESC 
  LOOP
    UPDATE update_unique SET id = id + 1 WHERE id = v;
  END LOOP;
END;
$$ LANGUAGE 'plpgsql';

In PostgreSQL 8.4, you might have to create a function to do this since you can't arbitrarily run PL/PGSQL from a prompt using DO (at least not to the best of my recollection).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM