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

16.12.14

Разработка - Запросы

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

Скачать файл

ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.

Наименование По подписке [?] Купить один файл
СклеитьСтрокиПериодов.epf
.epf 11,87Kb ver:1
0
0 Скачать (1 SM) Купить за 1 850 руб.

 

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

 

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

 

В общем, появилась необходимость переписать все это дело под запрос, но (вот тут и возникает вопрос терминологии) 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;
	КонецЦикла;
	
	ВремяОкончания = ТекущаяДата();
	ЗатрачееноеВремя = ВремяОкончания-ВремяНачала;
	
	Возврат Новый Структура("КоличествоИтераций,ЗатрачееноеВремя",КоличествоИтераций,ЗатрачееноеВремя);
КонецФункции

запрос рекурсия рекурсивный запрос

См. также

SALE! 15%

Инструментарий разработчика Роли и права Запросы СКД Программист Платформа 1С v8.3 Управляемые формы Запросы Система компоновки данных Конфигурации 1cv8 Платные (руб)

Набор инструментов программиста и специалиста 1С для всех конфигураций на управляемых формах. В состав входят инструменты: Консоль запросов, Консоль СКД, Консоль кода, Редактор объекта, Анализ прав доступа, Метаданные, Поиск ссылок, Сравнение объектов, Все функции, Подписки на события и др. Редактор запросов и кода с раскраской и контекстной подсказкой. Доработанный конструктор запросов тонкого клиента. Продукт хорошо оптимизирован и обладает самым широким функционалом среди всех инструментов, представленных на рынке.

10000 руб.

02.09.2020    159655    875    399    

862

Запросы Программист Бесплатно (free)

Увидел cheatsheet по SQL и захотелось нарисовать подобное, но про запросы.

18.10.2024    9937    sergey279    18    

64

Запросы Программист Платформа 1С v8.3 Запросы Конфигурации 1cv8 Бесплатно (free)

Столкнулся с интересной ситуацией, которую хотел бы разобрать, ввиду её неочевидности. Речь пойдёт про использование функции запроса АВТОНОМЕРЗАПИСИ() и проблемы, которые могут возникнуть.

11.10.2024    5198    XilDen    36    

80

Запросы Программист Запросы Бесплатно (free)

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

16.08.2024    7930    user1840182    5    

28

Математика и алгоритмы Запросы Программист Платформа 1С v8.3 Запросы Бесплатно (free)

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

08.07.2024    2403    ivanov660    9    

22

Запросы СКД Программист Стажер Система компоновки данных Россия Бесплатно (free)

Часто при разработке отчетов в СКД возникает ситуация, когда не совсем понятно, почему отчет выводит не те данные, которые нужны, либо не выводит вовсе. Возникает потребность увидеть конечный запрос, который формирует СКД. Как это сделать, рассмотрим в этой статье.

15.05.2024    8720    implecs_team    6    

47

Запросы Программист Стажер Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Часто поступают задачи по произвольному распределению общих сумм. После распределения иногда пропадают копейки. Суть решения добавить АвтоНомерЗаписи() в ВТ распределения, и далее используя функции МАКСИМУМ или МИНИМУМ можем положить разницу копеек в первую или последнюю строку знаменателя распределения.

11.04.2024    3393    andrey_sag    10    

36
Оставьте свое сообщение