Zero year in Oracle DB

In my third year at Peter the Great SPbPU, I had an exam in SQL in the Oracle database. The exam consisted of three tasks. This exam was one of the most difficult in all four years of study at the university. Three tasks are given for several hours. If you solved all three problems, then you get a grade of 5, one defect (for example, an extra space) – 4, one problem was solved incorrectly – 3, two defects – 3.

Today I want to talk about one of the tasks that was in this exam.

Something went wrong…

I failed this exam. I was able to pass it only on the second attempt. This was very unexpected for me, but for my parents something beyond the bounds, because I studied at 4 and 5, and this had not happened before.

Using only access to the DUAL table, build an SQL query that returns one column containing the calendar for a given month in a given year:

  • the number of the day in the month (in numbers),

  • full month name in English in capital letters (uppercase),

  • year (four digits),

  • full name of the day of the week in English in lowercase (lowercase).

Each “subfield” must be separated from the next by one space. As a result, there should be no leading or trailing spaces. The number of rows returned must exactly match the number of days in the current month. The lines should be ordered by the number of days in the month in ascending order.

The calendar must be generated for any valid Oracle date values. Solve the problem without using Model clauses and recursive With.

An example of the output of the result:

1 MAY 2020 friday

2 MAY 2020 saturday

The task seemed to me the simplest. I will give a request with which I solved this problem.

with input as (
    select to_date(:dt, 'mm.syyyy') dt
    from dual
)
select to_number(to_char(level + dt - 1, 'dd'),'99') ||
       to_char(level + dt - 1, ' MONTH ', 'nls_date_language=american') ||
       to_char(level + dt - 1, 'syyyy ' , 'nls_date_language=american') ||
       to_char(level + dt - 1, 'day', 'nls_date_language=american') calendar
from dual
     join input on 1 = 1
connect by level + dt - 1 <= last_day(dt);

At first glance, everything looks good, but let’s take a look at one interesting point. The valid range for the year is an integer from -4172 to 9999, excluding 0. Take December 1 BC. and January 1 A.D. As a result, we will see that after Wednesday comes Saturday, which means the problem is solved incorrectly.

An example of how a query works with years -0001 and 0001
An example of how a query works with years -0001 and 0001

In the Gregorian and Julian calendars no zero year… Also in Oracle there is no zero year, and -0001 of Oracle corresponds to 2 BC. according to traditional chronology.

--Результат 01/01/0001
select to_date('31.12.-0001', 'dd.mm.syyyy') + 1
from dual; 
Comparison of ISO 8601 standard and traditional chronology
Comparison of ISO 8601 standard and traditional chronology

Then we continue to create dates that belong to our era, as it was done in the first request. -0002 Oracle will “shift” to the right, that is, it will become 2 BC. in traditional chronology, -0003 Oracle, would become 3 BC. etc. But we cannot also shift the Oracle -0001 year by adding days / months, etc. However, we can find a year that is similar to year zero. For example, this is 420 A.D. Then we will use this year to display the month of the zero year, and when displaying it, indicate that it is -0001. And then you can move away from the concept of a zero year, because there were ..., -2, -1, 1, 2, ... of the year.

with input as (
    select to_date(:dt, 'mm.syyyy') dt
    from dual
)
select case
        when extract (year from dt) > 0 then 
          to_char(level + dt - 1, 'fm dd ') || 
          to_char(level + dt - 1, 'MONTH ', 'nls_date_language=american') ||
          to_char(level + dt - 1, 'syyyy ', 'nls_date_language=american') ||
          to_char(level + dt - 1, 'day', 'nls_date_language=american')
        when extract (year from dt) = -1 then
          to_char(level + to_date(regexp_replace(to_char(dt, 'mm.syyyy'), '-0001', '0420'), 'mm.syyyy') - 1, 'fm dd ') ||
          to_char(level + to_date(regexp_replace(to_char(dt, 'mm.syyyy'), '-0001', '0420'), 'mm.syyyy') - 1, 'MONTH ', 'nls_date_language=american') ||
          to_char(level + dt - 1, 'syyyy ', 'nls_date_language=american') ||
          to_char(level + to_date(regexp_replace(to_char(dt,'mm.syyyy'), '-0001', '0420'),'mm.syyyy') - 1, 'day', 'nls_date_language=american')
        else
          to_char(level + add_months(dt, 12) - 1, 'fm dd ') ||
          to_char(level + add_months(dt, 12) - 1, 'MONTH ', 'nls_date_language=american') ||
          to_char(level + dt - 1, 'syyyy ', 'nls_date_language=american') ||
          to_char(level + add_months(dt, 12) - 1, 'day', 'nls_date_language=american')
        end calendar
from dual
     join input on 1 = 1
connect by level + dt - 1 <= last_day(dt);

The resulting request helps to achieve the desired result. This is how the problem, in which I was absolutely sure, was solved incorrectly by me on the exam.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *