Frågan är gammal, men vi fick en ny fråga från en desperat användare på dba.SE efter att ha försökt tillämpa det som föreslås här. Hitta ett svar med mer information och förklaring där :
Det för närvarande accepterade svaret misslyckas i de flesta fall .
-
Vanligtvis har du en
PRIMARY KEY
ellerUNIQUE
begränsning på ettid
kolumn, som ärNOT DEFERRABLE
som standard. (OP nämnerreferences and constraints
.) Sådana begränsningar kontrolleras efter varje rad, så du får med största sannolikhet unik överträdelse fel vid försök. Detaljer: -
Vanligtvis vill man behålla den ursprungliga ordningen av rader samtidigt som man täpper till luckor. Men ordningen i vilken rader uppdateras är godtycklig , vilket leder till godtyckliga siffror. Det visade exemplet verkar behålla den ursprungliga sekvensen eftersom fysisk lagring fortfarande sammanfaller med den önskade ordningen (infogade rader i önskad ordning bara ett ögonblick tidigare), vilket nästan aldrig är fallet i verkliga tillämpningar och helt opålitligt.
Saken är mer komplicerad än den kan tyckas först. En lösning (bland annat) om du har råd att ta bort PK/UNIKA-begränsningen (och relaterade FK-begränsningar) tillfälligt:
BEGIN;
LOCK tbl;
-- remove all FK constraints to the column
ALTER TABLE tbl DROP CONSTRAINT tbl_pkey; -- remove PK
-- for the simple case without FK references - or see below:
UPDATE tbl t -- intermediate unique violations are ignored now
SET id = t1.new_id
FROM (SELECT id, row_number() OVER (ORDER BY id) AS new_id FROM tbl) t1
WHERE t.id = t1.id;
-- Update referencing value in FK columns at the same time (if any)
SELECT setval('tbl_id_seq', max(id)) FROM tbl; -- reset sequence
ALTER TABLE tbl ADD CONSTRAINT tbl_pkey PRIMARY KEY(id); -- add PK back
-- add all FK constraints to the column back
COMMIT;
Detta är också mycket snabbare för stora tabeller, eftersom att kontrollera PK (och FK) begränsning(ar) för varje rad kostar mycket mer än att ta bort begränsningen(erna) och lägga till den (dem) tillbaka.
Om det finns FK-kolumner i andra tabeller som hänvisar till tbl.id
, använd datamodifierande CTE:er för att uppdatera dem alla.
Exempel på en tabell fk_tbl
och en FK-kolumn fk_id
:
WITH u1 AS (
UPDATE tbl t
SET id = t1.new_id
FROM (SELECT id, row_number() OVER (ORDER BY id) AS new_id FROM tbl) t1
WHERE t.id = t1.id
RETURNING t.id, t1.new_id -- return old and new ID
)
UPDATE fk_tbl f
SET fk_id = u1.new_id -- set to new ID
FROM u1
WHERE f.fk_id = u1.id; -- match on old ID
Mer i referat svar på dba.SE .