postgresql: вложенная вставка - PullRequest
4 голосов
/ 11 мая 2011

У меня есть две таблицы. Скажем, tblA и tblB.
Мне нужно вставить строку в tblA и использовать возвращенный идентификатор в качестве значения для вставки в качестве одного из столбцов в tblB.

Я попытался выяснить это в документации, но не смог получить это. Что ж, можно ли написать заявление (предназначенное для использования в подготовленном виде), например

INSERT INTO tblB VALUES 
(DEFAULT, (INSERT INTO tblA (DEFAULT, 'x') RETURNING id), 'y')

как мы делаем для SELECT?

Или я должен сделать это путем создания хранимой процедуры? Я не уверен, что смогу создать подготовленное утверждение из хранимой процедуры.

Пожалуйста, сообщите.

С уважением,
Mayank

Ответы [ 4 ]

7 голосов
/ 11 мая 2011

Вам нужно подождать PostgreSQL 9.1 для этого:

with
ids as (
insert ...
returning id
)
insert ...
from ids;

Между тем вам нужно использовать plpgsql, временную таблицу или некоторую дополнительную логику в вашем приложении ...

3 голосов
/ 11 мая 2011

Это возможно с 9.0 и новым DO для анонимных блоков:

do $$
declare 
  new_id integer;
begin
  insert into foo1 (id) values (default) returning id into new_id;
  insert into foo2 (id) values (new_id);
end$$;

Это может быть выполнено как один оператор.Я не пытался создать PreparedStatement из этого, хотя.

Редактировать

Другой подход состоит в том, чтобы просто сделать это в два шага, сначала запустить вставку в tableA с помощью выражения return, получить сгенерированное значение через JDBC, а затем запустить вторую вставку, что-то вроде этого:

PreparedStatement stmt_1 = con.prepareStatement("INSERT INTO tblA VALUES (DEFAULT, ?) returning id");
stmt_1.setString(1, "x");
stmt_1.execute(); // important! Do not use executeUpdate()!
ResultSet rs = stmt_1.getResult();
long newId = -1;
if (rs.next()) {
   newId = rs.getLong(1);
}
PreparedStatement stmt_2 = con.prepareStatement("INSERT INTO tblB VALUES (default,?,?)");
stmt_2.setLong(1, newId);
stmt_2.setString(2, "y");
stmt_2.executeUpdate();
2 голосов
/ 11 мая 2011

Это можно сделать двумя вставками, используя currval() для извлечения внешнего ключа (при условии, что ключ serial ):

create temporary table tb1a (id serial primary key, t text);
create temporary table tb1b (id serial primary key,
                             tb1a_id int references tb1a(id),
                             t text);
begin;
insert into tb1a values (DEFAULT, 'x');
insert into tb1b values (DEFAULT, currval('tb1a_id_seq'), 'y');
commit;

Результат:

select * from tb1a;
 id | t
----+---
  3 | x
(1 row)

select * from tb1b;
 id | tb1a_id | t
----+---------+---
  2 |       3 | y
(1 row)

Использование currval таким способом безопасно как внутри, так и вне транзакции.Из документации Postgresql 8.4 :

currval

Возвращает значение, наиболее недавно полученное nextval для этой последовательности в текущем сеансе.(Сообщается об ошибке, если nextval никогда не вызывался для этой последовательности в этом сеансе.) Поскольку он возвращает локальное значение сеанса, он дает предсказуемый ответ, выполняли ли nextval другие сеансы со времени текущего сеанса или нет.

1 голос
/ 11 мая 2011

Для этого вы можете использовать AFTER INSERT триггер.Что-то вроде:

create function dostuff() returns trigger as $$
begin
 insert into table_b(field_1, field_2) values ('foo', NEW.id);
 return new; --values returned by after triggers are ignored, anyway
end;
$$ language 'plpgsql';

create trigger trdostuff after insert on table_name for each row execute procedure dostuff();

after insert необходимо, потому что вам нужен идентификатор для ссылки на него.Надеюсь, это поможет.

Редактировать

Триггер будет вызываться в том же «блоке», что и команда, которая его инициировала, даже если не используются транзакции - другими словами, это становится несколько частью этой команды. Следовательно, нет риска, что что-то изменит ссылочный идентификатор между вставками.

...