oracle первый день предыдущего месяца

Некоторые примеры нестандартных возможностей синтаксиса sql. Часть вторая: форматы дат

Введение

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

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

Первая часть статьи, посвященная особенностям оператора order by, функционированию not in и примеру неявного преобразования типов, находится здесь.

Функция to_date и форматы даты

Мало кто из программистов любит тематику форматирования. Например, на некоторых курсах темы форматирования дат и региональных стандартов специально ставят на последние часы последнего дня тренинга, т.к. слушателям нудно. Причина в большом количестве существующих форматов при относительно редком их использовании в стандартных задачах. Чаще всего маски используются в трех функциях: to_number, to_char и to_date. Во всех трех случаях маска идет вторым необязательным параметром. И если масок для форматирования чисел еще более-менее вменяемое количество, то масок для форматирования дат до неприличия много, плюс еще суффиксы и модификаторы.

Безусловно, доступность большого количества масок является позитивным моментом, поскольку расширяет возможности, например, проверить является ли 13 сентября 2011 днем программиста, можно с помощью маски ‘DDD’, которая возвращает номер дня в году:

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

Перед тем как перейти непосредственно к основной теме раздела, давайте рассмотрим несколько нестандартных примеров работы с датами.

Пример №1. Использование урезанных шаблонов
Начнем со стандартного форматирования. Пускай сегодняшняя дата 16.09.2011, выполнятся ли следующие запросы, и что они вернут?

Запрос №2 является типичным примером конвертации даты в строку с приведением ее к нужному формату. Единственное отличие – вместо более привычных масок вида ‘DD.MM.YY’ или ‘DD-MON-YYYY’ мы использовали маску, задающую только год. Запрос №2 выполнится успешно и вернет текущий год в четырехзначном формате, т.е. ‘2011’.

Запрос №3 немного интереснее, он представляет собой типичный пример явного преобразования строки в дату с урезанной маской формата, поэтому, с точки зрения синтаксиса, запрос верный и выполнится успешно. Более важным вопросом является результат его выполнения, т.е. какую дату он вернет, если задан только день? Перед ответом на данный вопрос давайте вспомним, как Oracle устанавливает время, если оно явно не задано:

В запросе №4 время не указано, в запросе №5 указано только количество минут, часы и секунды опущены. В Oracle существует правило, согласно которому, если в дате отсутствует временной компонент, то время автоматически устанавливается в значение 00:00:00 (т.е. полночь), если задана только часть элементов времени (как в запросе №5), то пропущенные элементы устанавливаются в 00. Поэтому, запрос №4 вернет строку ‘03.02.2011 00:00:00’, а запрос №5 — ‘03.02.2011 00:30:00’.

Вернемся к запросу №3, верно ли данное правило для дат, т.е. заменяются ли пропущенные при конвертации элементы даты на 00 или 01? Да заменяются, но не все, точнее, для пропущенных элементов даты используются значения из sysdate (первый день текущего месяца текущего года). Поэтому запрос №3 будет использовать 09 в качестве месяца и 2011 в качестве года, таким образом, результатом выполнения запроса будет дата 03.09.2011.

Пример №2. Порядок параметров форматирования
Выполнится ли следующий запрос, и если да, то какую дату он вернет?

На первый взгляд, отсутствие разделителей в строке с датой может показаться критическим фактором несовместимым с выполнением запроса, однако маска даты также задана без разделителей и строка для преобразования соответствует указанному шаблону. Поэтому запрос №6 выполнится успешно и вернет 20.11.2009 (формат результата может несколько отличаться в зависимости от настроек сессии). Детальнее вопросы, связанные с разделителями, мы рассмотрим в следующем примере.

Пример №3. Неявная конвертация
Пусть формат даты по умолчанию DD.MON.RR, а язык даты – русский, отработает ли следующий запрос:

В данном запросе указано два строковых параметра, которые должны быть преобразованы в даты с помощью неявной конвертации. Согласно документации, при использовании форматов по умолчанию, строка для неявного преобразования в дату должна удовлетворять шаблону: [D|DD] separator1 [MM|MON|MONTH] separator2 [R|RR|YY|YYYY]. В качестве separator1 и separator2 можно использовать большинство разделительных знаков и специальных символов, в том числе пробелы, табуляцию, а также » и удвоенную одинарную кавычку ». Более того, если в строке указано не менее двух цифр для задания дней, месяцев и лет, то separator вообще может быть опущен. Например:

Поскольку обе строки указанные в запросе №7 соответствуют приведенному шаблону, то запрос выполнится успешно и вернет число 11.

Пример №4. Параметры функции to_date
Пусть формат даты по умолчанию DD.MON.RR, а язык даты – русский, отработает ли следующий запрос:

Схожий запрос фигурировал в одном из обсуждений на странице ask Tom. Ловушка запроса в том, что мы пытаемся преобразовать дату (sysdate) в дату. Если бы запрос выглядел так:

То выполнение прошло бы успешно, и он вернул строку ’09/15/2011 23:00:11′. Однако функция to_date в качестве первого параметра ожидает строку поэтому, вначале происходит неявная конвертация даты в строку (что эквивалентно вызову to_char(sysdate) с маской по умолчанию). Результатом данной конвертации является строка ‘15.09.11’, далее происходит вызов to_date. Таким образом, запрос №11 эквивалентен следующему запросу:

Как не сложно убедиться, запрос №13 не может быть выполнен, поскольку строка ‘15.09.11’ не соответствует установленной маске, соответственно, запрос №11 так же не может быть выполнен.

Установка формата даты по умолчанию
Формат дат по умолчанию задается двумя параметрами: NLS_DATE_FORMAT (отвечает за сам формат как таковой) и NLS_DATE_LANGUAGE (отвечает за язык, который будет использован при написании названий дней, месяцев и т.д.). Если эти параметры не заданы явно, то их значения устанавливаются на основе параметра NLS_LANG.

Логично предположить, что преобразование строки ‘11.09.11’ в дату пройдет успешно, а строки ’11.SEP.11′ – нет. Однако это не так, успешно выполнятся оба преобразования. Вначале я предполагал, что в случае невозможности преобразовать строку по маске сессии Oracle пытается задействовать маски других уровней (маска уровня БД у меня установлена в ‘DD-MON-RR’). Чтение документации показало, что это не так, и Oracle руководствуется принципами, описанными в предыдущем пункте.

Попробуем другой пример:

Если вы думаете, что результат будет идентичен предыдущему запросу, то вы ошибаетесь. Одно из преобразований не выполнится. В данном случае строка ‘11.09.11’ не удовлетворяет шаблону. Возможно, это мистика?

Увы, нет. Чтение документации показало, что существуют правила автозамены элементов форматирования даты. Ниже привожу таблицу замен.

Original Format ElementAdditional Format Elements to Try in Place of the Original
‘MM’‘MON’ and ‘MONTH’
‘MON‘MONTH’
‘MONTH’‘MON’
‘YY’‘YYYY’
‘RR’‘RRRR’

Глядя на содержимое этой таблицы, становится понятно, что в формате ‘DD.MM.RR’ неявным образом присутствует формат ‘DD.MON.RR’ (а также ‘DD.MONTH.RR’ и другие), а вот в формате ‘DD.MON.RR’ формат ‘DD.MM.RR’ не присутствует, что и объясняет поведение запросов №14 и №15.

Пример №6. Формат RR vs YY
Большинству пользователей отличия масок RR и YY хорошо известны, однако есть и те, кому данная информация окажется полезной. Перейдем сразу к рассмотрению примера. Какие данные вернут следующие запросы:

Оба приведенных выше запроса выполнятся успешно и вернут даты в соответствии с правилами, описанными в примере №1 для запроса №3. Таким образом, значение дня во всех полученных датах будет равно 01, а значение месяца 09 (если вы выполняете запрос в сентябре). Главный вопрос, каким будет значение года?

Как несложно предположить, в запросе №16 под ’11’ я подразумевал 2011 год и обе маски мне его вернули, т.е. результат выполнения запроса №16 это 01.09.2011 и 01.09.2011.

В запросе №17 под ’99’ я подразумевал 1999 год и тут мнения масок разделились: маска RR вернула ожидаемый 1999 год, а маска YY – 2099, т.е. результат выполнения запроса №17 это 01.09.1999 и 01.09.2099.

Рассмотрим, как работают эти элементы форматирования более детально:

Поэтому запрос №19 вернет 1950 год в обоих случаях.

Пример №7. Некоторые другие примеры
В завершение обзора рассмотрим немного экзотики. Будет ли ошибка в результате выполнения следующего запроса:

Если вы решили, что это бессмысленная запись, то вы ошибаетесь – это вполне корректное задание даты в соответствии со стандартом ANSI, запрос №20 выполнится успешно и вернет 25.12.1928.

Какой из запросов не выполнится?

Данный пример призван продемонстрировать наличие третьего параметра в функции to_date. Данный параметр позволяет установить значение одного из NLS (National Language Support) параметров только для этого вызова функции to_date. Установку NLS параметров для сессии мы рассматривали в примере №5. Ключевая разница запросов №20 и №21 состоит не в названии месяца (маска MON автоматически замещается маской MONTH, как это описано в примере №5), а в указании разных языков даты. Запрос №21 ожидает название месяца на английском и, соответственно, не выполнится, запрос №22 ожидает название месяца на русском и выполнится успешно.

В каком случае следующее объявление таблицы может приводить к возникновению ошибок при вставке данных?

Каждый раз, когда сессия с форматом даты по умолчанию ‘DD.MON.RR’ будет производить вставку с указанием только значения первого столбца будет возникать ошибка.

Особенности отображения дат в различных приложениях

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

Правда заключается в том, что при выполнении запроса
вы получаете дату, но для отображения результата на экран конкретная утилита, с помощью которой вы обращаетесь к БД, должна провести конвертацию даты в строку. Таким образом, для отображения дат (и чисел) неявным образом вызывается to_char, т.е. имеем классический случай неявной конвертации (это конвертация только для вывода на экран, ее результаты не участвуют ни в каких вычислениях и ни на что кроме отображения данных не влияют). Если есть неявная конвертация, значит, существует и маска, по которой она выполняется. В классическом случае это должна быть маска, установленная для сессии, т.е. маска, указанная в параметре NLS_DATE_FORMAT таблицы nls_session_parameters, с которой мы активно работали на протяжении всей статьи.

Давайте проверим работу некоторых приложений. Проверять будем с помощью следующего скрипта:

Проверим, какие параметры для отображения дат использует sqlplus.
oracle первый день предыдущего месяца. Смотреть фото oracle первый день предыдущего месяца. Смотреть картинку oracle первый день предыдущего месяца. Картинка про oracle первый день предыдущего месяца. Фото oracle первый день предыдущего месяца
Рис. 1. Результат выполнения запроса №25 в sqlplus.

Как видно из рис.1, формат отображения даты меняется в зависимости от настроек сессии, т.е. sqlplus использует настройки сессии. Это упрощает понимание процесса преобразования дат в строки и обратно, поскольку и для преобразования и для отображения используются одинаковые маски.

Некоторые продвинутые средства разработки используют свои собственные NLS настройки, не имеющие отношения к настройкам Оракл. В качестве примера проверим, какие параметры для отображения дат использует PL/SQL Developer. Для этого выполним в нем запрос №25.
oracle первый день предыдущего месяца. Смотреть фото oracle первый день предыдущего месяца. Смотреть картинку oracle первый день предыдущего месяца. Картинка про oracle первый день предыдущего месяца. Фото oracle первый день предыдущего месяца
Рис. 2. Результат выполнения запроса №25 в PL/SQL Developer.

Как видно из рис.2, формат отображения даты не меняется при изменении настроек сессии. Более того, если посмотреть внимательно, то видно, что и первый и второй результаты вывода даты на экран не соответствовали параметрам сессии (в первом случае выведенная дата имела год в четырехзначном формате, а маска указывала год в двухзначном формате). Это означает, что утилита использует собственные NLS настройки, в случае PL/SQL Developer’а их расположение указано на рис. 3.
oracle первый день предыдущего месяца. Смотреть фото oracle первый день предыдущего месяца. Смотреть картинку oracle первый день предыдущего месяца. Картинка про oracle первый день предыдущего месяца. Фото oracle первый день предыдущего месяца
Рис. 3. Установка NLS параметров отображения дат в PL/SQL Developer.

Чем могут быть вредны NLS настройки утилит
Отображение даты в формате отличном от формата сессии вредно по одной причине – оно вводит пользователя в заблуждение и может привести к возникновению ошибок. Выполним в sqlplus и PL/SQL Developer следующий запрос:
В последнюю строку запроса вместо ХХХХХХХХ мы будем вставлять полученные из предыдущей строки данные.

Результаты выполнения запроса представлены на рисунках ниже.
oracle первый день предыдущего месяца. Смотреть фото oracle первый день предыдущего месяца. Смотреть картинку oracle первый день предыдущего месяца. Картинка про oracle первый день предыдущего месяца. Фото oracle первый день предыдущего месяца
Рис. 4. Результат выполнения запроса №26 в sqlplus.

oracle первый день предыдущего месяца. Смотреть фото oracle первый день предыдущего месяца. Смотреть картинку oracle первый день предыдущего месяца. Картинка про oracle первый день предыдущего месяца. Фото oracle первый день предыдущего месяца
Рис. 5. Результат выполнения запроса №26 в PL/SQL Developer.

Почему в sqlplus выведенные на экран данные были успешно конвертированы в дату, а данные выведенные на экран PL/SQL Developer’ом не смогли сконвертироваться? Потому что для конвертации Оракл использует формат данных указанный в сессии, а данные выведенные PL/SQL Developer’ом были приведены для отображения в свой формат, отличный от формата сессии.

Заключение

В качестве заключения хочу напомнить, что почти в каждом своем посте посвященном работе с датами, Том Кайт говорит о необходимости использования явных преобразований и обязательном указании маски. «При конвертации строки в дату никогда не полагайтесь на формат даты по умолчанию, всегда явно задавайте маску» — примерно так звучат его слова. Дополнительные примеры и возможные ошибки при работе с преобразованием дат вы можете найти, воспользовавшись страничкой ask Tom.

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

Источник

Oracle первый день предыдущего месяца

Функция SYSDATE
Функция SYSDATE возвращает текущую дату и время по часам сервера.

Пример:
SELECT SYSDATE FROM dual;

Функция ADD_MONTHS (d, x)
Возвращает дату, полученную в результате прибавления к дате d одного или нескольких месяцев. Количество месяцев задается параметров x, причем x может быть отрицательным — в этом случае указанное количество месяцев вычитается из заданной даты.

Функция LAST_DAY (d)
Возвращает последнее число месяца, указанного в дате d.

Пример:
SELECT SYSDATE d, LAST_DAY ( SYSDATE ) d1 FROM dual;

Функция MONTHS_BETWEEN (d1, d2)
Функция MONTHS_BETWEEN возвращает количество месяцев между двумя датами d1 и d2 с учетом знака как d1-d2, возвращаемое число является дробным.

Функция TRUNC (d[,mask])
Производит усечение указанной даты в соответствии с маской. Если маска не указана, то усечение производится до даты (время отбрасывается).

Пример:
SELECT SYSDATE d1, TRUNC ( SYSDATE ) d2 FROM dual;

Усечение даты до часов, дней, месяца и года. Форматная маска по умолчанию равна «DD»

Функция TO_DATE (str[,mask [,nls_lang]])
Функция TO_DATE преобразует строку str в дату. Преобразование ведется по маске mask, если она указана. Если маска не указана, то берется маска по умолчанию. В случае указания маски можно указать еще один параметр — язык, используемый при форматировании названий месяцев и дней. В случае ошибки анализа строки str в соответствии с заданной маской возникает исключительная ситуация. Наиболее распространенная ошибка «ORA-01830: шаблон формата даты завершается перед преобразованием всей строки ввода». Кроме того, нередко встречается ошибка «ORA-01821: формат даты не распознан» — она возникает при указании недопустимой форматной маски.

Пример:
SELECT TO_DATE ( ‘12.09.2006’ ) d FROM dual;

Функция TO_CHAR (d[,mask])
Преобразует дату d в символьную строку в соответствии с заданной маской. В случае указания недопустимой маски возникает исключительная ситуация «ORA-01821: формат даты не распознан».

Форматные маски функции TO_CHAR :

Источник

Функция Oracle TRUNC (дата)

Как обрезать функции даты в Oracle?

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

Синтаксис:

Параметры:

названиеОписание
ДатаДата для усечения.
FMT
(Необязательный)
Единица измерения для усечения. Если fmt отсутствует, то дата усекается до ближайшего дня.

Тип возвращаемого значения:

Возвращаемое значение всегда имеет тип данных DATE

Модели формата даты для функции TRUNC:

Модель форматаОкругление
ЦК ГТКОдна больше, чем первые две цифры четырехзначного года
SYYYY YYYY YEAR ГОД YYY YY YГод (итоги 1 июля)
IYYY IY IY IISO год
QКвартал (округляется в шестнадцатый день второго месяца квартала)
MONTH MON MM RMМесяц (округление на шестнадцатый день)
WWТот же день недели, что и первый день года
IWТот же день недели, что и первый день года ISO
WТот же день недели, что и первый день месяца
DDD DD JДень
DAY DY DНачальный день недели
ЧЧ НН12 НН24Час
Мичиганминут

Относится к:
Oracle 12c, Oracle 11g, Oracle 10g, Oracle 9i

Иллюстрированная презентация

oracle первый день предыдущего месяца. Смотреть фото oracle первый день предыдущего месяца. Смотреть картинку oracle первый день предыдущего месяца. Картинка про oracle первый день предыдущего месяца. Фото oracle первый день предыдущего месяца

Пример: функция Oracle TRUNC (дата)

Следующий оператор усекает указанную дату:

Предыдущий: TO_YMINTERVAL
Далее: TZ_OFFSET

Источник

Олимпиада SQL: разбор задачи про календарь

oracle первый день предыдущего месяца. Смотреть фото oracle первый день предыдущего месяца. Смотреть картинку oracle первый день предыдущего месяца. Картинка про oracle первый день предыдущего месяца. Фото oracle первый день предыдущего месяцаЗдравствуйте, в эфире Радио SQL!

Продолжаем тему популяризации языка SQL среди широких масс IT-населения нашей планеты, на этот раз в русскоязычной его части. Впрочем, жители других планет, тоже подтягивайтесь.

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

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

Обращаю внимание, что это именно разбор, а не готовое решение. Чтобы избежать тупого copy-paste, я намерено предприму пару действий, которые позволят получить готовый результат только тем, кто немного поработает головой.

Во-первых, я не буду приводить полный и окончательный код запроса. Чтобы получить финальное решение, придётся осмысленно собрать все части запроса в одно целое. Для человеков это несложно, достаточно задействовать головные ганглии. Также я опущу некоторые громоздкие, но совершенно неинтересные части (типа выравнивания названий месяцев по центру) для доработки желающими напильником по месту самостоятельно. Соответственно, собранный результат без некоторой доводки формально не будет правильным решением исходной задачи. Но меня это не волнует ни капли, так как моя цель — показать как подобные задачи решаются в принципе, а не получить готовый результат в данном частном случае.

Поколебавшись некоторое время, я остановил свой выбор на PostgreSQL, чтобы пощупать поближе, как он по сравнению с оракловой базой будет смотреться в этой задаче. Выразить все необходимые шаги решения на другом диалекте SQL не должно быть проблемой, лично я с этим справился быстро. Напомню, что на олимпиаде, откуда взята задача, использовался именно Oracle SQL, на котором и было изначально написано референсное решение. Ну и мне интереснее будет, не всё же только публике развлечения.

Что ж, шутки в сторону, приступим к разбору. Напомню условие.

Напишите один SQL-запрос, который сгенерирует карманный календарь. В параметрах к задаче указывается год календаря и количество строк и столбцов для формирования матрицы из месяцев. Параметры задаются следующим запросом:

Месяцы расположены в клетках матрицы календаря по порядку слева направо и потом сверху вниз. Числа в каждом месяце расположены по дням недели, первый день недели в первом столбце и так далее. Начало недели должно соответствовать настройкам локализации базы на момент запуска запроса. Название месяца берётся тоже из настроек локализации и выводится по центру над числами. Между месяцами нужно оставить промежуток, чтобы числа соседних месяцев «не слипались». Самой первой строчкой должен идти выровненный по центру год. Пустых строк быть не должно.

Например, при следующих заданных параметрах:

должен получиться следующий вывод запроса:

Подход к решению

, но специальная конструкция будет короче. Так можно получить нужное количество строк.

Теперь определимся с тем, как сгенерировать количество столбцов, заданных параметром. В принципе всё так же, как и со строками выше, можно сгенерировать необходимое количество записей. А потом, когда их надо будет вывести, сгруппируем и склеим эти записи агрегатной функцией для работы со строками. В PostgreQSL для этого нашлась подходящая функция string_agg() :

С помощью этой техники мы сгенерируем матрицу-заготовку для календаря, в которой по горизонтали и вертикали будет нужное количество месяцев, заданное в параметрах. Каждый месяц будем представлять в виде знакомест в 6 строк по 7 столбцов для каждого из дней месяца, вот так:

Назовём каждую такую конструкцию месяцеместом, так дальше будет удобно ссылаться. По столбцам месяцеместа мы потом будем расставлять числа в соответствии с днями недели. А по строкам в соответствии с номером недели в месяце — сначала первую строчку заполнять, потом вторую и так далее. Столбцов нужно семь по количеству дней недели, строк пусть будет шесть. Больше шести недель в месяце никак не может быть. Я конечно имею в виду земной Григорианский календарь. Просьба жителям других планет отнестись с пониманием, эта задача была придумана в первую очередь для землян XXI века (013 в тентуре, налево от БМ).

Теперь, когда с самым нетривиальным стало всё понятно, займёмся остальными техническими деталями. Нам нужно будет сгенерировать все дни года, чтобы потом их расставить в полученную выше матрицу. Тут закавыка может быть с тем, чтобы правильно определить это количество дней. Например, с учётом того, что год может быть високосный. Или вот в земном Григорианском календаре в 1582 году отсутствуют дни с 05 по 14 октября (и Oracle честно это показывает!). Так что дни нужного года будем получать так: все дни с первого дня года, указанного в параметрах, и до (но не включая) первого дня следующего года.

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

Реализация

Вот наши исходные параметры для задачи:

Теперь сгенерируем матрицу календаря с нужным количеством месяцев в колонках и строках. Для удобства дальнейшей расстановки дат по местам, в каждом месяцеместе (которое 7х6 клеточек) мы сразу запишем номер месяца, который будем на это место подставлять, и номер позиции дня в месяце по порядку. Понадобится некоторое количество мутноватой целочисленной арифметики с делением нацело и остатками, зато потом расстановка дат будет простой и удобной:

Теперь собираем вместе, расставляя дни по местам. Как я и сказал, с учётом предварительной подготовки, это делается на раз:

Что характерно, если в параметрах задачи матрица календаря по размерам получится больше или меньше 12 месяцев, то всё сработает корректно. Либо заполнятся все месяцеместа и часть останется пустыми, либо лишние месяцы не поместятся, но в обоих случаях матрица не разъедется.

Тут склеивается в одну строчку функцией string_agg() всё, что попало в одну строку матрицы календаря. При этом пустые значения дней заменяются на пробелы, и все числа дополняются слева пробелами, чтобы их выровнять. Причём внутри одного месяцеместа на каждое число отводится по 3 знакоместа, а между месяцами (условие c%7=0 ) — по 5. Это позволяет визуально отделить месяцы друг от друга. Также обращаю внимание на то, что мы сохраняем номер строки. По нему будет определяться правильный порядок в финальном выводе.

Осталось сверху посередине дописать год:

Теперь осталось из cal_all выбрать все строки в правильном порядке и отбросить пустые, если таковые найдутся:

Это и есть финальная часть нашего запроса.

Что осталось за кадром. Напрашивается сделать представление params2 и вынести в него константы типа «количество знакомест на каждое число месяца» и «количество пробелов между месяцеместами». Потому что если вдруг понадобится их поменять, то придётся выискивать эти числа в коде, который не везде очевиден, что чревато ошибками. Ну и все функции выравнивания я упростил, чтобы не перегружать код. А по условию задачи всё нужно выравнивать по центру.

Выводы

Главный и основной, который я старался донести до аудитории: нет ничего сверхсложного в решении подобных задач.

Что можно сказать про PostgreSQL в сравнении с Oracle по итогам данной задачи. Функционально всё соответствует, выразить можно приблизительно то же и приблизительно так же. Некоторые нюансы удобнее в одном диалекте, некоторые в другом. Функции отличаются, но на то и документация нам дана. Есть ли в чём-то существенная разница? Да, есть. На примере данной задачи я вижу по крайней мере в двух местах.

Во-первых, Oracle поддерживает локали, и в разных локалях неделя может начинаться с разных дней недели. Например, в большей части Европы неделя начинается понедельником, а в США с воскресенья. В PostgreSQL нет настроек локали для первого дня недели и невозможно сгенерировать календарь так, чтобы он начинался с привычного пользователю дня.

Во-вторых, отличается поддержка преобразования дат и работа с календарём. В Oracle за 04 октября 1582 года идёт 15 октября (как и было определено при вводе Григорианского календаря), в PostgreSQL есть 05 и все остальные числа октября 1582 года. Вопрос не так прост, как может показаться сходу, в документации PostgreSQL есть даже специальный раздел, объясняющий проблему и почему в PostgreSQL она решена таким образом. Но факт остаётся фактом: календари в Oracle и PostgreSQL разные, хоть оба и Григорианские, и, соответственно, логика работы с датами существенно отличается. Это может быть важно при портировании.

Если вам понравилось изложение, и у вас есть идеи о чём ещё околоSQLном следует написать, жду вас в комментариях. Также можете ответить на вопрос внизу, поддерживаете ли вы публикацию разбора олимпиадных задач.

На этом сегодня я прощаюсь с нашей аудиторией, stay tuned.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *