Каталог решений - Пропорциональное распределение в запросе

Пропорциональное распределение в запросе

Пропорциональное распределение в запросе

В наличии

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

Категория:

Описание

Основная проблема с которой сталкивается разработчик это остатки от распределения. Остатки возникают из-за округлений и представляют собой разницу между распределяемой и распределенной суммами.

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

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

  • более затратен по вычислительным ресурсам
  • в результате распределения может случиться, что на элементы с одинаковыми значениями были распределены разные суммы

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

Ниже пример пропорционального распределения в запросе 1С методом выравнивания с определением распределяемых сумм в запросе.

ВЫБРАТЬ
	"Элемент1" КАК Элемент,
	"Группа1" КАК Группа,
	2 КАК Значение
ПОМЕСТИТЬ Элементы

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Элемент2",
	"Группа2",
	2

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Элемент3",
	"Группа2",
	3

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Элемент4",
	"Группа3",
	3

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Элемент5",
	"Группа3",
	3

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Элемент6",
	"Группа3",
	5

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Элемент7",
	"Группа3",
	5
	
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	"Группа1" КАК Группа,
	3 КАК РаспределяемаяСумма
ПОМЕСТИТЬ РаспределяемыеГруппы

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Группа2",
	9

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	"Группа3",
	7
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	Элементы.Группа КАК Группа,
	СУММА(Элементы.Значение) КАК Итог
ПОМЕСТИТЬ ИтогиПоГруппам
ИЗ
	Элементы КАК Элементы

СГРУППИРОВАТЬ ПО
	Элементы.Группа
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	Элементы1.Элемент КАК Элемент,
	Элементы1.Группа КАК Группа,
	ВЫРАЗИТЬ(Элементы1.Значение КАК ЧИСЛО(15, 2)) КАК Значение,
	ВЫРАЗИТЬ(ЕСТЬNULL(СУММА(Элементы2.Значение), 0) КАК ЧИСЛО(15, 2)) КАК ЗначениеНакопленное
ПОМЕСТИТЬ ЭлементыСНакоплением
ИЗ
	Элементы КАК Элементы1
		ЛЕВОЕ СОЕДИНЕНИЕ Элементы КАК Элементы2
		ПО Элементы1.Элемент > Элементы2.Элемент
			И Элементы1.Группа = Элементы2.Группа

СГРУППИРОВАТЬ ПО
	Элементы1.Элемент,
	Элементы1.Значение,
	Элементы1.Группа
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	Элементы.Элемент КАК Элемент,
	Элементы.Группа КАК Группа,
	Элементы.Значение КАК Значение,
	РаспределяемыеГруппы.РаспределяемаяСумма КАК РаспределяемаяСумма,
	ВЫБОР
		КОГДА ИтогиПоГруппам.Итог = 0
			ТОГДА 0
		ИНАЧЕ (ВЫРАЗИТЬ(РаспределяемыеГруппы.РаспределяемаяСумма * (Элементы.ЗначениеНакопленное + Элементы.Значение) / ИтогиПоГруппам.Итог КАК ЧИСЛО(15, 2))) - (ВЫРАЗИТЬ(РаспределяемыеГруппы.РаспределяемаяСумма * Элементы.ЗначениеНакопленное / ИтогиПоГруппам.Итог КАК ЧИСЛО(15, 2)))
	КОНЕЦ КАК РаспределеноНаЭлемент
ИЗ
	ЭлементыСНакоплением КАК Элементы
		ЛЕВОЕ СОЕДИНЕНИЕ ИтогиПоГруппам КАК ИтогиПоГруппам
		ПО (Элементы.Группа = ИтогиПоГруппам.Группа)
		ЛЕВОЕ СОЕДИНЕНИЕ РаспределяемыеГруппы КАК РаспределяемыеГруппы
		ПО (Элементы.Группа = РаспределяемыеГруппы.Группа)

 

В приложенном файле отчет с приведенным примером распределения. Проверено на релизе 8.3.10.2753.

Пропорциональное распределение в запросе

Пропорциональное распределение в запросе

В наличии

Решение задачи распределения суммы пропорционально каким-либо значениям (например, оплату по договорам) с помощью одного запроса таким образом, чтобы при округлении не получалось суммарного отклонения.

Категория:

Описание

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

Итак, предположим, у нас есть таблица Долги с полями Договор и Долг. Кроме того у нас есть параметр СуммаОплаты.

Первым действием нам необходимо в поле РаспределеннаяОплата распределить сумму оплаты по договорам в соответствии с коэффициентом Долг/СуммаДолга с округлением до 2 знака после запятой (сумма долга подсчитывается с помощью вложенного запроса).  В этом же запросе в поле Порядок одновременно пронумеруем договоры в порядке убывания суммы. Это нам необходимо для распределения образовавшегося «излишка». Нумерация выполняется путем самосоединения таблицы с условием на долг по договору, а также на код договора, на случай если долг окажется одинаковым.

ВЫБРАТЬ
	Долги.Договор,
	Долги.Долг,
	СуммаДолга.Сумма КАК СуммаДолга,
	КОЛИЧЕСТВО(Порядок.Долг) КАК Порядок,
	&СуммаОплаты КАК СуммаОплаты,
	ВЫРАЗИТЬ(&СуммаОплаты / СуммаДолга.Сумма * Долги.Долг КАК ЧИСЛО(15, 2)) КАК РаспределеннаяОплата
ПОМЕСТИТЬ РаспределениеОплаты
ИЗ
	Долги КАК Долги
		ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
			СУММА(Долги.Долг) КАК Сумма
		ИЗ
			Долги КАК Долги) КАК СуммаДолга
		ПО (ИСТИНА)
		ЛЕВОЕ СОЕДИНЕНИЕ Долги КАК Порядок
		ПО (Порядок.Долг > Долги.Долг
				ИЛИ Порядок.Долг = Долги.Долг
					И Порядок.Договор.Код < Долги.Договор.Код)

СГРУППИРОВАТЬ ПО
	Долги.Договор,
	Долги.Долг,
	СуммаДолга.Сумма

Мы получим таблицу:

 Первый этап

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

ВЫБРАТЬ
	РаспределениеОплаты.Договор,
	РаспределениеОплаты.Долг,
	РаспределениеОплаты.Порядок,
	РаспределениеОплаты.СуммаДолга,
	РаспределениеОплаты.РаспределеннаяОплата,
	СуммаРаспределения.Сумма КАК СуммаРаспределения,
	РаспределениеОплаты.СуммаОплаты - СуммаРаспределения.Сумма КАК Отклонение,
	ВЫБОР
		КОГДА РаспределениеОплаты.СуммаОплаты - СуммаРаспределения.Сумма < 0
				И РаспределениеОплаты.Порядок < -(РаспределениеОплаты.СуммаОплаты - СуммаРаспределения.Сумма) * 100
			ТОГДА -0.01
		КОГДА РаспределениеОплаты.СуммаОплаты - СуммаРаспределения.Сумма > 0
				И РаспределениеОплаты.Порядок < (РаспределениеОплаты.СуммаОплаты - СуммаРаспределения.Сумма) * 100
			ТОГДА 0.01
		ИНАЧЕ 0
	КОНЕЦ КАК ДополнителноеРаспределение,
	ВЫБОР
		КОГДА РаспределениеОплаты.СуммаОплаты - СуммаРаспределения.Сумма < 0
				И РаспределениеОплаты.Порядок < -(РаспределениеОплаты.СуммаОплаты - СуммаРаспределения.Сумма) * 100
			ТОГДА -0.01
		КОГДА РаспределениеОплаты.СуммаОплаты - СуммаРаспределения.Сумма > 0
				И РаспределениеОплаты.Порядок < (РаспределениеОплаты.СуммаОплаты - СуммаРаспределения.Сумма) * 100
			ТОГДА 0.01
		ИНАЧЕ 0
	КОНЕЦ + РаспределениеОплаты.РаспределеннаяОплата КАК Оплата
ИЗ
	РаспределениеОплаты КАК РаспределениеОплаты
		ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
			СУММА(РаспределениеОплаты.РаспределеннаяОплата) КАК Сумма
		ИЗ
			РаспределениеОплаты КАК РаспределениеОплаты) КАК СуммаРаспределения
		ПО (ИСТИНА)

В результате получим таблицу:

 Второй этап

Все, оплата распределена.

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

ВЫБРАТЬ
	Долги.Договор,
	КОЛИЧЕСТВО(Порядок.Долг) КАК Порядок,
	&СуммаОплаты КАК СуммаОплаты,
	ВЫРАЗИТЬ(&СуммаОплаты / СуммаДолга.Сумма * Долги.Долг КАК ЧИСЛО(15, 2)) КАК РаспределеннаяОплата
ПОМЕСТИТЬ РаспределениеОплаты
ИЗ
	Долги КАК Долги
		ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
			СУММА(Долги.Долг) КАК Сумма
		ИЗ
			Долги КАК Долги) КАК СуммаДолга
		ПО (ИСТИНА)
		ЛЕВОЕ СОЕДИНЕНИЕ Долги КАК Порядок
		ПО (Порядок.Долг > Долги.Долг
				ИЛИ Порядок.Долг = Долги.Долг
					И Порядок.Договор.Код < Долги.Договор.Код)

СГРУППИРОВАТЬ ПО
	Долги.Договор,
	Долги.Долг,
	СуммаДолга.Сумма
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	РаспределениеОплаты.Договор,
	ВЫБОР
		КОГДА РаспределениеОплаты.СуммаОплаты - СуммаРаспределения.Сумма < 0
				И РаспределениеОплаты.Порядок < -(РаспределениеОплаты.СуммаОплаты - СуммаРаспределения.Сумма) * 100
			ТОГДА -0.01
		КОГДА РаспределениеОплаты.СуммаОплаты - СуммаРаспределения.Сумма > 0
				И РаспределениеОплаты.Порядок < (РаспределениеОплаты.СуммаОплаты - СуммаРаспределения.Сумма) * 100
			ТОГДА 0.01
		ИНАЧЕ 0
	КОНЕЦ + РаспределениеОплаты.РаспределеннаяОплата КАК Оплата
ИЗ
	РаспределениеОплаты КАК РаспределениеОплаты
		ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
			СУММА(РаспределениеОплаты.РаспределеннаяОплата) КАК Сумма
		ИЗ
			РаспределениеОплаты КАК РаспределениеОплаты) КАК СуммаРаспределения
		ПО (ИСТИНА)

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

has been added to your cart:
Оформление заказа