INSERTAR llano
INSERT INTO bar (description, foo_id)
SELECT val.description, f.id
FROM (
VALUES
(text 'testing', text 'blue') -- explicit type declaration; see below
, ('another row', 'red' )
, ('new row1' , 'purple') -- purple does not exist in foo, yet
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type);
El uso de a en LEFT [OUTER] JOIN
lugar de [INNER] JOIN
significa que las filas de val
no se descartan cuando no se encuentra ninguna coincidencia foo
. En cambio, NULL
se ingresa para foo_id
.
La VALUES
expresión en la subconsulta hace lo mismo que el CTE de @ ypercube . Las expresiones de tabla comunes ofrecen características adicionales y son más fáciles de leer en grandes consultas, pero también se presentan como barreras de optimización. Por lo tanto, las subconsultas suelen ser un poco más rápidas cuando no se necesita ninguna de las anteriores.
id
como nombre de columna es un antipatrón generalizado. En caso de ser foo_id
y bar_id
descriptiva o nada. Al unir un montón de tablas, terminas con múltiples columnas todas llamadas id
...
Considere simple text
o en varchar
lugar de varchar(n)
. Si realmente necesita imponer una restricción de longitud, agregue una CHECK
restricción:
Es posible que deba agregar conversiones de tipo explícito. Dado que la VALUES
expresión no se adjunta directamente a una tabla (como en INSERT ... VALUES ...
), los tipos no se pueden derivar y los tipos de datos predeterminados se usan sin una declaración de tipo explícita, que puede no funcionar en todos los casos. Es suficiente hacerlo en la primera fila, el resto se alineará.
INSERTAR filas FK faltantes al mismo tiempo
Si desea crear entradas inexistentes foo
sobre la marcha, en una sola instrucción SQL , los CTE son instrumentales:
WITH sel AS (
SELECT val.description, val.type, f.id AS foo_id
FROM (
VALUES
(text 'testing', text 'blue')
, ('another row', 'red' )
, ('new row1' , 'purple')
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type)
)
, ins AS (
INSERT INTO foo (type)
SELECT DISTINCT type FROM sel WHERE foo_id IS NULL
RETURNING id AS foo_id, type
)
INSERT INTO bar (description, foo_id)
SELECT sel.description, COALESCE(sel.foo_id, ins.foo_id)
FROM sel
LEFT JOIN ins USING (type);
Tenga en cuenta las dos nuevas filas ficticias para insertar. Ambos son de color púrpura , que aún no existe foo
. Dos filas para ilustrar la necesidad DISTINCT
en la primera INSERT
declaración.
Explicación paso a paso
El primer CTE sel
proporciona múltiples filas de datos de entrada. La subconsulta val
con la VALUES
expresión se puede reemplazar con una tabla o subconsulta como fuente. Inmediatamente LEFT JOIN
para foo
agregar foo_id
las type
filas preexistentes . Todas las demás filas se ponen de foo_id IS NULL
esta manera.
El segundo CTE ins
inserta nuevos tipos distintos ( foo_id IS NULL
) foo
y devuelve el recién generado foo_id
, junto con el type
para volver a unir e insertar filas.
El exterior final INSERT
ahora puede insertar un foo.id para cada fila: ya sea el tipo preexistente o se insertó en el paso 2.
Estrictamente hablando, ambas inserciones ocurren "en paralelo", pero dado que esta es una declaración única , las FOREIGN KEY
restricciones predeterminadas no se quejarán. La integridad referencial se aplica al final de la declaración por defecto.
SQL Fiddle para Postgres 9.3. (Funciona igual en 9.1.)
Hay una pequeña condición de carrera si ejecuta varias de estas consultas al mismo tiempo. Lea más en preguntas relacionadas aquí y aquí y aquí . Realmente solo ocurre bajo una gran carga concurrente, si alguna vez. En comparación con las soluciones de almacenamiento en caché como se anuncia en otra respuesta, la posibilidad es muy pequeña .
Función para uso repetido
Para uso repetido, crearía una función SQL que toma una matriz de registros como parámetro y la usa unnest(param)
en lugar de la VALUES
expresión.
O, si la sintaxis para las matrices de registros es demasiado complicada para usted, use una cadena separada por comas como parámetro _param
. Por ejemplo de la forma:
'description1,type1;description2,type2;description3,type3'
Luego use esto para reemplazar la VALUES
expresión en la declaración anterior:
SELECT split_part(x, ',', 1) AS description
split_part(x, ',', 2) AS type
FROM unnest(string_to_array(_param, ';')) x;
Función con UPSERT en Postgres 9.5
Cree un tipo de fila personalizado para pasar parámetros. Podríamos prescindir de él, pero es más simple:
CREATE TYPE foobar AS (description text, type text);
Función:
CREATE OR REPLACE FUNCTION f_insert_foobar(VARIADIC _val foobar[])
RETURNS void AS
$func$
WITH val AS (SELECT * FROM unnest(_val)) -- well-known row type
, ins AS (
INSERT INTO foo AS f (type)
SELECT DISTINCT v.type -- DISTINCT!
FROM val v
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
RETURNING f.type, f.id
)
INSERT INTO bar AS b (description, foo_id)
SELECT v.description, COALESCE(f.id, i.id) -- assuming most types pre-exist
FROM val v
LEFT JOIN foo f USING (type) -- already existed
LEFT JOIN ins i USING (type) -- newly inserted
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
$func$ LANGUAGE sql;
Llamada:
SELECT f_insert_foobar(
'(testing,blue)'
, '(another row,red)'
, '(new row1,purple)'
, '(new row2,purple)'
, '("with,comma",green)' -- added to demonstrate row syntax
);
Rápido y sólido como una roca para entornos con transacciones concurrentes.
Además de las consultas anteriores, esto ...
... aplica SELECT
o INSERT
activa foo
: type
se inserta todo lo que no existe en la tabla FK. Asumiendo que la mayoría de los tipos preexisten. Para estar absolutamente seguro y descartar las condiciones de carrera, las filas existentes que necesitamos están bloqueadas (para que las transacciones concurrentes no puedan interferir). Si eso es demasiado paranoico para su caso, puede reemplazar:
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
con
ON CONFLICT(type) DO NOTHING
... aplica INSERT
o UPDATE
(verdadero "UPSERT") en bar
: Si el description
ya existe, type
se actualiza:
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
Pero solo si type
realmente cambia:
... pasa valores como tipos de fila bien conocidos con un VARIADIC
parámetro. ¡Tenga en cuenta el máximo predeterminado de 100 parámetros! Comparar:
Hay muchas otras formas de pasar varias filas ...
Relacionado: