gifts2017

Невозможность исполнения рекурсивных запросов в 1С 8.х, или как впихнуть невпихуемое

Опубликовал Руслан Новиков (ruslan0277) в раздел Программирование - Практика программирования

На обсуждение: Попытка эмуляции рекурсивного запроса на примере склейки строк периодов.

 

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

 

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

 

В общем, появилась необходимость переписать все это дело под запрос, но (вот тут и возникает вопрос терминологии) 1С не умеет работать с рекурсивными запросами - пришлось это дело обходить. В результате появился запрос, который за N+1 итераций соединяет все соседствующие периоды. N - количество итераций, необходимое для соединения строк, +1 - холостая итерация (без нее никуда - проверка на наличие хоть одной соединенной строки, в противном случае (нет соединений) цикл прерывается).

 

Писал и тестировал на 8.2, скорее всего, будет работать и на 8.3, да и на младших наверное тоже.

 

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

В данном случае "результат расчета зарплаты" (Исходные данные) преобразуется в более читаемый вид для "расчетного листка" (Результат). Таблица исходных данных заполняется вручную, при отсутствии строк заполняется автоматически тестовыми данными.

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

Описание параметров представлено в заголовке функции.

 

Функция универсальная (задел на будущее), сопутствующие функции в приложенной обработке.

//выходная ВТ будет иметь поля, указанные в:
//	- ПоляСвязи
//	- ПоляСуммы
//	- пДатаНачала
//	- пДатаОкончания
//
//предполагается, что в пространстве имен менеджера ВТ:
//	- ИмяИсходнойВТ сеществует
//	- ИмяВыходнойВТ не существует
//
//Параметры:
//	МВТ								- МенеджерВременныхТаблиц
//	ИмяИсходнойВТ					- имя временной таблицы с исходными данными
//	ИмяВыходнойВТ					- имя временной таблицы, в которой будут находиться данные после обработки
//	ПоляСвязи						- поля, по которым производится анализ периодов. Например Сотрудник,ВидРасчета,Показатель1 и т.д.
//	ПоляСуммы						- суммируемые поля
//	пДатаНачала и пДатаОкончания	- имена полей дат начала и окончания в исходной ВТ
//	РазмерПериода					- количество секунд, при такой разнице между двумя периодами их можно считать "соседствующими"
//	МаксКоличествоИтераций			- максимальное количество итераций, при превышении будет вызвано исключение
Функция СклеитьПериоды(МВТ,ИмяИсходнойВТ,ИмяВыходнойВТ,ПоляСвязи,ПоляСуммы,пДатаНачала="ДатаНачала",пДатаОкончания="ДатаОкончания",РазмерПериода=86400,МаксКоличествоИтераций=10000)
	ВремяНачала = ТекущаяДата();
	
	ПостФикс = СтрЗаменить(Строка(Новый УникальныйИдентификатор),"-","_");
	
	ПоляСвязи = Поля(ПоляСвязи);
	ПоляСуммы = Поля(ПоляСуммы);
	
	ПоляДат = пДатаНачала + "," + пДатаОкончания;
	
	мПоляСвязи = мПоля(ПоляСвязи);
	мПоляСуммы = мПоля(ПоляСуммы);
	
	мЗамены = Новый Массив;//для уникальности имен временных таблиц
	//замена [Параметр] на Параметр_УникальныйИдентификатор
	мЗамены.Добавить("ВТВошедшие");
	мЗамены.Добавить("ВТВошедшиеПервые");
	мЗамены.Добавить("ВТОставшиеся");
	мЗамены.Добавить("ТаблицаПериодов1");
	
	сЗамены = Новый Соответствие;//замена <Параметр> на ЗначениеПараметра
	сЗамены.Вставить("ИмяИсходнойВТ",ИмяИсходнойВТ);
	сЗамены.Вставить("ТаблицаПериодов",ИмяВыходнойВТ);
	сЗамены.Вставить("ДатаНачала",пДатаНачала);
	сЗамены.Вставить("ДатаОкончания",пДатаОкончания);
	
	Для Индекс=0 По мПоляСуммы.Количество()-1 Цикл
		Поле = мПоляСуммы[Индекс];
		сЗамены.Вставить("ПолеСуммы"+Индекс,Поле);
	КонецЦикла;
	
	Запрос = Новый Запрос;
	Запрос.МенеджерВременныхТаблиц = МВТ;
	Запрос.УстановитьПараметр("РазмерПериода",РазмерПериода);
	
	#Область ПервичнаяВыборка
	ПоляВыборки = "";
	ПоляИндексирования = "";
	Для Каждого Поле Из мПоляСвязи Цикл
		ПоляВыборки = ПоляВыборки + ?(ПоляВыборки="","",",") + "
		|"+Поле;
		ПоляИндексирования = ПоляИндексирования + ?(ПоляИндексирования="","",",") + "
		|"+Поле;
	КонецЦикла;
	
	ПоляВыборки = ПоляВыборки + ?(ПоляВыборки="","",",") + "
	|"+пДатаНачала;
	
	ПоляИндексирования = ПоляИндексирования + ?(ПоляИндексирования="","",",") + "
	|"+пДатаНачала;
	
	ПоляВыборки = ПоляВыборки + ?(ПоляВыборки="","",",") + "
	|"+пДатаОкончания;
	
	ПоляИндексирования = ПоляИндексирования + ?(ПоляИндексирования="","",",") + "
	|"+пДатаОкончания;
	
	Для Каждого Поле Из мПоляСуммы Цикл
		ПоляВыборки = ПоляВыборки + ?(ПоляВыборки="","",",") + "
		|"+Поле;
	КонецЦикла;
	
	сЗамены.Вставить("ПоляИндексирования",ПоляИндексирования);
	
	ТекстЗапроса =
	"ВЫБРАТЬ
	|"+ПоляВыборки+",
	|ДАТАВРЕМЯ(1, 1, 1) КАК ИсходнаяДатаНачала,
	|ДАТАВРЕМЯ(1, 1, 1) КАК ИсходнаяДатаОкончания,
	|ДАТАВРЕМЯ(1, 1, 1) КАК ПрисоединеннаяДатаНачала,
	|ДАТАВРЕМЯ(1, 1, 1) КАК ПрисоединеннаяДатаОкончания
	|ПОМЕСТИТЬ <ТаблицаПериодов>
	|ИЗ <ИмяИсходнойВТ>
	|ИНДЕКСИРОВАТЬ ПО <ПоляИндексирования>";
	
	ТекстЗапроса = ИсправитьИмена(ТекстЗапроса,мЗамены,Постфикс,сЗамены);
	
	Запрос.Текст = ТекстЗапроса;
	Запрос.Выполнить();
	#КонецОбласти
	
	ПоляБезДат = ПоляСвязи + ",
	|"+ПоляСуммы;
	
	
	#Область ОсновнойТекстЗапроса
	ТекстЗапроса =
	"ВЫБРАТЬ
	|	"+ПоляТаблицы(ПоляСвязи,"т")+",
	|	т.<ДатаНачала> КАК <ДатаНачала>,
	|	т.<ДатаОкончания> КАК <ДатаОкончания>,
	|	"+ПоляТаблицы(ПоляСуммы,"т")+",
	|	т.ИсходнаяДатаНачала,
	|	т.ИсходнаяДатаОкончания,
	|	т.ПрисоединеннаяДатаНачала,
	|	т.ПрисоединеннаяДатаОкончания
	|ПОМЕСТИТЬ [ВТВошедшие]
	|ИЗ
	|	(ВЫБРАТЬ
	|		"+ПоляТаблицы(ПоляСвязи,"т")+",
	|		т.<ДатаНачала> КАК <ДатаНачала>,
	|		ВЫБОР
	|			КОГДА ДОБАВИТЬКДАТЕ(т.<ДатаОкончания>, СЕКУНДА, &РазмерПериода) = т1.<ДатаНачала>
	|				ТОГДА т1.<ДатаОкончания>
	|			ИНАЧЕ т.<ДатаОкончания>
	|		КОНЕЦ КАК <ДатаОкончания>,";
	Для Индекс=0 По мПоляСуммы.Количество()-1 Цикл
		Поле = мПоляСуммы[Индекс];
		ТекстЗапроса = ТекстЗапроса + "
		|		т.<ПолеСуммы"+Индекс+"> + ВЫБОР
		|			КОГДА ДОБАВИТЬКДАТЕ(т.<ДатаОкончания>, СЕКУНДА, &РазмерПериода) = т1.<ДатаНачала>
		|				ТОГДА т1.<ПолеСуммы"+Индекс+">
		|			ИНАЧЕ 0
		|		КОНЕЦ КАК <ПолеСуммы"+Индекс+">,";
	КонецЦикла;
	ТекстЗапроса = ТекстЗапроса + "
	|		ВЫБОР
	|			КОГДА ДОБАВИТЬКДАТЕ(т.<ДатаОкончания>, СЕКУНДА, &РазмерПериода) = т1.<ДатаНачала>
	|				ТОГДА т.<ДатаНачала>
	|			ИНАЧЕ NULL
	|		КОНЕЦ КАК ИсходнаяДатаНачала,
	|		ВЫБОР
	|			КОГДА ДОБАВИТЬКДАТЕ(т.<ДатаОкончания>, СЕКУНДА, &РазмерПериода) = т1.<ДатаНачала>
	|				ТОГДА т.<ДатаОкончания>
	|			ИНАЧЕ NULL
	|		КОНЕЦ КАК ИсходнаяДатаОкончания,
	|		ВЫБОР
	|			КОГДА ДОБАВИТЬКДАТЕ(т.<ДатаОкончания>, СЕКУНДА, &РазмерПериода) = т1.<ДатаНачала>
	|				ТОГДА т1.<ДатаНачала>
	|			ИНАЧЕ NULL
	|		КОНЕЦ КАК ПрисоединеннаяДатаНачала,
	|		ВЫБОР
	|			КОГДА ДОБАВИТЬКДАТЕ(т.<ДатаОкончания>, СЕКУНДА, &РазмерПериода) = т1.<ДатаНачала>
	|				ТОГДА т1.<ДатаОкончания>
	|			ИНАЧЕ NULL
	|		КОНЕЦ КАК ПрисоединеннаяДатаОкончания
	|	ИЗ
	|		<ТаблицаПериодов> КАК т
	|			ЛЕВОЕ СОЕДИНЕНИЕ <ТаблицаПериодов> КАК т1
	|			"+ПоляСвязи(ПоляСвязи)+"
	|				И т.<ДатаНачала> <= т1.<ДатаНачала>) КАК т
	|ГДЕ
	|	НЕ т.ПрисоединеннаяДатаНачала ЕСТЬ NULL 
	|
	|ИНДЕКСИРОВАТЬ ПО
	|	<ПоляИндексирования>
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	т.ИндексЗаписи,
	|	"+ПоляТаблицы(ПоляСвязи,"т")+",
	|	т.<ДатаНачала> КАК <ДатаНачала>,
	|	т.<ДатаОкончания> КАК <ДатаОкончания>,
	|	"+ПоляТаблицы(ПоляСуммы,"т")+",
	|	т.ИсходнаяДатаНачала,
	|	т.ИсходнаяДатаОкончания,
	|	т.ПрисоединеннаяДатаНачала,
	|	т.ПрисоединеннаяДатаОкончания
	|ПОМЕСТИТЬ [ВТВошедшиеПервые]
	|ИЗ
	|	(ВЫБРАТЬ
	|		КОЛИЧЕСТВО(ВТВошедшие1.<ДатаНачала>) КАК ИндексЗаписи,
	|		"+ПоляТаблицы(ПоляСвязи,"ВТВошедшие")+",
	|		ВТВошедшие.<ДатаНачала> КАК <ДатаНачала>,
	|		ВТВошедшие.<ДатаОкончания> КАК <ДатаОкончания>,
	|		"+ПоляТаблицы(ПоляСуммы,"ВТВошедшие")+",
	|		ВТВошедшие.ИсходнаяДатаНачала КАК ИсходнаяДатаНачала,
	|		ВТВошедшие.ИсходнаяДатаОкончания КАК ИсходнаяДатаОкончания,
	|		ВТВошедшие.ПрисоединеннаяДатаНачала КАК ПрисоединеннаяДатаНачала,
	|		ВТВошедшие.ПрисоединеннаяДатаОкончания КАК ПрисоединеннаяДатаОкончания
	|	ИЗ
	|		[ВТВошедшие] КАК ВТВошедшие
	|			ЛЕВОЕ СОЕДИНЕНИЕ [ВТВошедшие] КАК ВТВошедшие1
	|			"+ПоляСвязи(ПоляСвязи,,"ВТВошедшие","ВТВошедшие1")+"
	|				И ВТВошедшие.<ДатаНачала> > ВТВошедшие1.<ДатаНачала>
	|	
	|	СГРУППИРОВАТЬ ПО
	|		"+ПоляТаблицы(ПоляСвязи,"ВТВошедшие",Ложь)+",
	|		ВТВошедшие.<ДатаНачала>,
	|		ВТВошедшие.<ДатаОкончания>,
	|		"+ПоляТаблицы(ПоляСуммы,"ВТВошедшие",Ложь)+",
	|		ВТВошедшие.ИсходнаяДатаНачала,
	|		ВТВошедшие.ИсходнаяДатаОкончания,
	|		ВТВошедшие.ПрисоединеннаяДатаНачала,
	|		ВТВошедшие.ПрисоединеннаяДатаОкончания) КАК т
	|ГДЕ
	|	т.ИндексЗаписи = 0
	|
	|ИНДЕКСИРОВАТЬ ПО
	|	<ПоляИндексирования>
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	"+ПоляТаблицы(ПоляСвязи,"ТаблицаПериодов")+",
	|	ТаблицаПериодов.<ДатаНачала> КАК <ДатаНачала>,
	|	ТаблицаПериодов.<ДатаОкончания> КАК <ДатаОкончания>,
	|	"+ПоляТаблицы(ПоляСуммы,"ТаблицаПериодов")+",
	|	ТаблицаПериодов.ИсходнаяДатаНачала,
	|	ТаблицаПериодов.ИсходнаяДатаОкончания,
	|	ТаблицаПериодов.ПрисоединеннаяДатаНачала,
	|	ТаблицаПериодов.ПрисоединеннаяДатаОкончания
	|ПОМЕСТИТЬ [ВТОставшиеся]
	|ИЗ
	|	<ТаблицаПериодов> КАК ТаблицаПериодов
	|ГДЕ Не ("+ПоляТаблицы(ПоляСвязи,"ТаблицаПериодов",Ложь)+",ТаблицаПериодов.<ДатаНачала>,ТаблицаПериодов.<ДатаОкончания>)
	|	В (ВЫБРАТЬ "+ПоляТаблицы(ПоляСвязи,,Ложь)+",ИсходнаяДатаНачала,ИсходнаяДатаОкончания ИЗ [ВТВошедшиеПервые]
	|		ОБЪЕДИНИТЬ ВСЕ
	|		ВЫБРАТЬ "+ПоляТаблицы(ПоляСвязи,,Ложь)+",ПрисоединеннаяДатаНачала,ПрисоединеннаяДатаОкончания ИЗ [ВТВошедшиеПервые]
	|	)
	|
	|ИНДЕКСИРОВАТЬ ПО
	|	<ПоляИндексирования>
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	"+ПоляТаблицы(ПоляСвязи,"ВТВошедшиеПервые")+",
	|	ВТВошедшиеПервые.<ДатаНачала>,
	|	ВТВошедшиеПервые.<ДатаОкончания>,
	|	"+ПоляТаблицы(ПоляСуммы,"ВТВошедшиеПервые")+",
	|	ДАТАВРЕМЯ(1, 1, 1) КАК ИсходнаяДатаНачала,
	|	ДАТАВРЕМЯ(1, 1, 1) КАК ИсходнаяДатаОкончания,
	|	ДАТАВРЕМЯ(1, 1, 1) КАК ПрисоединеннаяДатаНачала,
	|	ДАТАВРЕМЯ(1, 1, 1) КАК ПрисоединеннаяДатаОкончания
	|ПОМЕСТИТЬ [ТаблицаПериодов1]
	|ИЗ
	|	[ВТВошедшиеПервые] КАК ВТВошедшиеПервые
	|
	|ОБЪЕДИНИТЬ ВСЕ
	|
	|ВЫБРАТЬ
	|	"+ПоляТаблицы(ПоляСвязи,"ВТОставшиеся")+",
	|	ВТОставшиеся.<ДатаНачала>,
	|	ВТОставшиеся.<ДатаОкончания>,
	|	"+ПоляТаблицы(ПоляСуммы,"ВТОставшиеся")+",
	|	ДАТАВРЕМЯ(1, 1, 1),
	|	ДАТАВРЕМЯ(1, 1, 1),
	|	ДАТАВРЕМЯ(1, 1, 1),
	|	ДАТАВРЕМЯ(1, 1, 1)
	|ИЗ
	|	[ВТОставшиеся] КАК ВТОставшиеся
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|УНИЧТОЖИТЬ <ТаблицаПериодов>
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	"+ПоляТаблицы(ПоляСвязи,"ТаблицаПериодов1")+",
	|	ТаблицаПериодов1.<ДатаНачала> КАК <ДатаНачала>,
	|	ТаблицаПериодов1.<ДатаОкончания> КАК <ДатаОкончания>,
	|	"+ПоляТаблицы(ПоляСуммы,"ТаблицаПериодов1")+",
	|	ТаблицаПериодов1.ИсходнаяДатаНачала,
	|	ТаблицаПериодов1.ИсходнаяДатаОкончания,
	|	ТаблицаПериодов1.ПрисоединеннаяДатаНачала,
	|	ТаблицаПериодов1.ПрисоединеннаяДатаОкончания
	|ПОМЕСТИТЬ <ТаблицаПериодов>
	|ИЗ
	|	[ТаблицаПериодов1] КАК ТаблицаПериодов1
	|
	|ИНДЕКСИРОВАТЬ ПО
	|	<ПоляИндексирования>
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|УНИЧТОЖИТЬ [ТаблицаПериодов1]
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|УНИЧТОЖИТЬ [ВТВошедшие]
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|УНИЧТОЖИТЬ [ВТОставшиеся]
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	КОЛИЧЕСТВО(ВТВошедшиеПервые.ИндексЗаписи) КАК КоличествоВошедших
	|ИЗ
	|	[ВТВошедшиеПервые] КАК ВТВошедшиеПервые
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|УНИЧТОЖИТЬ [ВТВошедшиеПервые]";
	#КонецОбласти
	
	ТекстЗапроса = ИсправитьИмена(ТекстЗапроса,мЗамены,Постфикс,сЗамены);
	
	Запрос.Текст = ТекстЗапроса;
	
	//эмуляция рекурсивного обхода таблицы <ТаблицаПериодов>
	КоличествоИтераций = 0;
	Продолжать = Истина;
	Пока Продолжать Цикл
		ОбработкаПрерыванияПользователя();
		
		КоличествоИтераций = КоличествоИтераций + 1;
		
		Если КоличествоИтераций>МаксКоличествоИтераций Тогда
			ВызватьИсключение "Ошибка при выполнении метода СклеитьПериоды: количество итераций превысило "+МаксКоличествоИтераций+". Возможно произошло зацикливание.";
		КонецЕсли;
		
		мРезультатов = Запрос.ВыполнитьПакет();
		
		Выборка = мРезультатов[мРезультатов.Количество()-2].Выбрать();
		Выборка.Следующий();
		Продолжать = Выборка.КоличествоВошедших>0;
	КонецЦикла;
	
	ВремяОкончания = ТекущаяДата();
	ЗатрачееноеВремя = ВремяОкончания-ВремяНачала;
	
	Возврат Новый Структура("КоличествоИтераций,ЗатрачееноеВремя",КоличествоИтераций,ЗатрачееноеВремя);
КонецФункции

Скачать файлы

Наименование Файл Версия Размер
СклеитьСтрокиПериодов.epf
.epf 11,87Kb
16.12.14
0
.epf 1 11,87Kb Скачать

См. также

Подписаться Добавить вознаграждение
В этой теме еще нет сообщений.