MW211 EXIT

devlog
PostgreSQL/一意性制約列の値振り直し
2012年12月21日
一意制約およびNOT NULL制約がある列に値が入っているとして、
その値を全体的に再編成したい場合がある。

でも、一つ一つ更新していたら、昔の値と重複エラーとなってしまったりして
めんどくさい。
NOT NULL制約がなければ一旦NULLにするって方法があるのだが。

んなわけで、一旦、ありえない値に更新してから、新しい値を更新する方法を考える。
例えば「1~99」の値を再編成する場合、
一旦「100~」とかの値に振り直してから、再度「1~99」の値を更新すれば、
昔の値に影響されることなく(重複エラーが発生することなく)
更新ができるというわけだ。

まず考えたのは、最大値に+1していけばいいんじゃないかという案。
┌──────────────────────────────────────┐
│UPDATE 表                                                                   │
│   SET 列 = (SELECT MAX(列) FROM 表) + 1;                                   │
└──────────────────────────────────────┘
しかし、これは都度最大値を計算してくれるわけではなく、
最初の一回しか計算してくれないから例えば全件「100」とかになって
うまくいかない(更新できない)。

これを実現するにはこんなSQL文となる。
┌──────────────────────────────────────┐
│WITH 一時表(キー,                                                           │
│            列) AS (                                                        │
│         SELECT キー,                                                       │
│                (SELECT max(列) FROM 表) + 100                  --最大+100  │
│              + row_number() OVER()                             --連番になる│
│             FROM 表                                                        │
│     )                                                                      │
│UPDATE 表                                                                   │
│    SET 列 = 一時表.列                                                      │
│    FROM 一時表                                                             │
│    WHERE 一時表.キー = 表.キー;                                            │
└──────────────────────────────────────┘
これなら確実だけど、やや複雑だ。

いっそのこと、乱数に頼った方がシンプルかも。
┌──────────────────────────────────────┐
│UPDATE 表                                                                   │
│   SET 列 = 100 + ROUND((RANDOM() * 1000)::numeric, 0);                     │
└──────────────────────────────────────┘
稀に、重複するかもしれないが、その場合は、再度実行するか、
RANDOM()に掛ける値を広げるとよい。
分類:PostgreSQL