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] JOINlugar de [INNER] JOINsignifica que las filas de val no se descartan cuando no se encuentra ninguna coincidencia foo. En cambio, NULLse ingresa para foo_id.
La VALUESexpresió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.
idcomo nombre de columna es un antipatrón generalizado. En caso de ser foo_idy bar_iddescriptiva o nada. Al unir un montón de tablas, terminas con múltiples columnas todas llamadas id...
Considere simple texto en varcharlugar de varchar(n). Si realmente necesita imponer una restricción de longitud, agregue una CHECKrestricción:
Es posible que deba agregar conversiones de tipo explícito. Dado que la VALUESexpresió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 foosobre 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 DISTINCTen la primera INSERTdeclaración.
Explicación paso a paso
El primer CTE selproporciona múltiples filas de datos de entrada. La subconsulta valcon la VALUESexpresión se puede reemplazar con una tabla o subconsulta como fuente. Inmediatamente LEFT JOINpara fooagregar foo_idlas typefilas preexistentes . Todas las demás filas se ponen de foo_id IS NULLesta manera.
El segundo CTE insinserta nuevos tipos distintos ( foo_id IS NULL) fooy devuelve el recién generado foo_id, junto con el typepara volver a unir e insertar filas.
El exterior final INSERTahora 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 KEYrestricciones 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 VALUESexpresió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 VALUESexpresió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 SELECTo INSERTactiva foo: typese 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 INSERTo UPDATE(verdadero "UPSERT") en bar: Si el descriptionya existe, typese 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 typerealmente cambia:
... pasa valores como tipos de fila bien conocidos con un VARIADICparámetro. ¡Tenga en cuenta el máximo predeterminado de 100 parámetros! Comparar:
Hay muchas otras formas de pasar varias filas ...
Relacionado: