суббота, 2 октября 2021 г.

Каррирование и декаррирование

 Немного красивой функциональщины на c#. Функторы каррирования и декаррирования:


    public static class Functors

    {

        public static Func<X, Func<Y, Z>> Currying<X, Y, Z>(this Func<X, Y, Z> f) => (x) => (y) => f(x, y);

        public static Func<X, Y, Z> UnCurrying<X, Y, Z>(this Func<X, Func<Y, Z>> f) => (x, y) => f(x)(y);

    }

Эффект основан на том, что между следующими множествами есть взаимно-однозначное соответствие:
А само это соответствие и есть функции каррирования и декаррироания.
Особенно интересно, что в теории множеств "скобочки в степенях" раскрываются также как и в обычной  арифметики.
Жаль, что c# не поддерживает переменное число типов-параметров, для обобщения на произвольное количество аргументов.

вторник, 14 апреля 2020 г.

Агрегатные функции в запросах SQL без секции GROUP BY

Надо помнить, что если секции GROUP BY нет, то запрос с агрегатными функциями все равно вернет единственную строку данных, даже если условия в секции WHERE не выполнились.
Вот пример функции из типового решения 1С с этой ошибкой:
Функция ПолучитьДатуПоследнегоДокументаОснования()
 
 УстановитьПривилегированныйРежим(Истина);
 
 Запрос = Новый Запрос;
 Запрос.Текст = 
 "ВЫБРАТЬ
 | МАКСИМУМ(ДанныеПервичныхДокументов.ДатаРегистратора) КАК Дата
 |ИЗ
 | РегистрСведений.ДанныеПервичныхДокументов КАК ДанныеПервичныхДокументов
 |ГДЕ
 | ДанныеПервичныхДокументов.Организация = &amp;Организация
 | И ДанныеПервичныхДокументов.Документ В (&amp;ДокументыОснования)";
 Запрос.УстановитьПараметр("ДокументыОснования", ДокументыОснования.Выгрузить().ВыгрузитьКолонку("ДокументОснование"));
 Запрос.УстановитьПараметр("Организация", Организация);
 Результат = Запрос.Выполнить();
 
 УстановитьПривилегированныйРежим(Ложь);
 
 Если Результат.Пустой() Тогда
  Возврат Дата(1,1,1);                     //вот эта ветка никогда не выполнится
 Иначе
  Возврат Результат.Выгрузить()[0].Дата;   //здесь будет NULL, если условие в секции ГДЕ не выполнится
 КонецЕсли;
КонецФункции

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



понедельник, 20 февраля 2017 г.

Скрещиваем разнородные данные в СКД 1С 8. На примере остатков и продаж товаров

Зачастую требуется сводно получать не совсем связанные данные в одну таблицу. Например, требуется в одной таблице получить данные по продажам товаров и их остаткам на складах.
НоменклатураПроданоОстаток всегоСклад 1Склад 2Склад 3
Цветок1245432
Носки60202020
Лампа10
Карандаш555
Итого27110682022
При этом желательно иметь возможность независимо фильтровать данные в каждом наборе. Это могут быть продажи контрагентам определенной группы или остатки только на определенных складах.
В СКД добавляем набор данных запрос, следующего содержания:

ВЫБРАТЬ
ПродажиОбороты.Номенклатура КАК Номенклатура,
ПродажиОбороты.Контрагент КАК Контрагент,
ПродажиОбороты.КоличествоОборот КАК КоличествоПродажи,
NULL КАК Склад,
NULL КАК КоличествоОстаток
{ВЫБРАТЬ
Номенклатура.*,
Контрагент.*,
КоличествоПродажи,
Склад,
КоличествоОстаток}
ИЗ
РегистрНакопления.Продажи.Обороты({(&НачПериода)}, {(&КонПериода)}, , {(Номенклатура).*, (Контрагент).*}) КАК ПродажиОбороты
{ГДЕ
ПродажиОбороты.КоличествоОборот КАК КоличествоПродажи}

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

ВЫБРАТЬ
ТоварыНаСкладахОстатки.Номенклатура,
NULL,
NULL,
ТоварыНаСкладахОстатки.Склад,
ТоварыНаСкладахОстатки.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки({(&КонПериода)}, {(Номенклатура).*, (Склад).*}) КАК ТоварыНаСкладахОстатки
{ГДЕ
ТоварыНаСкладахОстатки.КоличествоОстаток}

То есть по каждому набору отдельная секция объединения.
Отдельно отмечу, чтобы отборы по количеству действительно работали независимо, их описываем в запросе явно в секциях {ГДЕ}.
В полях СКД Склад и Контрагент добавляем в ролях признаки "Измерение" (вообще-то оно автоматом устанавливается) и признак "Игнорировать NULL". Это делается, чтобы не было пустых фантомных строк и колонок, если продажи есть, а остатков нет или наоборот. Редакируем параметры и ресурсы. Наконец, сами настройки:


В группировке строк "Номенклатура" оставляем саму номенклатуру и ее реквизиты, например, артикул.










В детальных записях информация о продажах:



В группировке колонок Склад - информация об остатках:

И для выразительности устанавливаем у этой группировки "Расположение общих итогов" в значение "Начало".

Вот и все, теперь можно проверить, что отборы по продажам и остаткам работают независимо и корректно. При желании у группировки строк "Номенклатура" можно добавить подгруппировку "Контрагент".


четверг, 25 июня 2015 г.

Алгоритм Нарайаны для 1С 8.2

Данный код реализует алгоритм Нарайаны на движке 1С 8.2.

Функция ВсеПерестановки(ЧислоЭлементов) Экспорт

	Результат = Новый Массив;
	Массив = Новый Массив;
	Для й=1 По ЧислоЭлементов Цикл
		Массив.Добавить(й);	
	КонецЦикла; 
	Результат.Добавить(Массив);
	
	Пока Истина Цикл
		Список = Новый СписокЗначений;
		Список.ЗагрузитьЗначения(Массив);
		Массив = Список.ВыгрузитьЗначения();
		
		Ключ = Неопределено;
		Для й=0 по ЧислоЭлементов-2 Цикл
			Если Массив[й]<Массив[й+1] Тогда
				Ключ = й;			
			КонецЕсли; 			
		КонецЦикла; 
		
		Если Ключ=Неопределено Тогда
			Прервать;		
		КонецЕсли; 
		
		Для й=1-ЧислоЭлементов По 0 Цикл
			Если Массив[Ключ]<Массив[-й] Тогда
				Прервать;			
			КонецЕсли; 		
		КонецЦикла; 	
		Поменять(Массив[Ключ],Массив[-й]);
		
		й = Ключ+1;
		к = ЧислоЭлементов-1;
		Пока й<к Цикл
			Поменять(Массив[й],Массив[к]);
			й = й+1;
			к = к-1;
		КонецЦикла; 
		
		Результат.Добавить(Массив);
		
	КонецЦикла; 
	
    Возврат Результат;
	
КонецФункции

Процедура Поменять(А,Б)

	Т = А;
	А = Б;
	Б = Т;

КонецПроцедуры


среда, 8 апреля 2015 г.

Библиотека функций работы с наборами записей регистров

Реально неудобно сделан в 1С 8 отбор для наборов записей регистров.
Начну с независимых регистров сведений. Зачем столько писать?
НЗ = РегистрыСведений.СохраненныеНастройки.СоздатьНаборЗаписей();
НЗ.Отбор.Пользователь.Установить(ВыборкаПольз.Пользователь);
НЗ.Отбор.ИмяОбъекта.Установить(Выборка.ИмяОбъекта);
НЗ.Отбор.НаименованиеНастройки.Установить(Выборка.НаименованиеНастройки);
Если все равно отбор можно установить только на равенство? Мне кажется структура тут была бы гораздо удобнее:
НЗ = СоздатьНаборЗаписейНезависимогоРС(РегистрыСведений.СохраненныеНастройки, Новый Структура("Пользователь,ИмяОбъекта,НаименованиеНастройки",Пользователь,ИмяОбъекта,НаименованиеНастройки));
Вот и сама функция:
//Создает набор записей независимого непериодического регистра сведений
//Параметры:
//Менеджер - менеджер региста сведений
//СтруктураОтбора - структура, содержащая измерения отбора для набора записей
Функция СоздатьНаборЗаписейНезависимогоРС(Менеджер,СтруктураОтбора) Экспорт
  
 НЗ = Менеджер.СоздатьНаборЗаписей();
 
 Для каждого Эл Из НЗ.Отбор Цикл
  ЗначениеОтбора = Неопределено;
  Если СтруктураОтбора.Свойство(Эл.Имя,ЗначениеОтбора) Тогда
   Эл.Установить(ЗначениеОтбора);
  КонецЕсли; 
 КонецЦикла; 
 
 Возврат НЗ;
 
КонецФункции
Для периодических РС в структуру отбора можно добавить период.
Наборы записей регистров (любых), подчиненных регистраторам. Вынуждены писать так:
НЗ = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
НЗ.Отбор.Регистратор.Установить(Ссылка);
Считаю более удобной запись такую:
НЗ = СоздатьНаборЗаписейРегистратора(РегистрыСведений.ЦеныНоменклатуры,Ссылка);
И соответствующая функция:
Функция СоздатьНаборЗаписейРегистратора(Менеджер,Регистратор) Экспорт
  
 НЗ = Менеджер.СоздатьНаборЗаписей();
 НЗ.Отбор.Регистратор.Установить(Ссылка); 
 Возврат НЗ;
 
КонецФункции 
Наконец совершенно непонятно из каких соображений нужно принудительно заполнять измерения/период/регистратор по которым делается отбор, если все равно нельзя записать набор несоответствующий отбору? Добавим соответствующую процедуру:
//Записывает набор записей с установленными отборами
//Параметры:
//НЗ - набор записей
//Замещать - режим замещения существующих записей, необязательный
Процедура ЗаписатьНаборЗаписей(НЗ,Замещать=Истина) Экспорт

 МассивЗаполнения =  Новый Массив(НЗ.Количество());
 Для каждого Эл Из НЗ.Отбор Цикл
  Если Эл.Использование Тогда
   Для й=0 По МассивЗаполнения.ВГраница() Цикл
    МассивЗаполнения[й] = Эл.Значение;   
   КонецЦикла; 
   НЗ.ЗагрузитьКолонку(МассивЗаполнения,Эл.Имя);
  КонецЕсли; 
 КонецЦикла; 
 НЗ.Записать(Замещать);

КонецПроцедуры

среда, 11 сентября 2013 г.

Альтернатива периодическому регистру сведений в клиент-серверной базе данных 1С 8

Как известно в 1С 8 виртуальная таблица "срез последних" периодического регистра сведений раскрывается в подзапрос, составленный из таблицы регистра, соединенной сама с собой. То есть вот это

ВЫБРАТЬ
ЦеныНоменклатурыСрезПоследних.Номенклатура,
ЦеныНоменклатурыСрезПоследних.Валюта,
ЦеныНоменклатурыСрезПоследних.Цена,
ЦеныНоменклатурыСрезПоследних.ЕдиницаИзмерения
ИЗ
РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, ТипЦен = &ТипЦен) КАК ЦеныНоменклатурыСрезПоследних


превращает платформа 1С примерно в такой запрос:

ВЫБРАТЬ
ЦеныНоменклатуры.Номенклатура,
ЦеныНоменклатуры.Валюта,
ЦеныНоменклатуры.Цена,
ЦеныНоменклатуры.ЕдиницаИзмерения
ИЗ
(ВЫБРАТЬ
ЦеныНоменклатуры.Номенклатура КАК Номенклатура,
ЦеныНоменклатуры.ТипЦен КАК ТипЦен,
МАКСИМУМ(ЦеныНоменклатуры.Период) КАК Период
ИЗ
РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
ГДЕ
ЦеныНоменклатуры.Период <= &Дата
И ЦеныНоменклатуры.ТипЦен = &ТипЦен
СГРУППИРОВАТЬ ПО
ЦеныНоменклатуры.Номенклатура,
ЦеныНоменклатуры.ТипЦен) КАК ВложенныйЗапрос
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
ПО ВложенныйЗапрос.Номенклатура = ЦеныНоменклатуры.Номенклатура
И ВложенныйЗапрос.ТипЦен = ЦеныНоменклатуры.ТипЦен
И ВложенныйЗапрос.Период = ЦеныНоменклатуры.Период

Аналогичные вещи приходится писать и в случае "среза последних на каждую дату в запросе": Классический вариантАльтернативный и их сравнение.
Гораздо удобнее были бы запросы такого вида:

ВЫБРАТЬ
ЦеныНоменклатуры.Номенклатура,
ЦеныНоменклатуры.Валюта,
ЦеныНоменклатуры.Цена,
ЦеныНоменклатуры.ЕдиницаИзмерения
ИЗ
РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
ГДЕ
ЦеныНоменклатуры.ТипЦен = &ТипЦен
И &Дата МЕЖДУ ЦеныНоменклатуры.Период И ЦеныНоменклатуры.ПериодОкончания

Но что это за поле ПериодОкончания и как оно должно быть заполнено?
Это был добавлен реквизит регистра сведений с типом "ДатаВремя". Ни в коем случае это поле не должно быть измерением, но вполне могло быть ресурсом, ведь в запросах ресурсы и реквизиты регистра сведений ничем не различимы. Реквизитом, а не ресурсом оно сделано чисто из эстетических принципов )). Зато встроенный механизм виртуальной таблицы "срез последних" нисколько не поврежден и может по-прежнему использоваться.
Заполняется "период окончания" согласно правилу: Если есть запись с тем же набором измерений, стоящая следующей в хронологии, то берется её период за вычетом одной секунды, в противном случае решено было взять дату 01.01.3000. Пример значений периодов таблицы по одному набору измерений:

Период ПериодОкончания
13.07.09 0:00:00 07.09.09 23:59:59
08.09.09 0:00:00 13.09.09 23:59:59
14.09.09 0:00:00 13.12.09 23:59:59
14.12.09 0:00:00 15.03.10 23:59:59
16.03.10 0:00:00 18.07.10 23:59:59
19.07.10 0:00:00 09.09.10 23:59:59
10.09.10 0:00:00 15.11.10 23:59:59
16.11.10 0:00:00 17.04.11 23:59:59
18.04.11 0:00:00 10.07.11 23:59:59
11.07.11 0:00:00 03.10.11 23:59:59
04.10.11 0:00:00 19.01.12 23:59:59
20.01.12 0:00:00 21.10.12 23:59:59
22.10.12 0:00:00 01.01.3000 0:00:00

Естественно это поле должно заполняться автоматически. А значит  нужно использовать события модуля набора записей ПередЗаписью и ПриЗаписи. Но так как база клиент-серверная в привязке с MS SQL Server, то воспользовались триггерами СУБД After Insert/Update/Delete. Впрочем триггер AfterUpdate был для перестраховки - 1С не изменяет записи регистра сведений, она их удаляет и вставляет заново. Вот пример триггера After Insert.
Индексирование реквизита средствами 1С привело к созданию составного индекса: Период+Все измерения. Помимо этого был добавлен индекс средствами SQL: Период+ПериодОкончания+Период окончания.
Да, все эти манипуляции приводят к уменьшению скорости записи, но когда запись происходит гораздо реже чтения, то затраты оправданы.
Аналогичная операция была сделана с курсами валют. Проведены тесты (1000 выполнений запроса) с разными параметрами на запросах:
1. Стандартные срезы:

ВЫБРАТЬ
ЦеныНоменклатурыСрезПоследних.Номенклатура,
ЦеныНоменклатурыСрезПоследних.Цена * КурсыВалютСрезПоследних.Курс / ЦеныНоменклатурыСрезПоследних.ЕдиницаИзмерения.Коэффициент КАК Цена
ИЗ
РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата,
ТипЦен = &ТипЦен
И Номенклатура В ИЕРАРХИИ (&Номенклатура)) КАК ЦеныНоменклатурыСрезПоследних
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют.СрезПоследних(&Дата, ) КАК КурсыВалютСрезПоследних
ПО ЦеныНоменклатурыСрезПоследних.Валюта = КурсыВалютСрезПоследних.Валюта

2. Модифицированные регистры:

ВЫБРАТЬ
ЦеныНоменклатуры.Номенклатура,
ЦеныНоменклатуры.Цена * КурсыВалют.Курс / ЦеныНоменклатуры.ЕдиницаИзмерения.Коэффициент КАК Цена
ИЗ
РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК КурсыВалют
ПО ЦеныНоменклатуры.Валюта = КурсыВалют.Валюта
И (&Дата МЕЖДУ ЦеныНоменклатуры.Период И КурсыВалют.ПериодОкончания)
ГДЕ
&Дата МЕЖДУ ЦеныНоменклатуры.Период И ЦеныНоменклатуры.ПериодОкончания
И ЦеныНоменклатуры.ТипЦен = &ТипЦен
И ЦеныНоменклатуры.Номенклатура В ИЕРАРХИИ(&Номенклатура)

Срез последних Модификация   Экономия времени,   %
0,235 0,191 18,72
0,232 0,199 14,22
0,228 0,206 9,65
0,210 0,189 10,00
Замечание 1: срез последних без указания параметра даты, это срез на самую последнюю дату в таблице. У нас это запрос:

ВЫБРАТЬ
ЦеныНоменклатуры.Номенклатура,
ЦеныНоменклатуры.Валюта,
ЦеныНоменклатуры.Цена,
ЦеныНоменклатуры.ЕдиницаИзмерения
ИЗ
РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
ГДЕ
ЦеныНоменклатуры.ТипЦен = &ТипЦен
И ЦеныНоменклатуры.ПериодОкончания=ДАТАВРЕМЯ(3000,1,1)

Замечание 2: все записи считаются активными..
Замечание 3: здесь не рассматривается случай периодичности "позиция регистратора".

четверг, 5 сентября 2013 г.

Жесткая связанность таблиц и реструктуризация таблиц в 1С 8

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

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

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

А в чем же бонус этих манипуляций? А в том, что это отношение можно более гибко расширять, например сделать "основной договор" для каждой из организаций.


Надеюсь нарисовал и написал понятно.