Вставить строку в таблицу, если значение из ссылочной таблицы не найдено - PullRequest
0 голосов
/ 30 мая 2018

У меня есть следующие две таблицы:

CREATE TABLE employee
(
  id serial NOT NULL,
  name character(32) NOT NULL,
  CONSTRAINT employee_id_pkey PRIMARY KEY (id),
  CONSTRAINT employee_name_ukey UNIQUE (name)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE employee
  OWNER TO postgres;

CREATE TABLE worklog
(
  id serial NOT NULL,
  activity character(32),
  employee integer NOT NULL,
  "time" timestamp without time zone NOT NULL DEFAULT now(),
  CONSTRAINT log_id_pkey PRIMARY KEY (id),
  CONSTRAINT fk_employee FOREIGN KEY (employee)
      REFERENCES employee (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
  OIDS=FALSE
);
ALTER TABLE worklog
  OWNER TO postgres;

-- Index: fki_employee

-- DROP INDEX fki_employee;

CREATE INDEX fki_employee
  ON worklog
  USING btree
  (employee);

Я хотел бы сделать что-то вроде этого:

insert into
    worklog (activity, employee)
values
('work in progress',
    coalesce(
        (select id from employee where name = 'jonathan'),
        (insert into employee (name) values ('jonathan') returning id)
    )
);

Однако, это возвращает ошибку:

ERROR:  syntax error at or near "into"
LINE 8:   (insert into employee (name) values ('jonathan') returning...)

Я где-то читал, что я мог бы просто вставить «имя» в employee заранее и игнорировать возможную ошибку дублирующегося ключа, используя механизм ON CONFLICT ....Однако у меня есть две проблемы с этим подходом:

  1. Я использую postgres 9.4, а для конструкции AFAIK ON CONFLICT требуется 9.5
  2. Что еще более важно, ситуация, когда сотрудник не будетбыть в базе данных крайне редко.Так что большую часть времени ведущий вкладыш будет пустой тратой времени.

Возможно ли то, что я имею в виду, с postgres 9.4?

1 Ответ

0 голосов
/ 30 мая 2018

Примерно так должно работать:

with new_name as (
   insert into employee (name) 
   select 'jonathan'
   where not exists (select * 
                     from employee
                     where name = 'jonathan')
   returning id
), name as (
   select id
   from employee
   where name = 'jonathan'
   union all
   select id
   from new_name
)
insert into worklog (activity, employee)
select 'work in progress', id 
from name;

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

Второй CTE name вернет только одну строку. Если строка вставлена ​​на первом шаге, второй CTE не увидит ее, и, таким образом, вторая часть объединения вернет идентификатор вставленной строки.Если строка не была вставлена, то существующий сотрудник будет возвращен первой частью UNION, но вторая не вернет строку.

Цитата из руководства

Следовательно, при использовании операторов модификации данных в WITH порядок, в котором действительно происходят указанные обновления, непредсказуем.Все операторы выполняются с одним и тем же моментальным снимком (см. Главу 13), поэтому они не могут «видеть» влияние друг друга на целевые таблицы

Это смягчает последствия непредсказуемости фактического порядка обновления строк, иозначает, что ВОЗВРАЩАЯ данные - это единственный способ сообщить об изменениях между различными под-операторами WITH и основным запросом

Онлайн пример: http://rextester.com/HYEN40017

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...