PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Insira dados e defina chaves estrangeiras com o Postgres


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 .