В таблице users
должен быть некоторый первичный ключ , который вы не раскрыли.Для целей этого ответа я назову его users_id
.
Вы можете решить эту проблему довольно элегантно с помощью CTE, модифицирующих данные , представленных в PostgreSQL 9.1:
Если мы можем предположить, что country
уникален , вся операция довольно тривиальна:
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;
Вы упомянули версию 8.3 в вашем вопросе.Если вы не нашли время для обновления, вы можете рассмотреть возможность обновления. Конец жизни скоро наступит для 8.3.
Как бы то ни было, это достаточно просто с версией 8.3.Вам просто нужно два утверждения:
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
не уникален , он становится более сложным.Вы могли бы просто создать один адрес и ссылку на него несколько раз.Но вы упомянули соотношение 1: 1, которое исключает такое удобное решение.
Для версии 9.1 :
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;
Поскольку невозможно однозначно назначить ровно одну id
, возвращаемую из INSERT
каждому пользователю в наборес идентичным country
я использую оконную функцию row_number()
, чтобы сделать их уникальными.
Не так просто с версией 8.3 .Один из возможных способов:
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
Повторяйте это , пока последнее значение NULL
не изменится с users.address_id
.