Перечень статей   Choose language


Тропиночная нотация против
CURSOR, Datalog, SQL/XML и
рекурсивного SELECT

Против CURSOR и Datalog

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

Одна состояла в создании указателя и последовательном перемещении его от записи к записи. В реальности получилось даже хуже: был внедрен ограниченный вариант указателя, называемый курсором (CURSOR), который может перемещаться не по всей базе данных, а только в пределах одной таблицы [1]. Наличие более одного одновременно открытого курсора указывает, что мы работаем фактически с иерархическими данными, а не с декартовыми произведениями. А применение оператора LOOP (мы же не используем его для формирования декартовых произведений!) говорит о том, что мы заставили человека трассировать работу машины, а наш оператор является калькой с ассемблерной мнемоники LOOP. Решение откровенно неприемлемое, приводящее к непроизводительному [2] расходованию человеческого времени. Выходом является манипулирование целым деревом сразу, а не обработка индивидульных записей. Предлагаю осудить использование курсоров и не рекомендовать их к дальнейшему использованию.

Другая состояла в отказе от нотации WHERE field1=field2 и обращении к синтаксису tablename1(_, X), tablename2(X, _). Авторов [3] не смутил тот факт, что этот синтаксис труден как в изучении, так в контроле промежуточных результатов выполнения программы, что было уже известно на примере первородного Пролога. Провал японской программы ЭВМ пятого поколения лишь подтвердил неюзабельность этого решения. Последствия этой второй попытки были поистине ужасны: в стандарт SQL:1999 была введена конструкция WITH RECURSIVE - теперь человек стал отслеживать итератор в прологовском стиле. Как будто кальки с ассемблерной мнемоники не хватало. Разумеется, также предлагаю осудить использование конструкции WITH RECURSIVE и не рекомендовать ее к дальнейшему использованию.


Почему когда мы пишем адрес на конверте, мы не используем указатель для стран, указатель для городов, указатель для улич, а перечисляем "записи разных таблиц" через запятую? Почему когда мы пишем путь в shell-е по файловой системе, мы не создаем функцию, вызывающую саму себя, а перечисляем "записи одной таблицы" через слэш? То же самое мы делаем, когда пишем путь на XPath. Почему в тех случаях мы оставляем конкретную реализацию в за кадром, а в случае SQL не догадываемся??.

Что мешает повторить это очевидно хорошее решение, указав требования к записям в WHERE и перечислив таблицы, в которых записи находятся, через другой знак, отличный от запятой и слэша - 'tablename1.tablename2.tablename3 WHERE field1a=field2a and field2b=field3b' [4]? И если дерево ветвится, взять обе его ветви в скобки 'tablename1.(tablename2.tablename3   tablename7.tablename8)'. А, например, в операторе UPDATE указывать, с каких точках дерева должны происходить обновление полей 'UPDATE tablename1.tablename2.tablename3 SET field2c=5 WHERE field1a=field2a and field2b=field3b)'.

Против SQL/XML и рекурсивного SELECT

Рассмотрим SQL/XML как способ извлечения иерархических данных (а не как способ оформления иерархических данных в xml-виде). Использование SQL/XML-функций, равно как и синтаксиса проприетарного веб-сервера [5], дают очень громоздкий код, который тяжело писать. В то же время обычно извлекаются записи, уже связанные внешним ключом - мы имеем дерево, уже сформированное в схеме базы данных. Предлагаю лаконичное 'select a.b.c' для выбора данных из таких уже связанных таблиц 'a', 'b', 'c' [6] (предполагается, что таблицы ссылаются друг на друга только одним внешним ключом, и что две таблицы не ссылаются друг на друга одновременно [7]).

Например, для нахождения самого дешевого маршрута между городами

create table city (
  id   number  primary key,
  name varchar
);
create table price (
  id1  number  references city (id),
  id2  number  references city (id),
  cost money
);
мы могли бы написать лаконично и прозрачно, используя

select sum(@cost)
from   city[@id=5].price*.city[@id=700]
where  previous(price)/@id2=next(price)/@id1;
вместо громоздкого выражения

with recursive res (@id1, @id2, @total) as (
  select  @id1, @id2, @cost
    from  price
    where @id1=5
  union
  select  res/@id1, price/@id2, res/@total+price/@cost
    from  res, price
    where res/@id2=price/@id1
) select  @id1, @id2, @total
    from  res
    where @id2=700;

Примечания

[1] В общем случае представления (view)

[2] Человек должен формулировать решение, а не прослеживать выполнение отдельных команд. Оставим в стороне тот факт, что этот подход приводит к посторяющимся пересылкам отдельных записей на сервер и обратно, забивает трафик, сужает полосу пропускания, жрет ресурсы машин

[3] 1978 год, Herve Gallaire и Jack Minker провели семинар, посвященный этой теме; 1980 год, David Maier ввел термин "даталог", datalog (DATAbase + proLOG, очевидно)

[4] Желательно через какой-нибудь много меньший буквы, чтобы свободное пространство естественным образом разделяло названия таблиц, например через точку

[5] Кроме того, превращение всех реляционных полей не в xml-аттрибуты, а в xml-элементы подходит для браузера, но не подходит для mash-up сервисов и всевозможных языков, основанных на XML

[6] Но ничего не стоит соединить два дерева (BTT), как это показано на с.25

[7] Если обе таблицы ссылаются друг на друга, или одна ссылается на другую несколькими внешними ключами, то эта неоднозначность разрешается в запросе дерева: после собственно имени таблицы, содержащей нужный внешний ключ, ставится знак '#' и имя нужного ссылающегося поля (не имя constraint-а, т.е. внешнего ключа). Выглядит как новое имя таблицы с символом '#' в середине имени (с. 12-14). Такое указание имени ссылающегося поля будем называть термином 'рафинирование'. Аналогично, если таблица содержит список и ссылается на саму себя несколькими внешними ключами, то эта неоднозначность также разрешается в запросе дерева: после собственно имени таблицы ставится знак '$' и имя нужного ссылающегося поля (не имя constraint-а, т.е. внешнего ключа). Это также выглядит как новое имя таблицы с символом '$' в середине имени (с. 15-16). Такое указание имени ссылающегося поля также будем называть это термином 'рафинирование'. В двух разных видах рафинирования использованы разные знаки - '#' и '$' - чтобы оба вида рафинирования можно было использовать одновременно: 'table#field1$field2'



Тюрин Дмитрий, dmitryturin@yandex.ru



Перечень статей   Choose language


Используются технологии uCoz