sql >> Base de Datos >  >> RDS >> PostgreSQL

Insertar datos y establecer claves foráneas con Postgres

La tabla users debe tener alguna clave principal que no revelaste. A los efectos de esta respuesta, lo nombraré users_id .

Puede resolver esto de manera bastante elegante con CTE de modificación de datos introducido con PostgreSQL 9.1 :

country es único

Toda la operación es bastante trivial en este 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;

Mencionas la versión 8.3 en tu pregunta ¡Mejora! Postgres 8.3 ha llegado al final de su vida útil.

Sea como fuere, esto es bastante simple con la versión 8.3. Solo necesita dos declaraciones:

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 no es único

Eso es más desafiante. podrías simplemente cree una dirección y vincúlela varias veces. Pero mencionaste una relación 1:1 que descarta una solución tan 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 no hay forma de asignar sin ambigüedades exactamente un id devuelto desde el INSERT a cada usuario en un conjunto con idéntico country , uso la función de ventana row_number() para hacerlos únicos.

No es tan sencillo con Postgres 8.3 . Una forma posible:

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 esto hasta el último NULL el valor se ha ido de users.address_id .