Парсер - Разработка программного средства, позволяющего оптимизировать SQL-скрипты

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

Задача парсера - разобрать исходный SQL-скрипт и уложить каждый его шаг в объект специально разработанного класса, список таких объектов будет представлять собой направленный граф.

Разрабатываемая программа рассчитана на то, чтобы оптимизировать скрипты, на основе которых далее будут строиться ETL-процессы, это ограничивает утверждения, которые надо разбирать до "create table as select". Остальные операции, присутствующие в скрипте, как правило, носят разовый характер и не должны учитываться.

Ранее, в разделе "Цели и задачи", говорилось о том, что программа должна работать в трех режимах.

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

При данном режиме накладываются ограничения и на разбор SQL-скриптов. Так, при выборе из исходных таблиц (не промежуточных) не допускается конструкция "select *".

Второй - анализ скрипта на основе имеющегося кода и дополнительных исходных данных. Если у программы нет возможности подключиться к базе самостоятельно, для парсера дополнительным преимуществом будет "знание" DDL объектов - определение исходных таблиц со списком их полей и индексами. При наличии такой информации можно разобрать конструкцию "select *".

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

В программе есть 3 основных типа объектов, представляющих собой таблицы - TableNode (таблица-узел), TableStep (таблица-шаг) и TableStepUnion (шаг-объединение).

Все объекты этих типов хранятся в списке, по которому можно построить направленный граф (пример на рис. 5).

способ представления sql-скрипта в виде графа

Рис. 5 Способ представления SQL-скрипта в виде графа

Класс TableStep наследуется от класса TableNode и расширяет его. Класс TableStepUnion также наследует класс TableNode, но в качестве исходных таблиц он использует только TableStep-объекты.

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

TableNode - класс, представляющий таблицу без учета того, из каких источников и как она собирается.

    - Название схемы БД, в которой лежит таблица - Имя таблицы - Список колонок - PI

Все атрибуты представляют собой строки (имя схемы или таблицы) или списки строк (список колонок, PI)

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

До описания атрибутов объектов этого класса рассмотрим структуру SQL-запросов на выборку данных в СУБД Teradata.

    - SELECT - список выражений - FROM - список исходных таблиц и условий их соединения - WHERE - дополнительные условия на отдельные строки итогового результата - GROUP BY - список полей/выражений, по которым надо группировать - HAVING - условия на группы - ORDER BY - упорядочивание вывода строк (только в простом select) - QUALIFY - условия на аналитические функции

QUALIFY - особенность диалекта SQL, используемого в Teradata. Выражения, использующиеся в этой части - аналог части having, но для аналитических функций. Такой вид функций позволяет работать с группой данных, при этом не прибегая к группировке, что в некоторых ситуациях значительно упрощает скрипты, уменьшая количество необходимых для достижения результата шагов. Конструкция qualify же позволяет отфильтровать неподходящие записи при том же чтении из таблицы, тогда как в oracle, например, для этого понадобится дополнительная выборка с условием where на результат аналитической функции.

Еще одна особенность диалекта SQL Teradata - возможность использования в GROUP BY номеров колонок из части SELECT. Это позволяет сократить длину запроса, но иногда делает его менее удобным для чтения, особенно если SELECT-список достаточно длинный. Так вместо GROUP BY FIELD_1, FIELD_2, FIELD_3 можно написать GROUP BY 1,2,3. Цифры, названия колонок и выражения можно использовать в произвольном сочетании.

Из представленной выше структуры в исходных SQL-скриптах могут встречаться все конструкции, кроме ORDER BY. Из остальных обязательно должны присутствовать только SELECT и FROM, а HAVING может быть только при наличии GROUP BY.

Класс TableStepUnion наследует класс TableNode. Кроме атрибутов основного класса он содержит только список TableStep-шагов (приведенных к типу TableNode) и то, какой именно операцией эти исходные таблицы объединяются: union (all) - объединение (all - не убирая дубли из финального результата), except (all) - исключение результата второй выборки из первой, intersect (all) - данные, совпадающие в витринах, minus аналог Teradata для except. На практике очень часто intersect, except и minus реализовываются с помощью inner или left объединений, тогда основным случаем использования типа таблиц TableStepUnion становится именно объединение данных.

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

В таблице 5 приведено описание основных классов, используемых для реализации хранения в программе SQL-кода (часть TableStep), и их назначения:

Таблица 5 Краткое описание основных классов в TableStep

Класс

Краткое описание

TableSelect

"Обертка" для класса TableNode, к таблице приписывается псевдоним.

TableColumn

Название колонки и таблица (TableSelect), из которой она выбирается

Expression

Выражение - текст и список колонок, которые в этом выражении используются. В самом тексте колонки заменены на маркеры вида "[0]".

SelectColumn

Колонки в select-списке. Представляет собой выражение (Expression) и псевдоним (при наличии).

Condition

Условие. Объекты данного класса могут использоваться в QUALIFY, WHERE, HAVING и внутри FROM в соединениях таблиц

JoinCondition

Условие соединения таблиц. Тип Join'а и список условий (Condition) при их наличии.

После выделения из шага скрипта select-части она представляется в следующем виде:

    - select expression_list: - expression alias - ... - from join_list: - left table join right table on condition_list - left_expression condition right_expression - where condition_list - left_expression condition right_expression - group by expression_list (ссылки на выражения из select) - having condition_list - qualify condition_list

Для разбора SQL-скриптов были разработаны 2 класса: SQLСleanUp и SQLParser. Первый принимает на вход текст скрипта целиком, очищает его от мусора (в т. ч. не интересующие нас для оптимизации шаги).

Такая структура близка к способу хранения информации о ETL-процессе в инструменте для их построения - SAS Data Integration Studio, поэтому данный парсер может быть в дальнейшем использован и для других проектов, в частности - автоматической сборки простейших ETL по SQL-коду.

Похожие статьи




Парсер - Разработка программного средства, позволяющего оптимизировать SQL-скрипты

Предыдущая | Следующая