Альтернативный вариант расчета возраста (лет, месяцев, дней) запросом

Публикация № 1041981

Программирование - Практика программирования

запрос лет месяцев дней возраст

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

Сначала хорошим решением мне показались запросы в этой публикации. Но тестируя на разные случаи (29.02.2016 и 28.02.2017 и тому подобное) получал не совсем верные результаты. + мне сложно было вникнуть во всех хитросплетения и условия, поэтому решил написать свой вариант:

ВЫБРАТЬ
	ГОД(&БольшаяДата) = ГОД(&МеньшаяДата) КАК ГодаРавны,
	МЕСЯЦ(&БольшаяДата) > МЕСЯЦ(&МеньшаяДата) КАК МесяцБольше,
	МЕСЯЦ(&БольшаяДата) = МЕСЯЦ(&МеньшаяДата) КАК МесяцыРавны,
	ДЕНЬ(&БольшаяДата) > ДЕНЬ(&МеньшаяДата) КАК ДеньБольше,
	ДЕНЬ(&БольшаяДата) = ДЕНЬ(&МеньшаяДата) КАК ДниРавны
ПОМЕСТИТЬ Условия
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	Условия.ГодаРавны КАК ГодаРавны,
	Условия.МесяцБольше КАК МесяцБольше,
	Условия.МесяцыРавны КАК МесяцыРавны,
	Условия.ДеньБольше КАК ДеньБольше,
	Условия.ДниРавны КАК ДниРавны,
	ДЕНЬ(КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&БольшаяДата, МЕСЯЦ, -1), МЕСЯЦ)) КАК ПоследнийДеньПредыдущегоМесяца,
	ДЕНЬ(КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&БольшаяДата, МЕСЯЦ, -1), МЕСЯЦ)) < ДЕНЬ(&МеньшаяДата) И ДЕНЬ(&БольшаяДата) < 3 КАК ДеньПредыдущегоМесяцаМеньше,
	Условия.ДеньБольше ИЛИ Условия.ДниРавны КАК ДеньБольшеИлиРавен,
	Условия.МесяцБольше ИЛИ Условия.МесяцыРавны КАК МесяцБольшеИлиРавен,
	Условия.ДеньБольше ИЛИ Условия.ДниРавны КАК ПоследнийМесяцПрошел,
	Условия.МесяцБольше ИЛИ Условия.МесяцыРавны И (Условия.ДеньБольше ИЛИ Условия.ДниРавны) КАК ПоследнийГодПрошел
ПОМЕСТИТЬ СложныеУсловия
ИЗ
	Условия КАК Условия
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	ВЫБОР
		КОГДА СложныеУсловия.ГодаРавны
			ТОГДА 0
		КОГДА СложныеУсловия.ПоследнийГодПрошел
			ТОГДА РАЗНОСТЬДАТ(&МеньшаяДата, &БольшаяДата, ГОД)
		ИНАЧЕ РАЗНОСТЬДАТ(&МеньшаяДата, &БольшаяДата, ГОД) - 1
	КОНЕЦ КАК Лет,
	ВЫБОР
		КОГДА СложныеУсловия.ПоследнийМесяцПрошел И СложныеУсловия.МесяцБольшеИлиРавен
			ТОГДА МЕСЯЦ(&БольшаяДата) - МЕСЯЦ(&МеньшаяДата)
		КОГДА СложныеУсловия.МесяцБольше
			ТОГДА МЕСЯЦ(&БольшаяДата) - МЕСЯЦ(&МеньшаяДата) - 1
		КОГДА СложныеУсловия.ПоследнийМесяцПрошел
			ТОГДА 12 - (МЕСЯЦ(&МеньшаяДата) - МЕСЯЦ(&БольшаяДата))
		ИНАЧЕ 12 - (МЕСЯЦ(&МеньшаяДата) - МЕСЯЦ(&БольшаяДата) + 1)
	КОНЕЦ КАК Месяцев,
	ВЫБОР
		КОГДА СложныеУсловия.МесяцБольшеИлиРавен И СложныеУсловия.ДеньБольшеИлиРавен
			ТОГДА ДЕНЬ(&БольшаяДата) - ДЕНЬ(&МеньшаяДата)
		КОГДА СложныеУсловия.ПоследнийМесяцПрошел И СложныеУсловия.ДеньБольшеИлиРавен
			ТОГДА ДЕНЬ(&БольшаяДата) - ДЕНЬ(&МеньшаяДата)
		КОГДА СложныеУсловия.ДеньПредыдущегоМесяцаМеньше ТОГДА 0
		ИНАЧЕ СложныеУсловия.ПоследнийДеньПредыдущегоМесяца - (ДЕНЬ(&МеньшаяДата) - ДЕНЬ(&БольшаяДата))
	КОНЕЦ КАК Дней	 		
ИЗ
	СложныеУсловия КАК СложныеУсловия

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

Сверялся с этим онлайн калькулятором, но если кто-то найдет ошибку - жду в комментарии!

7

См. также

Специальные предложения

Комментарии
Избранное Подписка Сортировка: Древо
1. user-z99999 18 11.04.19 11:42 Сейчас в теме
5. lex_hrabovskyi 11.04.19 12:08 Сейчас в теме
(1) запрос видимо проще. а правильнее? я добавил в консоль, поставил: ДР: 12.04.1993, ТД: 11.04.2019 и мне выдало:
25 0 29 12.04.1993 0:00:00
что никак неправильно, потому что должно быть 11 месяцев. стоит проверять дальше первый комментарий?
2. Agregadus 13 11.04.19 11:44 Сейчас в теме
А есть замеры улучшения?

Куда удобнее иметь один универсальный клиент-серверный метод, чем при любом изменении даты рождения в поле на форме, делать серверный вызов, чтоб узнать возраст
4. lex_hrabovskyi 11.04.19 12:06 Сейчас в теме
(2) для меня не было критично, т.к. возраст вычислялся при проведении, т.е. на сервере.
при проведении и 5-разовом использовании метода с циклом замер показывал около 92% от всего времени. правда там и нет "тяжеловесных" операций.
тем не менее разность дат там были маленькие, поэтому циклы не были длительными.
сейчас все вычисление занимает около 30% времени. при этом используется моветон - запрос в цикле. и для пяти дат запрос выполняется 5 раз. преимущество - запрос я перепишу, чтобы передавать просто массивы дат и выполнять запрос 1 раз. с вычислением в цикле такой фокус не получится. разве что выполнять вычисления параллельно, запуская фоновые задания, например.
9. Agregadus 13 11.04.19 12:38 Сейчас в теме
(4) Без замеров сложно оценить целесообразность улучшения.
Мы все помним фразу "Преждевременная оптимизация — корень всех зол".
И если было 0.05 сек, а стало 0.01 сек, стоит ли это того, чтобы улучшать и тратить время, разве нету более проблемных мест в других кусках конфигурации?
Ваши 92% совершенно ни о чем не говорят.
10. lex_hrabovskyi 11.04.19 12:47 Сейчас в теме
(9) целесообразность была в том, чтобы был определенный правильный шаблон, для вычисления возраста именно запросом для использования в будущем.
одна из предполагаемых задач - выдавать список из 500 человек указывая возраст в годах и месяцах на текущую дату. есть разница между 1. рассчитать возраст сразу средствами SQL
2. перебрать все строки выборки/выгрузки, делая вычисления для каждого.
также мне тут напомнили, что сервер предприятия и SQL сервер могут физически на разном железе находиться.

но для локальной задачи "вычислять возраст на сегодня на форме объекта" вполне может хватить и вычисления циклом на клиенте, не спорю.
3. lex_hrabovskyi 11.04.19 11:57 Сейчас в теме
(1) да, типа проще. а правильнее? я добавил в консоль, поставил: ДР: 12.04.1993, ТД: 11.04.2019 и мне выдало:
25 0 29 12.04.1993 0:00:00
что никак неправильно, потому что должно быть 11 месяцев. стоит проверять дальше первый комментарий?
Прикрепленные файлы:
6. duhh 119 11.04.19 12:11 Сейчас в теме
ВЫБРАТЬ
	ВЫБОР
		КОГДА ДЕНЬ(&Д1) > ДЕНЬ(&Д2)
			ТОГДА ДЕНЬ(КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&Д2, МЕСЯЦ, -1), МЕСЯЦ)) - ДЕНЬ(&Д1) + ДЕНЬ(&Д2)
		ИНАЧЕ ДЕНЬ(&Д2) - ДЕНЬ(&Д1)
	КОНЕЦ КАК Дней,
	ВЫБОР
		КОГДА ДОБАВИТЬКДАТЕ(&Д2, ГОД, ГОД(&Д2) * -1) < ДОБАВИТЬКДАТЕ(&Д1, ГОД, ГОД(&Д1) * -1)
			ТОГДА 12 - (МЕСЯЦ(&Д1) - МЕСЯЦ(&Д2))
		ИНАЧЕ МЕСЯЦ(&Д2) - МЕСЯЦ(&Д1)
	КОНЕЦ - ВЫБОР
		КОГДА ДЕНЬ(&Д1) > ДЕНЬ(&Д2)
			ТОГДА 1
		ИНАЧЕ 0
	КОНЕЦ КАК Месяцев,
	ВЫБОР
		КОГДА ДОБАВИТЬКДАТЕ(&Д2, ГОД, ГОД(&Д2) * -1) < ДОБАВИТЬКДАТЕ(&Д1, ГОД, ГОД(&Д1) * -1)
			ТОГДА ГОД(&Д2) - ГОД(&Д1) - 1
		ИНАЧЕ ГОД(&Д2) - ГОД(&Д1)
	КОНЕЦ КАК Лет
Показать
7. lex_hrabovskyi 11.04.19 12:20 Сейчас в теме
(6) круто! конечно в хитросплетениях "почему и зачем что-то добавляется" придется разобраться, но компактный и почти рабочий. внесите еще поправку для случая: Д1 = 31.01.2019 и Д2 = 01.03.2019. сейчас выдает:
-2 13 -1

если это поправится - отличный вариант, для тех кто любит компактность
8. lex_hrabovskyi 11.04.19 12:28 Сейчас в теме
(7) хотя нет. запрос надо пересмотреть. ошибка носит постоянный характер:
Прикрепленные файлы:
11. pun4er 16.04.19 13:32 Сейчас в теме
Спасибо, взял на заметку
12. riposte 89 21.04.19 15:36 Сейчас в теме
Недавно на форуме 1С натыкался и давал ответ. Без всяких циклов.

Процедура ПоказатьРазностьДатНажатие(Элемент)
    Если НЕ ЗначениеЗаполнено(ДатаНачала) ИЛИ НЕ ЗначениеЗаполнено(ДатаКонца) Тогда
        ЭлементыФормы.Н_РазностьСтрокой.Заголовок = "Не указана дата";
        ЭлементыФормы.Н_РазностьСтрокой.ЦветТекста = ЦветаСтиля.ЦветОтрицательногоЧисла;
        Возврат;
    КонецЕсли;
    ЭлементыФормы.Н_РазностьСтрокой.ЦветТекста = ЦветаСтиля.ПоясняющийТекст;
    
    РазностьДат = ДатаКонца - ДатаНачала;
    Мод = ?(РазностьДат < 0, -1, 1);  // Модификатор
    // Если Мод > 0 - значит ДатаКонца - будущее, иначе - прошлое и считать будем в обратную сторону.
    
    Лет = 0;
    Месяцев = 0;
    Дней = 0;
    
    Если Мод > 0 Тогда
        РазобратьРазностьДат(НачалоДня(ДатаКонца), НачалоДня(ДатаНачала), Лет, Месяцев, Дней);
    Иначе
        РазобратьРазностьДат(НачалоДня(ДатаНачала), НачалоДня(ДатаКонца), Лет, Месяцев, Дней);
    КонецЕсли;
    Дней = Дней - 1;    
    
    Если Мод > 0 Тогда
        РазницаЧМС = (КонецДня(ДатаНачала)+1 - ДатаНачала) + (ДатаКонца - НачалоДня(ДатаКонца)); 
    Иначе
        РазницаЧМС = (КонецДня(ДатаКонца)+1 - ДатаКонца) + (ДатаНачала - НачалоДня(ДатаНачала));
    КонецЕсли;
    
    Если РазницаЧМС >= 86400 Тогда
        Дней = Дней + 1;
           РазницаЧМС = РазницаЧМС - 86400;
    КонецЕсли;
    Часов = ?(РазницаЧМС >= 3600, Цел(РазницаЧМС / 3600), 0);
    РазницаЧМС = РазницаЧМС - Часов * 3600;
    
    Минут = ?(РазницаЧМС >= 60, Цел(РазницаЧМС / 60), 0);
    РазницаЧМС = РазницаЧМС - Минут * 60;
    Секунд = РазницаЧМС;
    
    СО = Новый Структура; // Структура ответа
    СО.Вставить("Префикс", ?(Мод < 0, "Прошло", "Наступит через"));
    СО.Вставить("ЗначЛет", ?(Лет > 0, " " + Лет + " лет", ""));
    СО.Вставить("ЗначМесяцев", ?(Месяцев > 0, " " + Месяцев + " месяцев", ""));
    СО.Вставить("ЗначДней", ?(Дней > 0, " " + Дней + " дней", ""));
    СО.Вставить("ЗначЧасов", ?(Часов > 0, " " + Часов + " часов", ""));
    СО.Вставить("ЗначМинут", ?(Минут > 0, " " + Минут + " минут", ""));
    СО.Вставить("ЗначСекунд", ?(Секунд > 0, " " + Секунд + " секунд", ""));
                
    СтрокаОтвета = "" + СО.Префикс + СО.ЗначЛет + СО.ЗначМесяцев + СО.ЗначДней + СО.ЗначЧасов + СО.ЗначМинут + СО.ЗначСекунд;
    ЭлементыФормы.Н_РазностьСтрокой.Заголовок = СтрокаОтвета;
    
КонецПроцедуры

Процедура РазобратьРазностьДат(Дата1, Дата2, Лет = 0, Месяцев = 0, Дней = 0)
    
    Лет        = 0;
    Месяцев    = 0;
    Дней    = 0;
    Если Дата1 > Дата2 Тогда
        
        ВременнаяДата = Дата1;
        Если День(ВременнаяДата) < День(Дата2) Тогда
            Дней = (ВременнаяДата - ДобавитьМесяц(ВременнаяДата,-1))/86400;
            ВременнаяДата = ДобавитьМесяц(ВременнаяДата,-1);
        КонецЕсли;
        Если Месяц(ВременнаяДата) < Месяц(Дата2) Тогда
            ВременнаяДата = ДобавитьМесяц(ВременнаяДата,-12);
            Месяцев = 12;
        КонецЕсли;
        Лет        = Макс(             Год(ВременнаяДата)        - Год(Дата2),    0);
        Месяцев    = Макс(Месяцев    + Месяц(ВременнаяДата)    - Месяц(Дата2),    0);
        Дней    = Макс(Дней        + День(ВременнаяДата)    - День(Дата2),    0);
        
       // скорректируем отображаемое значение, если "вмешалось" разное количество дней в месяцах
 
        Если Дата2 <> (ДобавитьМесяц(Дата1,-Лет*12-Месяцев)-Дней*86400) Тогда
            Дней = Дней + (День(КонецМесяца(Дата2)) - День(НачалоМесяца(Дата2))) - (День(КонецМесяца(ДобавитьМесяц(Дата1,-1))) - День(НачалоМесяца(ДобавитьМесяц(Дата1,-1))));
        КонецЕсли;
        
    КонецЕсли;

КонецПроцедуры   // РазобратьРазностьДат
Показать
Оставьте свое сообщение