Компилятор запроса 0.6
Описание:
Данная разработка предназначена для оптимизации работы стандартного объекта 1С «Запрос» (далее — 1СЗапрос).
Всем известно, что 1С77 считает MSSQL продолжением FoxPro.
При выполнении запроса, Предприятие вначале закачивает всю информацию к клиенту, а затем производит необходимые вычисления. Т.е. возможности SQL сервера практически не используются.
ТЗапрос выполняет эту работу. Перед выполнением он компилирует текст запроса в текст TransactSQL, и выполняет его на сервере, а клиенту передает только результат, что, собственно, и требуется. Причем работа ТЗапроса полностью аналогична работе 1СЗапроса, т.е. для уже написанных программ потребуется только заменить слово Запрос на ТЗапрос и все! Отличия от 1СЗапроса даны ниже. Кстати, ТЗапрос заметно расширяет возможности 1СЗапроса. Детали — в разделе ДОПОЛНЕНИЯ.
Не смотря на то, что программа написана на языке 1С, скорость ее работы практически не уступает скорости работы 1СЗапроса (замеры производительности даны в конце описания).
Требования:
- 1С Предприятие 7.7
- 1С++ от OXY.
- Microsoft SQL Server (на DBF не работает)
- Компонента «Оперативный учет» (т.к. поддерживаются только Справочник, Документ и Регистр).
- С дугой компонентой (зарплата, бухучет) можно использовать только Справочник и Документ.
Установка:
- Установить 1С++
- Скопировать в каталог базы папку classes.
- Скопировать файл functions.sql в корневой каталог базы. (При обновлении со старой рекомендуется запустить в QueryAnalyser скрипт update.sql)
- Скопировать текст файла ГлобальныйМодуль.ert в глобальный модуль.
- Добавить в файл defcls.prm строку: //#include «classes\hpp.Запрос.ert»
- В каждом регистре поставить галочку «Быстрая обработка движений». Кстати, это повысит общее быстродействие конфигурации.
- Не забудьте для пользователей в меню Сервис->Параметры->1С++ выбрать «Оптимизация» и убрать «Проверка типов» и «Отладчик»
Использование:
Полностью аналогично применению стандартного Запроса 1С, отличия только в символе «Т»:
Вместо:
1 2 3 |
Запрос=СоздатьОбъект("Запрос"); ТекстЗапроса="..."; Запрос.Выполнить(ТекстЗапроса); |
Надо писать:
1 2 3 |
Запрос=СоздатьОбъект("ТЗапрос"); ТекстЗапроса="..."; Запрос.Выполнить(ТекстЗапроса); |
Обращение к атрибутам производится так:
1 2 |
Запрос.ПолучитьАтрибут(«ЧтоТо»);//или Запрос.Т.ЧтоТо |
ВНИМАНИЕ! Переменные, используемые в запросе должны быть глобальными в контексте вызова!
ОТЛИЧИЯ
- Операторы запроса не чувствительны к регистру. Переменные чувствительны к регистру!
- В редких случаях порядок записей при сортировке может не совпадать с сортировкой Запроса 1С
- Логика sql — запроса работает лучше, чем логика Запроса 1С. Поэтому результаты в специфических ситуациях могут отличатся.
Например:
1 2 3 4 5 |
ТекстЗапроса=" |Владелец=Справочник.Партия.Владелец; |Партия=Справочник.Партия.ТекущийЭлемент; |Группировка Владелец; |Группировка Партия;" |
Здесь, по идее, в запрос не должны быть включены группы товаров, т.к. у групп не может быть подчиненных элементов из таблицы Партия. Однако у 1С группы присутствуют.
Или, к примеру, если в запросе по регистрам отсутствуют функции НачОст и т.п., то, по идее, результатом не должно быть пустое значение. Поэтому 1СЗапрос дает NULL, а правильный ТЗапрос дает не нулевой результат.
- Вместо функции запроса Запрос.ЭтоГруппа() используется переменная запроса, например:
1 2 3 4 5 6 7 8 9 |
ТекстЗапроса=".... |ЭтоГр=Справочник.Товары.ЭтоГруппа; |Условие(ЭтоГр<>1); |"; Запрос.Выполнить(ТекстЗапроса); ... ... Если Запрос.ЭтоГр<>1 Тогда ... КонецЕсли; |
- В функциях макс(n,n,…) и мин(n,n,…) допускается не более 5 значений.
- Нельзя использовать уточняющие переменные в функциях и условиях, определяйте все необходимые переменные заранее.
- ГРУППЫ справочника всегда сортируются по алфавиту, не зависимо от сортировки, указанной в Группировке.
Особенности:
- Пока нельзя использовать переменные типа Перем1=Документ.ПрихНакл.ЧтоТо,Документ.РасхНакл.ЧтоТо;
или
1 2 |
Перем1=Документ.ПрихНакл.ЧтоТо; Перем2=Документ.РасхНакл.ЧтоТо; |
Корневой объект метаданных должен совпадать у всех переменных. Это будет что-то из разряда UNION ALL.
- Не работают периодические реквизиты.
- Не работает обратная группировка Запрос.Группировать(1,-1);
- Методы Запрос.ИспользоватьГрафуОтбора(ГрафаОтбора), Запрос.Получить() не реализованы.
- Проверка соответствия типов в условиях и функциях плохо работает на агрегатных типах. Будьте внимательны.
ЗАМЕЧАНИЯ
- Для справочника нельзя писать условие (ЭтоГруппа=0), надо (ЭтоГруппа<>1)
- Нельзя использовать служебные качестве имен переменных (напр. Группировка,Счётчик,Строка,Дата,Число).
- Справочники в условиях «В». ВАЖНО: Порядок переменных имеет значение. Слева от условия должны стоять внутренние переменные. Значение справа может быть любым. Но нельзя использовать значения функций в качестве аргументов выражений. Пример:
1 2 3 |
Партия=Справочник.Партия.ТекущийЭлемент; //|Условие(ВыбПартия в Партия); - так нельзя |Условие(Партия в ВыбПартия); |
Надо заметить, что в подобных условиях имеет смысл только выражение ?(,,) :
1 2 3 4 5 6 |
|Кол=Справочник.Партия.КоличествоПрихода; |Партия1=Справочник.Партия.ТекущийЭлемент; |Партия2=Справочник.ПартияНов.ТекущийЭлемент; |Функция Сум=Сумма(Кол); //|Условие(?(Сум>0,Партия1,Партия2) в ВыбПартия); - нельзя |Условие(?(Кол>0,Партия1,Партия2) в ВыбПартия); |
Причем типы данных обоих результатов операции ?(,,) должны полностью совпадать.
Для того, чтобы отбор по группе сработал, необходимо в условии не использовать агрегатных функций.
- Переменные в выражении могут быть либо ВСЕ группировками/агрегатными функциями, либо все — нет. Например:
1 2 3 4 5 6 |
|Номенклатура = Регистр.ОстаткиТМЦ.Номенклатура; |Количество = Регистр.ОстаткиТМЦ.Количество; |Функция КонОст = КонОст(Количество); |Группировка Номенклатура; //|Условие(?(Количество=sql('1',Число),Номенклатура,Номенклатура) в ВыбНоменклатура); - нельзя |Условие(?(Сумма(Количество)=sql('1',Число),Номенклатура,Номенклатура) в ВыбНоменклатура); |
ДОПОЛНЕНИЯ
- можно сделать упорядочивание с любой глубины, например Группировка Товар упорядочить по Товар.БазоваяЕдиница.Наименование
- Если перед выполнением запроса установить Запрос.ВключитьОтладку=n, где n=[1-3], то в сообщния будет выводится отладочная информация. Так же можно увидеть сгенерированный скрипт.
- Поддерживаются функции НачТик(Таймер) и КонТик(Таймер) для замера производительности.
Например:
1 2 3 4 |
Таймер=0; Запрос.НачТик(Таймер); Запрос.Выполнить(ТекстЗапроса); Запрос.КонТик(Таймер) |
- Запрос.ЗначениеГруппировки(Имя_Номер_Группировки) и Запрос.ЗначениеФункции(Имя_Номер_Функции) работают.
- Можно использовать следующие описания переменных, например:
1 2 3 4 5 6 |
"Перем1=Справочник.<ИмяСпр>.этогруппа; |Перем2=Справочник.<ИмяСпр>.помечен; |Перем3=Справочник.<ИмяСпр>.колуровней;//номер уровня в иерарх. справочнике |Перем4=Документ.<ИмяДок>.номерстроки; |Перем5=Документ.<ИмяДок>.проведен; |Перем6=Документ.<ИмяДок>.помечен;" |
- В функции поддерживаются выражения sql в формате sql(‘SQLExpression’,ТипРезультата), например
1 2 3 4 |
"... |Количество=Регистр.Остатки.Количество; |Функция Ф=Минимум(Количество)+sql('min(t.Количество)',Число); ..." |
- Можно использовать расширенный вариант Функции, например:
1 2 3 4 5 6 7 8 9 10 11 12 |
ДобавДата=Дата("01.02.43"); ТекстЗапроса=".... |Хор=Справочник.ХорошиеТовары.ТекущийЭлемент; |ХорДат=Справочник.ХорошиеТовары.ДатаЛицензии; |Плох=Справочник.ПлохиеТовары.ТекущийЭлемент; |ПлохДат=Справочник.ПлохиеТовары.ДатаЛицензии; |СтрДата=Справочник.ХорошиеТовары.Описание; |Функция Т=?(ПустоеЗначение(Хор)=1,Хор,Плох); |Функция Д=10+?(ХорДат<ПлохДат+1,ХорДат,ПлохДат);//дата+10дней |Функция Д2=10+Дата(Прав(СокрП(Док.ВремяЗавИсп), 8))+'ddd'; |Условие(Д>'01.02.03'); |"; |
Поддерживается трансляция всех стандартных функций из разделов:
[Синтаксис-помошник]
->[Встроенный язык]
->[Конструкции языка]
->[Функции/процедуры]
->[Математические]
->[Строковые]
->[Работа с датой]
->[Преобразование типов]
а также функция выбора по условию ?(,,) .
Поддерживаются все, кроме:
СтрЧислоВхождений;СтрКоличествоСтрок;СтрПолучитьСтроку;OemToAnsi;AnsiToOem;РабочаяДата;ПериодСтр;НачалоСтандартногоИнтервала;КонецСтандартногоИнтервала;
Все это (включая функции и условия) будет скомпилировано в TSQL и выполнено.
Прим: Преобразование типов из строки в дату производится только в формате ДД.ММ.ГГ
- Поддерживается группировка регистра остатков и всех документов по периоду Год, Квартал, Месяц, Неделя, День, Час, Минута, Секунда!
При использовании группировки по регистру, нельзя использовать функции НачОст() и КонОст().
Для использования, укажите в имени группировки переменную с объектом Регистр или Документ. Какой реквизит выбран (Фирма, Количество и т.д.) — не существенно, например:
1 2 3 4 5 6 7 8 |
"... |Рег=Регистр.ОстаткиТоваров.Фирма; |Рег2=Регистр.ОстаткиСумм.Позиция; |Док=Документ.ПриходнаяНакл.СуммаДок; |Группировка Рег.Месяц; |Группировка Рег2.Квартал; |Группировка Док.Секунда; ..." |
- ВНИМАНИЕ! Спец. функция sql(,) считается агрегатной. Со всеми вытекающими.
- Можно использовать инструкцию «ВыбратьПервые» для вывода первых n записей, где n=Число/Переменная, аналогично значению TOP n в sql., т.е. результат будет: SELECT TOP 1000 … FROM …
- «Без Групп» работает правильно. Т.е. группировки будут работать, просто результат будет без итогов.
- Добавлена работа с реквизитами недоопределенного типа <<Документ>> и <<Справочник>>, например, если:
1 2 3 4 5 |
Регистр.Остатки.Док – тип <<Документ>> Регистр.Остатки.Спр – тип <<Справочник>> Тогда можно сделать запрос: “Имя=Регистр.Остатки.Док.Номенклатура.Наименование; |Цена=Регистр.Остатки.Спр.Цена;” |
Будут выбраны все документы, записанные в регистр остатков, у которых есть реквизит Номенклатура, причем он должен совпадать у всех документов, записанных в регистр, и имеющих реквизит номенклатура. Если тип отличается, то данный документ не будет включен в выборку. В принципе, логично.
Такая же ситуация у типа <<Справочник>>.
Нужно учитывать, что подобное использование ТЗапроса снижает эффективность выполнения, т.к. необходимо вызвать дополнительный мини-запрос, и в конечном запросе появится еще один вложенный.
Общие реквизиты документа для типа <<Документ>> выбираются без потерь в производительности наравне с остальными реквизитами.
ПРОИЗВОДИТЕЛЬНОСТЬ
(2 группировки, 12728 записей, 2000МГц Athlon, 512Mb DDR, представлено время на обработку всех записей) версия 0.1:
- Запрос.Выполнить() по понятным причинам работает до 100 раз БЫСТРЕЕ.
Время компиляции запроса (10 переменных, 4 функции, 4 группировки, 2 условия) – 260 мс.
- Запрос.ПолучитьАтрибут() 320мс против 265, 1.2 раз медленнее.
- Запрос.Группировка() — 781мс против 517 мс., 1.51 раз медленнее. При обходе данных по «верхним» группировкам, разность скоростей растет, т.к. ТЗапрос использует только последовательный перебор записей.
- Полный цикл 2-й вложенности (по 2-м группировкам) с выводом строк в Таблицу 17300мс против 17013мс, т.е. итоговая общая потеря производительности в 1.01 раз.
ВСЕ СКОРОСТИ ПРИВОДЯТСЯ БЕЗ УЧЕТА ВРЕМЕНИ ВЫПОЛНЕНИЯ ЗАПРОСА Запрос.Выполнить()!!!