Обычная вставка
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 параметров! Для сравнения:
Есть много других способов передать несколько строк ...
Связанный: