Как стать автором
Обновить

Виртуальная СУБД. Язык определения данных (DDL)

Уровень сложностиСредний
Время на прочтение25 мин
Количество просмотров555

Эта статья является продолжением серии статей посвященной новой системе разработки клиентских приложений KISS Virtual XML DBMS.

Предполагается, что Вы знакомы с реляционной моделью данных и языком SQL. В статье используются понятия и терминология, определенные в предыдущих статьях.

Виртуальная СУБД

Виртуальная СУБД является универсальным объектным инструментом для работы с любыми реляционными базами данных. Она была создана для обеспечения реальной независимости разработанного Вами Приложения от физической организации данных на внешних носителях.

Пользовательское Приложение ничего "не знает" о физической организации базы данных. Оно имеет дело только с виртуальной базой данных, к которой обращается посредством средств виртуальной СУБД.

Виртуальная база данных состоит из виртуальных таблиц, каждая запись которых представлена не в виде линейного списка полей, а в виде XML документа, имеющего иерархическую структуру произвольного уровня вложенности. Конечные узлы (атрибуты) этой структуры привязываются к соответствующим им физическим полям. Виртуальная база данных может одновременно состоять из физических таблиц различных реляционных СУБД, при этом связанных с другими таблицами таким же способом, как и однородные таблицы.

В любой момент времени можно сменить тип физической таблицы, выбрав другую СУБД. Для этого нужно использовать Конструктор таблиц (пункт <DB Workbench> Основного меню Системы). Смена типа физического источника никаким образом не отразится на Вашем Приложении, поскольку в нем нет прямых обращений к физическим данным.

Для того чтобы создать универсальную СУБД, совместимую с существующими реляционными СУБД и удовлетворяющую стандартам, принятым в настоящее время для систем управления базами данных, пришлось разработать свой собственный стандарт.

Виртуальная СУБД является не просто объектной оболочкой над физическими СУБД. Она взяла на себя выполнение наиболее сложных функций, традиционно реализованных средствами физических реляционных СУБД, а именно управление связями между таблицами, создание и выполнение сложных SQL-запросов, управление доступом к базе данных и ряд других функций, которые будут описаны в следующих статьях. С этими функциями виртуальная СУБД справляется значительно эффективнее, чем это делают любые реляционные СУБД, причем, это делается на стороне Клиента, а не на стороне Сервера!

Виртуальная база данных по своей природе является реляционной XML базой данных, которая обеспечивает хранение и обработку XML-документов, размещенных в связанных между собой виртуальных таблицах.

Виртуальная СУБД представлена двумя базовыми классами:

  • tblschema - схема таблицы (язык определения данных)

  • recset - набор записей (язык манипулирования данными)

Таким образом, виртуальная СУБД является объектной реляционной системой управления XML базой данных. В связи с этим, стоит упомянуть СУБД PostgreSQL, в которой реализовано наследование схемы таблицы, что стало основанием классифицировать данную СУБД как объектно-реляционную. Насколько она объектная, можно узнать из раздела "Наследование" официальной документации по данной СУБД. Я не буду комментировать эту информацию (выводы сделайте сами, прочитав данный раздел), а только замечу, что основным препятствием для создания полноценного наследования схемы таблицы является использование в данной СУБД (и только в ней!) глобальных имен отношений (индексов).

Виртуальная СУБД является чисто объектной, что обеспечивает работу с любой используемой физической СУБД (в том числе и PostgreSQL) исключительно объектными средствами.

Классическая база данных любой реляционной СУБД, с точки зрения Системы, являются всего лишь частным случаем XML базы данных (если использовать одноуровневую структуру для представления записи таблицы).

Кроме хранимых в базе данных документов, виртуальная СУБД позволяет создавать производные документы, созданные на основании элементов и атрибутов из связанных между собой хранимых документов.

Язык определения данных (DDL - Data Definition Language)

 Как известно, любая физическая СУБД в процессе своей работы использует метаданные, описывающие состав и структуру баз данных, таблиц, представлений, хранимых процедур и прочих элементов СУБД. Эти метаданные, в подавляющем большинстве, хранятся в виде системных таблиц той же самой СУБД. Каждая СУБД используют собственные уникальные форматы для хранения метаданных, да и сам состав метаданных отличается для разных СУБД.

Поскольку одной из целей создания виртуальная СУБД было обеспечение ее независимости от конкретных физических СУБД, пришлось создать собственный универсальный инструмент для определения и корректировки стандартизированных метаданных. Причем этот инструмент должен использоваться на стороне Клиента, а не стороне Сервера какой либо конкретной СУБД. Таким инструментом стал базовый класс tblschema, который фактически представляет собой объектную реализацию языка определения данных. Базовый класс tblschema включает в себя определение структуры логической записи (объект .Rec базового класса element), список стандартных представлений логической таблицы (объекты базового класса view) и список логических связей данной таблицы с другими логическими таблицами XML базы данных (объекты базового класса link).

Рассмотрим подробнее схему логической таблицы с точки зрения соответствия составляющих ее объектов средствам и понятиям, используемых в реляционных СУБД.

Схема таблицы (базовый класс tblschema)

Схема таблицы предназначена для определения структуру хранимых в таблице XML-документов и их связей с хранимыми XML-документами в других таблицах или внутри одной и той же таблицы (внутренние связи). Виртуальная СУБД позволяет с помощью декларативных средств связывания (binding) создавать произвольное количество производных XML-документов или набора XML-документов, собранных из различных источников (хранимых XML-документов, XML-документов в любых объектах, а также других производных XML-документов).

С точки зрения виртуальной СУБД, физическая СУБД представляет собой только набор физических таблиц и их индексов. Определения связей между физическими таблицы, сделанные в схеме таблицы не сохраняются в словаре физической СУБД, а если эти определения уже присутствуют в словаре, то они просто игнорируются. То есть, физическая СУБД с точки зрения виртуальной  СУБД не является реляционной. Связывание таблиц (например, для выполнения сложных запросов) осуществляется Системой на основании определений связей, сделанных в схеме виртуальной таблицы. Физические таблицы используются Системой лишь для эффективного хранения больших объемов XML-документов, а индексы - для максимально быстрого отбора необходимого для работы ограниченного набора XML-документов, удовлетворяющих условиям отбора, определенным в запросе к базе данных.

Привязка к физической таблице (свойство .:AsClass)

Базовый класс tblschema имеет квази-родителя, в качестве которого выступает определение привязанной к данной схеме физической таблицы, взятое из метаданных соответствующей этой таблице физической СУБД.

в свойстве .:AsClass подложки схемы указывается путь до физической таблицы в виде <КодСУБД>/<ИмяБазыДанных>/<ИмяТаблицы>:

oSchema.:AsClass = "M/Banking/PaymOrders"

Для отладки были использованы следующие СУБД:

  • M - MySQL

  • S - SQLServer

  • O - Oracle

  • F - Firebird

  • P - PostgreSQL

  • V - Visual FoxPro

Базовый класс tblschema может иметь дочерние классы, но в них нельзя переопределить свойство .:AsClass. Переопределить свойство .:AsClass (назначить другую физическую таблицу) можно только в корневом классе. Все дочерние классы наследуют значение этого свойства из корневого класса и при смене физической таблицы автоматически будут привязаны к новой таблице. Использование дочерних схем особенно полезно в случае, если записи таблицы содержат разнотипные документы, которые при этом имеют некоторые общие для всех документов элементы и атрибуты. Имеет смысл в корневую схему включить определение только общих элементов и атрибутов, а в дочерних схемах доопределить элементы и атрибуты, специфические для конкретных документов.

В свою очередь, базовый класс tblschema является квази-родительским классом для базового класса recset. При создании объекта базового класса recset, этот объект наследует из привязанной к нему схемы общие для указанных базовых классов определения. Привязка объекта recset к соответствующей ему схеме происходит при его создании, при этом имя класса выбранной схемы запоминается в свойстве .:AsClass его подложки и не может быть переопределено:

oRecSet.:AsClass = "tblschema.ИмяКласса"

Базовый класс recset будет подробно описан в следующей статье, посвященной языку манипулирования данными, основой которого является этот базовый класс.

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

Привязка виртуальной таблицы к физической таблице
Привязка виртуальной таблицы к физической таблице

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

Панель инструментов в верхней левой части формы позволяет выполнять следующие функции (кнопки слева направо):

  • добавить новый атрибут

  • добавить новый элемент

  • доопределить пустой элемент

  • добавить или изменить определение связи (вызов Мастера связи)

  • добавить или изменить определение представления (вызов Мастера представления)

  • удалить текущий узел дерева (атрибут, элемент, связь или представление)

  • сохранить текущий элемент или виртуальную таблицу в качестве класса

  • вызвать Редактор элементов объекта

  • вызвать Конструктор таблиц для создания или корректировки физической таблицы, назначенной данной схеме.

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

Панель инструментов синхронизирована с текущим узлом дерева виртуальной таблицы. Кнопки этой панели становятся доступными, если только функции этих кнопок применимы для типа текущего узла.

Привязка виртуальной таблицы к физической таблице
Привязка виртуальной таблицы к физической таблице

Определение структуры записи (объект .Rec)

Структура записи виртуальной таблицы, представлена объектом базового класса element (объект c именем .Rec). Название этого базового класса подчеркивает его соответствие понятию "элемент" XML модели данных. С этой же целью свойства объекта element были названы атрибутами. Вновь созданный  объект element является пустым, т.е. не содержит никаких вложенных в него элементов объекта. Поскольку объект element предназначен исключительно для хранения данных, то он не может иметь процедур. В него можно добавлять только атрибуты и другие объекты element.

Базовый класс element может быть использован для сознания пользователем собственных классов. Причем, имена этих классов могут быть не только глобальными (как у всех других классов) но, и определены в специальном контейнере (базовый класс xmlschema). Имя этого класса определяет уникальное пространство имен, которое нужно указывать при обращении к такому классу:

element.[ИмяXMLсхемы:]ИмяКласса

Таким образом, element является объектным образом XML-документа. Чтобы представить этот образ в виде реального XML-документа, нужно всего лишь вызвать метод .:XMLText() подложки объекта element:

cXMLText = oRec.:XMLText()

Представление записи таблицы в виде структуры - это самое естественное для нее представление. Линейный список полей не отображает смысловую группировку полей - для этого приходится использовать имена полей или какие-либо другие приемы. Все оперативные и справочные данные основаны на каких-либо документах, в которых визуальными средствами уже обозначена их структура. Более того, входные данные часто уже представлены XML-документами или плоскими файлами (которые легко преобразовать в структуры).

Для дальнейшего изложения нам потребуется полное описание подложки атрибута. Ниже представлен список элементов подложки атрибута.

u :Access() - событие возникает при чтении значения атрибута

l :Assign(uValue) - событие возникает при присвоении нового значения

c :Name /* имя атрибута

c :MemberType /* тип атрибута:

  • A - Attribute

  • T - Text

  • L - Link

c :As /* список допустимых классов (всегда element)

c :DataType /* тип данных атрибута:

  • c - char /* строка символов

  • h - hex  /* двоичная строка символов

  • n - numeric /* вещественное число

  • i - integer /* целое число

  • l - logical /* логическое значение

  • d - date /* дата

  • t - time /* время

  • s - stamp /* штамп (дата, время, часовой пояс и номер пользователя)

  • k - keystamp /* штамп данной записи

  • r - recstamp /* штамп записи, связанной с данной записью

  • p - updstamp /* штамп последнего изменения данных записи

  • e - extension /* двоичная строка переменной длины

  • o - object /* ссылка на связанный элемент (link)

u :DefaultValue /* значение по умолчанию

c :Length /* определение размерности атрибута:

  • 0

  • FixedLength

  • MinLength[,MaxLength]

  • Width[,Dec]

  • c :Tip /* список допустимых значений

    c :Inherited /* имя наследуемого класса

    c :Status /* статус атрибута:

    • N - New

    • C - Class

    c :Comment /* любые комментарии

    c :BoundTo /* путь до привязанного атрибута или выражение

    u :Value /* текущее значение атрибута

    l :Changed /* признак измененного значения

    l :Null /* признак нулевого значения

    l :NullAllowed /* признак допустимости нулевого значения

    l :Protected /* признак запрета переопределения

    l :ReadOnly /* признак "только чтение"

    l :Hidden /* признак скрытого атрибута

    l :Unsigned /* признак положительного числа

    l :Field /* имя привязанного поля таблицы

    Привязка (bounding) атрибутов (:BoundTo)

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

    Можно использовать атрибуты, которые непосредственно не связаны с полями таблицы. Атрибут может быть привязан с помощью свойства подложки атрибута :BoundTo к какому-либо атрибуту, непосредственно привязанному к полю таблицы. При обращении для чтения и записи к такому атрибуту происходит полная переадресация на указанный атрибут. Имена таких атрибутов используются в качестве альтернативных имен для привязанных к полям записи атрибутов.

    Можно получить вычисляемый атрибут, если в свойстве подложки атрибута :BoundTo указать не ссылку на атрибут, а выражение над одним или несколькими атрибутами той же записи. Вычисляемый атрибут доступен только для чтения.

    Следует заметить, что определенные в схеме вычисляемые или альтернативные атрибуты наследуются всеми объектами recset, использующими данную схему и не могут быть удалены. Если эти атрибуты не предполагается использовать во всех указанных выше объектах, то их можно определить не в схеме, в самом объекте recset.

    Тип атрибута (:MemberType)

    Одни и те же данные XML-документа могут быть представлены либо в виде атрибута, либо в виде элемента, например: Text = "Привет!" или <Text>Привет!</Text>. С точки зрения объектной модели никакой разницы между этими типами нет - все они являются свойствами. Однако атрибуты в XML-документе должны быть размещены сразу после открывающего тэга и могут следовать в любом порядке, а порядок элементов строго определен и все они должны следовать после атрибутов. В связи с этим, атрибуты объекта element пришлось разделить на два типа: A- Attribute и T - Text, в зависимости от того, как они представлены в исходном XML документе.

    Объект element может иметь свойства-ссылки (тип L - Link), которые не относится к атрибутам, и непосредственно не связанны с хранением данных XML-документа. Они предназначены для ссылок из данного документа на другие документы или элементы этих документов. В XML модели аналогом этого свойства-ссылки является пустой элемент (<Element></Element>). Пустой элемент используется для того, чтобы зарезервировать имя элемента для будущего определения и использования.

    Тип данных (:DataType)

     В подложке атрибута появились новые типы данных, отсутствующие в подложке свойства.

    Тип данных h - hex был добавлен, чтобы выделить двоичное представление строк. Это связано с тем, что хранимые символьные данные подразделяются на символьные и двоичные, в то время как в языке ULCA двоичные данные рассматриваются как обычные символьные данные. Отличие двоичных данных от символьных данных заключается в том, что для первого типа данных не выполняется автоматическое преобразования кодовых страниц.

    Список типов пополнен новыми типами данных (k, r, p, e), которые определяют не столько тип хранимых данных, сколько ту роль, которую они играют в организации хранения и выборки записей физической таблицы. Это системные свойства и, в строгом смысле, атрибутами не являются.

    Тип данных k - keystamp (первичный ключ записи) является подтипом базового типа данных stamp, содержащего кроме хронологических данных (дату, время, часовой пояс) еще и номер пользователя, создавший этот штамп.

    Поле указанного типа данных доступно только для чтения. Виртуальная СУБД автоматически заполняет это поле значением системного ключа, содержащего номер пользователя, дату и время создания новой записи таблицы. Запись может содержать только одно поле типа keystamp. Значение этого поля является уникальным символьным идентификатором текущей записи и используется для ссылки на данную запись из других записей этой же или другой таблицы.

    В связи с особой ролью первичного ключа, разработчики реляционных СУБД предложили свой вариант создания такого ключа. Практически во всех СУБД используется специальный тип целочисленных данных для автоматической генерации первичного ключа записи, например, integer (AutoInc), integer identity, или integer auto_increment, или еще как-то, в зависимости от типа СУБД. Таким образом, при добавлении новой записи, СУБД автоматически увеличивает максимальное значение ключа на единицу или на определенное значение приращения и присваивает полученное значение полю первичного ключа.

    Указанный выше способ автоматической генерации первичного ключа имеет существенный недостаток, заключающийся в том, что ключ автоматически генерируется на стороне Сервера СУБД и неизвестен на стороне Клиента. Чтобы узнать значение этого ключа необходимо после добавления записи каким-то образом найти эту запись и прочитать ее на стороне клиента. Проблеме доступа на стороне клиента к сгенерированному ключу записи посвящены целые главы в документации по СУБД. Значение keystamp автоматически генерируется на стороне Клиента, становится доступным сразу же после успешного добавления новой записи и позволяет избежать указанных выше проблем.

    Тип данных recstamp (ссылка на первичный ключ связанной записи) доступно только для чтения/записи и содержит значение первичного ключа связанной записи. Запись может содержать произвольное число полей типа recstamp.

    Тип данных updstamp (штамп последнего изменения). Поле указанного типа данных доступно только для чтения. При внесении изменений в поля записи, виртуальная СУБД автоматически заполняет это поле значением, содержащим номер пользователя, дату и время изменения. Запись может содержать только одно поле типа updstamp.

    Соответствие между типами  данных атрибутов и типами данных полей записи таблицы (свойство :Length)

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

    • c - char /* строка фиксированной длины :Length = Length

    • v - varchar/text /* строка переменной длины :Length = MinLength, MaxLength/0

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

    • b - binary /* двоичная строка фиксированной длины. :Length = Length

    • q - varbinary/blob /* двоичная строка переменной длины. :Length = MinLength, MaxLength/0

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

    • n - numeric /* число с фиксированной точкой. :Length = Width[,Dec]

    • f - double /* число с плавающей точкой :Length = 0

    Типу данных logical соответствует однобайтовая символьная строка:

    • " " - false

    • "t" - true

    В большинстве физических СУБД имеется тип данных boolean для представления логического значения, однако возникают различные проблемы, например, в операциях сравнения. Если заглянуть под капот MySQL, то можно увидеть, что в словаре этой СУБД для представления логического значения используется вместо типа данных boolean тип данных enum("Y","N"). Разумное, надежное и универсальное решение.

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

    Всем хронологическим типам данных (date, time, stamp) соответствуют строки фиксированной длины (8,9,10 байт). Хронологические типы данных являются строками символов и хранятся в записи таблицы любой СУБД как строки фиксированной длины char(iLength). Тип данных char является универсальным типом данных и поддерживается всеми СУБД. Каждая из реляционных СУБД имеет свой собственный набор хронологических типов данных. Некоторые из этих типов данных не могут быть однозначно преобразованы в указанные выше типы данных. Хранение хронологических типов данных в форме универсального типа данных char позволяет обеспечить независимость представления данных этого типа от особенностей конкретной СУБД.

    Нулевое значение (:NullAllowed,:Null)

     Под нулевым значением поля записи таблицы понимается отсутствие значения (не указано, неизвестно, нет значения). Не нужно путать нулевое значение с пустым значением. Пустое значение - это одно из возможных значений, а не отсутствие такового. Нулевое значение это не значение, а признак того, что значение неизвестно.

    Нулевое значение - это очень полезная возможность для хранимых данных, именно поэтому она и появилось именно в языках программирования для СУБД. В этих языках значению поля таблицы или переменной можно присвоить нулевое значение с помощью именованной константы Null. Именно значение, а не признак нулевого значения. Значение Null как раз и является таким признаком. Решение хранить признак нулевого значения в качестве самого этого значении привело к множеству проблем при использовании нулевых значений в выражениях и операциях сравнения. Нужно очень осторожно использовать нулевые значения в выражениях. Результат таких выражений интуитивно непонятен и может быть совершенно неожиданным.

    В виртуальной СУБД значения полей таблицы представлены в виде значений атрибутов, а нулевое значение - это один из признаков значения атрибута, а именно признак :Null. Этот признак хранится в подложке атрибута, а не в его значении, и доступен для чтения/записи. Поэтому с нулевыми значениями можно работать так же, как и с пустыми ненулевыми значениями. Нулевое значение поля таблицы запоминается в атрибуте в виде пустого значения, а признаку :Null подложки этого атрибута присваивается значение true. Никаких проблем в использовании нулевых значений в выражениях и операциях сравнения не возникает.

    Свойство :NullAllowed определяет, может ли данный атрибут иметь нулевое значение и является объектным аналогом фразы NULL при определении полей физической таблицы реляционной СУБД в команде SQL create table.

    Имя физического поля (:Field).

    Свойство :Field доступно для корректировки только в объекте .Rec схемы таблицы.

    В этом свойстве подложки атрибута хранится привязанное к данному атрибуту имя поля физической таблицы, назначенной в схеме логической таблицы. Типы данных атрибута и соответствующего ему поля должны совпадать или допускать автоматическое преобразование типа данных атрибута в тип данных поля и обратно. Атрибут может быть привязан не к персональному физическому полю, а к квази полю, значение которого, наряду с другими квази полями, хранится в специальном типе физического поля (тип e - extension). Имя квази-поля имеет следующий формат: ExtName.QuasiName.

    Тип данных extension предназначен для определения одного физического текстового поля переменной длины, используемого для хранения значений произвольного количества логических полей записи (квази полей). Количество полей типа данных extension в записи таблицы может быть несколько.

    Тип данных extension(e) является подтипом текста переменной длины q - varbinary(0). Значения квазиполей хранятся в этом контейнере вместе с именем и типом этих полей в специальном формате. Квази поля определяются только в схеме логической таблицы и неизвестны на физическом уровне.

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

    При добавлении в схеме определения нового квазиполя никаких действий со стороны Системы не происходит, но при обращении к значению этого квазиполя виртуальная СУБД возвращает пустое или нулевое (если в определении квазиполя указано  null) значение, поскольку пустые значения квазиполей не хранятся в поле extension.

    Поскольку квазиполя не являются физическими полями таблицы (неизвестны физическим СУБД), то они не могут использоваться в качестве ключей отбора записей таблицы. По этой же причине нельзя в качестве квазиполей использовать поля специальных типов данных (keystamp, recstamp, updstamp, extension). Тем не менее, квазиполя можно использовать для сортировки, фильтрации и связывании записей наравне с физическими полями.

    Использование квазиполей очень эффективно при хранении необязательных данных (разреженные колонки), поскольку пустые значения квазиполей в записях таблицы не хранятся.

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

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

    Привязка атрибутов к полям физической таблицы

    Привязка атрибута к полю физической таблицы или к квази-полю.
    Привязка атрибута к полю физической таблицы или к квази-полю.

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

    Представления (views)

    Далее по тексту мы будем использовать описания некоторых SQL команд. Это делается лишь с целью сравнения средств виртуальной СУБД с аналогичными средствами реляционных СУБД. Система явным образом не использует язык запросов SQL.

    Под представлением в реляционных СУБД понимают хранимую на Севере процедуру,  содержащую команды SQL Select для выборки данных. Эта процедура, как правило, имеет параметры, с помощью которых можно определять условия отбора нужных записей в статье where команды Select. В команде Select чаще всего указан список необходимых полей, для того чтобы ограничить объем выборки. Представление используется для хранения часто используемых стандартных SQL запросов. Основным достоинством представления является то, запрос хранится в уже откомпилированном виде, что сильно сокращает время его выполнения. Основной  недостаток представления - это достаточно жесткое статическое определение запроса, что приводит к необходимости использовать отдельные представления для незначительно отличающихся друг от друга запросов.

    Представления таблиц в виртуальной СУБД существенно отличаются от своих реляционных аналогов и предназначены не только для определения запросов на выборку наборов записей таблицы, но и для определения индексов и логических связей между записями таблиц.

    Для определения представления используется объект базового класса view. Этот объект имеет следующие свойства:

    c .Parameters /* Список параметров отбора (список имен ключевых атрибутов)

    c .IndexName /* Имя физического индекса таблицы

    c .ViewType /* Тип представления

    tip

    R - Regular (default)

    U - Unique

    P - Primary

    endtip

    В свойстве .Parameters хранится список имен ключевых атрибутов символьного, логического или хронологического типа, которые используются в качестве параметров для отбора записей. В любой таблице должен быть уникальный ключ записи (unique key) в виде одного или нескольких ключевых атрибутов. Этот ключ используется для поиска нужной записи таблицы при выполнении операции корректировки или удаления записи. Запись может иметь несколько уникальных ключей. Среди них выбирается один, как правило, самый короткий ключ записи, называемый первичным ключом (primary key), который будет использоваться для ссылки на эту запись из записей других таблиц (а возможно, и из других записей этой же таблицы).

    В качестве первичного ключа рекомендуется использовать атрибут k-типа.

    Свойство .ViewType предназначено для указания Системе, является ли набор ключевых атрибутов, определенный в свойстве .Parameters уникальным ключом записи и какой из этих ключей выбран в качестве первичного ключа. Обратите внимание на то, что если представление определено как уникальное (unique или primary) и содержит несколько имен атрибутов в списке параметров, то это представление будет уникальным, только если при выполнении запроса были указаны все значения параметров запроса. 

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

    par значение 1[, значение 2[,...[значение N]]]

    select * from <имя таблицы>

     where <имя физ. поля для атрибутa1> = значение 1

      [and <имя физ. поля для атрибутa2> = значение 2

      [....

      [and <имя физ. поля для атрибутaN> = значение N]]]

    Таким образом, указав в качестве значения свойства .Parameters  список имен ключевых атрибутов "атрибут1, атрибут2, ..., атрибутN" мы определим указанный выше SQL запрос. А теперь обратим внимание на следующие особенности этого запроса:

    1. В примере запроса используются метасимволы - квадратные скобки, чтобы продемонстрировать вид запроса в случае, если в запросе указаны не все значения для параметров запроса. Это означает, что в случае использования в представлении нескольких ключевых атрибутов, представление определяет сразу несколько разных запросов. Окончательный вариант запроса динамически генерируется Системой в зависимости от количества переданных значений параметров при выполнении запроса с помощью метода .Select() объекта recset.

    2. В выражение для условия отбора используются только операции сравнения на равенство значения поля и соответствующего ему аргумента. Результаты сравнения объединены логическим оператором and. Это самые простые и удобные для реляционных СУБД условия отбора, обеспечивающие максимально высокую скорость выполнения, поскольку в отборе необходимых записей используется только индекс без обращения к данным самих записей. Для ускорения выборки ограниченного набора записей (указана статья where команды select), реляционные СУБД используют индексы по заданным ключевым полям записи. Получив запрос на выборку, Сервер реляционной СУБД пытается найти в своих системных таблицах индексы для указанных в статье where ключевых полей, которые позволили бы отобрать нужные записи без последовательного просмотра всех записей физической таблицы. Теперь сами попытаемся определить индекс, который однозначно использовался бы средствами любой СУБД для выборки записей для указанного выше SQL запроса. Не трудно догадаться, что определение этого индекса будет включать имена физических полей таблицы, назначенным указанным в свойстве .Parameters атрибутам, причем в том же самом порядке, например:

       create [unique | primary] index <имя физического индекса>

        on table <имя физической таблицы >

       (<имя физ. поля для атрибутa1>,

        <имя физ. поля для атрибутa2>,

        ...

        <имя физ. поля для атрибутaN>)

    Таким образом, представление автоматически определяет индекс, который необходимо создать для сокращения времени выполнения запроса.

    3. В качестве списка выбираемых полей в определении запроса указана звездочка, а это означает, что будут выбраны все поля физической таблицы. Список необходимых в запросе полей определяется не в представлении, а в соответствующем данной схеме объекте recset средствами языка манипулирования данными. Таким образом, представление - это рамочное максимально гибкое определение запроса, которое может быть динамически переопределено.

    Индексы в схеме таблицы используются в первую очередь для организации связей между записями таблицы, а уже потом, для определения сортировки по умолчанию для уже выбранного набора записей. Индекс должен иметь список ключей отбора, достаточный для определения указанных выше связей. Причем, эти ключи должны располагаться в начале списка. Кроме этих ключей, в индекс могут включаться дополнительные ключи, которые определят порядок сортировки выбранных записей набора (если этот порядок явно не определен в соответствующем данной схеме объекте recset).

    Может показаться, что условия отбора в выражении where сильно ограничены, поскольку в качестве условия отбора не допускается использовать произвольное выражение. Тем не менее, условие отбора, определенные в схеме таблицы могут быть дополнены произвольным логическим выражением, определенным в свойстве .WhereExpr объекта recset.

    Фактически, статья where будет выглядеть следующим образом:

    where <имя физ. поля для атрибутa1> = значение 1

      /* ----- условия отбора для Сервера СУБД.-----

      [and <имя физ. поля для атрибутa2> = значение 2

      [....

      [and <имя физ. поля для атрибутaN> = значение N

      /* ----- условия отбора, определенное .WhereExpr ----

      [and <сложное логическое выражение для отбора> ]]]]

    Создание и корректировка представления

    Определение представления достаточно простая процедура, однако, для исключения ошибок, определить представление можно только с помощью мастера представлений (Link Builder), показанного на следующем скриншоте. Этот мастер вызывается из Конструктора схемы нажатием кнопки, имеющей ту же иконку, что указанная ниже форма.

    Мастер представления
    Мастер представления

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

     В качестве ключей отбора могут использоваться атрибуты строкового типа с фиксированной длиной, логического или хронологического типа данных. Добавление текущего выбранного из дерева атрибута в список ключей отбора происходит двойным щелчком по этому атрибуту или нажатием клавиши <Enter>. Удалить ключ отбора можно кнопкой <Erase>. Поскольку ключи отбора используются на стороне Сервера, то все они должны быть привязаны к физическим полям. Можно изменить порядок ключей в списке с помощью полоски перемещения.

     

    Привязка представления к физическому индексу

    Привязка представления к физическому индексу
    Привязка представления к физическому индексу

    Связи (links)

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

    Связь между записями разных таблиц или разными записями одной и той же таблицы осуществляется с помощью атрибутов таблицы, которые содержат одинаковые для связанных между собой записей значения. При этом атрибуты могут иметь разные имена в связанных записях. Рассмотрим организацию связи на примере записей таблицы подразделений и таблицы работников. Общим атрибутами, используемым для связи между записями этих таблиц, являются атрибуты, содержащие код подразделения. Допустим, что в таблице подразделений этот атрибут будет иметь имя Code, а таблице работников - Department. В этом случае каждая запись таблицы подразделений связана со всеми записями таблицы работников, в которых значение атрибута Department совпадает со значение атрибута Code в записи таблицы подразделений (связь один ко многим). Каждая запись таблицы работников связана с одной единственной записью подразделений, в которой значение атрибута Code совпадает со значение атрибута Department в записи таблицы работников (связь многие к одному).

    В литературе по реляционным СУБД для связей между записями используется термин "отношение" (relation), например, говорят, что между записями таблицы подразделений и таблицы работников отношение один ко многим. Вместо этого термина будем использовать понятие связь (link), которое определяет отношения между таблицами только со стороны одной из таблиц. В нашем примере со стороны подразделений существует связь один ко многим, а со стороны клиентов - связь многие к одному. Такое разделение отношения на две части удобнее при определении в схеме таблицы связи этой таблицы с другими таблицами базы данных.

    Атрибуты логической записи таблицы, которые используются для связи с записями другой таблицы или с разными записями одной и той же таблицы, называются внешними ключами (foreign keys). В связанной таблице должно быть определено представление со списком ключей отбора, значения которых должны совпадать со значениями соответствующих им внешним ключей. Количество ключей отбора в представлении может быть больше, чем количество внешних ключей.

    В нашем примере внешним ключом в таблице подразделений является атрибут Code, а в таблице работников должно быть определено представление со списком параметров, состоящим только из имени "Department".

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

    Для определения связей логической таблицы с другими таблицами базы данных используются объекты базового класса link, имеющие следующие свойства:

    c .Arguments - Список внешних ключей

    c .LinkType/*Тип связи

    tip

     0 - 1:0 /* один к одному (нулю)

     1 - 1:1 /* один к одному

     2 - 1:M /* один ко многим

     3 - M:1 /* многие к одному

    endtip

    c .Schema/*Имя схемы связанной таблицы

    c .ViewName/*Имя представления в схеме связанной таблицы

    Определение связей достаточно сложная процедура, поэтому в Системе имеется специальный визуальный инструмент (Link Builder), позволяющий легко и безошибочно создать, а точнее, "нарисовать" определение связи. Для исключения ошибок определить связь в Системе можно только с помощью мастера связей, показанного на следующем скриншоте. Этот мастер вызывается из Конструктора схемы нажатием кнопки, имеющей ту же иконку, что указанная ниже форма.

    Мастер связей
    Мастер связей

    Прежде всего, нужно выбрать из списка <Schema> имя схемы виртуальной таблицы, с которой устанавливается связь, а затем из появившегося списка <View> выбрать представление, списку ключей которого (параметрам представления) нужно поставить в соответствие список аргументов (внешних ключей).

    В левой части формы отображаются доступные для определения связей атрибуты (внешние ключи). В качестве внешних ключей могут использоваться атрибуты строкового типа с фиксированной длиной, логического или хронологического типа данных. Назначение выбранных из дерева атрибутов текущему параметру в списке параметров происходит двойным щелчком по этому атрибуту или нажатием клавиши <Enter>. Удалить назначение можно кнопкой <Erase>. Поскольку внешние ключи используются на стороне Клиента, то нет никаких ограничений на связь этих атрибутов с физическими полями или квази-полями. Нажав кнопку <Expr...>, можно определить выражение, определяющее внешний ключ с использованием построителя выражений, как это показано на следующем скриншоте.

    Построителя выражений
    Построителя выражений

    Наконец, нужно выбрать из списка <Link Type> нужный тип связи и нажать кнопку <OK>.

    Проектирование структуры базы данных

    Проектирование структуры базы данных - это сложный и ответственный процесс. Цена ошибок, допущенных на этом этапе создания Приложения, очень высока. Вообще говоря, разработка структуры базы является искусством, точнее искусством нахождения компромисса между противоречивыми требованиями, предъявляемыми к конечному программному продукту. Сам процесс разработки плохо формализован, поскольку имеется слишком много факторов, влияющих на принимаемые решения, и эти факторы, очень часто, индивидуальны для каждого конкретного Приложения.

    Конечно же, есть формальные правила, которыми рекомендуются следовать при разработке структуры базы данных. Например, правила нормализации, которые описаны в любом учебнике по реляционным СУБД. На практике, есть смысл использовать только первые три нормальные формы. Но на то и написаны правила, чтобы их осознано нарушать. Приведу пример такого нарушения.

    Когда встречается отношение многие ко многим, то рассуждать не нужно - требуется отдельная связующая таблица, которая, как минимум, должна содержать ссылки на две связанные этим отношение записи. Эта техническая таблица, но в ней можно хранить данные, характеризующие эту связью и взятые из связанных записей. Налицо нарушение правил, поскольку дублирование данных в нескольких таблицы - это, конечно же, не статья уголовного кодекса, но тяжкое административное нарушение. Такое нарушение оправдано тем, что есть железная уверенность, что данные, размещенные в связующей таблице, корректироваться не будут. Правила, конечно, знать необходимо, но руководствоваться нужно здравым смыслом и интуицией, которая появляется только с опытом. Самый полезный опыт - это опыт, полученный в результате Ваших собственных ошибок.

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

Теги:
Хабы:
0
Комментарии0

Публикации

Ближайшие события