В примерах предыдущей и данной лекций присутствовало много запросов с соединениями двух или более таблиц. Условия соединения задавались предикатами сравнения столбцов таблиц, специфицированных в разделе FROM, и входили в состав логических выражений раздела WHERE (или, реже, раздела HAVING). Поскольку на практике требуются разные виды соединений, в стандарте SQL/92 появилась альтернативная возможность спецификации соединений – соединенная таблица (joined table). Соответствующая конструкция может использоваться в разделе FROM выражения запросов и фактически позволяет строить выражения соединений таблиц. Синтаксические правила построения таких выражений выглядят следующим образом:
joined_table ::= cross_join | qualified_join | natural_join | union_join cross_join ::= table_reference CROSS JOIN table_primary qualified_join ::= table_reference [ join_type ] JOIN table_primary join_specification natural_join ::= table_reference NATURAL [ join_type ] JOIN table_primary union_join ::= table_reference UNION JOIN table_primary join_type ::= INNER | { LEFT | RIGHT | FULL } [ OUTER ] join_specification ::= ON conditional_expression | USING (column_comma_list)
Напомним, что синтаксические правила для table_reference и table_primary были показаны в подразделе лекции 17.
Как показывает сводка синтаксических правил, в SQL поддерживается много вариантов соединений. Чтобы объяснить особенности разных видов соединений на неформальном уровне, требуется очень большой объем текста с большим числом повторений. Поэтому сначала мы приведем достаточно формальное описание порядка определения заголовка и тела результирующей таблицы для всех разновидностей соединений. Фактически это описание напрямую позаимствовано из стандарта SQL:1999 с некоторыми незначительными упрощениями. Затем мы представим ряд иллюстрирующих примеров.
Я считаю, что история современных моделей данных началась с 1989 г., когда группа известных специалистов в области языков программирования баз данных опубликовала статью под названием «Манифест систем объектно-ориентированных баз данных» . К этому времени уже существовало несколько реализаций объектно-ориентированных СУБД (ООСУБД), но каждая из них опиралась на некоторое расширение объектной модели какого-либо объектно-ориентированного языка программирования (Smalltalk, Object Lisp, C++), отсутствовали какие-либо общие подходы.
В не предлагалась единая объектно-ориентированная модель данных, но выделялся набор требований к ООСУБД. Базовыми требованиями являлось преодоление несоответствия между типами данных, используемыми в языках программирования, и типами данных, поддерживаемыми в набравших к тому времени силу реляционных (вернее, SQL-ориентированных) СУБД, а также придание СУБД возможностей хранить в БД данные произвольно сложной структуры. Эти требования сопровождались утверждениями об ограниченности реляционной модели данных и языка SQL и потребности использовать более развитые модели данных.
Под влиянием в 1991 г. возник консорциум ODMG (Object Database Management Group), задачей которого была разработка стандарта объектно-ориентированной модели данных. В течение более чем десятилетнего существования ODMG опубликовала три базовых версии стандарта, последняя из которых называется ODMG 3.0 . На этот документ мы и будем опираться в дальнейшем изложении.
В ответ на публикацию группа исследователей, близких к индустрии баз данных, в 1990 г. опубликовала документ «Манифест систем баз данных третьего поколения» , который во многом направлен на защиту инвестиций крупных компаний-производителей программного обеспечения SQL-ориентированных СУБД. Соглашаясь с авторами относительно потребности обеспечения развитой системы типов данных в СУБД, авторы утверждали, что можно добиться аналогичных результатов, не производя революцию в области технологии баз данных, а эволюционно развивая технологию SQL-ориентированных СУБД.
Для создания новой роли используется оператор CREATE ROLE, определяемый следующим синтаксическим правилом:
CREATE ROLE role_name [ WITH ADMIN { CURRENT_USER | CURRENT_ROLE } ]
Имя создаваемой роли должно отличаться от любого идентификатора авторизации, уже определенного и сохраненного в базе данных. В случае успешного создания роли некоторый authID получает привилегию на исполнение данной роли. Если в операторе CREATE ROLE не содержится раздел WITH ADMIN, то привилегию на исполнение роли получает текущий идентификатор пользователя SQL-сессии, если значение этого идентификатора отлично от NULL; иначе привилегия на исполнение роли дается текущему имени роли сессии.
Если в состав оператора включается раздел WITH ADMIN, то можно выбрать, будет ли являться владельцем роли authID, соответствующий текущему идентификатору пользователя SQL-сессии, или authID, соответствующий текущему имени роли (при условии, что соответствующие текущий идентификатор или текущее имя не содержат NULL). Кроме того, включение этого раздела означает, что authID-владелец роли получает право на передачу привилегии исполнения данной роли другим authID.
В соответствии со стандартом SQL:1999, привилегии, требуемые для выполнения оператора CREATE ROLE, определяются в реализациях SQL. Например, в некоторых реализациях выполнение этой операции разрешается только администратору базы данных.
Существующую роль можно ликвидировать с помощью оператора
DROP ROLE role_name
Для выполнения этой операции требуется, чтобы текущий authID SQL-сессии прямо или косвенно (через цепочку ролей) являлся владельцем ликвидируемой роли. При ликвидации роли, прежде всего, изымается привилегия на ее исполнение у всех authID, которым данная привилегия была ранее передана.
Напомним, что в этом курсе мы не касаемся вопросов интернационализации и локализации языка SQL.
Как будет показано в следующем подразделе, термин роль в языке SQL полностью соответствует своему житейскому смыслу. И в мире баз данных люди большей частью играют чью-то роль, а не представляют себя лично.
В соответствии со стандартом любые зарегистрированные в системе пользователь или роль автоматически являются владельцами части схемы базы данных, имена объектов которой начинаются с соответствующего идентификатора, за которым следует символ <.>.
Для каждого объекта базы данных и для каждого пользователя, обладающего какими-либо привилегиями доступа к этому объекту, требуется хранить список его привилегий. Если учесть еще и возможность передачи привилегий от одного пользователя к другому, то образуется произвольно сложный граф, за которым трудно следить администраторам базы данных.
Чтобы хотя бы немного облегчить чтение данного подраздела, забегая вперед, заметим, что понятия сессии и подключения относятся к сеансу работы клиентского приложения с некоторым сервером SQL-ориентированной базы данных.
В этом разделе мы несколько подробнее рассмотрим специальные реляционные операции реляционной алгебры, такие, как ограничение, проекция, соединение и деление.
Самоссылающиеся столбцы всегда имеют REF-тип. Конкретный REF-тип зависит от двух факторов: структурного типа, ассоциированного с типизированной таблицей: REF-тип всегда связан с некоторым структурным типом;выбранного способа генерации ссылочных значений; эта информация задается в определении структурного типа и не присутствует в спецификации ссылочного типа.
Для объявления местоположения ссылочного типа используется следующий синтаксис:
reference_type ::= REF (referenced_type) [ SCOPE table_name ] referenced_type ::= UDT_name
UDT_name должно задавать имя типа (referenced_type), на экземпляры которого будут указывать значения ссылочного типа. REF-тип может использоваться в качестве типа атрибута структурного типа, и в этом случае referenced_type может быть тем же самым, что и определяемый структурный тип. Во все остальных случаях referenced_type должен являться некоторым существующим структурным типом.
В необязательном разделе SCOPE задается имя типизированной таблицы. Ассоциированным структурным типом этой таблицы должен быть referenced_type REF-типа, в спецификации которого содержится данный раздел SCOPE. Хотя можно было бы ожидать, что значение REF-типа можно использовать для ссылки на строки типизированных таблиц, ассоциированный структурный тип которых является собственным подтипом указанного referenced_type, в SQL такая разновидность ссылок не допускается. Ассоциированный структурный тип таблицы, на строки которой указывают значения REF-типа, должен быть в точности тем же, что и referenced_type этого REF-типа. Но, конечно, можно объявить REF-тип, у которого referenced_type является ассоциированным структурным типом подтаблицы, хотя самоссылающийся столбец этой подтаблицы необходимо наследуется от максимальной супертаблицы семейства таблиц.
Виды ограничений целостности, с которыми мы имели дело в предыдущих разделах этой лекции, образуют иерархию ().
Рис. 16.2. Иерархия видов ограничений целостности
Ограничения целостности, входящие в определение домена, наследуются всеми столбцами, определенными на этих доменах, и являются ограничениями этих столбцов. Кроме того, в определение столбца могут входить определения дополнительных ограничений. Ограничения целостности, входящие в определение столбца (включая ограничения, унаследованные из определения домена), являются ограничениями таблицы, в состав определения которой входит определение данного столбца. Кроме того, в определение таблицы могут входить определения дополнительных ограничений.
Но иерархия видов ограничений целостности этим не исчерпывается. Ограничения целостности, входящие в определение таблицы (включая явные и унаследованные от определения доменов ограничения столбцов), представляют собой ограничения базы данных, частью которой является данная таблица. Кроме того, могут определяться дополнительные ограничения базы данных. В стандарте SQL такие дополнительные ограничения базы данных называются ASSERTION, а мы их будем называть общими ограничениями целостности.
Базовые (реально хранимые в базе данных) таблицы создаются (определяются) с использованием оператора CREATE TABLE. Для изменения определения базовой таблицы применяется оператор ALTER TABLE. Уничтожить хранимую таблицу (отменить ее определение) можно с помощью оператора DROP TABLE.
Замечание: хотя внешне операторы CREATE TABLE, ALTER TABLE и DROP TABLE похожи на соответствующие операторы определения, изменения определения и отмены определения домена, между ними имеется принципиальное различие. Определение домена приводит всего лишь к созданию некоторых новых описателей, входящих в состав метаданных базы данных. Создание базовой таблицы, кроме создания соответствующих описателей, порождает новую область внешней памяти, в которой будут храниться данные, поставляемые пользователями. Тем самым, базовая таблица SQL-ориентированной базы данных является прямым аналогом переменной отношения реляционной модели данных.
Как неоднократно упоминалось выше, при определении столбцов таблицы требуется явно указывать тип данных каждого столбца. Для этого можно использовать описанные выше средства спецификации типа. Но в SQL поддерживается и другой механизм— механизм доменов. Домен является долговременно хранимым, именованным объектом схемы базы данных. Домены можно создавать (определять), изменять (изменять определения) и ликвидировать (отменять определение). Имена доменов можно использовать при определении столбцов таблиц. Можно считать, что в SQL определение домена представляет собой вынесенное за пределы определения индивидуальной таблицы «родовое» определение столбца, которое можно использовать для определения различных реальных столбцов реальных базовых таблиц. В языке SQL обеспечиваются средства определения доменов, изменения и отмены существующих определений.
Теперь мы можем завершить обсуждение разновидностей ссылок на таблицу в разделе FROM. Для удобства повторим синтаксические правила (опустив конструкции, рассмотрение которых отложено на следующие лекции или выходит за пределы материала данного курса):
table_reference ::= table_primary table_primary ::= table_or_query_name [ [ AS ] correlation_name [ (derived_column_list) ] ] | derived_table [ AS ] correlation_name [ (derived_column_list) ] table_or_query_name ::= { table_name | query_name } derived_table ::= (query_expression)
Итак, в самом простом случае в качестве ссылки на таблицу используется имя таблицы (базовой или представляемой) или имя запроса, присоединенного к данному запросу с помощью раздела WITH. В другом случае (derived_table) порождаемая таблица задается выражением запроса, заключенным в круглые скобки. Явное указание имен столбцов результата запроса из раздела WITH или порождаемой таблицы требуется в том случае, когда эти имена не выводятся явно из соответствующего выражения запроса. Обратите внимание, что в таких случаях в соответствующем элементе списка раздела FROM должен указываться псевдоним (correlation_name), потому что иначе таблица была бы вообще лишена имени. Можно считать, что выражение запроса вычисляется и сохраняется во временной таблице при обработке раздела FROM.
Возможно, некоторых читателей смутила рекурсивная природа синтаксических определений, приведенных в этом подразделе. Чтобы определить понятие ссылки на таблицу в разделе FROM оператора выборки, который опирается на спецификацию запроса, нам пришлось ввести более общее понятие выражения запросов, в определении которого используется спецификация запроса. Да, действительно, многие синтаксические конструкции SQL определяются рекурсивно. Но эта рекурсия никогда не приводит к зацикливанию. В частности, раскрутка рекурсии операторов выборки основывается на базовой, не выделяемой отдельными синтаксическими правилами форме, в которой в разделе FROM указываются только имена базовых таблиц.
В этом разделе мы приведем несколько примеров запросов, в разделе FROM которых содержатся выражения запросов (ссылки на порождаемые таблицы, см. подраздел лекции 17).
Напомним, что раздел FROM оператора выборки определяется синтаксическим правилом
FROM table_reference_commalist
Рассмотрим более подробно, какой вид могут иметь элементы этого списка. Для начала приведем полный набор синтаксических правил SQL:1999, определяющий table_reference.
table_reference ::= table_primary | joined_table table_primary ::= table_or_query_name [ [ AS ] correlation_name [ (derived_column_list) ] ] | derived_table [ [ AS ] correlation_name [ (derived_column_list) ] ] | lateral_derived_table [ [ AS ] correlation_name [ (derived_column_list) ] ] | collection_derived_table [ [ AS ] correlation_name [ (derived_column_list) ] ] | ONLY (table_or_query_name)[ [ AS ] correlation_name [ (derived_column_list) ] ] | (joined_table) table_or_query_name ::= { table_name | query_name } derived_table ::= (query_expression) lateral_derived_table ::= LATERAL (query_expression) collection_derived_table ::= UNNEST (collection_value_exression) [ WITH ORDINALITY ]
Мы отложим до лекции 19 обсуждение порождаемых таблиц с горизонтальной связью (lateral_derived_table) и «соединенных таблиц» (joined_table). Кроме того, мы не будем рассматривать в этом курсе конструкции collection_derived_table и ONLY (table_or_query_name), поскольку они относятся к объектным расширениям языка SQL, которые в данном курсе подробно не рассматриваются (на неформальном уровне объектно-реляционный подход обсуждается в лекции 23). Но даже при таких самоограничениях для дальнейшего продвижения нам придется определить несколько дополнительных синтаксических конструкций языка SQL.
Мы сознательно используем здесь термин набор, поскольку в обем случае результатом выполнения оператора выборки не является таблица.
Не следует понимать эту схему таким образом, что запросы к SQL-ориентированной базе данных действительно должны выполняться именно таким образом. Более того, ни одна реализация SQL не придерживается в точности этой схеме. Но как бы реально не выполнялся оператор выборки, результат должен быть таким же, как если бы он получался при точном следовании описываемой схеме выполнения.
Эта категория типов данных связана с объектными расширениями языка SQL, и мы снова отложим подробное обсуждение этого механизма до лекции 23 и рассмотрим его здесь очень коротко. Обеспечивается механизм конструирования типов (ссылочных типов), которые могут использоваться в качестве типов столбцов некоторого вида таблиц (типизированных таблиц). Фактически значениями ссылочного типа являются строки соответствующей типизированной таблицы. Более точно, каждой строке типизированной таблицы приписывается уникальное значение (нечто вроде первичного ключа, назначаемого системой или приложением), которое может использоваться в методах, определенных для табличного типа, для уникальной идентификации строк соответствующей таблицы. Эти уникальные значения называются ссылочными значениями, а их тип – ссылочным типом. Ссылочный тип может содержать только те значения, которые действительно ссылаются на экземпляры указанного типа (т. е. на строки соответствующей типизированной таблицы).
В стандарте SQL:2003 имеется следующее уточнение: «В этой спецификации не проводится различие между NULL-значением булевского типа данных и истинностным значением UKNOWN, являющимся результатом вычисления предиката, условия поиска или булевского выражения; они могут использоваться взаимозаменяемо и означают в точности одно и то же». С моей точки зрения такой подход во многом является некорректным, но я не буду здесь на этом останавливаться.
Конечно, на практике такие ограничения устанавливаются в документации конкретной используемой СУБД, либо даже администратором конкретной базы данных.
В тексте стандарта SQL:1999 используется термин anonymous row type. Следуя соглашениям предыдущего пункта, мы должны были бы использовать термин анонимные типы строк. Но тогда уж точно возникла бы путаница с типами символьных строк. Конечно, можно было бы радикально отказаться от использования термина строка таблицы и вернуться к кортежам отношений. Но, к сожалению, этого сделать нельзя, покольку в SQL таблицы – это не совсем (а иногда и совсем не) отношения, а строки таблиц – не совсем (совсем не) кортежи.
Соответствующие определения сохраняются как часть метаданных базы данных (другими словами, являются частью схемы базы данных).
Требуется, чтобы в определении структурного типа A использовались только те типы, которые были определены ранее.
Понятия ссылочных значений и ссылочных (REF) типов являются, по существу, неразделимыми. В SQL:1999 ссылочный тип может использоваться в качестве типа данных столбцов обычных таблиц, атрибутов структурных типов, SQL-переменных и параметров – словом, везде, где можно использовать другие типы данных SQL. Значения местоположения ссылочного типа всегда являются ссылочными значениями строк типизированных таблиц (т. е. значениями самоссылающихся столбцов этих строк).
Для удобства повторим синтаксис спецификации ссылочного типа:
reference_type_specification ::= system_generated_representation | user_defined_representation | derived_representation system_generated_representation :== REF IS SYSTEM GENERATED user_defined_representation :== REF USING predefined_type derived_representation ::= REF USING (commalist_of_attributes)
Библиография System R насчитывает гораздо больше публикаций, однако не все из них свободно доступны в Internet. Думаю, что мне удалось подобрать представительную выборку.
5.1. Chamberlin D.D., Boyce R.F. SEQUEL: A Structured English Query Language // ACM SIGMOD Workshop Data Descr., Acc. Contr., Proc., Ann Arbol, Mich., May 1974. New-York, 1974.- C. 249-264.
5.2. Chamberlin D.D., Gray J.N., Traiger I.L. View, autorization, and locking in a relational database system // AFIPS Conf. Proc.: Nat. Comput. Conf., Chicago, Ill., May 4-7, 1975. Reston, Virg., 1975.- C. 425-430.
5.3. Gray J.N., Lorie R.A., Putzolu G.R., Traiger I.L. Granularity of Locks in a Large Shared Data Base // 1st Int. Conf. Very Large Data Bases, Framingham, Mass., Sept. 1975. New-York, 1976.- C.428-451.
5.4. Chamberlin D.D., Astrahan M.M., Eswaran K.P., Griffits P.P., Lorie R.A., Mehl J.W., Reisner P., Wade B.W. SEQUEL 2: A Unified Approach to Data Definition, Manipulation, and Control // IBM J. Res. and Dev.- 1976.- 20, N 6.- C. 560-575.
5.5. Astrahan M.M., Blasgen M.W., Chamberlin D.D., Eswaran K.P., Griffits P.P., King W., Lorie R.A., McJones P., Mehl J.W., Putzolu G.R., Traiger I.L., Wade B.W., Watson V. System R: A Relational Approach to Data Base Management // ACM Trans. Database Syst.- 1976.- 1, N 2.- C. 97-137.
5.6. Gray J.N., Lorie R.A., Putzolu G.R., Traiger I.L. Granularity of Locks and Degrees of Consistency in a Shared Database // Proc. IFIP Work. Conf. Model. Data Base Manag. Syst., Freudenstadt, Germany, Jan. 1976. New-York, 1976.- C. 695-723.
5.7. Eswaran K.P., Gray J.N., Lorie R.A., Traiger I.L. The Notions of Consistency and Predicate Locks in a Database System // Commun. ACM.- 1976.- 19, N 11.- C. 624-633.
5.8. Griffiths P.P., Wade B.W. An Authorization Mechanism for a Relational Database System // ACM Trans. Database Syst.1976.- 1, N 3.- C. 242-255.
5.9. Blasgen M.W., Eswaran K.P. Storage and Access in Relational Data Bases // IBM Syst. J.- 1977.- 16, N 4, C.363-377.
5.10.
Blasgen M.W., Gray J.N., Mitoma M., Price T.G. The Convoy Phenomenon // ACM Oper. Syst. Rev.- 1979.- 13, N 2.C.20-25.
5.11. Lorie R.A., Nilsson J.F. An Access Specification Language for a Relational Data Base System // IBM J. Res. and Dev.- 1979.- 23, N 3.- C. 1-13.
5.12. Gray J.N. Notes on Database Operating Systems // Lect. Notes Comput. Sci.- 1979.- 60.- C. 396-481.
5.13. Selinger P.G., Astrahan M.M., Chamberlin D.D., Lorie R.A., Price T.G. Access Path Selection in a Relational Database Management System // Proc. ACM SIGMOD Int. Conf. Manag. Data, Boston, Mass., May 30 - June 1, 1979. New York, 1979.- C. 23-34.
5.14. Gray J.N., McJones, P., Blasgen M.W., Lindsay B.G., Lorie R.A., Price T.G., Putzolu G.R., Traiger I.L. The Recovery Manager of the System R Database Manager // ACM Comput. Surv.- 1981.- 13, N 2.- C. 223-242.
5.15. Chamberlin D.D., Astrahan M.M., Blasgen M.W., Gray J.N., King W.F., Lindsay B.G., Lorie L.A., Mehl J.W., Price T.G., Putzolu G.R., Selinger P.G., Schkolnick M., Slutz D.R., Traiger I.L., Wade B.W., Yost R.A. A History and Evaluation of System R // Commun. ACM.- 1981.- 24, N 10.- C. 632-646
5.16. D.D.Chamberlin, Astrahan M.M., King W.F., Lorie L.A., Mehl J.W., Price T.G., Schkolnick M., Griffiths P.P., Selinger P.G., Slutz D.R., Wade B.W., Yost R.A. Support for Repetitive Transactions and Ad Hoc Queries in System R // ACM Trans. Database Syst.- 1981.- 6, N 1.- C. 70-94
5.17. Blasgen M.W., Astrahan M.M., Chamberlin D.D., Gray J.N., King W.F., Lindsay B.G., Lorie L.A., Mehl J.W., Putzolu G.R., Schkolnick M., Selinger P.G., Slutz D.R., Strong H.R., Traiger I.L., Wade B.W., Yost R.A. System R: An Architectural Overview // IBM Syst. J.- 1981.- 20, N 1.- C. 41-62.
В каждой странице данных хранятся кортежи одной или нескольких таблиц. Фундаментальным понятием RSS является идентификатор кортежа (tuple identifier – tid). Гарантируется неизменяемость tid'а во все время существования кортежа в базе данных независимо от перемещений кортежа внутри страницы и даже при перемещении кортежа в другую страницу. Потребность в перемещении кортежей возникает по той причине, что кортеж, занесенный в некоторую таблицу базы данных, вообще говоря, во время своего существования может увеличиваться в размерах (если к этой таблице добавляется новое поле, или если в ней имеется хотя бы одно поле, типом данных которого являются строки символов переменного размера). Реально tid представляет собой пару <номер страницы, индекс описателя кортежа в странице>. При этом кортеж может реально располагаться в данной странице (рис. 12.1a) или в другой странице (рис. 12.1b).
Рис. 12.1. Идентификатор кортежа и расположение кортежа в странице данных
Как показывает рис. 12.1, в каждой странице данных имеются две области: область хранения описателей кортежей и область хранения самих кортежей. Один из остроумных приемов, примененных в System R, состоит в том, что обе эти области являются динамическими, т.е. в странице данных заранее не резервируется место под описатели кортежей. Легко видеть, что выделение фиксированной части страницы данных под описатели кортежей (вмещающей, скажем, k
описателей) потенциально привело бы к потери памяти в этой странице, поскольку при размещении в ней k
кортежей очень маленького размера пропадало бы место в области хранения кортежей, а при размещении p<k
крупных кортежей полностью заполнялась бы область хранения кортежей, но пропадало бы место в области описателей. Для динамического распределения памяти внутри страницы память на описатели кортежей выделяется вниз от начала страницы, а память для хранения кортежей – вверх от конца страницы.
Второй вариант хранения кортежей возникает в том случае, когда некоторый кортеж после своего создания был размещен системой в странице с номером N, а после обновления с увеличением размера перестал помещаться в этой странице, и система была вынуждена разместить его в странице с номером M.
Тогда исходный tid этого кортежа не изменится, но его описатель в странице N будет содержать не координаты кортежа в данной странице, а новый tid, указывающий на реальное положение кортежа в странице M. Легко видеть, что применение такого подхода позволяет ограничиться максимум одним уровнем косвенности (если данный кортеж в какой-то момент времени перестанет помещаться в странице M, и система переместит его в страницу P, то достаточно будет изменить косвенную ссылку на этот кортеж в странице N, и его исходный tid не изменится).
Поскольку допускается нахождение в одной странице данных кортежей разных таблиц, каждый кортеж должен, кроме содержательной части, включать служебную информацию, идентифицирующую таблицу, которой принадлежит данный кортеж. Кроме того, в System R (точнее, в языке SQL) допускается динамическое добавление полей к существующим таблицам. При этом реально происходит лишь модификация описателя таблицы в таблице-каталоге таблиц. В существующем кортеже таблицы новое поле возникает только при модификации этого кортежа, затрагивающей новое поле. Это позволяет избежать массовой перестройки хранимой таблицы при добавлении к ней новых полей, но, естественно, требует хранения при кортеже дополнительной служебной информации, определяющей реальное число полей в данном кортеже. (Заметим, что удалять существующие поля существующей таблицы в SQL System R не разрешалось.)
В данной лекции мы начинаем систематически описывать базовые механизмы языка SQL. Чтобы пояснить, о какой части языка пойдет речь в этой и следующих лекциях, обратимся к .
Рис. 15.1. Один из способов разделения языка SQL на уровни
Язык SQL, соответствующий последним стандартам SQL:2003, SQL:1999 (и даже SQL/92), это очень богатый и сложный язык, все возможности которого трудно сразу осознать и тем более понять. Поэтому приходится разбивать язык на уровни, или слои, такие, что каждый уровень языка включает все конструкции, входящие в более низкие уровни. В стандарте определяется несколько способов разбиения языка на уровни. В одной из классификаций язык разбивается на базовый (entry), промежуточный (intermediate) и полный (full) уровни.
Эта классификация ориентирована, прежде всего, на производителей СУБД, в которых поддерживается SQL. Реализация базового уровня языка является обязательным условием хотя бы какого-то соответствия стандарту. Реализация промежуточного уровня желательна, и обычно именно такой уровень языка поддерживается ведущими компаниями-производителями SQL-ориентированных СУБД. Наконец, полный уровень языка является целью, к достижению которой следует стремиться. В данной классификации критерием отнесения той или иной возможности языка к некоторому уровню является оцениваемая создателями стандарта SQL (большая часть которых является сотрудниками ведущих компаний, производящих SQL-ориентированные СУБД) техническая сложность реализации этой возможности. Конечно, такая классификация важна и для программистов приложений баз данных, но только для того, чтобы оценить реальные возможности конкретной СУБД. Для понимания языка SQL это разбиение на уровни несущественно.
Другая классификация показана на . Среди всех конструкций языка SQL можно выделить такие конструкции, которые можно было использовать при прямом (direct) взаимодействии конечного пользователя с СУБД (например, в интерактивном режиме). В некотором смысле этот уровень также является базовым, поскольку соответствующие средства языка в наибольшей степени отражают его ориентированность на работу с мультимножествами.
На следующем уровне, уровне встраиваемого (embedded) SQL, язык расширяется конструкциями, позволяющими использовать возможности прямого SQL в программах, написанных на традиционных языках программирования. Наконец, на уровне динамического (dynamic) SQL во встраиваемый SQL добавляются конструкции, позволяющие приложениям обращаться к СУБД с конструкциями прямого SQL, которые динамически образуются во время выполнения программы.
Нам кажется, что вторая классификация является более полезной для читателя, постигающего основы языка SQL. По нашему мнению, дополнительные возможности, присутствующие во встраиваемом и в динамическом SQL, не слишком сильно влияют на модельное представление языка. Конечно, возможности встраиваемого и динамического SQL необходимо хорошо знать разработчикам приложений SQL-ориентированных баз данных. Но поскольку задачей этого курса не является обучение использованию языка SQL при программировании приложений баз данных, мы не будем затрагивать эти темы. Обратимся к прямому SQL, причем не в полном объеме стандартов SQL:2003 и SQL:1999 (этого не позволяет сделать объем курса). Обсудим только наиболее важные аспекты.
В этой лекции обсуждаются основные аспекты системы типов данных языка SQL и средства определения доменов.
Замечание: Лекции, посвященные языку SQL, опираются, главным образом, на стандарт SQL:1999. В тех случаях, когда будут упоминаться дополнительные возможности, специфицированные в наиболее свежей версии стандарта – SQL:2003, мы будем явно на это указывать. Поэтому здесь мы используем терминологию стандарта (таблицы, строки, столбцы и т. д.).
В этом абзаце применяется терминология, которая использовалась в публикациях, посвященных System R.
Как это ни странно, компания IBM, имевшая уникальный и положительный опыт реализации экспериментальной реляционной СУБД System R, не стала первой компанией, выпустившей на рынок коммерческую реляционную СУБД. Компанию IBM опередила на два года незадолго до того образованная компания Oracle, выпустившая свою первую систему в 1979 г.
Современные эксперты по разному объясняют причины этой «заторможенности» IBM, но, по всей видимости, основная причина кроется в традиционном консерватизме руководства компании.
Например, одной из выигрышных черт SQL System R являлось то, что в одной транзакции разрешалось комбинировать все возможные операторы SQL. Поскольку технически это обеспечить достаточно трудно, почти во всех сегодняшних SQL-ориентированных СУБД имеются ограничения на состав операторов, которые можно выполнять в одной транзакции.
Это практически обесценивает стандарт с точки зрения программистов приложений баз данных, поскольку не дает возможности создавать приложения, не привязанные к особенностям конкретных СУБД.
Среди прочих достижений System R нельзя не отметить то, что в базах данных, управляемых этой СУБД, хранились как данные, так и метаданные – описатели отношений, их полей, представлений, ограничения целостности и т.д. Для хранения метаданных использовались специальные служебные отношения, которые стали называть отношениями-каталогами. Из отношений-каталогов можно было выбрать данные с помощью обычных средств языка SQL. Конечно, организация служебных данных – это вопрос реализации SQL, но этот вопрос непосредственно касается потенциальных пользователей SQL-ориентированных СУБД, и поэтому стандартизация представления пользователю отношений-каталогов (в стандарте SQL, информационной схемы базы данных) является исключительно важным делом.
К сожалению, приходится использовать термин строка в двух смыслах: строка таблицы (table row) и символьная или битовая строка (character or bit string). Постараемся обеспечить правильное понимание смысла термина в контексте его использования.
Предположим, что мы решили основывать эту информационную систему на файловой системе и пользоваться одним файлом СЛУЖАЩИЕ, расширив базовые возможности файловой системы за счет специальной библиотеки функций. Поскольку минимальной информационной единицей в нашем случае является служащий, в этом файле должна содержаться одна запись для каждого служащего. Чтобы можно было удовлетворить указанные выше требования, запись о служащем должна иметь следующие поля: полное имя служащего (СЛУ_ИМЯ);номер его удостоверения (СЛУ_НОМЕР);данные о соответствии служащего занимаемой должности (СЛУ_СТАТ; для простоты «да» или «нет»);размер зарплаты (СЛУ_ЗАРП);номер отдела (СЛУ_ОТД_НОМЕР).
Поскольку мы решили ограничиться одним файлом СЛУЖАЩИЕ, та же запись должна содержать имя руководителя отдела (СЛУ_ОТД_РУК). (Иначе было бы невозможно, например, получить имя руководителя отдела с известным номером.)
Чтобы информационная система могла эффективно выполнять свои базовые функции, необходимо обеспечить многоключевой доступ к файлу СЛУЖАЩИЕ по уникальным ключам (ключ называется уникальным, если его значения гарантированно различны во всех записях файла) СЛУ_ИМЯ и СЛУ_НОМЕР. Очевидно, что в противном случае для выполнения наиболее часто используемых операций получения данных о конкретном служащем понадобится последовательный просмотр в среднем половины записей файла. Кроме того, должна обеспечиваться возможность эффективного выбора всех записей с общим значением СЛУ_ОТД_НОМЕР, т. е. доступ по неуникальному ключу. Если не поддерживать специальный механизм доступа, то для получения данных об отделе в целом в общем случае потребуется полный просмотр файла. Требуемая общая структура файла СЛУЖАЩИЕ показана на . Но даже в этом случае, чтобы получить численность отдела или общий размер зарплаты, система должна будет выбрать все записи о служащих указанного отдела и посчитать соответствующие общие значения.
Таким образом, мы видим, что при реализации даже такой простой информационной системы на базе файловой системы возникают следующие затруднения:
Практически во всех современных компьютерах основными устройствами внешней памяти являются магнитные диски с подвижными головками, и именно они служат для хранения файлов. Как отмечалось ранее, аппаратура магнитных дисков допускает выполнение обмена с дисками порциями данных произвольного размера. Однако возможность обмениваться с магнитными дисками порциями, размеры которых меньше полного объема блока, в настоящее время в файловых системах не используется. Это связано с двумя обстоятельствами.
Во-первых, как указывалось в разделе , считывание или запись только части блока не приводит к существенному выигрышу в суммарном времени обмена. Во-вторых, для работы с частями блоков файловая система должна обеспечить буферы оперативной памяти соответствующего размера, что существенно усложняет распределение оперативной памяти. Алгоритмы распределения памяти порциями произвольного размера плохи тем, что любой из них рано или поздно приводит к внешней фрагментации памяти. В памяти образуется большое число маленьких свободных фрагментов. Их совокупный размер может быть больше размера любого требуемого буфера, но его можно выделить, только если произвести сжатие памяти, т. е. подвижку всех занятых фрагментов таким образом, чтобы они располагались вплотную один к другому. Во время выполнения операции сжатия памяти нужно приостановить выполнение обменов, а сама эта операция занимает много времени.
Поэтому во всех современных файловых системах явно или неявно выделяется уровень, обеспечивающий работу с базовыми файлами, которые представляют собой наборы блоков, последовательно нумеруемых в адресном пространстве файла и отображаемых на физические блоки диска (). Размер логического блока файла совпадает с размером физического блока диска или кратен ему; обычно размер логического блока выбирается равным размеру страницы виртуальной памяти, поддерживаемой аппаратурой компьютера совместно с операционной системой.
В некоторых файловых системах базовый уровень был доступен пользователю, но чаще он прикрывался некоторым более высоким уровнем, стандартным для пользователей.
Существуют два основных подхода. При первом подходе, свойственном, например, файловым системам операционных систем компании DEC RSX и VMS, пользователи представляют файл как последовательность записей. Каждая запись – это последовательность байтов, имеющая постоянный или переменный размер. Можно читать или писать записи последовательно либо позиционировать файл на запись с указанным номером. Некоторые файловые системы позволяют структурировать записи на поля и объявлять некие поля ключами записи.
До сих пор мы не вычленяли СУБД из состава информационной системы, имея в виду общую организацию системы, подобную той, которая показана на .
Рис. 1.8. СУБД в составе информационной системы
Здесь видны два дефекта. Во-первых, очевидно, что СУБД должна поддерживать достаточно развитую функциональность. Повторять эту функциональность в каждой информационной системе неразумно. С другой стороны, неясно, каким образом можно обеспечить готовый к использованию компонент СУБД, который можно было бы встраивать в информационные системы. Во-вторых, уже должно быть понятно, что набор файлов можно назвать базой данных только при наличии метаданных. На метаданные являются принадлежностью информационной системы, и поэтому, например, файлы СЛУЖАЩИЕ и ОТДЕЛЫ можно эффективно использовать только через нашу гипотетическую систему регистрации служащих.
Предположим, что предприятию нужна еще и информационная бухгалтерская система. Очевидно, что для ее работы также потребуются данные о служащих и отделах. При показанной выше организации системы возможны два варианта выполнения задачи, ни один из которых не является удовлетворительным. Внедрить бухгалтерскую систему в состав системы регистрации служащих. Но ведь, как правило, бухгалтерские системы покупаются в виде готовых и отдельных продуктов, не приспособленных к подобному «внедрению».Скопировать метаданные системы регистрации служащих в бухгалтерскую систему. Но метаданные (как и данные) не обязательно являются статичными. Структура базы данных может со временем изменяться, могут исчезать одни правила целостности и появляться другие. Как согласовывать копии метаданных, поддерживаемые независимыми информационными системами?
Так мы приходим к организации системы, показанной на .
Рис. 1.9. Отдельная СУБД и базы данных с метаданными
Здесь мы видим три информационные системы, которые через одну СУБД работают с двумя разными базами данных, причем первая и вторая системы работают с общей базой данных. Это возможно, поскольку метаданные каждой базы данных содержатся в самих базах данных, и достаточно лишь указать СУБД, с какой базой данных желает работать данное приложение. Поскольку СУБД функционирует отдельно от приложений, и ее работа с базами данных регулируется метаданными, совместное использование одной базы данных двумя информационными системами не вызовет потери согласованности данных, и доступ к данным будет должным образом синхронизироваться. Заметим, что вплотную приближает нас к наиболее распространенной в последние десятилетия архитектуре «клиент-сервер». СУБД играет роль «сервера», обсуживающего нескольких «клиентов» – прикладных информационных систем.
Таким образом, СУБД решают множество проблем, которые затруднительно или вообще невозможно решить при использовании файловых систем. При этом существуют приложения, для которых вполне достаточно файлов; приложения, для которых необходимо решать, какой уровень работы с данными во внешней памяти для них требуется, и приложения, для которых, безусловно, нужны базы данных.
Ассоциацией называется структурная связь, показывающая, что объекты одного класса некоторым образом связаны с объектами другого или того же самого класса. Допускается, чтобы оба конца ассоциации относились к одному классу. В ассоциации могут связываться два класса, и тогда она называется бинарной. Допускается создание ассоциаций, связывающих сразу n классов (они называются n-арными ассоциациями). Графически ассоциация изображается в виде линии, соединяющей класс сам с собой или с другими классами.
С понятием ассоциации связаны четыре важных дополнительных понятия: имя, роль, кратность и агрегация. Во-первых, ассоциации может быть присвоено имя, характеризующее природу связи. Смысл имени уточняется с помощью черного треугольника, который располагается над линией связи справа или слева от имени ассоциации. Этот треугольник указывает направление чтения имя связи. Пример именованной ассоциации показан на . Треугольник показывает, что именованная ассоциация должна читаться как «Студент учится в Университете».
Рис. 11.7. Пример именованной ассоциации
Другим способом именования ассоциации является указание роли каждого класса, участвующего в этой ассоциации. Роль класса, как и имя конца связи в ER-модели, задается именем, помещаемым под линией ассоциации ближе к данному классу. На показаны две ассоциации между классами Человек и Университет, в которых эти классы играют разные роли. Как мы видим, объекты класса Человек могут выступать в роли РАБОТНИКОВ при участии в ассоциации, в которой объекты класса Университет играют роль НАНИМАТЕЛЯ. В другой ассоциации объекты класса Человек играют роль СТУДЕНТА, а объекты класса УНИВЕРСИТЕТ – роль ОБУЧАЮЩЕГО.
Рис. 11.8. Две ассоциации с разными ролями классов
В общем случае, для ассоциации могут задаваться и ее собственное имя, и имена ролей классов. Это связано с тем, что класс может играть одну и ту же роль в разных ассоциациях, так что в общем случае пара имен ролей классов не идентифицирует ассоциацию. С другой стороны, в простых случаях, когда между двумя классами определяется только одна ассоциация, можно вообще не связывать с ней дополнительные имена.
Связью-обобщением называется связь между общей сущностью, называемой суперклассом, или родителем, и более специализированной разновидностью этой сущности, называемой подклассом, или потомком. Обобщения иногда называют связями «is a», имея в виду, что класс-потомок является частным случаем класса-предка. Класс-потомок наследует все атрибуты и операции класса-предка, но в нем могут быть определены дополнительные атрибуты и операции.
Объекты класса-потомка могут использоваться везде, где могут использоваться объекты класса-предка. Это свойство называют полиморфизмом по включению, имея в виду, что объекты потомка можно считать включаемыми во множество объектов класса-предка. Графически обобщения изображаются в виде сплошной линии с большой незакрашенной стрелкой, направленной к суперклассу. В качестве первой иллюстрации, приведенной на , воспользуемся классификацией летательных аппаратов с из предыдущей лекции. На показан пример иерархии одиночного наследования: у каждого подкласса имеется только один суперкласс.
Рис. 11.5. Иерархия одиночного наследования классов
Одиночное наследование является достаточным в большинстве случаев применения связи-обобщения. Однако в UML допускается и множественное наследование, когда один подкласс определяется на основе нескольких суперклассов. В качестве одного из разумных (не слишком распространенных) примеров рассмотрим диаграмму классов на (для упрощения диаграммы имена атрибутов и операций указывать не будем).
Рис. 11.6. Пример множественного наследования классов
На этой диаграмме классы Студент и Преподаватель порождены из одного суперкласса ЧеловекИзУниверситета. Вообще говоря, к классу Студент относятся те объекты класса ЧеловекИзУниверситета, которые соответствуют студентам, а к классу Преподаватель – объекты класса ЧеловекИзУниверситета, соответствующие преподавателям. Но, как это часто случается, многие студенты уже в студенческие годы начинают преподавать, так что могут существовать такие два объекта классов Студент и Преподаватель, которым соответствует один объект класса ЧеловекИзУниверситета.
Итак, среди объектов класса Студент могут быть преподаватели, а некоторые преподаватели могут быть студентами. Тогда мы можем определить класс СтудентПреподаватель путем множественного наследования от суперклассов Студент и Преподаватель. Объект класса СтудентПреподаватель обладает всеми свойствами и операциями классов Студент и Преподаватель и может быть использован везде, где могут применяться объекты этих классов. Так что полиморфизм по включению продолжает работать. Следует отметить, что множественное наследование, помимо того что не слишком часто требуется на практике, порождает ряд проблем, из которых одной из наиболее известных является проблема именования атрибутов и операций в подклассе, полученном путем множественного наследования. Например, предположим, что при образовании подклассов Студент и Преподаватель в них обоих был определен атрибут с именем номерКомнаты. Очень вероятно, что для объектов класса Студент значениями этого атрибута будут номера комнат в студенческом общежитии, а для объектов класса Преподаватель – номера служебных кабинетов. Как быть с объектами класса СтудентПреподаватель, для которых существенны оба одноименных атрибута (у студента-преподавателя могут иметься и комната в общежитии, и служебный кабинет)? На практике применяется одно из следующих решений: запретить образование подкласса СтудентПреподаватель, пока в одном из суперклассов не будет произведено переименование атрибута номерКомнаты;наследовать это свойство только от одного из суперклассов, так что, например, значением атрибута номерКомнаты у объектов класса СтудентПреподаватель всегда будут номера служебных кабинетов;унаследовать в подклассе оба свойства, но автоматически переименовать оба атрибута, чтобы прояснить их смысл; назвать их, например, номерКомнатыСтудента и номерКомнатыПреподавателя.
Ни одно из решений не является полностью удовлетворительным. Первое решение требует возврата к ранее определенному классу, имена атрибутов и операций которого, возможно, уже используются в приложениях.Второе решение нарушает логику наследования, не давая возможности на уровне подкласса использовать все свойства суперклассов. Наконец, третье решение заставляет использовать длинные имена атрибутов и операций, которые могут стать недопустимо длинными, если процесс множественного наследования будет продолжаться от полученного подкласса.
Но, конечно, сложность проблемы именования атрибутов и операций несопоставимо меньше сложности реализации множественного наследования в реляционных БД. Поэтому при использовании UML для проектирования реляционных БД нужно очень осторожно использовать наследование классов вообще и стараться избегать множественного наследования.
Табличное ограничение первичного или возможного ключа { PRIMARY_KEY | UNIQUE } (column_commalist) означает требование уникальности составных значений указанной группы столбцов (т. е. во все время существования определяемой таблицы во всех ее строках составные значения данной группы столбцов должны быть различны). Ограничение PRIMARY KEY, в дополнение к этому, влечет ограничение NOT NULL для всех столбцов, упоминаемых в определении ограничения. В определении таблицы допускается произвольное число определений возможного ключа (для разных комбинаций столбцов), но не более одного определения первичного ключа. Обратите особое внимание на последнюю часть предыдущего предложения: в языке SQL действительно допускается определение таблиц, у которых отсутствуют возможные ключи. Эта особенность языка, среди прочего, очевидным образом противоречит базовым требованиям реляционной модели данных.
Синтаксис и семантика определения внешнего ключа в операторе SQL определения базовой таблицы являются довольно запутанными и сложными. По этой причине мы посвящаем этой языковой конструкции отдельный подраздел.
Табличное ограничение FOREIGN KEY (column_commalist) references_definition означает объявление внешним ключом группы столбцов, имена которых перечислены в списке column_commalist. Обсудим теперь смысл ограничения внешнего ключа при разных вариантах формирования определения ссылок (references_definition). Для удобства повторим синтаксическое правило.
references_definition ::= REFERENCES base_table_name [ (column_commalist) ] [ MATCH { SIMPLE | FULL | PARTIAL } ] [ ON DELETE referential_action ] [ ON UPDATE referential_action ]
В этом определении base_table_name должно представлять собой имя некоторой базовой таблицы (пусть, например, эта таблица имеет имя T). Если определение ссылок включает список столбцов (column_commalist), то этот список должен совпадать (с точностью до порядка следования имен столбцов) со списком имен столбцов, использованных в некотором определении первичного или возможного ключа (PRIMARY_KEY или UNIQUE) в определении таблицы T. Если в определении ссылок список столбцов явно не задан, то считается, что он совпадает со списком столбцов, использованных в определении первичного ключа (PRIMARY_KEY) таблицы T.
Табличным выражением (table_expression) называется конструкция
table_expression ::= FROM table_reference_commalist [ WHERE conditional_expression ] [ GROUP BY column_name_commalist ] [ HAVING conditional_expression ]
Спецификацией запроса (query_specification) называется конструкция
query_specification SELECT [ ALL | DISTINCT ] select_item_commalist table_expression
Наконец, выражением запросов (query_expression) называется конструкция
query_expression ::= [ with_clause ] query_expression_body query_expression_body ::= { non_join_query_expression | joined_table } non_join_query_expression ::= non_join_query_term | query_expression_body { UNION | EXCEPT }[ ALL | DISTINCT ] [ corresponding_spec ] query_term query_term ::= non_join_query_term | joined_table non_join_query_term ::= non_join_query_primary | query_term INTERSECT [ ALL | DISTINCT ] [ corresponding_spec ] query_primary query_primary ::= non_join_query_primary | joined_table non_join_query_primary ::= simple_table | (non_join_query_expression) simple_table ::= query_specification | table_value_constructor | TABLE table_name corresponding_spec ::= CORRESPONDING [ BY column_name_comma_list ]
Если не обращать внимания на не обсуждавшиеся пока конструкции joined_table и table_value_constructor, синтаксические правила показывают, что выражение запросов строится из выражений, значениями которых являются таблицы, с использованием «теоретико-множественных» операций UNION (объединение), EXCEPT (вычитание) и INTERSECT (пересечение). Операция пересечения является «мультипликативной» и обладает более высоким приоритетом, чем «аддитивные» операции объединения и вычитания. Вычисление выражения производится слева направо с учетом приоритетов операций и круглых скобок. При этом действуют следующие правила. Если выражение запросов не включает ни одной теоретико-множественной операции, то результатом вычисления выражения запросов является результат вычисления простой или соединенной таблицы.Если в терме (non_join_query_term) или выражении запросов (non_join_query_expression) без соединения присутствует теоретико-множественная операция, то пусть T1, T2 и TR обозначают соответственно первый операнд, второй операнд и результат терма или выражения соответственно, а OP – используемую теоретико-множественную операцию.Если в операции присутствует спецификация CORRESPONDING, то: если присутствует конструкция BY column_name_comma_list, то все имена в этом списке должны быть различны, и каждое имя должно являться одновременно именем некоторого столбца таблицы T1 и именем некоторого столбца таблицы T2, причем типы этих столбцов должны быть совместимыми; обозначим данный список имен через SL;если список соответствия столбцов не задан, пусть SL обозначает список имен столбцов, являющихся именами столбцов и в T1, и в T2, в том порядке, в котором эти имена фигурируют в T1;вычисляемые терм или выражение запросов без соединения эквивалентны выражению (SELECT SL FROM T1) OP (SELECT SL FROM T2), не включающему спецификацию CORRESPONDING.При отсутствии в операции спецификации CORRESPONDING операция выполняется таким образом, как если бы эта спецификация присутствовала и включала конструкцию BY column_name_comma_list, в которой были бы перечислены все столбцы таблицы T1.При выполнении операции OP две строки s1 с именами столбцов c1, c2, …, cn и s2 с именами столбцов d1, d2, …, dn считаются строками-дубликатами, если для каждого i (i = 1, 2, …, n) либо ci и di не содержат NULL, и (ci = di) = true, либо и ci, и di содержат NULL.Если в операции OP не задана спецификация ALL, то в TR строки-дубликаты удаляются.Если спецификация ALL задана, то пусть s – строка, являющаяся дубликатом некоторой строки T1, или некоторой строки T2, или обеих; пусть m – число дубликатов s в T1, а n – число дубликатов s в T2. Тогда: если указана операция UNION, то число дубликатов s в TR равно m + n;если указана операция EXCEPT, то число дубликатов s в TR равно max ((m-n),0);если указана операция INTERSECT, то число дубликатов s в TR равно min (m,n).
Операции, которые должны быть выполнены при срабатывании триггера, специфицируются в синтаксической конструкции triggered_SQL_statement (будем называть ее инициируемым SQL-оператором).Как видно из синтаксических правил, возможны два вида построения этой конструкции: в виде одиночного оператора SQL и в виде списка операторов со скобками BEGIN ATOMIC и END.
Недоумение читателей может вызвать неуточненная конструкция SQL_procedure_statement. Постараемся объяснить ее происхождение и смысл. Дело в том, что в стандарте SQL:1999 определено процедурное расширение SQL, называемое SQL/PSM (от Persistent Stored Modules). Это достаточно большой язык, который мы не будем подробно рассматривать в этом курсе лекций. Тем не менее для понимания синтаксиса определения триггеров необходимо отметить, что: (a) SQL/PSM включает основные операторы SQL, связанные с обновлением данных; (b) язык является вычислительно полным, т.е. включает развитые средства вычислений; (c) в языке содержатся средства определения и вызова функций ипроцедур, и (d) SQL/PSM содержит стандартный комплект управляющих конструкций – циклы, ветвления разных типов и т. д. Тем самым, SQL_procedure_statement – это любая процедура, определенная на языке SQL/PSM. В частности, эта процедура может представлять собой оператор SQL обновления базы данных.
Обсудим теперь, откуда возникает потребность в составном инициируемом SQL-операторе. Дело в том, что на практике при определении триггеров в качестве SQL_procedure_statement чаще всего используются операторы SQL обновления базы данных. Иногда (и мы покажем это на примере) для корректного определения функциональности триггера одного оператора не хватает, а в SQL отсутствует возможность определения составных операторов. Поэтому допускается использование средств определения составных операторов, присутствующих в SQL/PSM (BEGIN ATOMIC и END).
Для иллюстрации случая, когда при определении триггера достаточно специфицировать один оператор SQL, приведем пример определения триггера, условием срабатывания которого является выполнение операции вставки новой строки в таблицу EMP (прием на работу нового служащего).
Если значение столбца DEPT_NO в очередной вставляемой строке отлично от NULL, то триггер должным образом модифицирует значения столбцов DEPT_EMP_NO и DEPT_TOTAL_SAL строки таблицы DEPT со значением столбца DEPT_NO, которое соответствует номеру отдела нового служащего (пример 21.10): CREATE TRIGGER DEPT_CORRECTION AFTER INSERT ON EMP FOR EACH ROW WHEN (EMP.DEPT_NO IS NOT NULL) UPDATE DEPT SET DEPT_EMP_NO = DEPT_EMP_NO + 1, DEPT_TOTAL_SAL = DEPT_TOTAL_SAL + EMP_SAL WHERE DEPT.DEPT_NO = EMP.DEPT_NO;
Теперь предположим, что при увольнении служащего (удалении строки из таблицы EMP) мы хотим не только должным образом модифицировать таблицу DEPT, но и сохранять (с целью аудита) данные об уволенном служащем в таблице EMP_DISMISSED:
EMP_NO : EMP_NO |
EMP_NAME : VARCHAR |
DEPT_NO : DEPT_NO |
Теневой механизм был изначально предложен для поддержания целостности файлов при аварийном отключении питания компьютера. Общая идея теневого механизма для файлов показана на рис. 14.2. Файл представляется как набор блоков внешней памяти, для доступа к которым поддерживается таблица отображения (см. лекцию 1). При открытии файла таблица отображения номеров его логических блоков в адреса физических блоков внешней памяти считывается в оперативную память. При модификации любого блока файла во внешней памяти выделяется новый блок. При этом текущая таблица отображения (в основной памяти) изменяется, а теневая остается неизменной. Если во время работы с открытым файлом происходит сбой, во внешней памяти автоматически сохраняется состояние файла до его открытия. Для явного восстановления файла достаточно повторно считать в основную память теневую таблицу отображения.
Рис. 14.2. Теневой механизм для файлов
В контексте базы данных теневой механизм используется следующим образом . Периодически выполняются операции установки точки физической согласованности базы данных. При выполнении этой операции все логические операции завершаются, все страницы буферного пула базы данных, содержимое которых отличается от содержимого соответствующих блоков внешней памяти, выталкиваются. Теневая таблица отображения файлов (сегментов) базы данных заменяется текущей таблицей отображения (правильнее сказать, текущая таблица отображения записывается на место теневой).
Здесь имеется некоторая проблема, состоящая в том, что в любой момент времени теневая таблица отображения должна быть корректной, т.е. соответствовать некоторому ранее зафиксированному физически целостному состоянию базы данных. Для этого необходимо обеспечить атомарность операции замены теневой таблицы отображения. В общем случае таблица отображения может занимать несколько блоков внешней памяти, и для записи текущей таблицы отображения на место теневой таблицы в этом случае потребуется несколько обменов с дисками. Если в промежутке между этими обменами возникнет мягкий сбой, то будет благополучно утрачена текущая таблица отображения и безнадежно испорчена теневая таблица, т.е.
мы просто лишимся возможности восстанавливаться за счет использования последнего физически согласованного состояния базы данных.
Чтобы это не произошло, во внешней памяти поддерживаются две области хранения таблицы отображения файлов (будем называть их областями A
и B). Кроме того, в отдельном блоке внешней памяти хранится флаг F, показывающий, какая из этих областей в данный момент содержит действующую теневую таблицу отображения (назовем соответствующие значения флага FA
и FB). Тогда, если сохраненным во внешней памяти значением флага является FA, то текущая таблица отображения записывается в область B. Если эта операция выполняется успешно, то в блок флага записывается значение FB. Считается, что операция записи одного блока на диск является атомарной. Если эта операция заканчивается успешно, это означает, что новая теневая таблица отображения хранится в области B. Если же запись текущей таблицы отображения в область B
не удалась, или если не выполнилась операция записи блока с флагом F, то продолжает действовать старая теневая таблица отображения.
Восстановление хронологически последнего сохраненного физически согласованного состояния базы данных происходит мгновенно: текущая таблица отображения заменяет теневой таблицей (при восстановлении просто считывается действующая теневая таблица отображения). Все проблемы восстановления решаются, но за счет слишком большого перерасхода внешней памяти. В пределе может потребоваться вдвое больше внешней памяти, чем реально нужно для хранения базы данных.
Значения данных, хранимые в реляционной базе данных, являются типизированными, т. е. известен тип каждого хранимого значения. Понятие типа данных в реляционной модели данных полностью соответствует понятию типа данных в языках программирования. Напомним, что традиционное (нестрогое) определение типа данных состоит из трех основных компонентов: определение множества значений данного типа; определение набора операций, применимых к значениям типа; определение способа внешнего представления значений типа (литералов).
Обычно в современных реляционных базах данных допускается хранение символьных, числовых данных (точных и приблизительных), специализированных числовых данных (таких, как «деньги»), а также специальных «темпоральных» данных (дата, время, временной интервал). Кроме того, в реляционных системах поддерживается возможность определения пользователями собственных типов данных (более подробно мы обсудим это в лекции 23).
В примере на мы имеем дело с данными трех типов: строки символов, целые числа и «деньги».
Тип DATE. Значения этого типа состоят из компонентов-значений года, месяца и дня некоторой даты. Значение года состоит из четырех десятичных цифр и соответствует летоисчислению от Рождества Христова до 9999 г. Значение месяца состоит из двух десятичных цифр и варьируется от 01 до 12. Значение номера дня месяца состоит из двух десятичных цифр и варьируется от 01 до 31, хотя значение месяца даты может накладывать ограничения на возможность использования значений дня месяца 29, 30 и 31. В стандарте SQL не накладываются какие-либо ограничения на внутренний способ представления дат, используемый в реализации. При определении столбца типа DATE указывается просто DATE.Литералы типа DATE представляются в виде строки «’yyyy-mm-dd’», где символы y, m и d должны изображать десятичные числа. Например, литерал DATE ’1949-04-08’ представляет дату 8 апреля 1949 г.
Наряду с типизированными базовыми таблицами в SQL:1999 поддерживаются типизированные представления, иначе называемые представлениями, на которые можно ссылаться (referenceable views). Иногда такие представления также называют объектными представлениями, поскольку данные, видимые через представление, соответствуют строкам типизированных таблиц, поведение которых во многом похоже на поведение объектов в объектно-ориентированных системах. Между типизированными базовыми таблицами и типизированными представлениями имеется большое сходство, но есть и несколько отличий, связанных с различиями базовых таблиц и представлений.
В SQL в связи с объектными представлениями вводится ряд терминов – суперпредставление, подпредставление, непосредственное суперпредставление, непосредственное подпредставление, собственное суперпредставление и собственное подпредставление. Смысл этих терминов полностью аналогичен смыслу соответствующих терминов для типизированных базовых таблиц. Термин семейство подтаблиц применяется как к типизированным таблицам, так и к типизированным представлениям.
Определение типизированного представления задается в следующей синтаксической форме:
view_definition ::= CREATE VIEW table_name OF UDT_name UNDER table_name (view_element_commalist) AS query_expression [ WITH [ levels_clause ] CHECK OPTION ] view_element ::= self_referencing_column_specification | column_name WITH OPTIONS scope_clause
Указываемое UDT_name должно быть именем существующего структурного типа. Как и в определении обычных представлений, в разделе AS указывается выражение запроса. В случае типизированных представлений это выражение запроса должно основываться на единственной типизированной таблице (базовой таблице или представлении). Эта типизированная таблица должна быть ассоциирована с тем же структурным типом, что и определяемое представление. Такую таблицу иногда называют базисной таблицей представления.
Типизированное представление можно определить как подпредставление другого типизированного представления.
В этом случае структурный тип, ассоциированный с определяемым представлением, должен являться непосредственным подтипом структурного типа, который ассоциирован с суперпредставлением, специфицируемым в разделе UNDER. Базисная таблица определяемого представления должна являться собственной подтаблицей или собственным подпредставлением – не обязательно непосредственным – базисной таблицы непосредственного суперпредставления определяемого представления.
В определение типизированного представления может входить один или несколько элементов column_name WITH OPTIONS scope_clause. Если представление определяется как подпредставление другого типизированного представления, то в его определении не должна содержаться спецификация самоссылающегося столбца. Если определяется максимальное суперпредставление (т. е. в определении не содержится раздел UNDER), то эта спецификация может присутствовать. Если спецификация присутствует, то она может содержать только конструкции USER GENERATED или DERIVED (из этого следует, что нельзя определить типизированное представление, в ассоциированном структурном типе которого присутствует спецификация REF IS SYSTEM GENERATED). При указании USER GENERATED степень определяемого представления на единицу больше числа атрибутов ассоциируемого структурного типа; дополнительным столбцом является самоссылающийся столбец. В случае указания DERIVED дополнительный столбец не появляется, поскольку значение самоссылающегося столбца порождается из тех же столбцов, из которых порождается значение самоссылающегося столбца базисной таблицы.
В предыдущем подразделе уже упоминалась возможность определения типизированных таблиц, основанных на некотором структурном типе. Далее мы приведем и поясним соответствующие синтаксические правила, введем понятие иерархии типизированных таблиц и связь этой иерархии с иерархией структурных типов, а также обсудим соотношение понятия строки типизированной таблицы с понятием объекта в ООБД.
В SQL определены три параметризуемых типа битовых строк: BIT, BIT VARYING и BINARY LARGE OBJECT (или BLOB). Тип BIT. Значениями типа являются битовые строки. При определении столбца допускается использование спецификаций BIT (x) и просто BIT. Последний вариант эквивалентен заданию BIT (1). После определения столбца типа BIT (x) СУБД будет резервировать место для хранения x бит этого столбца во всех строках соответствующей таблицы.Тип BIT VARYING. При определении столбца допускается использование только спецификации без умолчания вида BIT VARYING (x), где значение x определяет максимальную длину битовой строки, которую можно хранить в данном столбце. Над битовыми строками определен ряд операций. Некоторые из них мы рассмотрим. Битовая конкатенация (обозначается в виде ), которая возвращает результирующую битовую строку, полученную путем конкатенации строк-аргументов в том порядке, в котором они заданы. Функция извлечения подстроки из битовой строки. Синтаксис и семантика этой функции идентичны синтаксису и семантике функции SUBSTRING для символьных строк, за исключением того, что первый аргумент и возвращаемое значение являются битовыми строками. Функция определения длины (OCTET_LENGTH, BIT_LENGTH) возвращает длину заданной битовой строки в октетах или битах в зависимости от выбранной функции. Функция определения позиции (POSITION) определяет первую позицию в битовой строке S, с которой в нее входит строка S1. Если строка S1 не входит в строку S, возвращается значение нуль. Тип BINARY LARGE OBJECT. Этот тип данных предназначен для определения столбцов, хранящих большие и разные по размеру группы байтов. При определении столбца задается спецификация BLOB (z), где z задает максимальный размер соответствующей группы байтов. С технической точки зрения типы CLOB и BLOB очень похожи. Их разделение требуется для того, чтобы подчеркнуть, что значения типа CLOB состоят из символов (в частности, в них может осмысленно производиться текстовый поиск), а значения типа BLOB состоят из произвольных байтов, не обязательно кодирующих символы.Литералы типов битовых строк представляются как заключенные в одинарные кавычки последовательности символов «0» и «1», предваряемые символом «B»; или предваряемые символом «X» последовательности символов, которые изображают шестнадцатеричные цифры (за цифрой «9» следуют «A», «B», «C», «D», «E» и «F»).
Примеры литералов типов битовых строк: B’0111001111000111111111’, X’78FBCD0012FFFFA’.
А также переменных, параметров и других типизированных объектов языка SQL, которые мы не затрагиваем в этом курсе.
В этот список не включен тип данных XML, поскольку в данном курсе вообще не рассматриваются проблемы управления базами XML-данных.
Спецификация предопределенного типа данных битовых строк была удалена в стандарте SQL:2003. Но поскольку эта спецификация появилась только в SQL:1999, мы сочли уместным оставить в курсе обсуждение этого типа данных.
См. ниже Булевский тип.
Следует подчеркнуть, что в стандарте SQL не определяется число байт, занимаемых при хранении в памяти значений целых типов. Не следует думать, что в SQL для хранения значения типа INTEGER требуется четыре байта, а SMALLINT требует двух байтов.
В контексте локализации SQL-ориентированной СУБД (средства локализации входят в стандарт языка) можно определить еще три типа символьных строк – NATIONAL CHARACTER, NATIONAL CHARACTER VARYING и NATIONAL CHARACTER LARGE OBJECT. Аспекты интернационализации и локализации составляют отдельное измерение языка и не обсуждаются в данном курсе.
Именно пробелами, а не «пустыми» символами!
Максимально допустимая длина строк постоянного и переменного размера (значение параметра x) определяется в реализации.
Поскольку значения z могут быть очень большими, допускается сокращенная форма их задания в виде nK, nM и nG, где n – положительное целое число, а K, M и G означают кило, мега и гига соответственно.
В литерале BLOB всегда должно содержаться четное число шестнадцатиричных цифр.
Данные, хранящиеся в столбцах таблиц SQL-ориентированной базы данных, являются типизированными, т. е. представляют собой значения одного из типов данных, предопределенных в языке SQL или определяемых пользователями путем применения соответствующих средств языка. Для этого при определении таблицы каждому ее столбцу назначается некоторый тип данных (или домен), и в дальнейшем СУБД должна следить, чтобы в каждом столбце каждой строки каждой таблицы присутствовали только допустимые значения. В этом разделе мы обсудим систему типов языка SQL.
Все допустимые в SQL типы данных, которые можно использовать при определении столбцов, разбиваются на следующие категории: точные числовые типы (exact numerics);приближенные числовые типы (approximate numerics);типы символьных строк (character strings);типы битовых строк (bit strings);типы даты и времени (datetimes);типы временных интервалов (intervals);булевский тип (Booleans);типы коллекций (collection types);анонимные строчные типы (anonymous row types);типы, определяемые пользователем (user-defined types);ссылочные типы (reference types).
В столбцах таблиц, определенных на любых типах данных, наряду со значениями этих типов, допускается сохранение неопределенного значения, которое обозначается ключевым словом NULL. В языке определено, что результатом выражений вида x a_op NULL, NULL a_op x, NULL a_op NULL является NULL для всех арифметических операций a_op (+, - и т. д.), допустимых для типа данных выражения x (выражение NULL a_op NULL является допустимым для любой арифметической операции a_op). Также по определению полагается, что значением выражений x comp_op NULL, NULL comp_op x, NULL comp_op NULL для всех операций сравнения (=,
, >, < и т. д.), определенных для типа выражения x, является третье логическое значение unknown (выражение NULL comp_op NULL является допустимым для любой операции сравнения comp_op).Возможность сохранения в базе данных информации о дате и времени очень важна с практической точки зрения. Достаточно вспомнить взбудоражившую весь мир «проблему 2000 года», одним из основных источников которой было некорректное хранение дат в базах данных. В стандарте SQL поддержке средств работы с датой и временем уделяется большое внимание. В частности, поддерживаются специальные «темпоральные» типы данных DATE, TIME, TIMESTAMP, TIME WITH TIME ZONE и TIMESTAMP WITH TIME ZONE. Коротко обсудим эти типы.
Кристофер Дейт и Хью Дарвен поставили перед собой трудную задачу: показать, что на основе идей Эдгара Кодда можно реализовать СУБД, обеспечивающие возможности по части представления и хранения данных сложной структуры, не меньшие тех, которые обеспечивают объектные и SQL-ориентированные СУБД. Этому мешал, прежде всего, тезис Кодда о нормализации отношений: в реляционной базе данных должны содержаться только отношения, атрибуты которых определены на «доменах, элементы которых являются атомарными (не составными) значениями» . В Дейт пишет: «Я согласен с Коддом, что желательно оставаться в рамках логики первого порядка, если это возможно. В то же время я отвергаю идею "атомарных значений", по крайней мере, в смысле абсолютной атомарности. В Третьем манифесте мы допускаем наличие доменов, содержащих значения произвольной сложности. (Они могут быть даже отношениями.) Тем не менее, мы остаемся в рамках логики первого порядка.» Если учесть, что является первой официальной публикацией Кодда по поводу реляционной модели данных, то трудно сказать, что Дейт очень уж строго следует всем его заветам. Те постулаты Кодда, которые вредят достижению цели Третьего манифеста, просто отвергаются.
В истинно реляционной модели очень большое внимание уделяется типам данных. Предлагаются три категории типов данных: скалярные типы, кортежные типы и типы отношений. Скалярный тип данных – это привычный инкапсулированный тип, реальная внутренняя структура которого скрыта от пользователей. Предлагаются механизмы определения новых скалярных типов и операций над ними. Типом атрибута определяемого скалярного типа может являться любой определенный к этому моменту скалярный тип, любой кортежный тип и тип отношения. Некоторые базовые скалярные типы данных должны быть предопределены в системе. В число этих типов должен входить тип truth value (так Дейт и Дарвен называют булевский тип) ровно с двумя значениями true
и false.
Кортежный тип – это безымянный тип данных, определяемый с помощью генератора типа TUPLE
В объектной модели данных вводятся две разновидности типов: литеральные и объектные типы. Литеральные типы данных – это обычные типы данных, принятые в традиционных языках программирования. Они подразделяются на базовые скалярные числовые типы, символьные и булевские типы (атомарные литералы), конструируемые типы записей (структур) и коллекций.
Литеральный тип записи – это традиционный определяемый пользователем структурный тип, подобный структурному типу языка C или типу записи языка Pascal. Отличие состоит лишь в том, что в объектной модели атрибут типа записи может определяться не только на литеральном, но и на объектном типе, т.е. значение литерального типа записи может в качестве компонентов включать объекты. На первый взгляд это звучит странно и страшновато, но здесь все странности проистекают из особенностей объектно-ориентированной терминологии. У любого существующего объекта имеется одно и только одно местоположение, характеризующееся его идентификатором (OID). Когда в модели говорится, что некоторое структурное значение в качестве компонента имеет некоторый объект, то, конечно, имеется в виду OID этого объекта, являющийся всего лишь аналогом указательного значения в традиционных языках программирования.
Имеются четыре вида типов коллекций: типы множеств, мультимножеств (неупорядоченные наборы элементов, возможно, содержащие дубликаты), списков (упорядоченные наборы элементов, возможно, содержащие дубликаты) и словарей (множества пар <ключ, значение>, причем все ключи в этих парах должны быть различными). Типом элемента любой коллекции может являться любой скалярный или объектный тип за исключением того же типа коллекции.
Объектные типы в объектной модели данных по смыслу ближе всего к понятию класса в объектно-ориентированных языках программирования. У каждого объектного типа имеется операция создания и инициализации нового объекта этого типа. Эта операция возвращает значение OID нового объекта, который можно хранить в любом месте, где допускается хранение объектов данного типа, и использовать для обращения к операциям объекта, определенным в его объектном типе.
SQL-ориентированная база данных представляет собой набор таблиц, каждая из которых в любой момент времени содержит некоторое мультимножество строк, соответствующих заголовку таблицы. В этом состоит первое и наиболее важное отличие модели данных SQL от реляционной модели данных. Вторым существенным отличием является то, что для таблицы поддерживается порядок столбцов, соответствующий порядку их определения. Другими словами, таблица – это вовсе не отношение, хотя во многом они похожи.
Имеется две основных разновидности таблиц, хранимых в базе данных: традиционная таблица и типизированная таблица. Традиционная таблица определяется как множество столбцов с указанными типами данных. В SQL поддерживаются следующие категории типов данных: точные числовые типы; приближенные числовые типы; типы символьных строк; типы битовых строк; типы даты и времени; типы временных интервалов; булевский тип; типы коллекций; анонимные строчные типы; типы, определяемые пользователем; ссылочные типы. Подробно система типов SQL описывается в лекции 15, а здесь мы ограничимся только пояснениями наименее очевидных случаев.
Булевский тип в SQL содержит три значения – true, false
и uknown. Это связано с интенсивным использованием в SQL так называемого неопределенного значения (NULL), которое разрешается использовать вместо значения любого типа данных. Как уже говорилось выше, здесь мы не будем более подробно затрагивать запутанную тему неопределенных значений и оставим подробности на следующие лекции.
В модели данных SQL допускается объявление двух видов типов коллекций: типы массива и типы мультимножества. Элементы типа коллекции могут быть любого типа данных, определенного к моменту определения данного типа коллекции. При объявлении типа мультимножества можно явно запретить наличие в его значениях элементов-дубликатов, что фактически приводит к объявлению типа множества.
Анонимный строчный тип – это безымянный структурный тип, значения которого являются строками, состоящими из элементов ранее определенных типов.
Начиная с SQL:1999, в языке поддерживается возможность использования типов данных, значения которых являются коллекциями значений некоторых других типов. Обычно под термином коллекция понимается одно из следующих образований: массив, список, множество и мультимножество. В варианте SQL:1999, принятом в 1999 г., были специфицированы только типы массивов. В новом стандарте SQL:2003 появилась спецификация типа мультимножества.
Любой возможный тип массива получается путем применения конструктора типов ARRAY. При определении столбца, значения которого должны принадлежать некоторому типу массива, используется конструкция dt ARRAY [ mc ], где dt специфицирует некоторый допустимый в SQL тип данных, а mc является литералом некоторого точного числового типа с нулевой длиной шкалы и определяет максимальное число элементов в значении типа массива (в терминологии SQL:1999 это значение называется максимальной кардинальностью массива). В стандарте SQL:1999 многомерные массивы и массивы массивов не поддерживались. Однако в стандарте SQL:2003 это ограничение было снято, и теперь типом элементов любого типа коллекций может быть любой допустимый в SQL тип данных, кроме самого конструируемого типа коллекции.
Элементам каждого значения типа массива соответствуют их порядковые номера, называемые индексами. Значение индекса всегда должно принадлежать отрезку [1, mc]. Значениями типа массива dt ARRAY [mc] являются все массивы, состоящие из элементов типа dt, максимальное значение индекса которых cs не превосходит значения mc. При сохранении в базе данных значения типа массива занимает столько памяти, сколько требуется для сохранения cs элементов. Обеспечивается доступ к элементам массива по их индексам. В частности, можно объявить столбец типа INTEGER ARRAY [10] и при вставке строки в соответствующую таблицу задать значение только пятого элемента массива. Тогда в строку будет занесен массив из пяти элементов, причем первые четыре элемента будут содержать неопределенное значение (NULL).
Основными операциями над массивами являются выборка значения элемента массива по его индексу, изменение некоторого элемента массива или массива целиком и конкатенация (сцепление) двух массивов. Кроме того, для любого значения типа массива можно узнать значение его cs.
При определении столбца таблицы типа мультимножеств используется конструкция dt MULTISET, где dt задает тип данных элементов конструируемого типа мультимножеств. Значениями типа мультимножеств являются мультимножества, т. е. неупорядоченные коллекции элементов одного и того же типа, среди которых допускаются дубликаты. Например, значениями типа INTEGER MULTISET являются мультимножества, элементами которых — целые числа. Примером такого значения может быть мультимножество {12, 34, 12, 45, -64}.
В отличие от массива, мультимножество является неограниченной коллекцией; при конструировании типа мультимножеств не указывается предельная кардинальность значений этого типа. Однако это не означает, что возможность вставки элементов в мультимножество действительно не ограничена; стандарт всего лишь не требует явного объявления границы. Ситуация аналогична той, которая возникает при работе с таблицами, для которых в SQL не объявляется максимально допустимое число строк.
Для типов мультимножеств поддерживаются операции преобразования типа значения-мультимножества к типу массивов или другому типу мультимножеств с совместимым типом элементов (операция CAST), для удаления дубликатов из мультимножества (функция SET), для определения числа элементов в заданном мультимножестве (функция CARDINALITY), для выборки элемента мультимножества, содержащего в точности один элемент (функция ELEMENT). Кроме того, для мультимножеств обеспечиваются операции объединения (MULTISET UNION), пересечения (MULTISET INTERSECT) и определения разности (MULTISET EXCEPT). Каждая из операций может выполняться в режиме с сохранением дубликатов (режим ALL) или с устранением дубликатов (режим DISTINCT).
Расширенные в SQL:2003 возможности работы с типами коллекций являются принципиально важными. Даже при наличии определяемых пользователями типов данных (см. ниже) и типов массивов SQL:1999 не предоставлял полных возможностей для преодоления исторически присущего реляционной модели данных вообще и SQL в частности ограничения «плоских таблиц». После появления конструктора типов мультимножеств и устранения ограничений на тип данных элементов коллекции это историческое ограничение полностью ликвидировано. Мультимножество, типом элементов которого является анонимный строчный тип (см. ниже), представляет собой полный аналог таблицы. Тем самым, в базе данных допускается произвольная вложенность таблиц. Возможности выбора структуры базы данных безгранично расширяются.
Эта категория типов данных связана с объектными расширениями языка SQL. Более подробно мы обсудим эту тему в лекции 23, а здесь для полноты картины приведем беглый набросок. Структурные типы (Structured Types). Соответствующие возможности SQL:1999 позволяют определять долговременно хранимые, именованные типы данных, включающие один или более атрибутов любого из допустимых в SQL типа данных, в том числе другие структурные типы, типы коллекций, строчные типы и т. д. Стандарт SQL не накладывает ограничений на сложность получаемой в результате структуры данных, однако не запрещает устанавливать такие ограничения в реализации. Дополнительные механизмы определяемых пользователями методов, функций и процедур позволяют определить поведенческие аспекты структурного типа. Индивидуальные типы (Distinct Types). Можно определить долговременно хранимый, именованный тип данных, опираясь на единственный предопределенный тип. Например, можно определить индивидуальный тип данных PRICE, опираясь на тип DECIMAL (5, 2). Тогда значения типа PRICE представляются точно так же, как значения типа DECIMAL (5, 2). Однако в SQL:1999 индивидуальный тип не наследует от своего опорного типа набор операций над значениями. Например, чтобы сложить два значения типа PRICE требуется явно сообщить системе, что с этими значениями нужно обращаться как со значениями типа DECIMAL (5, 2). Другая возможность состоит в явном определении методов, функций и процедур, связанных с данным индивидуальным типом. Похоже, что в будущих версиях стандарта появятся и другие, более удобные возможности.
В SQL определены три параметризуемых типа символьных строк: CHARACTER (или CHAR), CHARACTER VARYING (или CHAR VARYING, или VARCHAR) и CHARACTER LARGE OBJECT (или CLOB).
Тип CHARACTER. Значениями типа являются символьные строки. Конкретный набор допустимых символов определяется в реализации, но, как правило, включает набор символов ASCII. При определении столбца допускается использование спецификаций CHARACTER (x) и просто CHARACTER. Последний вариант эквивалентен заданию CHARACTER (1). После определения столбца типа CHARACTER (x) СУБД будет резервировать место для хранения x символов этого столбца во всех строках соответствующей таблицы. Если, например, определен столбец типа CHARACTER (8), и в некоторой строке таблицы в него заносится символьная строка длиной пять символов, то реально будут храниться восемь символов, последние три из которых будут пробелами.Тип CHARACTER VARYING. При определении столбца допускается использование спецификаций CHARACTER VARYING (x) и просто CHARACTER VARYING. Последний вариант эквивалентен заданию CHARACTER VARYING (1). Если в некоторой таблице определяется столбец типа CHARACTER VARYING (x), то в каждой строке этой таблицы значения данного столбца будут занимать ровно столько места, сколько требуется для сохранения соответствующей символьной строки (но ни одна такая строка не может состоять более чем из x символов).Определен ряд операций, которые можно выполнять над символьными строками. Перечислим некоторые из них. Операция конкатенации (обозначается в виде «») возвращает символьную строку, произведенную путем соединения строк-операндов в том порядке, в каком они заданы. Функция выделения подстроки (SUBSTRING) принимает три аргумента – строку, номер начальной позиции и длину – и возвращает строку, выделенную из строки-аргумента в соответствии со значениями двух последних параметров. Функция UPPER возвращает строку, в которой все строчные буквы строки-аргумента заменяются прописными. Функция LOWER, наоборот, заменяет в заданной строке все прописные буквы строчными. Функция определения длины (CHARACTER_LENGTH, OCTET_LENGTH, BIT_LENGTH) возвращает длину заданной символьной строки в символах, октетах или битах (в зависимости от вида вычисляющей функции) в виде целого числа. Функция определения позиции (POSITION) определяет первую позицию в строке S, с которой в нее входит заданная строка S1 (если не входит, то возвращается значение нуль). Тип CHARACTER LARGE OBJECT. Этот тип данных предназначен для определения столбцов, хранящих большие и разные по размеру группы символов. При определении столбца задается спецификация CLOB (z), где z задает максимальный размер соответствующей группы символов. Максимально возможное значение параметра z определяется в реализации, но, очевидно, что оно должно быть существенно больше максимально возможного значения параметра x, присутствующего в типах CHAR и CHAR VARYING.Литералы типов символьных строк представляются в виде последовательностей символов, заключенных в одинарные или двойные кавычки. В первом случае среди набора символов литерала допускается наличие символов двойной кавычки, а во втором – символов одинарной кавычки. Примеры литералов символьных строк: ’ABCDEF’, ’Ab"Ctd’, "Fbcdef", "ab’cdtF".
Тип TIME. Значения этого параметризованного типа состоят из компонентов-значений часа, минуты и секунды некоторого времени суток. Значение часа состоит ровно из двух десятичных цифр и варьируется от 00 до 23. Значение минуты состоит из двух десятичных цифр и варьируется от 00 до 59. Основное значение секунды также состоит из двух цифр, но может включать дополнительные цифры, представляющие доли секунды. Так что в целом значение секунды варьируется от 00 до 61.999... В значении времени присутствуют две лишние секунды, поскольку Всемирная служба времени иногда добавляет две секунды к последней минуте года для синхронизации мирового времени с реальным. Решение о поддержке этих «високосных» секунд принимается на уровне реализации. Число цифр в доле секунды также определяется в реализации. В стандарте требуется только то, чтобы это число было не меньше шести. При определении столбца типа TIME может указываться TIME (p) (значение p задает точность долей секунды) или просто TIME (в этом случае доли секунды не учитываются).Литералы типа TIME представляются в виде строки TIME ’hh:mm-ss:f...f’, где символы h, m, s и f должны изображать десятичные числа. Например, литерал TIME ’16:33-20:333’ представляет время суток 16 часов 33 минуты 20 и 333 тысячных секунды.
Тип TIME WITH TIME ZONE. Этот тип данных похож на тип TIME с тем лишь отличием, что значения типа TIME WITH TIME ZONE включают дополнительный компонент — значение, характеризующее смещение соответствующего времени относительно гринвичского времени (теперь его называют UTC – universal time coordinated). Деталей представления этого дополнительного компонента мы касаться не будем.Тип TIMESTAMP WITH TIME ZONE. Этот тип данных отличается от типа TIMESTAMP тем, что значения типа TIMESTAMP WITH TIME ZONE включают дополнительный компонент-значение, характеризующее смещение соответствующего времени относительно гринвичского.
Тип TIMESTAMP. Значения этого параметризованного типа состоят из компонентов — значений года, месяца и дня некоторой даты, а также компонентов — значений часа, минуты и секунды некоторого времени суток (т. е. каждое значение задает некоторую абсолютную временную метку – отсюда название типа TIMESTAMP). Число десятичных цифр в значениях-компонентах и ограничения этих значений такие же, как у значений типов DATE и TIME. При определении столбца типа TIMESTAMP может указываться TIMESTAMP (p) (значение p задает точность долей секунды) или просто TIMESTAMP (в этом случае, в отличие от типа данных TIME, по умолчанию принимается, что в доли секунды используются шесть десятичных цифр). Максимально допустимое значение p определяется в реализации. Литералы типа TIMESTAMP представляются в виде строки TIMESTAMP ’yyyy-mm-dd hh:mm-ss:f...f’, где символы y, m, d, h, m, s и f должны изображать десятичные числа. Например, литерал TIMESTAMP ’1949-04-08 16:33-20:333’ представляет временную метку 16 часов 33 минуты 20 и 333 тысячных секунды 8 апреля 1949 г.
Вообще говоря, временным интервалом называется разность между двумя значениями даты или времени. В SQL определены две категории типов временных интервалов: «год-месяц» и «день-время суток». Временные интервалы языка SQL не привязываются к начальному и/или конечному значению даты/времени, а описывают только протяженность во времени. В общем случае при определении столбца типа временного интервала указывается INTERVAL start (p) [ TO end (q) ], где в качестве «start» и «end» могут задаваться YEAR, MONTH, DAY, HOUR, MINUTE и SECOND. Параметр p задает требуемую точность лидирующего поля интервала (число десятичных цифр). Параметр q может задаваться только в том случае, когда в качестве end используется SECOND, и указывает точность долей секунды. Если говорить более точно, возможны следующие вариации типов временных интервалов. Типы категории «год-месяц». Можно определить столбцы следующих типов: INTERVAL YEAR, INTERVAL YEAR (p) (значения этих типов – временные интервалы в годах), INTERVAL MONTH, INTERVAL MONTH (p) (значения этих типов – временные интервалы в месяцах), INTERVAL YEAR TO MONTH, INTERVAL YEAR (p) TO MONTH (значения этих типов – временные интервалы в годах и месяцах). Если значение параметра p не указывается явно, по умолчанию принимается его значение «2». Типы категории «день-время суток». При определении столбца можно использовать следующие комбинации (для полноты перечислим все возможности):
INTERVAL DAY (p), INTERVAL DAY, INTERVAL DAY (p) TO HOUR, INTERVAL DAY TO HOUR, INTERVAL DAY (p) TO MINUTE, INTERVAL DAY TO MINUTE, INTERVAL DAY (p) TO SECOND (q), INTERVAL DAY TO SECOND (q), INTERVAL DAY (p) TO SECOND, INTERVAL DAY TO SECOND, INTERVAL HOUR (p), INTERVAL HOUR, INTERVAL HOUR (p) TO MINUTE, INTERVAL HOUR TO MINUTE, INTERVAL HOUR (p) TO SECOND (q), INTERVAL HOUR TO SECOND (q), INTERVAL HOUR TO SECOND, INTERVAL MINUTE (p), INTERVAL MINUTE, INTERVAL MINUTE (p) TO SECOND (q), INTERVAL MINUTE TO SECOND (q), INTERVAL MINUTE (p) TO SECOND, INTERVAL MINUTE TO SECOND, INTERVAL SECOND (p, q), INTERVAL SECOND (p), INTERVAL SECOND.
Если значение параметра p не указывается явно, по умолчанию принимается его значение «2». Значением параметра q по умолчанию является «6». Приведем только один пример литерала одной из разновидностей типа INTERVAL: INTERVAL ’10:20’ MINUTE TO SECOND – временной интервал в 10 минут и 20 секунд.Над значениями темпоральных типов могут выполняться арифметические операции, смысл которых определяется следующей таблицей:
Datetime | - | Datetime | Interval |
Datetime | + или - | Interval | Datetime |
Interval | + | Datetime | Datetime |
Interval | + или - | Interval | Interval |
Interval | * или / | Numeric | Interval |
Numeric | * | Interval | Interval |
Как мы уже отмечали, использование долговременных транзакций повышает риск полного аннулирования результатов транзакции по причине нарушения ограничений с отложенной проверкой при выполнении каких-либо экспериментальных (недостаточно проверенных) операций. Конечно, теоретически можно было бы оформлять выполнение каждой такой подозрительной операции в виде отдельной транзакции, но это часто противоречит общей логике приложения, когда последовательность действий должна быть атомарной.
Частичное решение этой проблемы предоставляет механизм точек сохранения (savepoint) SQL:1999. Точка сохранения представляет собой своего рода пометку в последовательности операций транзакции, которую в дальнейшем можно использовать для частичного отката транзакции с сохранением жизнеспособности транзакции и результатов операций, выполненных в транзакции до точки сохранения. Пример использования точки сохранения показан на .
Рис. 22.7. Пример транзакции с точкой сохранения
На этом рисунке после выполнения последовательности проверенных «безопасных» операций, которые, по мнению пользователя, не могут нарушить ограничения целостности с отложенной проверкой, устанавливается точка сохранения. За этой точкой следует серия «рискованных» операций. Если по каким-то причинам (например, путем немедленной проверки отложенных ограничений) затем принимается решение о нецелесообразности фиксации результатов данных операций, то выполняется частичный откат транзакции к точке сохранения, а затем фиксируются результаты безопасных операций.
Допускается установка в одной транзакции нескольких последовательных точек сохранения. При установке каждой точки сохранения ей назначается некоторое (локальное в пределах транзакции) имя, которое в дальнейшем может использоваться в операции ROLLBACK для задания точки частичного отката транзакции (см. выше синтаксис оператора ROLLBACK). Если последовательно устанавливаются две точки сохранения SP1 и SP2 и затем выполняется операция ROLLBACK TO SAVEPOINT SP1, то восстановление производится до SP1 (через SP2), и точка сохранения SP2 «забывается».
Для установления точки сохранения используется оператор SAVEPOINT c очевидным синтаксисом
SAVEPOINT savepoint_name
Можно также отказаться от ранее установленной точки сохранения, удалив ее из контекста транзакции. Для этого предназначен оператор RELEASE, синтаксис которого также очевиден:
RELEASE SAVEPOINT savepoint_name
После выполнения этой операции в данной транзакции невозможно выполнять какие-либо другие операции над точкой сохранения с данным именем, пока не будет образована другая одноименная точка сохранения.
К категории точных числовых типов в SQL относятся те типы, значения которых точно представляют числа. Типы данных этой категории распадаются на две части: истинно целые типы (INTEGER и SMALLINT) и типы, допускающие наличие дробной части (NUMERIC и DECIMAL). Охарактеризуем эти типы данных более подробно.
Тип NUMERIC. На самом деле, это не просто тип данных, а параметризуемый тип. При определении столбца можно указать спецификацию NUMERIC (p, s), где p и s – литералы истинно целого типа, и p задает точность значений (число сохраняемых бит), а s – шкалу (число десятичных цифр в дробной части). Задаваемая шкала не должна быть отрицательной и не должна превышать значение точности. При определении столбца можно использовать сокращенные формы спецификации типа – NUMERIC и NUMERIC (p). Первая форма предполагает использование точности, определяемое по умолчанию в реализации, и шкалы, равной нулю, а вторая – использование заданной точности и шкалы, равной нулю. Допустимые диапазоны значений p и s определяются в реализации.Тип DECIMAL. Этот тип аналогичен типу NUMERIC. Отличие состоит в том, что если при определении столбца типа DECIMAL задается точность p, то на самом деле используется точность m, определяемая в реализации, такая, что m > p. Шкала всегда устанавливается такой, как явно или неявно (по умолчанию) задается. При указании типа столбца можно использовать спецификации DECIMAL, DECIMAL (p) и DECIMAL (p, s). Литералы типов точных чисел, допускающих наличие дробной части, представляются в виде строк символов, изображающих десятичные числа, в начале которых могут присутствовать символы «+» или «-» (если символ знака отсутствует, подразумевается «+»), а внутри последовательности цифр может присутствовать символ «.». Примеры литералов типов NUMERIC и DECIMAL: 125, 26.36.
Понятие транзакции имеет непосредственную связь с понятием целостности базы данных. Очень часто база данных может обладать такими ограничениями целостности, которые просто невозможно не нарушить, выполняя только один оператор изменения базы данных. Например, в базе данных СЛУЖАЩИЕ-ОТДЕЛЫ
(см. лекцию 1) естественным ограничением целостности является совпадение значения атрибута ОТД_РАЗМЕР
в кортеже таблицы ОТДЕЛЫ, описывающей данный отдел (например, отдел 625), с числом кортежей таблицы СЛУЖАЩИЕ, таких, что значение поля СЛУ_ОТД_НОМЕР
равно 625. Как в этом случае принять на работу в отдел 625 нового сотрудника? Независимо от того, какая операция будет выполнена первой, вставка нового кортежа в таблице СОТРУДНИКИ
или модификация существующего кортежа в отношении ОТДЕЛЫ, после выполнения операции база данных окажется в нецелостном состоянии.
Поэтому для поддержки подобных ограничений целостности допускается их нарушение внутри транзакции с тем условием, чтобы к моменту завершения транзакции условия целостности были соблюдены. В системах с развитыми средствами ограничения и контроля целостности каждая транзакция начинается при целостном состоянии базы данных и должна оставить это состояние целостными после своего завершения. Несоблюдение этого условия приводит к тому, что вместо фиксации результатов транзакции происходит ее откат (т.е. вместо оператора COMMIT
выполняется оператор ROLLBACK), и база данных остается в таком состоянии, в котором находилась к моменту начала транзакции, т.е. в целостном состоянии.
Более точно, различаются два вида ограничений целостности: немедленно проверяемые и откладываемые. К немедленно проверяемым ограничениям целостности относятся такие ограничения, проверку которых бессмысленно или даже невозможно откладывать. Примером ограничения, проверку которого откладывать бессмысленно, являются ограничения домена (например, возраст сотрудника не может превышать 150 лет). Более сложным ограничением, проверку которого невозможно отложить, является следующее: зарплата сотрудника не может быть увеличена за одну операцию более чем на 100000 рублей.
Немедленно проверяемые ограничения целостности соответствуют уровню отдельных операторов языкового уровня СУБД. При их нарушениях не производится откат транзакции, а лишь отвергается соответствующий оператор.
Откладываемые ограничения целостности – это ограничения на базу данных, а не на какие-либо отдельные операции. По умолчанию такие ограничения проверяются при конце транзакции, и их нарушение вызывает автоматическую замену оператора COMMIT
на оператор ROLLBACK. Однако в некоторых системах поддерживается специальный оператор насильственной проверки ограничений целостности внутри транзакции. Если после выполнения такого оператора обнаруживается, что условия целостности не выполнены, пользователь может сам выполнить оператор ROLLBACK
с откатом транзакции до ее начала или до установленной ранее точки сохранения или постараться устранить причины нецелостного состояния базы данных внутри транзакции (видимо, это осмысленно только при использовании интерактивного режима работы).
Заметим, что концептуально в момент завершения транзакции проверяются все откладываемые ограничения целостности, определенные в этой базе данных. Однако в реализации стремятся при выполнении транзакции динамически выделить те ограничения целостности, которые действительно могли бы быть нарушены. Например, если при выполнении транзакции над базой данных СЛУЖАЩИЕ-ОТДЕЛЫ
в ней не выполнялись операторы вставки или удаления кортежей из отношения СЛУЖАЩИЕ, то проверять упоминавшееся выше ограничение целостности не требуется (а для проверки подобных ограничений требуется достаточно большая работа).
Понятно, что описанный механизм поддержки целостности баз данных обеспечивает требуемое свойство транзакций: никакая транзакция не может быть зафиксирована, если ее действия нарушили целостность базы данных. Однако в этом подходе имеются два серьезных дефекта.
Во-первых, если при выполнении транзакции не устанавливать точки сохранения и не проверять периодически соответствие текущего состояния базы данных (с точки зрения данной транзакции) ограничениям целостности, то долговременно выполняемая транзакция вполне вероятно может быть «откачена» системой при выполнении завершающего оператора COMMIT.
Конечно, это означает непроизводительный расход системных ресурсов и времени пользователей. Во-вторых, чем длиннее транзакция, модифицирующая состояние базы данных, тем потенциально больше ограничений целостности придется проверять при ее завершении и тем накладнее становится оператор COMMIT.
Простое и элегантное решение этой проблемы предлагается в . Авторы предлагают отказаться от откладываемых ограничений целостности базы данных, а вместо этого ввести составные операторы изменения базы данных (нечто наподобие блоков BEGIN … END, поддерживаемых в языках программирования). После выполнения каждого такого блока (или отдельного оператора изменения базы данных, используемого без операторов начала и конца блока) база данных должна находится в целостном состоянии. Если составной оператор нарушает ограничение целостности, то он целиком отвергается, и вырабатывается соответствующий код ошибки. Транзакция в этом случае не откатывается. Понятно, что при использовании такого подхода при выполнении оператора COMMIT
не требуется проверять ограничения целостности, и каждая зафиксированная транзакция будет оставлять базу данных в целостном состоянии.
Интересно, что для реализации описанного подхода не требуются какие-либо новые механизмы, кроме точек сохранения транзакции, насильственной проверки ограничений целостности и частичных откатов транзакций, а отмеченные ранее проблемы снимаются. К сожалению, насколько известно автору данной книги, этот подход на практике пока не применяется.
Материал этого подраздела уже излагался в подразделе лекции 16, но там это делалось в контексте определений ограничений целостности. Для полноты картины мы воспроизведем часть этого материала в контексте управления транзакциями.
Итак, любое ограничение целостности обладает атрибутом, определяющим время проверки данного ограничения. Этот атрибут может иметь значения DEFERRABLE (отложенная проверка) или NOT DEFERRABLE (немедленная проверка). Чтобы данное ограничение целостности могло когда-либо обладать свойством отложенной проверки, нужно, чтобы в определении такого ограничения присутствовали ключевые слова INITIALLY DEFERRED или INITIALLY IMMEDIATE. В любом случае, в каждый момент времени выполнения транзакции любое ограничение целостности находится в одном из двух состояний – отложенная проверка или немедленная проверка. Если начальным состоянием ограничения является INITIALLY DEFERRED, то в начале любой транзакции его текущим состоянием будет отложенная проверка. Аналогично для ограничений с начальным состоянием INITIALLY IMMEDIATE.
Любое ограничение, находящееся в состоянии немедленной проверки, всегда проверяется в конце выполнения любого оператора SQL. Немедленно проверяются и те ограничения, которые были определены как NOT DEFERRABLE, но для которых впоследствии был установлен режим немедленной проверки. Однако если текущим состоянием ограничения является отложенная проверка, оно будет проверяться только тогда, когда перейдет в состояние немедленной проверки. Это делается неявно при выполнении оператора COMMIT или явно при выполнении оператора SET CONSTRAINTS. Этот оператор имеет следующий синтаксис:
SET CONSTRAINTS { ALL | constraint_name_commalist} { DEFERRED | IMMEDIATE }
Ключевое слово ALL является сокращенной формой задания списка имен всех ограничений целостности, определенных в базе данных, которые специфицированы с указанием ключевого слова DEFERRABLE. Если список имен ограничений задается явно, то все входящие в него имена должны соответствовать ограничениям, определенным с указанием ключевого слова DEFERRABLE.
Далее, представим себе, что в первоначальной реализации информационной системы, основанной на использовании библиотек расширенных методов доступа к файлам, обрабатывается операция принятия на работу нового служащего. Следуя требованиям согласованного изменения файлов, информационная система вставляет новую запись в файл СЛУЖАЩИЕ и собирается модифицировать соответствующую запись файла ОТДЕЛЫ (или вставлять в этот файл новую запись, если служащий является первым в своем отделе), но именно в этот момент происходит (например) аварийное выключение питания компьютера.
Очевидно, что после перезапуска системы ее база данных будет находиться в рассогласованном состоянии (точно будут нарушены правила (3) и (4), а может быть, и правила (1)и (2)). Потребуется выяснить это (а для этого нужно явно проверить соответствие данных в файлах СЛУЖАЩИЕ и ОТДЕЛЫ) и привести данные в согласованное состояние. Проверку и коррекцию можно выполнить, например, следующим образом. Сгруппировать записи файла СЛУЖАЩИЕ по значениям поля СЛУ_ОТД_НОМЕР. Для каждой группы (a) проверить, существует ли в файле ОТДЕЛЫ запись, значение поля ОТД_НОМ которой равняется значению поля СЛУ_ОТД_НОМЕР записей данной группы; если такой записи в файле ОТДЕЛЫ нет, то (b) исключить группу из файла СЛУЖАЩИЕ и перейти к обработке следующей группы; иначе (c) посчитать число записей в группе и вычислить суммарное значение заработной платы; (d) обновить полученными значениями поля ОТД_РАЗМЕР и ОТД_СЛУ_ЗАРП соответствующей записи файла ОТДЕЛЫ и перейти к обработке следующей группы.
Настоящие СУБД берут такую работу на себя, поддерживая транзакционное управление и журнализацию изменений базы данных. Прикладная система не обязана заботиться о поддержке корректности состояния базы данных, хотя и должна знать, какие цепочки операций изменения данных являются допустимыми.
Представим теперь, что в информационной системе требуется обеспечить параллельную (например, многотерминальную) работу с базой данных служащих и отделов. Если опираться только на использование файлов, то для обеспечения корректности на все время модификации любого из двух файлов доступ других пользователей к этому файлу будет блокирован (вспомните возможности файловых систем в отношении синхронизации параллельного доступа, упоминавшиеся в разделе ). Таким образом, зачисление на работу Петра Ивановича Сидорова существенно затормозит получение информации о служащем Иване Сидоровиче Петрове, даже если они работают в разных отделах. Настоящие СУБД обеспечивают гораздо более тонкую синхронизацию параллельного доступа к данным.