Автор работы: Пользователь скрыл имя, 26 Января 2013 в 14:13, лекция
Программирование баз данных - очень большой и серьезный раздел самого что ни на есть практического программирования. На предыдущем курсе "Введение в программирование на Delphi" мы лишь коснулись этой темы, затронули даже не верхушку айсберга под названием Базы Данных, а только его макушку. Между тем, многие программисты большую часть своего времени тратят именно на проектирование баз данных и разработку приложений, работающих с ними. Это неудивительно - в настоящее время каждая государственная организация, каждая фирма или крупная корпорация имеют рабочие места с к
С модифицирующими командами дело обстоит иначе. Команда CREATE TABLE принадлежит к той части SQL, которая называется DDL (Data Definition Language) - Язык Определения Данных. Этот язык предназначен для изменения структуры базы данных. Команды INSERT, DELETE, UPDATE относятся к DML (Data Manipulation Language) - Язык Обработки Данных, предназначенный для модификации данных. Эти команды объединяет то, что они не возвращают результирующий набор данных. Чтобы выполнить эти команды, нужно присвоить соответствующий SQL-запрос свойству SQL, а затем вызвать метод ExecSQL.
Синтаксис создания таблицы несложный:
CREATE TABLE <TableName>
(<ColumnName> <DataType> [<Size>], …)
Здесь, TableName - имя таблицы; ColumnName - имя столбца; DataType - тип данных и Size - размер, который указывается для некоторых типов данных, например, строки. Описания столбцов таблицы разделяются запятыми. В различных СУБД синтаксис и типы данных SQL могут отличаться. Поэтому запрос, прекрасно работающий в одной СУБД, может вызвать ошибку в другой. Чтобы избежать ошибок, рекомендуется везде использовать типы ANSI, являющиеся стандартом SQL. Увы, но этих типов очень немного. Рассмотрим их:
Таблица 9.3 . Типы ANSI | |
Тип |
Описание |
CHAR (CHARACTER) TEXT |
Строковые типы данных. Обычно имеют размер до 255 символов. Требуют указания размера. |
INT (INTEGER) |
Целое число. Размер не указывается. |
SMALLINT |
Короткое целое. Размер не указывается. |
FLOAT REAL |
Вещественные числа. Размер не указывается. |
Как видите, многих типов просто нет. Вместо логического типа, вероятно, придется использовать строковый тип с размером в один символ; при этом 'Y' или '1' будут означать истину, а 'N' или '0' - ложь. Программисту придется самостоятельно делать проверку на это значение. Нет типа Memo. Нет автоинкрементного типа. Однако стандартные типы непременно будут корректно работать в любой СУБД.
Ниже приведен пример создания и открытия простой таблицы. В приложении должен иметься компонент ADOQuery, а если есть сетка и навигатор, то и DBSource. Для подключения к нужному провайдеру данных желательно использовать компонент TADOConnection. В его свойство ConnectionString нужно прописать строку подключения, например:
Provider=MSDASQL.1;Persist Security Info=False;Data Source=Файлы dBASE
Эту строку можно ввести программно, или создать подключение при проектировании (я так и сделал). Поставщик данных в примере оставлен по умолчанию - Microsoft OLE DB Provider for ODBC Drivers, а в качестве источника данных (вкладка "Подключение" редактора связей TADOConnection) используются файлы dBase. Не следует забывать и про свойство LoginPrompt, которое следует переводить в False, чтобы программа не запрашивала имя пользователя и пароль при каждом подключении. А также нужно сразу открыть TADOConnection, установив его свойство Connected в True. В свойстве Connection компонента TADOQuery следует выбрать ADOConnection1.
Пример реализован, как событие нажатия на кнопку:
procedure TfMain.Button1Click(Sender: TObject);
var
s: String;
begin
{Создаем текст запроса}
s := 'CREATE TABLE MyTab(Key1 INT, Name CHAR(20), '+
' MyFloat FLOAT, MyDate DATE)';
{Создаем таблицу}
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add(s);
ADOQuery1.ExecSQL;
{Открываем таблицу}
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('SELECT * FROM MyTab');
ADOQuery1.Open;
Как видите, создается четыре поля - целый тип, строковый размером 20 символов, вещественный и тип Дата. Последний тип не входит в стандартное описание ANSI-типов, тем не менее, работает в большинстве СУБД. Можете также поэкспериментировать и с типом BOOLEAN (Логический).
Итак, в переменную s мы вносим строку записи SQL-запроса. Затем очищаем свойство SQL, на случай, если там уже имелся запрос. Далее этот запрос мы заносим в свойство SQL, и методом ExecSQL выполняем его. С открытием таблицы мы уже неоднократно сталкивались. В результате выполнения кода создается и открывается файл MyTab.dbf, который находится в той же папке, что и приложение.
Древовидные структуры не относятся напрямую к программированию баз данных, тем не менее, программисту нередко приходится "изобретать велосипед", придумывая различные решения сохранения таких структур в таблице, и обратное их считывание в дерево.
Типичный пример дерева - всем знакомое дерево каталогов. Примеров таких структур множество - это могут быть отделы в каком-либо учреждении или разделы библиотеки. Посмотрим на рисунок с фрагментом дерева разделов библиотеки:
Рис. 10.1. Дерево разделов
Основная сложность хранения деревьев в таблице - это то, что мы не знаем заранее, какова будет глубина вложенности разделов. Можно было бы создать таблицу с 10 полями, например. Но если вложенных разделов будет меньше, то таблица будет неэффективна - останется много пустых полей. А если больше - ограничивать пользователя?
Самый простой способ сохранения структуры дерева и ее считывания обратно - воспользоваться тем, что дерево - это список узлов, и имеет хорошо знакомые нам методы:
//сохраняем в файл:
TreeView1.SaveToFile('myfile.
//читаем из файла:
TreeView1.LoadFromFile('
Однако этот способ имеет массу недостатков. Во-первых, в результате получим простой текстовый файл, в котором вложенные узлы располагаются ниже родителя и имеют отступ. Пользователь легко может случайно или намеренно испортить такой файл, отредактировав или просто удалив его с диска, и программа будет работать с ошибками. Во-вторых, обычно древовидная структура тесно связана с другими данными, например, таблица отделов предприятия связана со служащими этого предприятия - запись каждого служащего имеет ссылку на отдел, где он работает. Если структуру предприятия хранить в простом текстовом файле, то такую связь сложно будет обеспечить.
Когда программист впервые
Рецептов работы с деревьями в базах данных много, мы рассмотрим лишь один из них, достаточно эффективный и в то же время простой. Смысл этого способа состоит в том, чтобы в каждой записи таблицы сохранять номер узла раздела, номер его родителя, если он есть, и название узла. В случае если узел не имеет родителя (главный узел, например, "Художественная литература" в рисунке 10.1), то в соответствующее поле запишем ноль.
Для реализации примера нам потребуется новая база данных. Загрузите MS Access и создайте базу данных "TreeBD", а в ней таблицу "Razdels". Вообще-то, в базе данных MS Access как таблицы, так и поля могут иметь русские названия, однако мы будем использовать средства SQL, который не всегда корректно обрабатывает русские идентификаторы. Кроме того, данный способ можно использовать в любой СУБД, а далеко не все из них так предупредительны, как MS Access, поэтому название таблицы и ее полей выполним латиницей.
Таблица будет иметь три поля:
Таблица 10.1 . Поля таблицы "Разделы" | |||
№ |
Имя поля |
Тип поля |
Дополнение |
1 |
R_Num |
Счетчик |
Ключевое поле |
2 |
R_Parent |
Числовой |
Целое |
3 |
R_Name |
Текстовый |
Длина 50 символов |
Созданную базу данных сохраните в папке, где будем разрабатывать наш проект (не забудьте сделать резервную копию пустой базы данных на всякий случай.).
Далее создадим в Delphi новый проект и простую форму:
Рис. 10.2 . Форма для работы с деревом
Как всегда, назовите форму fMain, в свойстве Caption напишите "Реализация сохранения дерева в БД", модуль формы сохраните как Main, а проект в целом назовите, например, TreeToBD. Сделанная база данных TreeBD должна быть в той же папке, что и проект.
Далее установите компонент TreeView (дерево) с вкладки Win32. Его свойству Align присвойте alLeft, чтобы дерево заняло весь левый край. Затем можете установить сплиттер - разделитель, ухватившись за который пользователь сможет менять ширину дерева. Компонент Splitter находится на вкладке Additional и его свойство Align по умолчанию равно alLeft - разделитель "прилепится" к правому краю дерева.
Правее установите сетку DBGrid с вкладки Data Controls, и его свойству Align присвойте alClient, чтобы сетка заняла все оставшееся место. Ни главное меню, ни панель инструментов нам здесь не потребуются, используем лишь два всплывающих PopupMenu - первый для дерева, второй для сетки (выберите соответствующие PopupMenu в свойстве PopupMenu этих компонентов).
Далее с вкладки ADO нам потребуется компонент ADOConnection для соединения с базой данных, таблица ADOTable и запрос ADOQuery для вспомогательных нужд. С вкладки Data Access - компонент DataSource, для связи сетки с таблицей. Подключите ADOConnection к базе данных и откройте соединение (см. лекцию №2). Таблицу подключите к ADOConnection (свойство Connection), затем выберите в свойстве TableName нашу таблицу "Razdels", а свойство Name переименуйте в tRazdels - так будем обращаться к таблице. Для удобства отображения названия полей откройте редактор полей таблицы (дважды щелкнув по ней), добавьте все поля и у каждого поля измените свойство DisplayLabel, соответственно, на "№", "Родитель" и "Название". Не забудьте открыть таблицу.
Компонент DataSource подключите к tRazdels, а сетку - к DataSource, в сетке должны отобразиться поля. Кроме того, переименуйте свойство Name запроса ADOQuery1 в Q1, ведь нам часто придется обращаться к нему по имени. Запрос также подключите к ADOConnection, но делать его активным не нужно.
На этом приготовления закончены.
Работа с деревьями состоит из двух этапов:
В этом разделе лекции разберем первый этап. Щелкните дважды по компоненту PopupMenu1, который "привязан" к дереву, и создайте в нем следующие разделы:
Все эти команды относятся к работе с разделами дерева. Прежде всего, создадим обработчик для команды "Создать главный раздел". Листинг процедуры смотрите ниже:
{Создать главный раздел}
procedure TfMain.N1Click(Sender: TObject);
var
s: String; //для получения имени раздела (подраздела)
NewRazd: TTreeNode; //для создания нового узла дерева
begin
//вначале очистим s
s:= '';
//Получим в s имя нового раздела:
if not InputQuery('Ввод имени раздела',
'Введите заголовок раздела:', s) then Exit;
//снимаем возможное выделение у дерева:
TreeView1.Selected:= nil;
//создаем главный раздел (ветвь):
NewRazd:= TreeView1.Items.Add(TreeView1.
//Сразу же сохраняем его в базу:
tRazdels.Append; //добавляем запись
tRazdels['R_Parent']:= 0; //не имеет родителя
//присваиваем значение созданного раздела:
tRazdels['R_Name']:= NewRazd.Text;
//сохраняем изменения в базе:
tRazdels.Post;
end;
Разберем код. Переменная NewRazd имеет тип TTreeNode, к которому относятся все разделы и подразделы (узлы) дерева. В текстовую переменную s с помощью функции InputQuery() мы получаем имя нового главного узла. Функция имеет три строковых параметра:
Если переменная, передаваемая в качестве третьего параметра, пуста, то поле ввода будет пустым. Если же в ней содержался текст - он будет выведен как текст "по умолчанию". Функция возвращает True, если пользователь ввел (или изменил) текст, и False в противном случае. В результате работы функции для пользователя будет выведено простое окно с запросом:
Рис. 10.3 . Окно функции InputQuery()
Далее строкой
TreeView1.Selected:= nil;
мы снимаем выделение, если какой либо раздел был выделен, ведь мы создаем главный раздел, не имеющий родителя. Свойство Selected компонента TreeView указывает на выделенный узел и позволяет производить с ним различные действия, например, получить текст узла:
TreeView1.Selected.Text;
А присваиваемое значение nil (ничто) снимает всякое выделение, если таковое было. Далее мы создаем сам узел:
NewRazd:= TreeView1.Items.Add(TreeView1.
Разберем эту строку подробней. Переменная NewRazd - это новый узел дерева. Каждый узел - объект, обладающий своими свойствами и методами. Все узлы хранятся в списке - свойстве Items дерева TreeView, а метод Add() этого свойства позволяет добавить новый узел. У метода два параметра - выделенный узел (у нас он равен nil) и строка текста, которая будет присвоена новому узлу. Таким образом, в дереве появляется новый главный узел.
Затем мы сохраняем его в базу данных, предварительно добавив в таблицу новую запись:
tRazdels.Append; //добавляем запись
tRazdels['R_Parent']:= 0; //не имеет родителя
//присваиваем значение созданного раздела:
tRazdels['R_Name']:= NewRazd.Text;
//сохраняем изменения в базе:
tRazdels.Post;
Вы помните, что такие методы, как Append или Insert автоматически переводят таблицу в режим редактирования, поэтому вызывать метод Edit излишне?
Обратите внимание на то, что мы сохраняем ноль в поле "R_Parent", так как это - главный раздел, не имеющий родителя. Свойство Text нового узла NewRazd содержит название нового узла, которое мы присваиваем полю "R_Name".