Обычная вставка
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);
Использование LEFT [OUTER] JOINвместо [INNER] JOINозначает, что строки из val не удаляются, когда не найдено совпадений foo. Вместо этого NULLвводится для foo_id.
VALUESВыражение подзапроса делает то же самое , как @ ypercube в КТР. Стандартные табличные выражения предлагают дополнительные функции и их легче читать в больших запросах, но они также представляют собой барьеры для оптимизации. Таким образом, подзапросы обычно немного быстрее, когда ничего из вышеперечисленного не требуется.
idпоскольку имя столбца является широко распространенным анти-паттерном. Должно быть foo_idи / bar_idили что-нибудь описательное. При объединении нескольких таблиц вы получаете несколько столбцов с именами id...
Считайте простым textили varcharвместо varchar(n). Если вам действительно нужно наложить ограничение по длине, добавьте CHECKограничение:
Возможно, вам придется добавить явные приведения типов. Поскольку VALUESвыражение напрямую не связано с таблицей (как в INSERT ... VALUES ...), типы не могут быть получены, и типы данных по умолчанию используются без явного объявления типа, которое может работать не во всех случаях. Достаточно сделать это в первом ряду, остальные встанут в очередь.
Вставить пропущенные строки FK одновременно
Если вы хотите создать несуществующие записи fooна лету, в одном операторе SQL CTE являются полезными:
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);
Обратите внимание на две новые фиктивные строки для вставки. Оба пурпурные , которых пока не существует foo. Две строки, чтобы проиллюстрировать необходимость DISTINCTв первом INSERTутверждении.
Пошаговое объяснение
1-й CTE selпредоставляет несколько строк входных данных. Подзапрос valс VALUESвыражением может быть заменен таблицей или подзапросом в качестве источника. Сразу LEFT JOINчтобы fooдобавить foo_idдля уже существующих typeстрок. Все остальные ряды получают foo_id IS NULLэтот путь.
2-й CTE insвставляет различные новые типы ( foo_id IS NULL) в fooи возвращает вновь сгенерированный foo_id- вместе с typeприсоединяемым обратно для вставки строк.
Последний внешний INSERTэлемент теперь может вставлять foo.id для каждой строки: либо ранее существовавший тип, либо он был вставлен на шаге 2.
Строго говоря, обе вставки происходят «параллельно», но, поскольку это один оператор, FOREIGN KEYограничения по умолчанию не будут жаловаться. Ссылочная целостность применяется в конце оператора по умолчанию.
SQL Fiddle для Postgres 9.3. (Работает так же в 9.1.)
Если вы выполняете несколько запросов одновременно, возникает крошечное условие гонки . Читайте больше в связанных вопросах здесь и здесь и здесь . На самом деле происходит только при большой параллельной нагрузке, если вообще когда-либо. По сравнению с решениями для кеширования, такими как рекламируемые в другом ответе, вероятность очень мала .
Функция для многократного использования
Для повторного использования я бы создал функцию SQL, которая принимает массив записей в качестве параметра и использует unnest(param)вместо VALUESвыражения.
Или, если синтаксис для массивов записей слишком запутан для вас, используйте разделенную запятыми строку в качестве параметра _param. Например, формы:
'description1,type1;description2,type2;description3,type3'
Затем используйте это, чтобы заменить VALUESвыражение в приведенном выше утверждении:
SELECT split_part(x, ',', 1) AS description
split_part(x, ',', 2) AS type
FROM unnest(string_to_array(_param, ';')) x;
Функция с UPSERT в Postgres 9,5
Создайте пользовательский тип строки для передачи параметров. Мы могли бы обойтись без этого, но это проще:
CREATE TYPE foobar AS (description text, type text);
Функция:
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;
Вызов:
SELECT f_insert_foobar(
'(testing,blue)'
, '(another row,red)'
, '(new row1,purple)'
, '(new row2,purple)'
, '("with,comma",green)' -- added to demonstrate row syntax
);
Быстрый и надежный для сред с параллельными транзакциями.
В дополнение к запросам выше, это ...
... применяется SELECTили INSERTна foo: Любой , typeкоторый не существует в таблице FK, но, вставляется. Предполагая, что большинство типов уже существуют. Чтобы быть абсолютно уверенными и исключить условия гонки, существующие строки, которые нам нужны, заблокированы (чтобы параллельные транзакции не могли вмешиваться). Если это слишком параноидально для вашего случая, вы можете заменить:
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
с участием
ON CONFLICT(type) DO NOTHING
... применяется INSERTили UPDATE(истинно "UPSERT") на bar: если descriptionуже существует, typeоно обновляется:
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
Но только если typeдействительно изменится:
... передает значения как известные типы строк с VARIADICпараметром. Обратите внимание, что по умолчанию максимум 100 параметров! Для сравнения:
Есть много других способов передать несколько строк ...
Связанный: