Имя: Пароль:
1C
1С v8
Новый способ вычислить разницу между датами с учетом произв. календаря
,
0 Cerera
 
12.09.12
13:17
Задача - обойтись без вычисляемых полей в СКД для вычислении разницы между датами.

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



// Функция возвращает дату отстоящую на заданное количество рабочих дней от начальной в соответствии с
//регламентированным производственным календарем
//
//Параметры:
// ДатаНач      - начальная дата
// ЧислоДней    - количество рабочих дней, на которое искомая дата должна отстоять от начальной
//
Функция ОпределитьДату(ДатаНач, ЧислоДней) Экспорт
   
   Запрос = Новый  Запрос;
   Запрос.УстановитьПараметр("ДатаНач",             ДатаНач);
   Запрос.УстановитьПараметр("ЧислоДней",           ЧислоДней);
   Запрос.УстановитьПараметр("РабочийДень",         Перечисления.ВидыДнейПроизводственногоКалендаря.Рабочий);
   Запрос.УстановитьПараметр("ПредпраздничныйДень", Перечисления.ВидыДнейПроизводственногоКалендаря.Предпраздничный);
   
   Если ЧислоДней > 0 Тогда
       Запрос.Текст = "
       |ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ " + ЧислоДней + "
       |    РегламентированныйПроизводственныйКалендарь.ДатаКалендаря
       |ИЗ
       |    РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК РегламентированныйПроизводственныйКалендарь
       |ГДЕ РегламентированныйПроизводственныйКалендарь.ДатаКалендаря > &ДатаНач
       |     И (РегламентированныйПроизводственныйКалендарь.ВидДня = &РабочийДень
       |      ИЛИ РегламентированныйПроизводственныйКалендарь.ВидДня = &ПредпраздничныйДень)
       |";
       
   Иначе
       ЧислоДней = -ЧислоДней;
       
       Запрос.Текст = "
       |ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ " + ЧислоДней + "
       |    РегламентированныйПроизводственныйКалендарь.ДатаКалендаря
       |ИЗ
       |    РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК РегламентированныйПроизводственныйКалендарь
       |ГДЕ РегламентированныйПроизводственныйКалендарь.ДатаКалендаря < &ДатаНач
       |     И (РегламентированныйПроизводственныйКалендарь.ВидДня = &РабочийДень
       |      ИЛИ РегламентированныйПроизводственныйКалендарь.ВидДня = &ПредпраздничныйДень)
       |УПОРЯДОЧИТЬ ПО
       |    ДатаКалендаря УБЫВ
       |";
       
   КонецЕсли;
   
   Выборка = Запрос.Выполнить().Выбрать();
   
   Если Выборка.Количество() = ЧислоДней Тогда
       Пока Выборка.Следующий() Цикл
           ТекДата = Выборка.ДатаКалендаря;
       КонецЦикла;
       Возврат ТекДата;
   КонецЕсли;
   
   Возврат Неопределено;
   
КонецФункции    


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

   |ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ " + ЧислоДней + "
       |    РегламентированныйПроизводственныйКалендарь.ДатаКалендаря
       |ИЗ
       |    РегистрСведений.РегламентированныйПроизводственныйКалендарь

А число дней то для каждой строчки запроса может быть разное!

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

Что скажете про этот подход или есть более умный метод?
1 DrShad
 
12.09.12
13:20
(0) пипец а не подход? у тебя время на заполнение этого нового регистра уйдет несоизмеримо больше, чем потраченное на расчет в нескольких вызовах нескольких отчетов
2 DrShad
 
12.09.12
13:21
и чем не подходит РазностьДат()
3 Maxus43
 
12.09.12
13:21
(2) надо выходные исключить видимо и праздники
4 ChAlex
 
12.09.12
13:22
(0) - да все элементарно - два набора: один требуемые периоды - второй регистр сведений со всеми датами нужного диапазона и связь по датам - потом группировка: короче описывать как реализовать больше - чем сама реализация
5 Cerera
 
12.09.12
13:23
(2)разность дат не даст тебе количество дней с учетом праздников и выходынх.
6 Cerera
 
12.09.12
13:26
(1)так регистр это заполнить можно один раз. а отчетом пользуются ежедневно многие люди. и изза вычисляемых полей это происходит долго! Вот коммерческая прибыль у нас формируется в течении нескольких минут изза этих вычисляемых полей.
7 DrShad
 
12.09.12
13:29
(5) вот она как раз с учетом дает, а за вычетом нет
8 DrShad
 
12.09.12
13:30
(6) проще уже из РазностиДат() исключать праздники и выходные
9 ChAlex
 
12.09.12
13:34
(6) - в РегистрСведений.РегламентированныйПроизводственныйКалендарь - уже все есть (там еще по-моему есть и продолжительность дня) - остается только связать с входным набором периодов
10 dmpl
 
12.09.12
13:34
(0) Как вариант - взять сумму календарных дней по условию

ДатаКалендаря МЕЖДУ ДругаяТаблица.ДатаНачала И ДругаяТаблица.ДатаОкончания

Только не факт что будет быстрее вычисляемых полей.

(6) А если отчет попробуют за 10 лет сформировать, что тогда?
11 dmpl
 
12.09.12
13:37
(0) А вообще, можно во временную таблицу выбрать все сочетания дат в конкретном отчете, и на основании этих сочетаний формировать временную таблицу с полями ДатаНачала, ДатаОкончания, КоличествоДней, чтобы лишний раз не соединять с кучей строк производственного календаря.
12 Cerera
 
12.09.12
13:38
(10)производственный календарь обновляется раз в год. так же раз в год можно обновлять новый регистр... зато мнгновенно будет работать.
13 Cerera
 
12.09.12
13:39
(11)а если за 10 лет? долго. отчет не должен работать долго.
14 Cerera
 
12.09.12
13:40
я вот щас создам и заполню регистр и скажу на сколько гигово увеличилась база )
15 DrShad
 
12.09.12
13:40
(14) и количество записей не забудь
16 ChAlex
 
12.09.12
13:43
(14) - цитата ".. будут все возможные комбинации дат и в качестве ресурса будет количество дней между ними, просчитанное заранее. " -  вы представляете себе количество комбинаций скажем хотя бы за месяц? А за год? Не забудьте между 1-м и 5-м, 2-м и 5-м и т.д. Дурное дело конечно не хитрое ... :)
17 ChAlex
 
12.09.12
13:47
+(16) - насколько помню навскидку - это будет для года 365! (!- это фактериал)
18 dmpl
 
12.09.12
13:47
(12) Понимимаешь, тут надо будет сразу на все будущее время сформировать все возможные сочетания дат. Иначе потом будешь постоянно сражаться с нулями дней в отчете.

(13) Думаешь, копаться среди 13 с лишним млн. записей будет быстрее?
19 Cerera
 
12.09.12
13:50
(18)на будущее невозможно ведь неизвестны заранее праздничные дни. ну и нули так и так выходят если произв календарь не заполнен за анализируемый период. ну можно оставлять регистр заполненным только на  текущий квартал. просто такие отчеты как правило нужны для рассчета дней просрочки.
20 DrShad
 
12.09.12
13:51
(19) дни просрочки вообще ни о чем, вот если расчет пеней тогда другое дело
21 DrShad
 
12.09.12
13:52
+(20) а дни просрочки интересны только в интервалах, т.е. свыше 30, 60 и более дней
22 Cerera
 
12.09.12
13:52
(20)ну пени считаются исходя из дней просрочки без учета праздничных дней и выходных.... и зарплата так же зависит от таких просрочек.
23 DrShad
 
12.09.12
13:54
(22) пеня не может начисляться на небанковский день
24 Cerera
 
12.09.12
13:54
(21)а нам интересны дни просрочки в интервалах даже свыше одного дня поскольку бонусы менеджерам от них пляшут.
25 Cerera
 
12.09.12
13:54
(23)ну я имею ввиду что менеджерам зарплату считаем так.
26 ChAlex
 
12.09.12
13:56
Вот вам примерно готовый запрос с получением выборки (без СКД) - дальше модифицируйте как угодно:

ВЫБРАТЬ
   Т.Ссылка КАК ВидДня,
   ВЫБОР
       КОГДА Т.Ссылка = ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Воскресенье)
           ТОГДА 0
       КОГДА Т.Ссылка = ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Суббота)
           ТОГДА 0
       КОГДА Т.Ссылка = ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Рабочий)
           ТОГДА 8
       КОГДА Т.Ссылка = ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Предпраздничный)
           ТОГДА 7
       КОГДА Т.Ссылка = ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Праздник)
           ТОГДА 0
       ИНАЧЕ 0
   КОНЕЦ КАК Продолжительность
ПОМЕСТИТЬ ПродолжительностьДней
ИЗ
   Перечисление.ВидыДнейПроизводственногоКалендаря КАК Т

ИНДЕКСИРОВАТЬ ПО
   ВидДня
;

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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
   График.ПроизводственныйЗаказ КАК Заказ,
   График.ПроизводственныйЗаказ.Модель КАК Модель,
   График.ДатаНачала,
   График.ДатаОкончания,
   График.Исполнитель,
   График.Количество
ПОМЕСТИТЬ ГрафикРабот
ИЗ
   РегистрСведений.ГрафикПроизводства КАК График
ГДЕ
   График.ПериодПроизводства = &ПериодПроизводства
   И График.ЭтапПроизводства = &Этап

ИНДЕКСИРОВАТЬ ПО
   Заказ,
   Модель
;

ВЫБРАТЬ РАЗЛИЧНЫЕ
  График.ДатаНачала КАК ДатаНачала,
  График.ДатаОкончания КАК ДатаОкончания,
  Календарь.Смена
ИЗ
   ГрафикРабот КАК График
   ЛЕВОЕ СОЕДИНЕНИЕ Календарь КАК Календарь
       ПО График.ДатаНачала <= Календарь.ДатаКалендаря
       И График.ДатаОкончания >= Календарь.ДатаКалендаря
27 DrShad
 
12.09.12
13:56
(25) а (22) писал не ты, так?
28 dmpl
 
12.09.12
13:56
(19) Производственный календарь можно заполнять частями, раз в год. Тут же придется его перезаполнять постоянно, когда в очередной раз ноль вылезет в каких-нибудь местах.

Такой регистр если и делать - то с автозаполнением по факту использования данного сочетания в отчете. Т.е., нет нужного сочетания - посчитали и сохранили. Итого возвращаемся к (11).
29 Cerera
 
12.09.12
13:57
(27)я под пени подразумевал штрафы которые влияют на заработок.
30 DrShad
 
12.09.12
13:58
(29) штраф и пеня разные понятия! либо выражайтесь общепринятыми, либо подробно излагайте свои подразумевания
31 Cerera
 
12.09.12
13:58
(28)не спорю. можно и в отчете. просто время. просто в (11) не доконца понял что имелось ввиду и по умолчанию подумал что та же идея.
32 Cerera
 
12.09.12
14:00
(26)а что он делает в данном случае?
33 Feanorko
 
12.09.12
14:02
Таки не понимаю, в чем проблема сделать вычисляемое поле и считать разность дат как душе угодно?
34 ChAlex
 
12.09.12
14:05
(32) - получаете массив: требуемые исходные строки (началопериода, конецПериода еще какие-то реквизиты вам нужные, например мне нужен был заказ и т.п.) - и в моем случае количество секунд каждый день в интервале с учетом выходных, предпраздничных и т.п. (можете сделать просто 1 для рабочего, 0 - для выходно, в зависимости от потребности) - все - просчитайте итоги продолжительности по ключевым полям - и вся задача
35 ChAlex
 
12.09.12
14:06
+(34) - в частности сумма рабочих дней за интервал и будет разность дат
36 dmpl
 
12.09.12
14:07
(31) Время будет только при первом формировании отчета, когда такой регистр массово будет заполняться. Тут минус в одном - придется писать код вне СКД. Да и то смотреть надо, сколько времени в реале выйдет. Может и не стоит сохранять - и достаточно просто во временную таблицу поместить, тогда можно и одним запросом обойтись.
37 ChAlex
 
12.09.12
14:15
+(34) если последний подзапрос переписать в виде:

ВЫБРАТЬ РАЗЛИЧНЫЕ
  Т.ДатаНачала КАК ДатаНачала,
  Т.ДатаОкончания КАК ДатаОкончания,
  Сумма(Т.Смена) КАК РазностьДат
ИЗ
(ВЫБРАТЬ РАЗЛИЧНЫЕ
  График.ДатаНачала КАК ДатаНачала,
  График.ДатаОкончания КАК ДатаОкончания,
  Календарь.Смена
ИЗ
   ГрафикРабот КАК График
   ЛЕВОЕ СОЕДИНЕНИЕ Календарь КАК Календарь
       ПО График.ДатаНачала <= Календарь.ДатаКалендаря
       И График.ДатаОкончания >= Календарь.ДатаКалендаря) КАК Т
СГРУППИРОВАТЬ ПО Т.ДатаНачала,Т.ДатаОкончания

То получаете вам нужные данные, в предположении что "Смена" - формировали 1 - для рабочего дня и 0 - для выходного. Усе!!
38 Cerera
 
12.09.12
14:40
(33)ну не дело это... долго работают вычисляемые поля и это уже не запрос. а запрос переносим на другие отчеты... а как вычисляемое поле перенесёшь куда-нибудь помимо СКД?
39 Feanorko
 
12.09.12
14:43
(38) понял)
40 Kashemir
 
12.09.12
15:07
Тяжелый запрос - быстро вряд ли будет работать, для этого надо как минимум добавить ограничение на максимальную острочку платежа в календарных днях




ВЫБРАТЬ
   ЕСТЬNULL(ПродажиОбороты.ДоговорКонтрагента._ДнейОтсрочкиПлатежа, 0) КАК ДнейОтстрочкиПлатежа,
   ПродажиОбороты.Период КАК ДатаПродажи
ПОМЕСТИТЬ ДатыПродажСОтстрочкамиПлатежа
ИЗ
   РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, День, ) КАК ПродажиОбороты

СГРУППИРОВАТЬ ПО
   ПродажиОбороты.Период,
   ЕСТЬNULL(ПродажиОбороты.ДоговорКонтрагента._ДнейОтсрочкиПлатежа, 0)
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
   РегламентированныйПроизводственныйКалендарь.ДатаКалендаря КАК ДатаКалендаря
ПОМЕСТИТЬ РабочиеДни
ИЗ
   РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК РегламентированныйПроизводственныйКалендарь
ГДЕ
   РегламентированныйПроизводственныйКалендарь.ВидДня В(&РабочиеВидыДня)
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
   ДатыПродаж.ДатаПродажи,
   РабочиеДни.ДатаКалендаря
ПОМЕСТИТЬ ДатыПродажСПоследующимиДатами
ИЗ
   (ВЫБРАТЬ
       ДатыПродажСОтстрочкамиПлатежа.ДатаПродажи КАК ДатаПродажи
   ИЗ
       ДатыПродажСОтстрочкамиПлатежа КАК ДатыПродажСОтстрочкамиПлатежа
   
   СГРУППИРОВАТЬ ПО
       ДатыПродажСОтстрочкамиПлатежа.ДатаПродажи) КАК ДатыПродаж
       ВНУТРЕННЕЕ СОЕДИНЕНИЕ РабочиеДни КАК РабочиеДни
       ПО ДатыПродаж.ДатаПродажи < РабочиеДни.ДатаКалендаря
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
   ДатыПродажСОтстрочкамиПлатежа.ДатаПродажи КАК ДатаПродажи,
   ДатыПродажСОтстрочкамиПлатежа.ДнейОтстрочкиПлатежа КАК ДнейОтстрочкиПлатежа,
   ВложенныйЗапрос.ДатаКалендаря КАК ДатаВозникновенияЗадолженности
ИЗ
   (ВЫБРАТЬ
       ДатыПродажСПоследующимиДатами.ДатаПродажи КАК ДатаПродажи,
       ДатыПродажСПоследующимиДатами.ДатаКалендаря КАК ДатаКалендаря,
       КОЛИЧЕСТВО(ДатыПродажСПоследующимиДатами1.ДатаКалендаря) КАК ДнейОтДатыПродажи
   ИЗ
       ДатыПродажСПоследующимиДатами КАК ДатыПродажСПоследующимиДатами
           ВНУТРЕННЕЕ СОЕДИНЕНИЕ ДатыПродажСПоследующимиДатами КАК ДатыПродажСПоследующимиДатами1
           ПО ДатыПродажСПоследующимиДатами.ДатаПродажи = ДатыПродажСПоследующимиДатами1.ДатаПродажи
               И ДатыПродажСПоследующимиДатами.ДатаКалендаря > ДатыПродажСПоследующимиДатами1.ДатаКалендаря
   
   СГРУППИРОВАТЬ ПО
       ДатыПродажСПоследующимиДатами.ДатаПродажи,
       ДатыПродажСПоследующимиДатами.ДатаКалендаря) КАК ВложенныйЗапрос
       ВНУТРЕННЕЕ СОЕДИНЕНИЕ ДатыПродажСОтстрочкамиПлатежа КАК ДатыПродажСОтстрочкамиПлатежа
       ПО ВложенныйЗапрос.ДатаПродажи = ДатыПродажСОтстрочкамиПлатежа.ДатаПродажи
           И ВложенныйЗапрос.ДнейОтДатыПродажи = ДатыПродажСОтстрочкамиПлатежа.ДнейОтстрочкиПлатежа

УПОРЯДОЧИТЬ ПО
   ДатаПродажи,
   ДнейОтстрочкиПлатежа
41 Cerera
 
12.09.12
15:26
Кстати вот что сделал


   Док=Документы.УставнокаКоличестваРабочихДнейМеждуДатамиСУчетомПроизводственногоКалендаря.СоздатьДокумент();
   Док.Дата=ТекущаяДата();
   Запрос = Новый Запрос;
   Запрос.Текст =
       "ВЫБРАТЬ
       |    РегламентированныйПроизводственныйКалендарь.ДатаКалендаря
       |ИЗ
       |    РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК РегламентированныйПроизводственныйКалендарь
       |ГДЕ
       |    РегламентированныйПроизводственныйКалендарь.ДатаКалендаря МЕЖДУ &Дата1 И &Дата2";
       
   Запрос.УстановитьПараметр("Дата1",НачПериода);
   Запрос.УстановитьПараметр("Дата2",КонецДня(КонПериода));

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