Вставляйте дані в 3 таблиці за раз за допомогою Postgres


83

Я хочу вставити дані в 3 таблиці за допомогою одного запиту.
Мої таблиці виглядають так:

CREATE TABLE sample (
   id        bigserial PRIMARY KEY,
   lastname  varchar(20),
   firstname varchar(20)
);

CREATE TABLE sample1(
   user_id    bigserial PRIMARY KEY,
   sample_id  bigint REFERENCES sample,
   adddetails varchar(20)
);

CREATE TABLE sample2(
   id      bigserial PRIMARY KEY,
   user_id bigint REFERENCES sample1,
   value   varchar(10)
);

Я отримаю ключ взамін за кожну вставку, і мені потрібно вставити цей ключ у наступну таблицю.
Мій запит:

insert into sample(firstname,lastname) values('fai55','shaggk') RETURNING id;
insert into sample1(sample_id, adddetails) values($id,'ss') RETURNING user_id;
insert into sample2(user_id, value) values($id,'ss') RETURNING id;

Але якщо я запускаю одиничні запити, вони просто повертають мені значення, і я не можу негайно повторно використовувати їх у наступному запиті.

Як цього досягти?

Відповіді:


135

Використовуйте CTE, що змінюють дані :

WITH ins1 AS (
   INSERT INTO sample(firstname, lastname)
   VALUES ('fai55', 'shaggk')
-- ON     CONFLICT DO NOTHING         -- optional addition in Postgres 9.5+
   RETURNING id AS sample_id
   )
, ins2 AS (
   INSERT INTO sample1 (sample_id, adddetails)
   SELECT sample_id, 'ss' FROM ins1
   RETURNING user_id
   )
INSERT INTO sample2 (user_id, value)
SELECT user_id, 'ss2' FROM ins2;

Кожен INSERTзалежить від попереднього. SELECTзамість того, VALUESщоб переконатися, що нічого не вставлено в допоміжні таблиці, якщо жоден рядок не повертається з попереднього INSERT. (Оскільки Postgres 9.5+ ви можете додати ON CONFLICT.)
Це також трохи коротше і швидше.

Як правило, зручніше надавати повні рядки даних в одному місці :

WITH data(firstname, lastname, adddetails, value) AS (
   VALUES                              -- provide data here
      ('fai55', 'shaggk', 'ss', 'ss2') -- see below
    , ('fai56', 'XXaggk', 'xx', 'xx2') -- works for multiple input rows
       --  more?                      
   )
, ins1 AS (
   INSERT INTO sample (firstname, lastname)
   SELECT firstname, lastname          -- DISTINCT? see below
   FROM   data
   -- ON     CONFLICT DO NOTHING       -- UNIQUE constraint? see below
   RETURNING firstname, lastname, id AS sample_id
   )
, ins2 AS (
   INSERT INTO sample1 (sample_id, adddetails)
   SELECT ins1.sample_id, d.adddetails
   FROM   data d
   JOIN   ins1 USING (firstname, lastname)
   RETURNING sample_id, user_id
   )
INSERT INTO sample2 (user_id, value)
SELECT ins2.user_id, d.value
FROM   data d
JOIN   ins1 USING (firstname, lastname)
JOIN   ins2 USING (sample_id);

db <> скрипка тут

Можливо, вам знадобляться явні прив'язки типів в автономному VALUESвиразі - на відміну від VALUESвиразу, прикріпленого до, INSERTде типи даних походять з цільової таблиці. Подивитися:

Якщо кілька рядків можуть мати ідентичні (firstname, lastname), можливо, вам доведеться скласти дублікати для першого INSERT:

...
INSERT INTO sample (firstname, lastname)
SELECT DISTINCT firstname, lastname FROM data
...

Ви можете використовувати (тимчасову) таблицю як джерело даних замість CTE data.

Можливо, було б сенсом поєднати це з обмеженням UNIQUE, що входить (firstname, lastname)у таблицю, і ON CONFLICTреченням у запиті.

Пов’язані:


1
дякую за повторне відтворення, чи можу я додати транзакцію, яка розгортається, якщо трапляється будь-яка помилка. так, як я можу
Faisal

3
Це єдиний оператор SQL. Можна об’єднати кілька виписок в одну транзакцію, але не можна розділити цю. Також те, що говорить Денис у своєму коментарі. І я додав кілька посилань до своєї відповіді.
Ервін Брандштеттер

2
@mmcrae: Так, ти можеш. Пов’язані: dba.stackexchange.com/questions/151199/…
Ервін

1
@No_name: звичайно, різними способами. Я пропоную вам задати питання із зазначенням деталей. Ви завжди можете посилатися тут для контексту. або залиште тут коментар із посиланням назад, щоб привернути мою увагу.
Ервін Брандштеттер

1
Це друкарська помилка? У вашій відповіді INSERT INTO sample1 (user_id, adddetails)не повинно бути (sample_id, addetails)?
Адам Хьюз,

19

Щось на зразок цього

with first_insert as (
   insert into sample(firstname,lastname) 
   values('fai55','shaggk') 
   RETURNING id
), 
second_insert as (
  insert into sample1( id ,adddetails) 
  values
  ( (select id from first_insert), 'ss')
  RETURNING user_id
)
insert into sample2 ( id ,adddetails) 
values 
( (select user_id from first_insert), 'ss');

Оскільки згенерований ідентифікатор із вставки sample2не потрібен, я видалив returningпункт із останньої вставки.


Мені подобається такий підхід із обраними внутрішніми значеннями. Це більш послідовно, а також може скинути псевдоніми повернення всередині операторів with
mattdlockyer

6

Як правило, ви використовуєте транзакцію, щоб уникнути написання складних запитів.

http://www.postgresql.org/docs/current/static/sql-begin.html

http://dev.mysql.com/doc/refman/5.7/en/commit.html

Ви також можете використовувати CTE, вважаючи, що ваш тег Postgres правильний. Наприклад:

with sample_ids as (
  insert into sample(firstname, lastname)
  values('fai55','shaggk')
  RETURNING id
), sample1_ids as (
  insert into sample1(id, adddetails)
  select id,'ss'
  from sample_ids
  RETURNING id, user_id
)
insert into sample2(id, user_id, value)
select id, user_id, 'val'
from sample1_ids
RETURNING id, user_id;

1
дякую, як би я досяг транзакції в цьому запиті, якщо будь-яка вставка не вдалася, я міг зробити відкат
Faisal

1
Потім ви починаєте все спочатку, звичайно, після виправлення запитів, оскільки вся транзакція (або cte) повернеться назад. До речі, якщо ваші вставки часом не вдаються, ви, мабуть, робите щось не так. Єдиний випадок, коли розумно вкласти не вдається, це сценарій висхідного сценарію, який під час одночасних транзакцій стикається з повторюваними унікальними ключами, і навіть тоді ви можете отримати консультативний замок або блокування таблиці, якщо вам потрібно зробити речі кулестійкими.
Дені де Бернарді

3

Ви можете створити тригер після вставки в таблиці зразків, щоб вставити його в інші дві таблиці.

Єдина проблема, яку я бачу при цьому, полягає в тому, що у вас не буде способу вставляти додаткові деталі, вони завжди будуть порожніми, або в цьому випадку ss. Немає можливості вставити стовпець у зразок, який насправді не є у таблиці зразків, тому ви не можете надсилати його разом із вродженою вставкою.

Іншим варіантом було б створити збережену процедуру для запуску ваших вставок.

У вас є запитання із позначкою mysql та postgressql, про яку базу даних ми тут говоримо?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.