Здравствуйте, Flem1234, Вы писали:
F>Есть требование, что для одной мастер записи должна быть как минимум одна деталь. F>Как это реализовать?
Вставка данных в master /details всегда выполняется в одной транзакции — это будет гарантировать наличие как минимум одной записи в таблице details. Дополнительно, на таблице details создается триггер на обновление и удаление, в задачи которого входит проверка существования как минимум одной записи после выполнения модификаций в таблице.
On 26.06.2013 12:57, Olaf wrote:
> F>Есть требование, что для одной мастер записи должна быть как минимум > одна деталь. > F>Как это реализовать? > > Вставка данных в master /details всегда выполняется в одной транзакции — > это будет гарантировать наличие как минимум одной записи в таблице > details.
Это почему? А если приложение ничего в detail вставлять не собирается?
Здравствуйте, hrensgory, Вы писали:
>> Вставка данных в master /details всегда выполняется в одной транзакции — >> это будет гарантировать наличие как минимум одной записи в таблице >> details.
H>Это почему? А если приложение ничего в detail вставлять не собирается?
Если записи в master и details вставлять по отдельности, например с интервалом времени t (минута, час, день…), то в течение этого интервала будет 0 записей в таблице details – это нормально, но у автора требование наличие как минимум одной записи в details. Транзакция будет гарантировать, что если вставилась запись в master, значит существует как минимум одна связанная запись в details.
On 26.06.2013 13:47, Olaf wrote:
>>> Вставка данных в master /details всегда выполняется в одной транзакции — >>> это будет гарантировать наличие как минимум одной записи в таблице >>> details. > > H>Это почему? А если приложение ничего в detail вставлять не собирается? > > Если записи в master и details вставлять по отдельности, например с > интервалом времени t (минута, час, день…), то в течение этого интервала > будет 0 записей в таблице details – это нормально, но у автора > требование наличие как минимум одной записи в details. Транзакция будет > гарантировать, что если вставилась запись в master, значит существует > как минимум одна связанная запись в details.
Насколько я понял — речь идёт о том, чтобы обеспечить выполнение условия
"на каждый master — как минимум один detail" средствами БД.
При этом неясно как "транзакция будет гарантировать, что если вставилась
запись в master, значит существует как минимум одна связанная запись в
details" если приложение запишет данные только в master и не будет
вообще ничего писать в detail. В этом случае видимо должен сработать
триггер, но на какой таблице — непонятно (предполагая, что есть FK
master — detail и вставить в detail что-то заранее, не вставив в master
— нельзя).
Здравствуйте, hrensgory, Вы писали:
H>При этом неясно как "транзакция будет гарантировать, что если вставилась H>запись в master, значит существует как минимум одна связанная запись в H>details" если приложение запишет данные только в master и не будет H>вообще ничего писать в detail. В этом случае видимо должен сработать H>триггер, но на какой таблице — непонятно (предполагая, что есть FK H>master — detail и вставить в detail что-то заранее, не вставив в master H>- нельзя).
H>-- H>WBR, H>Serge.
Здесь важна реализация, при которой первый detail всегда вставляется вместе с master записью и только так, сама транзакция, внутри которой идет только вставка в master конечно никак не поможет решить задачу.
Здравствуйте, Olaf, Вы писали:
O>Здесь важна реализация, при которой первый detail всегда вставляется вместе с master записью и только так, сама транзакция, внутри которой идет только вставка в master конечно никак не поможет решить задачу.
Если клиент попробует записать только мастер без детайлса, то тогда у меня требование будет нарушено.
Можно, конечно, создать хранимку, но хочется решения без нее, т.к. sql у меня генерируется из описания и придется дорабатывать генератор.
Здравствуйте, Olaf, Вы писали:
O>Здравствуйте, Flem1234, Вы писали:
F>>Как это реализовать? O>Вставка данных в master /details всегда выполняется в одной транзакции — это будет гарантировать наличие как минимум одной записи в таблице details.
Это как минимум потребует контроля над вставляющим кодом. И дополнительных проверках вручную при необходимости. Например, вставка производится пачками из временных таблиц и консистентность нужно проверить вручную (данные строятся другими выборками). Так можно и все остальные операции вручную проверять при желании.
O>Дополнительно, на таблице details создается триггер на обновление и удаление, в задачи которого входит проверка существования как минимум одной записи после выполнения модификаций в таблице.
И это далеко не во всех сценариях работает. Например, в одной транзакции удалить все detail и потом вставить новые. Триггер не даст.
Здравствуйте, maxkar, Вы писали:
M>Хотя, конечно, все зависит от требований ТС.
Требования у ТС простые:
1. С точки зрения бизнеса, у мастера должна быть как минимум одна деталь, иначе ситуация не имеет смысла.
2. У меня есть генератор ескьюэля, которые генерирует запросы к базе, вставляя сначала мастеров, а потом их детали (как они и приходят из внешней системы) и удаляя в обратном порядке.
3. Надо не допустить сохранения некорректных данных с точки зрения бизнеса (1) как можно меньшими усилиями.
Здравствуйте, Flem1234, Вы писали:
F>Требования у ТС простые: F>1. С точки зрения бизнеса, у мастера должна быть как минимум одна деталь, иначе ситуация не имеет смысла.
Как уже заметили, единственный способ обеспечить строгое выполнение этого требования — вставлять мастер и деталь в одной транзакции.
F>2. У меня есть генератор ескьюэля, которые генерирует запросы к базе, вставляя сначала мастеров, а потом их детали (как они и приходят из внешней системы) и удаляя в обратном порядке.
Он делает это в одной транзакции? Если нет, его придется переписывать, без вариантов.
F>3. Надо не допустить сохранения некорректных данных с точки зрения бизнеса (1) как можно меньшими усилиями.
Есть несколько подходов.
1. Не давать приложению права на прямой insert, а дать некий API, который будет контролировать это требование.
2. Изобрести "триггер на commit", где и проверять выполнение условия. Такого триггера в SQL конечно нет. Но, к примеру, в Oracle возможен известный трюк с материализованным представлением. Может и в MSSQL можено придумать что-то подобное.
3. Попытаться реализовать чисто декларативными ограничениями. Например добавить в мастер таблицу внешний ключ со ссылкой на детальную. С отложенной проверкой, разумеется.
Здравствуйте, Flem1234, Вы писали:
F>Добрый день.
F>Есть требование, что для одной мастер записи должна быть как минимум одна деталь. F>Как это реализовать?
Какое-то долбанутое требование, которое маловероятно соответствует действительности. Хотелось бы подробностей.
F>Спасибо.
Здравствуйте, maloi_alex, Вы писали:
_>Какое-то долбанутое требование, которое маловероятно соответствует действительности. Хотелось бы подробностей.
Автору топика предъявлено требование, реализовать разновидность связи один ко многим (one-to-many), где заранее известно минимальное кол-во записей со стороны многие (many), в частности 1 to 1-or-many (1 к 1 или более). Бывают и другие случаи, когда известно максимальное кол-во записей, например: летчик в месяц делает не больше 50-и вылетов, спортсмен может использовать не больше 5-и попыток, в человеческом организме не больше 500 костей и т.п.
Единственный возможный способ реализовать связь (1 к 1 или более) использовать транзакцию для вставки родителя и, по крайней мере, одного потомка. Возможно, есть другие экзотические варианты, категорично заявлять не буду, просто я о них не знаю.
Обычно возникает, где требуется хранить первоначальное значение одного из атрибутов master сущности, который изменяется во времени и требует протоколирование (ведение журнала), например вес, температура, географическое положение, статус/состояние, склад/магазин и т.п.
Проблема данной задачи заключается в том, что в ней необходимо выполнить требование наличия как минимум одной деталь записи при отсутствии таковой и с минимальными изменениями приложения. Но даже в такой постановке автором топика она решаема, просто нужно переделать вставку данных, т.к. примитивными настройками на уровне СУБД здесь не обойдешься (PK, FK, constraint и т.п.). Кроме тех вариантов, что предлагали выше можно: Создать дополнительную структуру в БД аналогичную master и все новые записи добавлять только в нее. После того как пришли детали, объединять их с мастер записями и вставлять в целевую master таблицу в одной транзакции.
Если по бизнес-требованиям терпит отсутствие деталей для мастер записей в течение какого-то времени t (окно), тогда по приходу из внешнего источника загружать детали и чистить несвязанные мастер записи.
...
Раз уж дискуссия ушла в более общее русло, добавлю. W>2. Изобрести "триггер на commit", где и проверять выполнение условия. Такого триггера в SQL конечно нет. Но, к примеру, в Oracle возможен известный трюк с материализованным представлением. Может и в MSSQL можено придумать что-то подобное.
"Триггер на коммит" — это на любой коммит в базе, или выполнение триггера в момент коммита? Второй в некоторых базах есть. Например, код для решения исходной задачи на postres'е (9-ка):
Скрытый текст
CREATE FUNCTION chk_master_detail() RETURNS TRIGGER AS $$
BEGIN
PERFORM id FROM detail WHERE master_id = NEW.id;
IF NOT FOUND THEN
RAISE 'KEY VIOLATION!';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE FUNCTION chk_detail_master() RETURNS TRIGGER AS $$
DECLARE
FND boolean;
BEGIN
PERFORM id FROM master WHERE id = OLD.master_id;
IF NOT FOUND THEN
RETURN NEW;
END IF;
FND := EXISTS(SELECT id FROM detail WHERE master_id = OLD.master_id);
IF NOT FND THEN
RAISE 'KEY VIOLATION!';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TABLE master(
id integer NOT NULL PRIMARY KEY
);
CREATE TABLE detail(
id integer NOT NULL PRIMARY KEY,
master_id integer NOT NULL REFERENCES master(id)
);
CREATE CONSTRAINT TRIGGER master_detail_has_one AFTER INSERT OR UPDATE
ON master
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE PROCEDURE chk_master_detail();
CREATE CONSTRAINT TRIGGER master_detail_has_one_detail_t AFTER UPDATE OR DELETE
ON detail
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE PROCEDURE chk_detail_master();
Туда еще триггер на транкейт detail добавить нужно.
Кстати, с использованием deferred trigger можно сделать и триггер на коммит (выполняется один раз в момент коммита).
Скрытый текст
CREATE FUNCTION schedule_commit_check() RETURNS TRIGGER AS $$
BEGIN
CREATE TEMP TABLE IF NOT EXISTS on_commit_check (
v boolean primary key
) ON COMMIT DROP;
UPDATE on_commit_check SET v = TRUE;
IF NOT FOUND THEN
CREATE CONSTRAINT TRIGGER master_detail_has_one AFTER INSERT
ON on_commit_check
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE PROCEDURE chk_global_master_detail();
INSERT INTO on_commit_check VALUES (true);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE FUNCTION chk_global_master_detail() RETURNS TRIGGER AS $$
DECLARE
fnd boolean;
BEGIN
fnd := EXISTS(SELECT master.id
FROM master
LEFT OUTER JOIN detail on master.id = detail.master_id
WHERE detail.master_id IS NULL
);
IF FND THEN
RAISE 'M/1D RELATION VIOLATION!';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TABLE master(
id integer NOT NULL PRIMARY KEY
);
CREATE TABLE detail(
id integer NOT NULL PRIMARY KEY,
master_id integer NOT NULL REFERENCES master(id)
);
CREATE TRIGGER master_detail_has_one AFTER INSERT OR UPDATE
ON master
FOR EACH STATEMENT
EXECUTE PROCEDURE schedule_commit_check();
CREATE TRIGGER master_detail_has_one_detail_t AFTER UPDATE OR DELETE
ON detail
FOR EACH STATEMENT
EXECUTE PROCEDURE schedule_commit_check();
schedule_commit_check на таблицы навешивать триггерами на изменение системных таблиц/view.
Касательно MSSQL. Некоторые люди творят ужасы, чтобы проверить констрейны в момент коммита (как раз с использованием вычисляемых материализованных колонок в таблицах). W>3. Попытаться реализовать чисто декларативными ограничениями. Например добавить в мастер таблицу внешний ключ со ссылкой на детальную. С отложенной проверкой, разумеется.
Вот с отложенной проверкой будут как раз основные проблемы. Не поддерживает их MSSQL ни в каком виде. А так был бы, наверное, самый простой и понятный вариант. Где-то считать количество detail для master и на это значение повесить check constraint. В идеале (если все поддерживается) это был бы materialized view (master.id, sum(detail.id)) и check constraint на вторую колонку.