Продолжаем проверять работу ученика нашего курса по Программированию в PL/SQL (ORACLE). Начало статьи здесь.
Теперь посмотрим как реализована функция проверки даты на то, что она рабочая. Напомню, функция должна проверять даваемую ей на вход дату на то, является ли она рабочим днём, если да, то функция должна эту же дату и возвращать. Если даваемая функции на вход дата не является рабочим днём, то нужно, чтобы функция возвращала ближайшую следующую дату, являющуюся рабочим днём.
Итак, ниже созданная учеником функция:
Пожалуйста, если Вы обучаетесь на нашем курсе Программирования в PL/SQL (ORACLE) не используйте этот материал как готовое решение. От этого не будет пользы. Постарайтесь сначала максимально самостоятельно составить алгоритм. Используйте подсказки, которые я даю для решения домашнего задания.
До разбора функции и уж точно до её тестирования нам хорошо бы хотя бы немного заполнить созданную таблицу дней исключений - таблицу DATES_OF_YEAR. Сейчас 2023 год, поэтому (согласно статье 112 трудового кодекса Российской Федерации) на 2023 год установлены следующие нерабочие праздничные дни в Российской Федерации:
1, 2, 3, 4, 5, 6 и 8 января — Новогодние каникулы и Рождество;
23 февраля — День защитника Отечества, а также 24-ое февраля также установлен выходным днём из-за того, что 1-ое января итак выпадает на выходной день;
8 марта — Международный женский день;
1 мая — Праздник Весны и Труда;
9 мая — День Победы, а также 8ое мая также установлен выходным днём;
12 июня — День России;
4 ноября — День народного единства, и, так как он выпадает итак на выходной день, выходным днём сделан 6-ое ноября.
В нашу таблицу дней-исключений необходимо добавить из Новогодних праздников дни со второго по шестое января. Первое, седьмое и восьмое января итак выходные. Также нужно добавить 23 февраля и 8-ое марта, так как эти даты выпадают на будни. Из майских добавляем 1-ое, 8-ое и 9-ое, из июньских - 12-ое число. И ещё добавляем 6-ое ноября.
Все даты внесены в таблицу дней-исключений:
Начинаем рассматривать решение ученика. Первое, на что обращаем внимание, это то, что в функции во вложенном курсоре, выбираются все будни до 2027-го года. То есть, похоже, работа функции рассчитана только до указанной даты. Такого не было в постановке задачи. При такой реализации нужно будет вовремя перепрограммировать функцию. И главное про это не забыть :) Опасное решение. А если еще в нашем банковском софте есть такие подводные камни? Вдруг есть функционал который уже сегодня не работает и клиентам формируются продукты с ошибочными данными. Вобщем, такое делать не нужно.
Второе, что тоже сразу бросается в глаза, - это использование метки:
С их помощью можно организовать переход выполнения кода сразу на установленное место. Использование меток делает код обозримо менее читабельным и менее прогнозируемым. И, как я говорю на уроках, их использование должно быть оправдано. Почти всегда можно обойтись без них и код от этого не станет сложнее.
Начинаем проверять функцию, правильно ли она работает. Сразу возникает подозрение, вернёт ли функция субботнюю дату, если ей дать её на вход и если эта дата, согласно производственному календарю, установлена как рабочий день, то есть эта субботняя дата присутствует в таблице исключений. Функция должна вернуть её же. Тестируем! Добавляем субботнюю дату (например, 24.06.2023) в таблицу-исключений:
И проверяем что вернёт функция, если ей дать эту дату на вход:
Функция вернула дату следующего ближайшего понедельника. Если бы в таблице исключений не было бы этой субботней даты, то функция отработала бы верно. Но сейчас засчитываем как ошибку. Надо написать ученику об этом и приложить скриншоты тестирования. Сначала предложим попробовать самостоятельно найти ошибку и поправить.
Почему сразу появилось подозрение на то, что функция будет неправильно работать с рабочими субботами? Потому, что основная логика, реализованная с помощью курсора, выбирает только будни вплоть до 01.01.2027 включительно и то только те из них, которых нет в таблице исключений. А про выходные, установленные как рабочие, забыли:)
Естественно, курсор, в функции ученика, когда принял субботнюю дату, сначала не отработал. Но, после нескольких прибавлений дней к данной на вход дате, функция получила буднее число, отсутствующее в таблице исключений и его же вернула.
Для начала дорабатываем с учеником функцию, добиваемся, чтобы она работала правильно для всех случаев и потом упрощаем решение, то есть избавляемся от лишнего кода и, по возможности, от метки. И здесь тоже хорошо бы использовать в именовании входящих параметров функции префикс "p_". В предыдущей части в самом конце я объяснил почему. А в наименовании переменных можно использовать, например, префикс "v_".
Доработали функцию. Получаем:
Тестируем сначала на предыдущем примере:
Отработало корректно. Функции дали на вход субботнюю дату и, так как данная на вход дата присутствует в таблице исключений, то функция эту дату же и вернула. Протестируем на выходном дне, например 02.01.2023:
Так как данная на вход дата была праздничной, то функция вернула первое следующее рабочее число.
Попробуем и на рабочем числе - получаем его же. Все отлично. Пример ниже:
Разберём поправленную функцию. Сначала можно посмотреть в самый её конец (иногда я с этого и начинаю разбирать незнакомую мне функцию) - функция вернёт то, что будет положено в переменную V_FOUND_DATE ☺️ Именно в неё будет рассчитано результирующее значение.
Итак, поняли, что переменная V_FOUND_DATE служит для получения в неё ближайшей рабочей даты. В самом начале функции заполняем переменную V_FOUND_DATE той же датой, которую будут давать на вход. На случай, если подаваемая дата и есть рабочая, то функция её же и вернёт.
Дальше начинается цикл, бесконечно перебирающий даты (прибавляющий к дате V_FOUND_DATE один день), пока не будет найден день, являющийся рабочим и отсутствующим в таблице исключений или не будет найден выходной, присутствующий в таблице исключений.
Анализируем сначала данную на вход дату до прибавления к ней одного дня. В переменную V_EXISTS_IN_DATES_OF_YEAR кладётся значение 1, если есть в таблице дат исключений дата, которая сейчас анализируется в цикле (в начале анализируется дата, данная же и на вход). Если не удалось начитать в переменную V_EXISTS_IN_DATES_OF_YEAR значение 1, то есть если в таблице исключений DATES_OF_YEAR нет анализируемой даты, то переменной V_EXISTS_IN_DATES_OF_YEAR проставляется значение 0. Данную проверку можно реализовать многими другими способами, здесь приведён один из них.
Далее, зная к какому дню недели относится анализируемая дата и зная, есть ли она в таблице исключений написано условие выхода из цикла: если дата относится к будним дням и она отсутствует в таблице исключений, или же дата является субботой или воскресеньем, но дата занесена в исключения, то дата - рабочее число и её надо вернуть функции. В этом случае работу цикла завершаем (рабочая дата найдена).
Если проверка показала, что дата не рабочая, то к ней прибавляется один день и проверки начинаются заново: опять смотрим есть ли новая дата в таблице исключений и опять смотрим какой теперь день недели у новой даты.
Осталось проверить написанную учеником процедуру открытия кредитного договора с расчётом дат графика платежей, в которой бы использовалась написанная здесь функция получения максимально ближайшей рабочей даты. Ведь платежи по кредиту должны быть каждый месяц в тот же день при условии, что дата платежа является рабочим числом. Если дата выпадает на выходной или праздничный день, то датой должен быть определён следующий ближайший рабочий день. Но об этом в другой раз.
Если хочешь стать участником нашего курса по SQL или PL/SQL, то записаться можно на нашем сайте: https://prime-soft.biz/courses
Буду рад оценке статьи лайком, твоим комментариям или если поделишься статьёй с друзьями!