Лекция № 25

Тема: «Обработка данных в отношении многие-ко-многим»

 

План

1. Отображения связанных таблиц в одном наборе данных.

2. Обновление одной записи в наборе данных.

3. Работа с таблицами в отношении «один-ко-многим».

 

1. Отображения связанных таблиц в одном наборе данных

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

Для решения поставленной задачи будем использовать запросы по нескольким таблицам. Такой запрос в общем виде имеет вид:

 

SELECT список полей FROM таблица1

INNER JOIN таблица2

ON таблица1.поле = таблица2.поле

[WHERE условие]

 

В списке полей перечисляются в нужном порядке поля из двух связываемых таблиц. Если в таблицах имеются поля  одинаковыми именами, то их указывают так:

 

таблица.имя_поля

 

В разделе ON указывают, по каким полям две таблицы связаны между собой.

 

Если нужно связать больше таблиц в одном запросе, то запрос записывается так:

 

SELECT список полей FROM таблица1

INNER JOIN (таблица2 INNER join таблица3

            ON таблица2.поле = таблица3.поле)

ON таблица1=таблица2

[WHERE условие]

 

Например. Пусть имеются 3 таблицы, показанные на схеме данных.

 

 

Необходимо в одном наборе данных отобрать название специальности, группу, фамилию, имя и дату рождения студентов.

 

SELECT nazv_spec, nazv_grup, fam, imya, datar FROM spec

INNER JOIN (grup INNER JOIN stud

   ON grup.id_grup=stud.id_grup)

ON spec.id_spec=grup.id_spec

 

Если на форму нанести компонент ADOQuery и в свойстве SQL ввести данный запрос, то компонент отберет данные из 3 таблиц в один набор данных, который можно отобразить, например, в сетке DBGrid.

Для нашего примера пользователь увидит в сетке 5 колонок, при этом 1-я  принадлежит таблице spec, 2-я – таблице grup, остальные – таблице stud. Если пользователь начнет редактировать данные в колонке, то приложение отработает корректно, так как оно автоматически «знает» какие колонки в какие таблицы нужно записывать.

Однако, если пользователь попытается удалить строку в сетке, то появится сообщение об ошибке, которая вызвана тем, что программа не знает, из какой из 3-х таблиц, представленных в сетке, нужно удалить запись. Решением данной задачи является задание «главной таблицы» перед удалением. То есть, если нужно удалить запись о студенте из таблицы stud, данные о котором выведены в сетку через компонент ADOQuery (при этом в сетку также выведены название специальности и группа из 2-х других таблиц), то код удаления может иметь вид:

 

//задаем «главную» таблицу в запросе

ADOQuery1.Properties[‘Unique Table’].Value:=’stud’;

//удаляем запись из набора данных

ADOQuery1.Delete;

 

2. Обновление одной записи в наборе данных

Если данные в таблице изменились другими пользователями в сети или с помощью вызова запросов на обновление или удаление данных, то увидеть такие изменения на форме можно с помощью метода обновления набора данных Requery(), которым мы пользовались постоянно дол этого. Однако данный метод перегружает весь набор данных, даже если нам нужно было обновить только текущую запись, с которой работает пользователь. Перегрузка всего набора данных требует времени, нагружает сервер и сеть. Данной операцией стоит пользоваться только в крайних случаях, когда другими средствами получить актуальные данные невозможно.

Технология ADO содержит метод обновления только текущей записи в наборе данных. Для использования такого обновления нужно подключить модуль ADOInt.

Если в наборе данных отображается только одна таблица и эта таблица имеет ключевое поле, то обновить текущую запись можно с помощью кода:

 

//в качестве набора данных указана таблица ADOTable

//аналогично можно использовать компонент ADOQuery,

//если запрос в нем отбирает данные из одной таблицы

ADOTable1.UpdateCursorPos;

ADOTable1.Recordset.Resync(adAffectCurrent, adResyncAllValues);

ADOTable1.Resync([]);

 

Если в наборе данных отображаются данные из нескольких таблиц, связанных одним запросом, то обновить текущую запись можно с помощью кода:

 

//задаем дополнительные параметры перед обновлением

//задаем «главную» таблицу в наборе данных

ADOQuery1.Properties['Unique Table'].Value:='stud';

ADOQuery1.Properties['Update Resync'].Value:= adResyncAll;

//к тексту запроса добавляем условие в виде:

//ключевое поле «главной таблицы» = ?

ADOQuery1.Properties['Resync Command'].Value:= AdoQuery1.Sql.Text+' where id_stud=?';

 

//далее выполняем обновление записи как показано ранее

ADOQuery1.UpdateCursorPos;

ADOQuery1.Recordset.Resync(adAffectCurrent, adResyncAllValues);

ADOQuery1.Resync([]);

 

3. Работа с таблицами в отношении «многие-ко-многим»

В реальных базах данных, разрабатываемых для разных предметных областей, часто встречается ситуация, когда таблицы связываются отношением "многие-ко-многим". Такая связь предполагает наличие в БД специальной промежуточной таблицы, через которую связываются две основные.

Например, пусть имеются таблицы "Tovar" и "Client". Так как один товар могут купить несколько клиентов, а один клиент может купить несколько товаров, то такие таблицы необходимо связать соотношением "многие-ко-многим". Для этого создается вспомогательная таблица, например для учета продаж с указанием даты продажи и количества проданного товара под именем "Uchet". Схема БД показана на рисунке:

 

 

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

В этом случае таблицу tovar отображаем на форме с помощью компонента ADOTable. Данные о клиентах и продажах отображаем с помощью запроса по связанным таблицам вида:

 

SELECT client, data, kolvo

FROM client INNER JOIN uchet

ON client.id_client=uchet.id_client

AND uchet.id_tovar=:id_tovar

 

Форма просмотра может иметь вид:

 

 

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

В этом случае таблицу client отображаем на форме с помощью компонента ADOTable. Данные о товарах и продажах отображаем с помощью запроса по связанным таблицам вида:

 

SELECT data, kol, nazv_tovar

FROM tovar INNER JOIN uchet

ON tovar.id_tovar=uchet.id_tovar

AND uchet.id_client=:id_client

 

Форма просмотра может иметь вид:

 

 

Принцип работы с подчиненным запросом ничем не отличается от обычного подчиненного запроса:

- при переходе по главной таблице (событие OnAfterScroll компонента ADOTable) необходимо в подчиненный запрос в качестве значения параметра передавать ключевое поле из главной таблицы;

- перед удалением данных их подчиненного запроса необходимо указывать, какая из связанных таблиц в запросе считается «главной», см. выше ADOQuery1.Properties['Unique Table'].Value;

- при добавлении новой записи в промежуточную таблицу необходимо программно заполнять в новой записи связанные поля значениями ключевых полей из главных таблиц (в нашем примере поля id_tovar и id_client);

- после любого изменения данных (добавление, изменение, удаление) необходимо обновлять данные в подчиненном запросе методом Requery().

 

Вопросы для самоконтроля.

1. Как отобразить данные из связанных таблиц в одном наборе данных?

2. Если набор данных содержит несколько таблиц, как правильно организовать удаление данных из него?

3. Для чего нужно обновлять одну запись в наборе вместо обновления всех записей?

4. Как связать две таблицы отношением "многие-ко-многим" в БД?