A tabela
users
deve ter alguma chave primária que você não divulgou. Para os propósitos desta resposta, vou chamá-la de users_id
. Você pode resolver isso de maneira elegante com CTEs de modificação de dados introduzido com PostgreSQL 9.1 :
country
é único
Toda a operação é bastante trivial neste caso:
WITH i AS (
INSERT INTO addresses (country)
SELECT country
FROM users
WHERE address_id IS NULL
RETURNING id, country
)
UPDATE users u
SET address_id = i.id
FROM i
WHERE i.country = u.country;
Você menciona a versão 8.3 na sua pergunta. Melhoria! O Postgres 8.3 chegou ao fim da vida útil.
Seja como for, isso é bastante simples com a versão 8.3. Você só precisa de duas declarações:
INSERT INTO addresses (country)
SELECT country
FROM users
WHERE address_id IS NULL;
UPDATE users u
SET address_id = a.id
FROM addresses a
WHERE address_id IS NULL
AND a.country = u.country;
country
não é único
Isso é mais desafiador. Você poderia basta criar um endereço e vinculá-lo várias vezes. Mas você mencionou uma relação 1:1 que exclui uma solução tão conveniente.
WITH s AS (
SELECT users_id, country
, row_number() OVER (PARTITION BY country) AS rn
FROM users
WHERE address_id IS NULL
)
, i AS (
INSERT INTO addresses (country)
SELECT country
FROM s
RETURNING id, country
)
, r AS (
SELECT *
, row_number() OVER (PARTITION BY country) AS rn
FROM i
)
UPDATE users u
SET address_id = r.id
FROM r
JOIN s USING (country, rn) -- select exactly one id for every user
WHERE u.users_id = s.users_id
AND u.address_id IS NULL;
Como não há como atribuir inequivocamente exatamente um
id
retornado do INSERT
para cada usuário em um conjunto com country
idêntico , eu uso a função de janela row_number()
para torná-los únicos. Não é tão simples com o Postgres 8.3 . Uma forma possível:
INSERT INTO addresses (country)
SELECT DISTINCT country -- pick just one per set of dupes
FROM users
WHERE address_id IS NULL;
UPDATE users u
SET address_id = a.id
FROM addresses a
WHERE a.country = u.country
AND u.address_id IS NULL
AND NOT EXISTS (
SELECT * FROM addresses b
WHERE b.country = a.country
AND b.users_id < a.users_id
); -- effectively picking the smallest users_id per set of dupes
Repita isso até o último
NULL
o valor desapareceu de users.address_id
.