Простий ВСТУП
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
таким чином.
Другий КТР 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 параметрів! Порівняйте:
Існує багато інших способів проходження декількох рядків ...
Пов'язані: