Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Дерево. Определение принадлежности узлов. [Firebird 1.5]


Страницы:   1 2 3

Romkin ©   (2006-08-09 11:53:00) 

Проблема появилась, сижу голову ломаю. Есть у меня БД вопросов, которые разделяются по древовидной структуре разделов, то есть, есть таблица разделов
CREATE TABLE THEME (
   THEME_ID    INTEGER NOT NULL,
   THEME_NAME  VARCHAR(80),
   OWNER_ID    INTEGER,
);
ALTER TABLE THEME
 ADD CONSTRAINT PK_THEME
   PRIMARY KEY (THEME_ID);
alter table THEME
 add constraint FK_THEME_THEME
   foreign key (OWNER_ID)
   references THEME(THEME_ID)
   on delete CASCADE
   on update CASCADE;

В общем, обычное дерево. Есть таблица вопросов:
CREATE TABLE QUESTION (
   QUESTION_ID     INTEGER NOT NULL,
   QUESTION_NAME   VARCHAR(80),
   THEME_ID        INTEGER NOT NULL
);
ALTER TABLE QUESTION ADD CONSTRAINT FK_QUESTION PRIMARY KEY (THEME_ID, QUESTION_ID);

И есть таблица (на самом деле две), связывающая вопрос и тему через билет (утрировал, не особо принципиально):
CREATE TABLE CARDQUESTION (
   CARD_ID      INTEGER NOT NULL,
   SCRIPT_ID    INTEGER NOT NULL,
   QUESTION_ID  INTEGER NOT NULL,
   constraint PK_CARDQUESTION primary key (SCRIPT_ID, CARD_ID, QUESTION_ID)
);
еще есть таблица SCRIPT (THEME_ID, SCRIPT_ID, ...) (показан PK)

То есть, у каждой ветви дерева может быть список вопросов, из которых формируются билеты теста (два поля SCRIPT_ID, CARD_ID - как раз идентификатор билета, SCRIPT - тест). Тест такде принадлежит определенному разделу (THEME_ID). Пока все замечательно.
Все дело в том, что в тест, а именно в CARDQUESTION, можно вставлять вопросы из того раздела, которому принадлежит тест и из любого его потомка. То есть, встав в дереве на какой-либо узел, я вижу все вопросы, которые принадлежат этому узлу, плюс вопросы его потомков. Соответственно, выбрать я тоже могу любой.
Вопрос состоит в том, что мне нужна процедура, которая быстро и оптимально может определить "изолированность" узла раздела (таблица THEME). То есть, мне нужна процедура, которая получает на вход THEME_ID и возвращает RET integer >0 в том случае, когда ни один вопрос из данного раздела и его потомковне используется в тестах его предков.
Кто-нибудь может посоветовать? Есть процедура, выводящая QUESTION_ID для данного THEME_ID и его потомков. Есть процедура, выводящая всех предков для данного THEME_ID. Как бы так поуютнее спарить? join процедур делать не хочется. Предков мало, высота дерева 20 от силы, а вопросов в разделе может быть и под тысячу...


Romkin   (2006-08-09 18:39:00) [21]

Desdechado ©   (09.08.06 18:35) [22] А зачем мне городить постоянно join? Какая выгода от пользования того, как ты предлагаешь? Место? Мне хватает. Блюсти согласованность? Дык foreign key есть. Что еще?


Romkin   (2006-08-09 18:38:00) [22]

Понимаешь, простой вопрос на засыпку: а почему ты решил, что QUESTION_ID  - альтернативный ключ? :) Вопрос однозначно идентифицируется своим первичным ключем, (THEME_ID, QUESTION_ID). CARD тоже идентифицируется своим ключем, (THEME_ID, SCRIPT_ID, CARD_ID). И на данный момент у меня в БД нет альтернативных ключей для этих таблиц, не объявлены они, понимаешь? ;)
Следовательно, соединение многие-многие должно содержать полную комбинацию первичных ключей.
И даже если есть альтернативные - на каком основании я должен делать соединение по ним?! Такого требования нет.


Desdechado   (2006-08-09 18:35:00) [23]

Требований точно не помню.
Но повторения взаимосвязей сущностей, AFAIR, быть не должно. А у тебя все перечисленное в [20] дублируется в "доработанной" таблице.
Зачем? Это ведь можно соединить каскадно JOIN'ами при необходимости.


Romkin   (2006-08-09 18:32:00) [24]

Desdechado ©   (09.08.06 18:24) [20] Вот уж неправда. Приведи форму и требование, которому не удовлетворяет ;)
Вот именно, что есть. И поэтому в соединении все эти взаимоотношения учитываются.


Desdechado   (2006-08-09 18:24:00) [25]

>  Где там кольцо?! :)
В таблице QUESTIONS уже есть взаимоотношения вопросов и тем.
В таблице SCRIPT уже есть взаимоотношение тестов и тем.
В таблице THEME уже есть взаимоотношение тем более высокого и более низкого порядка.
Типичнейшая денормализация.


Romkin   (2006-08-09 18:19:00) [26]

Desdechado ©   (09.08.06 17:47) [17] Где там кольцо?! :) Как раз нормальная форма, причем не ниже четвертой ;) И именно в этом случае - полностью оправданно.
MsGuns ©   (09.08.06 17:58) [18] Э. Надо было сразу делать дерево разделов на вложенных множествах, там проще проверки делать потомок-предок.
Ничего, уже примерно вырисовалось: во-первых, триггер, во-вторых, буду выяснять, а не потомок ли данного узла... Так, похоже, проще. То есть, взять всех предков, и для каждого посмотреть, а нет ли в CARDQUESTION у этого предка потомков данного узла и его самого. Похоже, наименьший лаг получится.


MsGuns   (2006-08-09 17:58:00) [27]

>Romkin ©   (09.08.06 17:44) [16]

Этот вариант слишком жесткий. У меня похожая задача (состав изделий в приборостроении). От такого варианта отказался в пользу просчета цепочек в триггерах


Desdechado   (2006-08-09 17:47:00) [28]

> привел к нормальному виду:
ИМХО, сплошная денормализация. Закольцевал ключи, как только можно...
Иногда это оправдано, но, имхо, не в этом случае.


Romkin   (2006-08-09 17:44:00) [29]

Все, таки привел к нормальному виду:
CREATE TABLE CARDQUESTION (
   THEME_ID           INTEGER NOT NULL,
   SCRIPT_ID          INTEGER NOT NULL,
   CARD_ID            INTEGER NOT NULL,
   QUESTION_THEME_ID  INTEGER NOT NULL,
   QUESTION_ID        INTEGER NOT NULL,
   ORD_NUM            INTEGER
);
ALTER TABLE CARDQUESTION
 ADD CONSTRAINT PK_CARDQUESTION
   PRIMARY KEY (THEME_ID, SCRIPT_ID, CARD_ID, QUESTION_THEME_ID, QUESTION_ID);

ALTER TABLE CARDQUESTION
 ADD CONSTRAINT FK_CARDQUESTION_CARD
   FOREIGN KEY (THEME_ID, SCRIPT_ID, CARD_ID)
   REFERENCES CARD (THEME_ID, SCRIPT_ID, CARD_ID)
   ON DELETE CASCADE
   ON UPDATE CASCADE;

ALTER TABLE CARDQUESTION
 ADD CONSTRAINT FK_CARDQUESTION_QUESTION
   FOREIGN KEY (QUESTION_THEME_ID, QUESTION_ID)
   REFERENCES QUESTION (THEME_ID, QUESTION_ID)
   ON DELETE CASCADE
   ON UPDATE CASCADE;

И вот тут как раз и видна связь: два поля THEME_ID и QUESTION_THEME_ID. Первое - какой теме принадлежит тест, второе - какой теме принадлежит вопрос. Существует ограничение: QUESTION_THEME_ID - строго потомок (неважно, через сколько детей) THEME_ID или они равны.
Нужна процедура, которая для данного ID (THEME_ID) определяет, есть ли в его предках ссылки через эту таблицу на него или его потомков. То есть, есть ли записи в CARDQUESTION, где THEME_ID - предок ID, а QUESTION_THEME_ID - либо потомок ID, либо он сам. О как :)
Вопрос в том, как лучше повернуть.


Desdechado   (2006-08-09 17:43:00) [30]

> есть QUESTION_ID, надо вывести все SCRIPT_ID, в которых
> он употребляется. И как в твоей структуре это будет смотреться?
SELECT DISTINCT C.script_id
FROM Card C, CardQuestion CQ
WHERE CQ.question_id = :Q AND CQ.card_id = C.card_id

Вроде совсем не сложно...


evvcom   (2006-08-09 17:32:00) [31]

Ну так вопрос исчерпан или как? Мой [10] помог, или я его тоже туманно изложил? Или остановился на реконструкции таблиц?


Romkin   (2006-08-09 17:24:00) [32]

MsGuns ©   (09.08.06 16:26) [12] Наверно, можно сказать и так. Условие - через CARD узел может владеть вопросом только своих потомков.


MsGuns   (2006-08-09 16:26:00) [33]

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


Romkin   (2006-08-09 14:22:00) [34]

Desdechado ©   (09.08.06 13:36) [9] НУ да, ну да, конечно. Особенно в свете того, что мне сейчас надо делать join CARDQUESTION именно потому, что у нее не полный составной ключ! Был бы полный - я бы сразу взял оттуда пару THEME_ID, первая - которой принадлежит скрипт, вторая - которой принадлежит вопрос. А то, что ты предлагаешь - вообще приводит к непрерывным join!
Простой вопрос: есть QUESTION_ID, надо вывести все SCRIPT_ID, в которых он употребляется. И как в твоей структуре это будет смотреться?! У меня это - простой запрос к CARDQUESTION


evvcom   (2006-08-09 14:02:00) [35]

В Оракле есть такая замечательная функция SYS_CONNECT_BY_PATH. Приведу описание для пущей ясности:
Syntax
SYS_CONNECT_BY_PATH ( column , char )
Purpose
SYS_CONNECT_BY_PATH is valid only in hierarchical queries. It returns the path of
a column value from root to node, with column values separated by char for each
row returned by CONNECT BY condition.
Both column and char can be any of the datatypes CHAR, VARCHAR2, NCHAR, or
NVARCHAR2. The string returned is of VARCHAR2 datatype and is in the same
character set as column.
Examples
The following example returns the path of employee names from employee
Kochhar to all employees of Kochhar (and their employees):
SELECT LPAD(’ ’, 2*level-1)||SYS_CONNECT_BY_PATH(last_name, ’/’) "Path"
FROM employees
START WITH last_name = ’Kochhar’
CONNECT BY PRIOR employee_id = manager_id;
Path
---------------------------------------------------------------
/Kochhar
/Kochhar/Greenberg
/Kochhar/Greenberg/Faviet
/Kochhar/Greenberg/Chen
/Kochhar/Greenberg/Sciarra
/Kochhar/Greenberg/Urman
/Kochhar/Greenberg/Popp
/Kochhar/Whalen
/Kochhar/Mavris
/Kochhar/Baer
/Kochhar/Higgins
/Kochhar/Higgins/Gietz

Пробелы в результате удалились :( Таков уж HTML и IE.
Далее идея:
Разворачиваем дерево от потомков к предкам и используем вышеупомянутую функцию. Таким образом получаем для каждой THEME_ID несколько записей с путями, которые легко можно преобразовать к виду флага "есть child" или "нет". А далее агрегатным запросом получить конечное значение нужного флага.
Поскольку функцию или ее некий аналог придется реализовывать в FB самому, то и о флаге соответственно можно позаботиться уже в ней.


Desdechado   (2006-08-09 13:36:00) [36]

Мне кажется, чтотакая структура более удобна для твоей задачи.
CREATE TABLE THEME (
 THEME_ID    INTEGER NOT NULL,
 THEME_NAME  VARCHAR(80) NOT NULL,
 OWNER_ID    INTEGER NOT NULL,
);
ALTER TABLE THEME
 ADD CONSTRAINT PK_THEME
 PRIMARY KEY (THEME_ID);
ALTER TABLE THEME
 ADD CONSTRAINT FK_THEME_THEME
 FOREIGN KEY (OWNER_ID) REFERENCES THEME(THEME_ID);
 
CREATE TABLE QUESTION (
 QUESTION_ID     INTEGER NOT NULL,
 QUESTION_NAME   VARCHAR(80) NOT NULL,
 THEME_ID        INTEGER NOT NULL
);
ALTER TABLE QUESTION
 ADD CONSTRAINT PK_QUESTION
 PRIMARY KEY (QUESTION_ID);
ALTER TABLE QUESTION
 ADD CONSTRAINT FK_QUESTION_THEME
 FOREIGN KEY( THEME_ID ) REFERENCES THEME( THEME_ID );

CREATE TABLE SCRIPT (
 SCRIPT_ID   INTEGER NOT NULL,
 SCRIPT_NAME VARCHAR(80) NOT NULL,
 THEME_ID    INTEGER NOT NULL
);
ALTER TABLE SCRIPT
 ADD CONSTRAINT PK_SCRIPT
 PRIMARY KEY( SCRIPT_ID );
ALTER TABLE SCRIPT
 ADD CONSTRAINT FK_SCRIPT_THEME
 FOREIGN KEY( THEME_ID ) REFERENCES THEME( THEME_ID );

CREATE TABLE CARD (
 CARD_ID   INTEGER NOT NULL,
 CARD_NO   VARCHAR(30) NOT NULL,
 SCRIPT_ID INTEGER NOT NULL
);
ALTER TABLE CARD
 ADD CONSTRAINT PK_CARD
 PRIMARY KEY( CARD_ID );
ALTER TABLE CARD
 ADD CONSTRAINT FK_CARD_SCRIPT
 FOREIGN KEY( SCRIPT_ID ) REFERENCES SCRIPT( SCRIPT_ID );

CREATE TABLE CARDQUESTION (
 CARD_ID      INTEGER NOT NULL,
 QUESTION_ID  INTEGER NOT NULL
);
ALTER TABLE CARDQUESTION
 ADD CONSTRAINT UQ_CARDQUESTION
 UNIQUE (CARD_ID, QUESTION_ID);
ALTER TABLE CARDQUESTION
 ADD CONSTRAINT FK_CARDQUESTION_CARD
 FOREIGN KEY( CARD_ID ) REFERENCES CARD( CARD_ID );
ALTER TABLE CARDQUESTION
 ADD CONSTRAINT FK_CARDQUESTION_QUESTION
 FOREIGN KEY( QUESTION_ID ) REFERENCES QUESTION( QUESTION_ID );

Но при условии, конечно, что тест толькопо одной теме, а билет может включать вопросы одной темы и присутствовать только в одном тесте.


evvcom   (2006-08-09 13:04:00) [37]

> [7] Romkin ©   (09.08.06 13:01)

После [4] прояснилось. Когда я писал, [4] еще не видел.


Romkin   (2006-08-09 13:01:00) [38]

Неужели туманно?

Вот есть цепочка THEME -> QUESTION (вопрос принадлежит разделу). Прошу учесть, что THEME - дерево.
и есть вторая цепочка THEME -> SCRIPT -> CARD -> QUESTION (В разделе может быть тест, который состорит из билетов, которые могут включать в себя вопросы данного раздела и любого из его потомков). Связь CARD и QUESTION - многие-многие, поэтому есть CARDQUESTION.
Нужно выяснить, можно ли, допустим, убрать раздел. При удалении раздела удаляются и все его потомки. Раздел нельзя убрать, если вопросы используются в остающихся разделах (то есть, есть вопрос раздела или его потомков, который используется в тестах предка). Вот и сижу. Это касается не только удаления, просто сказать пользователю, можно ли переместить ветку и тд...


Sergey13   (2006-08-09 13:01:00) [39]

Не знаю поможет ли, мне в свое время здорово помогло.
В таблицу THEME можно добавить поле с головной родительской темой. Основываясь на нем можно многие запросы переделать на шустрые и плоские, минуя разбор дерева или хотя бы здорово ограничить число рабираемых записей.


evvcom   (2006-08-09 12:50:00) [40]

> [1] zdm ©   (09.08.06 12:08)
> [3] zdm ©   (09.08.06 12:26)

Где ты увидел вопрос о коде на паскале?

> [0] Romkin ©   (09.08.06 11:53)
> [2] Desdechado ©   (09.08.06 12:13)

Да, с тестами туманно. Как недавно кто-то говорил про английский, если непонятен смысл слова игнорируй его. Аналогично игнорирую "тест" и отношу все сказанное про него на "билет". Не зная все ж возможностей FB работы с деревьями, думаю, как бы это сделал на оракле без древовидных запросов и функций.


Страницы:   1 2 3

Наверх

Титульная страница Поиск, карта сайта Написать письмо