MyTetra Share
Делитесь знаниями!
О, смотри-ка какое хорошее место. Дайте два!
Варианты построения выборки диапазона чисел или дат с помощью запроса
08.09.2016
18:08
Автор: m..adm
Текстовые метки: 1c.quiries
Раздел: Программирование - 1с - queries

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

1. Генерация диапазона чисел запросом.

Данный метод уже не раз был описан. Повторюсь с некоторыми своими комментариями.

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

ВЫБРАТЬ 0 КАК Ч ПОМЕСТИТЬ ВТ

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 1;

ВЫБРАТЬ ВТ0.Ч + ВТ1.Ч*2 + ВТ2.Ч*4 + ВТ3.Ч*8 КАК Число

ИЗ ВТ КАК ВТ0, ВТ КАК ВТ1, ВТ КАК ВТ2, ВТ КАК ВТ3

В запросе выбираем числа 0,1 и помещаем во временную таблицу ВТ. Последующее соединение-сочетание таких таблиц образует необходимую выборку. Диапазон чисел, который сгенерирует такой запрос, будет равняться M в степени N, где M - количество чисел в системе счисления, N - количество соединяемых таблиц. В нашем примере имеем систему счисления с двумя числами и базовая таблица соединяется "сама с собой" 4 раза, поэтому результат получим в 16 чисел. Для получения большего диапазона чисел необходимо увеличивать количество соединений базовой таблицы, или же изменить систему счисления.

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

ВЫБРАТЬ 0 КАК Ч ПОМЕСТИТЬ ВТ

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 1

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 3

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 4

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 5

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 6

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 7

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 8

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 9;

ВЫБРАТЬ ВТ0.Ч + ВТ1.Ч*10 + ВТ2.Ч*100 + ВТ3.Ч*1000 КАК Число

ИЗ ВТ КАК ВТ0,ВТ КАК ВТ1,ВТ КАК ВТ2,ВТ КАК ВТ3

Этот запрос позволяет сформировать выборку чисел от 0 до 9999. Для увеличения диапазона выбранных чисел увеличиваем количество соединений базовой таблицы.

2. Генерация диапазона дат запросом в заданном периоде.

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

Можно, например, получить выборку дат из регистра сведений "Курсы валют":

ВЫБРАТЬ РАЗЛИЧНЫЕ

КурсыВалют.Период КАК Дата

ИЗ РегистрСведений.КурсыВалют КАК КурсыВалют

ГДЕ КурсыВалют.Период МЕЖДУ &НачалоПериода И &КонецПериода


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

ВЫБРАТЬ Рег.ДатаКалендаря КАК Дата

ИЗ РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК Рег

ГДЕ Рег.ДатаКалендаря МЕЖДУ &НачалоПериода И &КонецПериода


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

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

ВЫБРАТЬ 0 КАК Ч ПОМЕСТИТЬ ВТ

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 1

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 3

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 4

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 5

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 6

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 7

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 8

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 9;

ВЫБРАТЬ ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(&НачалоПериода,День),День,ВТ0.Ч + ВТ1.Ч*10 + ВТ2.Ч*100 + ВТ3.Ч*1000) КАК День

ИЗ ВТ КАК ВТ0,ВТ КАК ВТ1,ВТ КАК ВТ2,ВТ КАК ВТ3

ГДЕ ДОБАВИТЬКДАТЕ(&НачалоПериода,День,ВТ0.Ч + ВТ1.Ч*10 + ВТ2.Ч*100 + ВТ3.Ч*1000) <= НАЧАЛОПЕРИОДА(&КонецПериода,День)


Этот запрос позволяет сделать выборку всех дней с НачалоПериода по КонецПериода. Максимальное количество дней для этого запроса составляет 10000 (27 лет).

Такой запрос легко трансформируется в выборку секунд, минут, часов, лет или кварталов путем замены параметра "День" на "Секунда","Час","Минута","Год" или "Квартал". Выборка разных кварталов, например, будет выглядеть так:

ВЫБРАТЬ 0 КАК Ч ПОМЕСТИТЬ ВТ

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 1

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 3

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 4

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 5

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 6

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 7

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 8

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 9;

ВЫБРАТЬ ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(&НачалоПериода,Квартал),Квартал,ВТ0.Ч + ВТ1.Ч*10 + ВТ2.Ч*100 + ВТ3.Ч*1000) КАК Квартал

ИЗ ВТ КАК ВТ0,ВТ КАК ВТ1,ВТ КАК ВТ2,ВТ КАК ВТ3

ГДЕ ДОБАВИТЬКДАТЕ(&НачалоПериода,Квартал,ВТ0.Ч + ВТ1.Ч*10 + ВТ2.Ч*100 + ВТ3.Ч*1000) <= НАЧАЛОПЕРИОДА(&КонецПериода,Квартал)


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

3. Остатки товаров и срезы последних запросом на каждый день.

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

Например в теме http://infostart.ru/public/306536/ пользователем ildarovich был предложен следующий вариант получения остатков товаров на каждый день:

ВЫБРАТЬ РАЗЛИЧНЫЕ

КурсыВалют.Период

ПОМЕСТИТЬ Дни

ИЗ

РегистрСведений.КурсыВалют КАК КурсыВалют

ГДЕ

КурсыВалют.Период МЕЖДУ &НачалоПериода И &КонецПериода

;

////////////////////////////////////////////////////////////////////////////////

ВЫБРАТЬ

Движения.Номенклатура,

ДНИ.Период,

СУММА(ВЫБОР

КОГДА Движения.Период = &НачалоПериода

ТОГДА Движения.КоличествоКонечныйОстаток

ИНАЧЕ ВЫБОР

КОГДА Движения.Период < = ДНИ.Период

ТОГДА Движения.КоличествоОборот

ИНАЧЕ 0

КОНЕЦ

КОНЕЦ) КАК КоличествоКонечныйОстаток

ИЗ

Дни КАК ДНИ,

РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, День, , Номенклатура = &Номенклатура) КАК Движения

СГРУППИРОВАТЬ ПО

Движения.Номенклатура,

ДНИ.Период

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

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

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

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

Ближе к делу... Эксперимент

Беру предложенный вариант получения дневных конечных остатков по товарам на складах пользователя ildarovich и делаю из него следующий запрос для тестирования:

ВЫБРАТЬ 0 КАК Ч ПОМЕСТИТЬ ВТ

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 1

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 3

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 4

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 5

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 6

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 7

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 8

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 9;

ВЫБРАТЬ ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(&НачалоПериода,День),День,ВТ0.Ч + ВТ1.Ч*10 + ВТ2.Ч*100 + ВТ3.Ч*1000) КАК Период

ПОМЕСТИТЬ Дни

ИЗ ВТ КАК ВТ0,ВТ КАК ВТ1,ВТ КАК ВТ2,ВТ КАК ВТ3

ГДЕ ДОБАВИТЬКДАТЕ(&НачалоПериода,День,ВТ0.Ч + ВТ1.Ч*10 + ВТ2.Ч*100 + ВТ3.Ч*1000) <= НАЧАЛОПЕРИОДА(&КонецПериода,День);

ВЫБРАТЬ

Движения.Номенклатура,

ДНИ.Период,

СУММА(ВЫБОР

КОГДА Движения.Период = &НачалоПериода

ТОГДА Движения.КоличествоКонечныйОстаток

ИНАЧЕ ВЫБОР

КОГДА Движения.Период <= ДНИ.Период

ТОГДА Движения.КоличествоОборот

ИНАЧЕ 0

КОНЕЦ

КОНЕЦ) КАК КоличествоКонечныйОстаток

ПОМЕСТИТЬ ВТитог

ИЗ

Дни КАК ДНИ,

РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, День, , ) КАК Движения

СГРУППИРОВАТЬ ПО

Движения.Номенклатура,

ДНИ.Период;

ВЫБРАТЬ СУММА(ВТИтог.КоличествоКонечныйОстаток) КАК КоличествоКонечныйОстаток

ИЗ ВТИтог КАК ВТИтог

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

Для сравнения использую следующий запрос получения остатков на конец дня:

ВЫБРАТЬ 0 КАК Ч ПОМЕСТИТЬ ВТ

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 1

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 3

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 4

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 5

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 6

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 7

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 8

ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 9;

ВЫБРАТЬ ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(&НачалоПериода,День),День,ВТ0.Ч + ВТ1.Ч*10 + ВТ2.Ч*100 + ВТ3.Ч*1000) КАК Период

ПОМЕСТИТЬ Дни

ИЗ ВТ КАК ВТ0,ВТ КАК ВТ1,ВТ КАК ВТ2,ВТ КАК ВТ3

ГДЕ ДОБАВИТЬКДАТЕ(&НачалоПериода,День,ВТ0.Ч + ВТ1.Ч*10 + ВТ2.Ч*100 + ВТ3.Ч*1000) <= НАЧАЛОПЕРИОДА(&КонецПериода,День);

ВЫБРАТЬ

Движения.Номенклатура,

Движения.Период,

Движения.КоличествоКонечныйОстаток

ПОМЕСТИТЬ ВТД

ИЗ

РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, День, , ) КАК Движения

ИНДЕКСИРОВАТЬ ПО

Движения.Номенклатура,

Движения.Период;

ВЫБРАТЬ

ВН.Номенклатура,

ВН.Период,

ЕСТЬNULL(ВТД.КоличествоКонечныйОстаток, 0) КАК КоличествоКонечныйОстаток

ПОМЕСТИТЬ ВТИтог

ИЗ

(ВЫБРАТЬ РАЗЛИЧНЫЕ

Дни.Период КАК Период,

ВТД.Номенклатура КАК Номенклатура

ИЗ

Дни КАК Дни,

ВТД КАК ВТД) КАК ВН

ЛЕВОЕ СОЕДИНЕНИЕ ВТД КАК ВТД

ПО ВН.Номенклатура = ВТД.Номенклатура

И ВН.Период >= ВТД.Период

И (ВТД.Период В (ВЫБРАТЬ МАКСИМУМ(ВП.Период)

ИЗ ВТД КАК ВП

ГДЕ ВП.Номенклатура = ВТД.Номенклатура

И ВН.Период >= ВП.Период));

ВЫБРАТЬ СУММА(ВТИтог.КоличествоКонечныйОстаток) КАК КоличествоКонечныйОстаток

ИЗ ВТИтог КАК ВТИтог

Оба запроса выполняю в консоли запросов 10 раз и фиксирую время выполнения

Замер

Запрос 1

Запрос 2

1

4,609

4,297

2

4,531

4,297

3

4,563

4,328

4

4,609

4,297

5

4,703

4,313

6

4,578

4,454

7

4,469

4,297

8

4,484

4,313

9

4,515

4,281

10

4,703

4,297

Среднее,с.

4,5764

4,3174

Итоги тестирования показывают небольшой прирост производительности, чуть больше 5%. Однако эти замеры производились под полными правами. Можете проверить результаты под пользователем с ограниченной учетной записью на регистр "ТоварыНаСкладах", думаю, прирост производительности предлагаемого запроса будет еще выше.

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

Рассмотрим вариант применения такой реализации в запросе с регистром сведений.

За базу возьмем вариант получения цен номенклатуры на дату продажи товара предложенный в теме http://infostart.ru/public/77568/ пользователем _also . Его запрос выглядит так:

ВЫБРАТЬ

ПродажиОбороты.Период КАК Дата,

ПродажиОбороты.Контрагент КАК Контрагент,

ПродажиОбороты.Номенклатура КАК Номенклатура,

СУММА(ПродажиОбороты.КоличествоОборот) КАК Количество,

СУММА(ПродажиОбороты.СтоимостьОборот) КАК Стоимость

ПОМЕСТИТЬ втБезЦены

ИЗ

РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, День, ) КАК ПродажиОбороты

СГРУППИРОВАТЬ ПО

ПродажиОбороты.Период,

ПродажиОбороты.Контрагент,

ПродажиОбороты.Номенклатура

ИНДЕКСИРОВАТЬ ПО

Номенклатура,

Дата,

Контрагент

;

////////////////////////////////////////////////////////////////////////////////

ВЫБРАТЬ

втБезЦены.Дата КАК Дата,

втБезЦены.Контрагент КАК Контрагент,

втБезЦены.Номенклатура КАК Номенклатура,

втБезЦены.Количество,

втБезЦены.Стоимость,

МАКСИМУМ(ЦеныНоменклатуры.Период) КАК Период

ПОМЕСТИТЬ втМаксПериод

ИЗ

втБезЦены КАК втБезЦены

ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры

ПО втБезЦены.Номенклатура = ЦеныНоменклатуры.Номенклатура

И втБезЦены.Дата >= ЦеныНоменклатуры.Период

СГРУППИРОВАТЬ ПО

втБезЦены.Дата,

втБезЦены.Контрагент,

втБезЦены.Номенклатура,

втБезЦены.Количество,

втБезЦены.Стоимость

ИНДЕКСИРОВАТЬ ПО

Номенклатура,

Дата,

Контрагент,

Период

;

////////////////////////////////////////////////////////////////////////////////

ВЫБРАТЬ

втМаксПериод.Дата,

втМаксПериод.Контрагент,

втМаксПериод.Номенклатура,

втМаксПериод.Количество,

втМаксПериод.Стоимость,

ЦеныНоменклатуры.Цена

ИЗ

втМаксПериод КАК втМаксПериод

ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры

ПО втМаксПериод.Номенклатура = ЦеныНоменклатуры.Номенклатура

И втМаксПериод.Период = ЦеныНоменклатуры.Период

ГДЕ

ЦеныНоменклатуры.ТипЦен = &ТипЦен

АВТОУПОРЯДОЧИВАНИЕ


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

ВЫБРАТЬ

ПродажиОбороты.Период КАК Дата,

ПродажиОбороты.Контрагент КАК Контрагент,

ПродажиОбороты.Номенклатура КАК Номенклатура,

СУММА(ПродажиОбороты.КоличествоОборот) КАК Количество,

СУММА(ПродажиОбороты.СтоимостьОборот) КАК Стоимость

ПОМЕСТИТЬ втБезЦены

ИЗ

РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, День, ) КАК ПродажиОбороты

СГРУППИРОВАТЬ ПО

ПродажиОбороты.Период,

ПродажиОбороты.Контрагент,

ПродажиОбороты.Номенклатура

ИНДЕКСИРОВАТЬ ПО

Номенклатура,

Дата;

ВЫБРАТЬ

ВН.Дата,

ВН.Контрагент,

ВН.Номенклатура,

ВН.Количество,

ВН.Стоимость,

ЕСТЬNULL(Цены.Цена, 0) КАК Цена

ИЗ

(ВЫБРАТЬ

втБезЦены.Дата КАК Дата,

втБезЦены.Контрагент КАК Контрагент,

втБезЦены.Номенклатура КАК Номенклатура,

втБезЦены.Количество КАК Количество,

втБезЦены.Стоимость КАК Стоимость,

МАКСИМУМ(ЦеныНоменклатуры.Период) КАК Период,

МАКСИМУМ(ЦеныНоменклатуры.Регистратор) КАК Регистратор

ИЗ

втБезЦены КАК втБезЦены

ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры

ПО втБезЦены.Номенклатура = ЦеныНоменклатуры.Номенклатура

И втБезЦены.Дата >= ЦеныНоменклатуры.Период

И (ЦеныНоменклатуры.ТипЦен = &ТипЦен)

СГРУППИРОВАТЬ ПО

втБезЦены.Дата,

втБезЦены.Контрагент,

втБезЦены.Номенклатура,

втБезЦены.Количество,

втБезЦены.Стоимость) КАК ВН

ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены

ПО ВН.Номенклатура = Цены.Номенклатура

И ВН.Период = Цены.Период

И ВН.Регистратор = Цены.Регистратор

И (Цены.ТипЦен = &ТипЦен)

Мой запрос в данной реализации выглядел бы так:

ВЫБРАТЬ

Продажи.Дата,

Продажи.Контрагент,

Продажи.Номенклатура,

Продажи.Количество,

Продажи.Стоимость,

ЕСТЬNULL(Цены.Цена, 0) КАК Цена

ИЗ

(ВЫБРАТЬ

ПродажиОбороты.Период КАК Дата,

ПродажиОбороты.Контрагент КАК Контрагент,

ПродажиОбороты.Номенклатура КАК Номенклатура,

СУММА(ПродажиОбороты.КоличествоОборот) КАК Количество,

СУММА(ПродажиОбороты.СтоимостьОборот) КАК Стоимость

ИЗ

РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, День, ) КАК ПродажиОбороты

СГРУППИРОВАТЬ ПО

ПродажиОбороты.Период,

ПродажиОбороты.Контрагент,

ПродажиОбороты.Номенклатура) КАК Продажи

ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены

ПО Продажи.Номенклатура = Цены.Номенклатура

И Продажи.Дата >= Цены.Период

И (Цены.ТипЦен = &ТипЦен)

И ((Цены.Регистратор, Цены.Период) В

(ВЫБРАТЬ

МАКСИМУМ(ВТ.Регистратор),

МАКСИМУМ(ВТ.Период)

ИЗ

РегистрСведений.ЦеныНоменклатуры КАК ВТ

ГДЕ

ВТ.Номенклатура = Цены.Номенклатура

И ВТ.ТипЦен = &ТипЦен

И Продажи.Дата >= ВТ.Период))

Выполняю 10 раз подряд каждый запрос и фиксирую время выполнения:

Замер

Запрос 1

Запрос 2

1

0,875

0,313

2

0,844

0,265

3

0,844

0,266

4

0,843

0,297

5

0,844

0,282

6

0,859

0,281

7

0,844

0,297

8

0,843

0,281

9

0,984

0,283

10

0,844

0,297

Среднее, с.

0,8624

0,2862

Время выполнения предложенного мною запроса в среднем на 66% меньше. Интересно, какие результаты получатся у других пользователях на своем "железе".

4. Генерация нумерованных комбинаций свойств товара в запросе

На создание запроса, который для указанного перечня товара и набора свойств сгенерирует все возможные комбинации из этих свойств, подтолкнула тема http://infostart.ru/public/295343/ . Интересная задачка для разминки серого вещества.

Дабы не тратить уйму времени на универсальный запрос, оговорим, что в исходных данных может быть только два вида свойств. Ну, и чтобы запрос не был совсем детским, добавим изюминку - необходимо пронумеровать созданные пары свойств в пределах каждого товара.

К примеру, имеем мы на входе перечень товара с указанием их свойств:

Товар

Свойство

Значение свойства

Товар А

Материал обшивки

Сталь

Товар А

Материал обшивки

Алюминий

Товар А

Материал обшивки

Пластмасса

Товар А

Материал основания

Сталь

Товар А

Материал основания

Алюминий

Товар Б

Материал обшивки

Сталь

Товар Б

Материал обшивки

Алюминий

Товар Б

Материал основания

Алюминий

Товар Б

Материал основания

Сталь

Выберем эти данные запросом и поместим во временную таблицу ВТ, эта таблица и будет исходными данными для запроса:

ВЫБРАТЬ "Товар А" КАК Товар, "Материал обшивки" КАК Свойство, "Сталь" КАК Значение
ПОМЕСТИТЬ ВТ
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "Товар А", "Материал обшивки" ,"Алюминий"
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "Товар А", "Материал обшивки" ,"Пластмасса"
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "Товар А", "Материал основания" ,"Сталь"
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "Товар А", "Материал основания" ,"Алюминий"
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "Товар Б", "Материал обшивки" ,"Сталь"
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "Товар Б", "Материал обшивки" ,"Алюминий"
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "Товар Б", "Материал основания" ,"Алюминий"
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "Товар Б", "Материал основания" ,"Сталь";

Далее выполним следующую запрос:

ВЫБРАТЬ
ВТ1.Товар КАК Товар,
ВЫБОР
КОГДА
ВТ1.Свойство > ВТ2.Свойство
ТОГДА КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВТ4.Значение) * МАКСИМУМ(Ит.Итог) + КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВТ3.Значение)
ИНАЧЕ КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВТ3.Значение) * МАКСИМУМ(Ит.Итог) + КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВТ4.Значение)
КОНЕЦ +1 КАК Вариант,
ВТ1.Свойство КАК Свойство,
ВТ1.Значение
ИЗ
ВТ КАК ВТ1
ЛЕВОЕ СОЕДИНЕНИЕ ВТ КАК ВТ3
ПО ВТ1.Товар = ВТ3.Товар
И ВТ1.Свойство = ВТ3.Свойство
И ВТ1.Значение > ВТ3.Значение,
ВТ КАК ВТ2
ЛЕВОЕ СОЕДИНЕНИЕ ВТ КАК ВТ4
ПО ВТ2.Товар = ВТ4.Товар
И ВТ2.Свойство = ВТ4.Свойство
И ВТ2.Значение > ВТ4.Значение,
(ВЫБРАТЬ
И2.Товар КАК Товар, МАКСИМУМ(И2.Итог) КАК Итог
ИЗ (ВЫБРАТЬ И1.Товар КАК Товар, КОЛИЧЕСТВО(РАЗЛИЧНЫЕ И1.Значение) КАК Итог
ИЗ ВТ КАК И1
СГРУППИРОВАТЬ ПО
И1.Товар, И1.Свойство) КАК И2
СГРУППИРОВАТЬ ПО И2.Товар) КАК Ит
ГДЕ
ВТ1.Товар = ВТ2.Товар
И ВТ1.Товар = Ит.Товар
И ВТ1.Свойство <> ВТ2.Свойство
СГРУППИРОВАТЬ ПО
ВТ1.Товар,
ВТ1.Свойство,
ВТ1.Значение,
ВТ2.Свойство,
ВТ2.Значение
УПОРЯДОЧИТЬ ПО
Товар,Вариант, Свойство

И получим выборку товара с перечнем пронумерованных комбинаций свойств.

Товар

Вариант

Свойство

Значение

Товар А

1

Материал обшивки

Алюминий

Товар А

1

Материал основания

Алюминий

Товар А

2

Материал обшивки

Алюминий

Товар А

2

Материал основания

Сталь

Товар А

4

Материал обшивки

Пластмасса

Товар А

4

Материал основания

Алюминий

Товар А

5

Материал обшивки

Пластмасса

Товар А

5

Материал основания

Сталь

Товар А

7

Материал обшивки

Сталь

Товар А

7

Материал основания

Алюминий

Товар А

8

Материал обшивки

Сталь

Товар А

8

Материал основания

Сталь

Товар Б

1

Материал обшивки

Алюминий

Товар Б

1

Материал основания

Алюминий

Товар Б

2

Материал обшивки

Алюминий

Товар Б

2

Материал основания

Сталь

Товар Б

3

Материал обшивки

Сталь

Товар Б

3

Материал основания

Алюминий

Товар Б

4

Материал обшивки

Сталь

Товар Б

4

Материал основания

Сталь

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

На вход запроса можно подавать различное количество свойств и товаров, главное, чтобы видов свойств было только 2 в пределах одного товара.

5. Определение интервалов в запросе.

В продолжения раздела 3 статьи хотелбы привести еще один сравнительный эксперимент. Эксперимент будет заключаться в сравнительном анализе потраченного времени на построение интервалов в запросе двумя методами. Будем сравнивать скорость выполнения запроса с группировкой и запроса с паралельной фильтрующей выборкой.

Первый запрос:

ВЫБРАТЬ
Т1.Ч КАК НачалоИнтервала,
МИНИМУМ(Т2.Ч) КАК КонецИнтервала
ПОМЕСТИТЬ Темп
ИЗ
БазоваяТаблица КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ БазоваяТаблица КАК Т2
ПО Т1.Ч < Т2.Ч

СГРУППИРОВАТЬ ПО Т1.Ч;

УНИЧТОЖИТЬ Темп

Второй запрос:

ВЫБРАТЬ РАЗЛИЧНЫЕ
Т1.Ч КАК НачалоИнтервала,
Т2.Ч КАК КонецИнтервала
ПОМЕСТИТЬ Темп
ИЗ
БазоваяТаблица КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ БазоваяТаблица КАК Т2
ПО Т1.Ч < Т2.Ч
И (Т2.Ч В
(ВЫБРАТЬ МИНИМУМ(ВНЗ.Ч)
ИЗ
БазоваяТаблица КАК ВНЗ
ГДЕ Т1.Ч < ВНЗ.Ч));

УНИЧТОЖИТЬ Темп

Второй запрос построен мною по тому же принципу, что запрос в разделе 3 статьи.

Для тестирования я написал обработку которая сформирует N-индексированных временных таблиц определенного размера и выполнит тестовый замер времени выполнения первого и второго запроса.

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

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

Тестирование производил на стареньком двухпроцессорном Supermicro-сервере на 8 ядрах Xeon 2.3 ГГц с 6 ГГб оперативной памяти и бюджетным SSD. Сервер баз данных MS SQL 2012.

Результат тестирования:

В диаграмме эффективности я отобразил во сколько раз второй запрос выполняется быстрее от первого взависимости от объема выборки данных.

Я не эксперт, но для тех, кто хорошо разбирается в MS SQL привожу запросы сервера баз данных и их планы выполнения:

Первый запрос и план выполнения:

INSERT INTO #tt59 WITH(TABLOCK) (_Q_000_F_000, _Q_000_F_001)
SELECT
T1._Q_000_F_000, MIN(T2._Q_000_F_000)
FROM
#tt14 T1 WITH(NOLOCK)
INNER JOIN
#tt14 T2 WITH(NOLOCK)
ON (
T1._Q_000_F_000 < T2._Q_000_F_000)

GROUP BY T1._Q_000_F_000

Второй запрос и план выполнения:

INSERT INTO #tt59 WITH(TABLOCK) (_Q_000_F_000, _Q_000_F_001)
SELECT
DISTINCT
T1._Q_000_F_000
, T2._Q_000_F_000
FROM #tt10 T1 WITH(NOLOCK)
INNER JOIN
#tt10 T2 WITH(NOLOCK)
ON ((
T1._Q_000_F_000 < T2._Q_000_F_000) AND T2._Q_000_F_000 IN
(SELECT
MIN(T3._Q_000_F_000) AS Q_001_F_000_
FROM #tt10 T3 WITH(NOLOCK)
WHERE (
T1._Q_000_F_000 < T3._Q_000_F_000)))

Собственно говоря, прирост производительности запроса без группировки очевиден.

Однако есть недостатки такого метода реализации. Если запустить обработку анализа запросов на файловой базе, то картина будет совсем противоположной.

Собственно говоря, все.

В своей практике я использую именно второй метод связи таблиц для построения последовательности, это я и хотел отметить в двух разделах этой статьи.

← Содержание ...
 
MyTetra Share v.0.35
Яндекс индекс цитирования