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

16.12.14

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

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

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

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

 

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

 

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

 

В общем, появилась необходимость переписать все это дело под запрос, но (вот тут и возникает вопрос терминологии) 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! 20%

Infostart Toolkit: Инструменты разработчика 1С 8.3 на управляемых формах

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

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

10000 8000 руб.

02.09.2020    122348    673    389    

716

Для чего используют конструкцию запроса "ГДЕ ЛОЖЬ" в СКД на примере конфигурации 1С:ERP

Запросы СКД Платформа 1С v8.3 Запросы Система компоновки данных 1С:ERP Управление предприятием 2 Бесплатно (free)

В типовых конфигурациях разработчики компании 1С иногда используют в отчетах, построенных на СКД, такую конструкцию, как "ГДЕ ЛОЖЬ". Такая конструкция говорит о том, что данные в запросе не будут получены совсем. Для чего же нужен тогда запрос?

13.02.2024    5755    KawaNoNeko    23    

23

Набор-объект для СКД по тексту или запросу

Запросы СКД Платформа 1С v8.3 Управляемые формы Конфигурации 1cv8 Абонемент ($m)

Есть список полей в виде текста, или запрос - закидываем в набор СКД.

1 стартмани

31.01.2024    2006    2    Yashazz    0    

29

Запрос 1С copilot

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

Пишем на человеческом языке, что нам надо, и получаем текст запроса на языке 1С. Используются большие языковые модели (LLM GPT) от OpenAI или Яндекс на выбор.

5 стартмани

15.01.2024    6300    31    mkalimulin    25    

50

PrintWizard: поддержка представлений ЗУП в конструкторе

Инструментарий разработчика Запросы Платформа 1С v8.3 Бесплатно (free)

Одной из интересных задач, стоящих в процессе разработки, была поддержка механизма представлений в ЗУП. Но не просто возможность исполнения запросов с ними. Основная проблема была в том, чтобы с ними было удобно работать, а именно: создавать, модифицировать и отлаживать. Кратко о том, что в итоге получилось...

14.12.2023    1749    vandalsvq    7    

29

Объектная модель запроса "Схема запроса" 2

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

Далеко уже не новый тип данных "Схема запроса". Статья о том, как использовать его "попроще". Примеры создания текста запроса с нуля и изменение имеющегося запроса.

06.12.2023    5397    user1923546    26    

43

Начните уже использовать хранилище запросов

HighLoad оптимизация Запросы

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

11.10.2023    16198    skovpin_sa    14    

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