Team LiB   Previous Section   Next Section

6.3 Date Manipulation

Date arithmetic is an important aspect of our day-to-day life. We find the age of a person by subtracting his date of birth from today's date. We compute the date a warranty expires by adding the warranty period to the purchase date. Drivers' license expirations, bank interest calculation, and a host of other things all depend on date arithmetic. It is extremely important for any database to support such common date arithmetic operations.

Oracle provides some very good date arithmetic features. Not only can we add and subtract dates, but Oracle also provides a number of other helpful functions for manipulating date values. We discuss these features in detail in this section. Table 6-2 lists various date manipulation functions provided by Oracle SQL.

Table 6-2. Date functions

Function

Use

ADD_MONTHS

Adds months to a date

LAST_DAY

Computes the last day of the month

MONTHS_BETWEEN

Determines the number of months between two dates

NEW_TIME

Translates a time to a new time zone

NEXT_DAY

Returns the date of the next specified weekday

ROUND

Rounds a date/time value to a specified element

SYSDATE

Returns the current date and time

TO_CHAR

Converts dates to strings

TO_DATE

Converts strings and numbers to dates

TRUNC

Truncates a date/time value to a specific element

6.3.1 Addition

Adding two dates doesn't make sense. However, we can add days, months, years, hours, minutes, and seconds to a date to generate a future date and time. The "+" operator allows us to add numbers to a date. The unit of a number added to a date is assumed to be days. Therefore, to find tomorrow's date, we can add 1 to SYSDATE:

SELECT SYSDATE, SYSDATE+1 FROM DUAL;

SYSDATE   SYSDATE+1
--------- ---------
05-OCT-01 06-OCT-01

Any time we add a number to a date, Oracle assumes that the number represents a number of days. Therefore, if we want to add multiples of a day (week, month, year, etc.) to a date, we first need to multiply by a conversion factor. For example, to add one week to today's date, we add 7 (7 days in a week times 1 day) to SYSDATE:

SELECT SYSDATE+7 FROM DUAL;

SYSDATE+7
---------
12-OCT-01

Similarly, if we want to add fractions of a day (hour, minute, second) to a date, we first need to convert such fractions into a fractional number of days. Do this by dividing by a conversion factor. For example, to add 20 minutes to the current date and time, we need to add (20 minutes/1,440 minutes in a day) to SYSDATE:

SELECT TO_CHAR(SYSDATE,'DD-MON-YY HH:MI:SS'),
TO_CHAR(SYSDATE+(20/1440),'DD-MON-YY HH:MI:SS')
FROM DUAL;

TO_CHAR(SYSDATE,'D TO_CHAR(SYSDATE+(2
------------------ ------------------
05-OCT-01 01:22:03 05-OCT-01 01:42:03

Adding months to a date is not as easy as adding weeks, because all months don't have the same number of days—some have 30, some 31, some 28, and at times even 29. To add one month to a date, we need to know how many days that calendar month will have. Therefore, adding months to a date by converting those months to a number of days involves lots of homework, which is error-prone. Fortunately, Oracle does all the homework for us, and provides a built-in SQL function to add months to dates. This function is called ADD_MONTHS, and we call it as follows:

ADD_MONTHS (date, number)

The syntax elements are:

date

Specifies a database column defined as type DATE or a string with a date in the default date format.

number

Specifies the number of months to add to the input date.

The following example shows the computation of an employee's biannual review date by using ADD_MONTHS to add six months to the employee's HIRE_DATE:

SELECT FNAME, HIRE_DATE, ADD_MONTHS(HIRE_DATE, 6) REVIEW_DATE FROM EMPLOYEE;

FNAME                HIRE_DATE REVIEW_DA
-------------------- --------- ---------
John                 22-OCT-99 22-APR-00

Notice that in this example the input date and the result date both fall on the 22nd of the month. This would not have happened if we had added 180 days to the input date. ADD_MONTHS is "smart" in one other way too. The following example adds 6 months to 31st December, 1999:

SELECT ADD_MONTHS('31-DEC-99',6) FROM DUAL;

ADD_MONTH
---------
30-JUN-00

The ADD_MONTHS function is intelligent enough to know that adding 6 months to 31st December should result in the last day of June. And since the last of June is 30th (not 31st), it returns 30th June, 2000.

6.3.2 Subtraction

Even though no other arithmetic operation (addition, multiplication, division) between two dates makes any sense, subtracting one date from another date is a very common and useful operation. The "-" operator allows us to subtract a date from a date, or a number from a date.

Subtracting one date from another date returns the number of days between those two dates. Subtracting a number from a date returns a date that number of days in the past.

The following example displays the lead time of a set of orders by subtracting the date on which the order was placed (ORDER_DT) from the expected ship date (EXPECTED_SHIP_DT):

SELECT ORDER_NBR, EXPECTED_SHIP_DT - ORDER_DT LEAD_TIME
FROM CUST_ORDER;

 ORDER_NBR  LEAD_TIME
---------- ----------
      1001          1
      1000          5
      1002         13
      1003         10
      1004          9
      1005          2
      1006          6
      1007          2
      1008          2
      1009          4
      1012          1
      1011          5
      1015         13
      1017         10
      1019          9
      1021          2
      1023          6
      1025          2
      1027          2
      1029          4

Along with subtracting one date from another, we can also subtract a number from a date. For example, subtracting 1 from SYSDATE gives yesterday, and subtracting 7 from SYSDATE yields the same day last week:

SELECT SYSDATE, SYSDATE - 1, SYSDATE - 7 FROM DUAL;

SYSDATE   SYSDATE-1 SYSDATE-7
--------- --------- ---------
05-OCT-01 04-OCT-01 28-SEP-01

Unlike ADD_MONTHS, Oracle doesn't provide a SUBTRACT_MONTHS function. To subtract months from a date, use the ADD_MONTHS function, and pass a negative number as the second parameter:

SELECT SYSDATE, ADD_MONTHS(SYSDATE, -6) FROM DUAL;

SYSDATE   ADD_MONTH
--------- ---------
05-OCT-01 05-APR-01

Earlier in this section we saw that subtracting a date from another date returns the number of days between the two dates. There are times when we may want to know the number of months between two dates. Consider that subtracting an employee's HIRE_DATE from SYSDATE yields the number of days of experience the employee has with her employer:

SELECT SYSDATE-HIRE_DATE FROM EMPLOYEE;

SYSDATE-HIRE_DATE
-----------------
         714.0786

It's better, in most cases, to find the number of months of experience rather than the number of days. We know that dividing the number of days between two dates by 30 won't accurately calculate the number of months between those two dates. Therefore, Oracle provides the built-in SQL function MONTHS_BETWEEN for finding the number of months between two dates. MONTHS_BETWEEN is called as follows:

MONTHS_BETWEEN (date1, date2)

The syntax elements are:

date1

Specifies the end of the time period in question. This should be either a DATE value or a string in the default date format.

date2

Specifies the beginning of the time period in question. Like date1, this should also be a DATE value or a string in the default date format.

MONTHS_BETWEEN subtracts date2 from date1. So, if date2 comes later than date1 in the chronology, then MONTHS_BETWEEN will return a negative value. The following example demonstrates two calls to MONTHS_BETWEEN. Both calls use the same two dates, but in different orders.

SELECT MONTHS_BETWEEN(SYSDATE,HIRE_DATE) 
       MONTHS_BETWEEN(HIRE_DATE, SYSDATE)
FROM EMPLOYEE;

MONTHS_BETWEEN(SYSDATE,HIRE_DATE) MONTHS_BETWEEN(HIRE_DATE,SYSDATE)
--------------------------------- ---------------------------------
                       23.4542111                        -23.454218

There is no YEARS_BETWEEN function. To find the number of years between two dates, we can either subtract the two dates to find the number of days and then divide by 365, or use MONTHS_BETWEEN to find the number of months and then divide by 12. Years don't have the same number of days—some have 365 days and others have 366 days. Therefore, it is not accurate to divide the number of days by 365 to get the number of years. On the other hand, all years have 12 months, whether a leap year or not. Therefore, the most accurate way to calculate the number of years between two dates is to use the MONTHS_BETWEEN function to find the number of months and then divide by 12 to get the number of years.

6.3.3 Last Day of the Month

Oracle provides a built-in function to get the last day of a month. The function is LAST_DAY,and it's called as follows:

LAST_DAY (date)

The syntax element is:

date

Specifies a DATE value, or a string with a date in the default date format.

LAST_DAY returns the last day of the month containing the input date. For example, to find the last date of the current month, we can use the following SQL statement:

SELECT LAST_DAY(SYSDATE) "Next Payment Date" FROM DUAL;

Next Paym
---------
31-OCT-01

Sometimes it's useful to be able to determine the first day of a given month; it would be nice if Oracle would provide a FIRST_DAY function. One approach to getting the first day of the month for a given date is to use the following expression:

ADD_MONTHS((LAST_DAY(date)+1), -1)

This expression finds the last day of the month represented by date. It then adds 1 to get to the first day of the subsequent month, and finally uses ADD_MONTHS with an argument of -1 to go back to the beginning of the month in which we started. The result is the first day of the month in which the given date falls. Other approaches to this problem are possible; this is just one that works well for us. This approach has the advantage of preserving the time component of the date in question.

6.3.4 Next Day

Oracle provides a built-in function to get the date of the next occurrence of a specified day of the week. The function is NEXT_DAY, and it's called as follows:

NEXT_DAY (date, string)

The syntax elements are:

date

Specifies a DATE value, or a string with a date in the default date format.

string

Specifies the name of a weekday.

To find the date of the next Friday, we can use the following SQL statement:

SELECT NEXT_DAY(SYSDATE, 'Friday') "Vacation Start Date" FROM DUAL;

Vacation
---------
12-OCT-01

If the specified string is not a valid day of the week, we will get an error:

SELECT NEXT_DAY(SYSDATE, 'ABCD') FROM DUAL;
SELECT NEXT_DAY(SYSDATE, 'ABCD') FROM DUAL
                         *
ERROR at line 1:
ORA-01846: not a valid day of the week

6.3.5 Rounding and Truncating Dates

Rounding and truncating dates is similar in concept to the rounding and truncating of numbers, but more involved because an Oracle DATE contains date as well as time information. Use the ROUND function to round a date/time value to a specific element; use the TRUNC function to truncate a date/time value to a specific element. Following is the syntax for invoking these two functions:

ROUND(date [, format])
TRUNC(date [, format])

The syntax elements are:

date

Specifies a DATE value.

format

Specifies the date element to round or truncate to.

The return value depends upon the specified format, which is an optional parameter. If we don't specify a format in the call to ROUND, the function returns a date by rounding the input to the nearest day. If we don't specify a format in the call to TRUNC, that function returns a date by removing the fractional part of the day.

When using ROUND and TRUNC to round to the nearest day, or to truncate a date, the functions set the time fields of the return value to the beginning of the returned day, i.e., 12:00:00 AM (00:00:00 in HH24 format). For example:

SELECT TO_CHAR(SYSDATE, 'DD-MON-YY HH:MI:SS AM'),
       TO_CHAR(ROUND(SYSDATE), 'DD-MON-YY HH:MI:SS AM'),
       TO_CHAR(TRUNC(SYSDATE), 'DD-MON-YY HH:MI:SS AM')
FROM DUAL;

TO_CHAR(SYSDATE,'DD-M TO_CHAR(ROUND(SYSDATE TO_CHAR(TRUNC(SYSDATE
--------------------- --------------------- ---------------------
06-OCT-01 07:35:48 AM 06-OCT-01 12:00:00 AM 06-OCT-01 12:00:00 AM

Notice that since the input time (SYSDATE) is before 12 noon, the output of ROUND and TRUNC are the same. However, if the input time were after 12 noon, the output of ROUND and TRUNC would be different, as in the following example.

SELECT TO_CHAR(SYSDATE, 'DD-MON-YY HH:MI:SS AM'),
       TO_CHAR(ROUND(SYSDATE), 'DD-MON-YY HH:MI:SS AM'),
       TO_CHAR(TRUNC(SYSDATE), 'DD-MON-YY HH:MI:SS AM')
FROM DUAL;

TO_CHAR(SYSDATE,'DD-M TO_CHAR(ROUND(SYSDATE TO_CHAR(TRUNC(SYSDATE
--------------------- --------------------- ---------------------
06-OCT-01 05:35:48 PM 07-OCT-01 12:00:00 AM 06-OCT-01 12:00:00 AM

Since the input time is past 12 noon, ROUND returns the beginning of the next day. However, TRUNC still returns the beginning of the input date. This is similar to the rounding and truncating of numbers.

When we specify a format as an input to the ROUND and TRUNC functions, things become a bit more involved, but the concepts of rounding and truncating still remain the same. The difference is that the rounding and truncating are now based on the format we specify. For example, if we specify the format as YYYY, the input date will be truncated based on the year, which means that if the input date is before the middle of the year (July 1st), both ROUND and TRUNC will return the first day of the year. If the input date is after July 1st, ROUND will return the first day of the next year, whereas TRUNC will return the first day of the input year. For example:

SELECT TO_CHAR(SYSDATE-180, 'DD-MON-YYYY HH24:MI:SS'),
       TO_CHAR(ROUND(SYSDATE-180,'YYYY'),'DD-MON-YYYY HH24:MI:SS'),
       TO_CHAR(TRUNC(SYSDATE-180,'YYYY'),'DD-MON-YYYY HH24:MI:SS')
FROM DUAL;

TO_CHAR(SYSDATE-180, TO_CHAR(ROUND(SYSDAT TO_CHAR(TRUNC(SYSDAT
-------------------- -------------------- --------------------
09-APR-2001 20:58:33 01-JAN-2001 00:00:00 01-JAN-2001 00:00:00

SELECT TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS'),
       TO_CHAR(ROUND(SYSDATE,'YYYY'),'DD-MON-YYYY HH24:MI:SS'),
       TO_CHAR(TRUNC(SYSDATE,'YYYY'),'DD-MON-YYYY HH24:MI:SS')
FROM DUAL;

TO_CHAR(SYSDATE,'DD- TO_CHAR(ROUND(SYSDAT TO_CHAR(TRUNC(SYSDAT
-------------------- -------------------- --------------------
06-OCT-2001 20:58:49 01-JAN-2002 00:00:00 01-JAN-2001 00:00:00

Similarly, we can round or truncate a date to a specific month, quarter, week, century, hour, minute, and so forth by using the appropriate format. Table 6-3 lists the formats (and their meanings) that can be used with the ROUND and TRUNC functions.

Table 6-3. Date formats for use with ROUND and TRUNC

Rounding unit

Format

Remarks

Century

CC

SCC

TRUNC returns the first date of the century.

If the input date is before the middle of the century (01-JAN-xx51), ROUND returns the first date of the century; otherwise, ROUND returns the first date of the next century.

Year

SYYYY

YYYY

YEAR

SYEAR

YYY

YY

Y

TRUNC returns the first date of the year.

If the input date is before the middle of the year (01-JUL), ROUND returns the first date of the year; otherwise, ROUND returns the first date of the next year.

ISO

IYYY

IYY

IY

I

TRUNC returns the first date of the ISO year.

If the input date is before the middle of the ISO year, ROUND returns the first date of the ISO year; otherwise, ROUND returns the first date of the next ISO year.

Quarter

Q

TRUNC returns the first date of the quarter.

If the input date is before the middle of the quarter (the 16th day of the second month of the quarter), ROUND returns the first date of the year; otherwise, ROUND returns the first date of the next quarter.

Month

MONTH

MON

MM

RM

TRUNC returns the first date of the month.

If the input date is before the middle of the month (the 16th day of the month), ROUND returns the first date of the year; otherwise, ROUND returns the first date of the next month.

Week

WW

TRUNC returns the first date of the week.

If the input date is before the middle of the week (based on the first day of the year), ROUND returns the first date of the week; otherwise, the first date of the next week.

ISO Week

IW

TRUNC returns the first date of the ISO week.

If the input date is before the middle of the week (based on the first day of the ISO year), ROUND returns the first date of the week; otherwise, ROUND returns the first date of the next week.

Week

W

TRUNC returns the first date of the week.

If the input date is before the middle of the week (based on the first day of the month), ROUND returns the first date of the week; otherwise, ROUND returns the first date of the next week.

Day

DDD

DD

J

TRUNC returns the beginning of the day.

If the input time is before the middle of the day (12:00 noon), ROUND returns the beginning of the day, otherwise the beginning of the next day.

Day of the week

DAY

DY

D

TRUNC returns the first date of the week.

If the input date is before the middle of the week (based on the first day of the month), ROUND returns the first date of the week, otherwise the first date of the next week.

Hour

HH

HH12

HH24

TRUNC returns the beginning of the hour.

If the input time is before the middle of the hour (00:30), ROUND returns the beginning of the hour; otherwise, ROUND returns the beginning of the next hour.

Minute

MI

TRUNC returns the beginning of the minute.

If the input time is before the middle of the minute (00:00:30), ROUND returns the beginning of the minute; otherwise, ROUND returns the beginning of the next minute.

6.3.6 NEW_TIME

Let's say you work in an office in the New York City and want to schedule a video conference with a customer in Los Angeles. If you aren't careful about the time difference between the two cities, you might end up scheduling the meeting at 9:00 A.M. your time. Hopefully, you know that this is not the proper time to call your customer if you really want to make the deal, because it is too early to expect him to be in the office (9:00 A.M. in New York is 6:00 A.M. in Los Angeles). If you need to deal with time zones in the database, Oracle's built-in NEW_TIME function comes to your rescue. It converts a date and time in a given time zone into a date and time in another time zone. Call NEW_TIME as follows:

NEW_TIME (date, input_time_zone, output_time_zone)

The syntax elements are:

date

Specifies a literal, PL/SQL DATE variable, or a database column of DATE datatype.

input_time_zone

Specifies the name of the input time zone (as a string).

output_time_zone

Specifies the name of the output time zone (as a string).

As an example, to find out the time in Los Angeles when it is 9:00 A.M. at New York, you can use the following SQL:

ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YY HH:MI:SS AM';

Session altered.

SELECT NEW_TIME('11-NOV-01 09:00:00 AM','EST','PST') FROM DUAL;

NEW_TIME('11-NOV-0109
---------------------
11-NOV-01 06:00:00 AM

In this example, EST and PST correspond to Eastern Standard Time and Pacific Standard Time, respectively.

6.3.7 SELECTing Data Based on Date Ranges

There are times when we need to SELECT data from a table based on a given date range. Let's say you have been asked to print all orders placed on a given date, say 22-MAY-01. Probably, your immediate response would be a query such as the following:

SELECT * FROM CUST_ORDER
WHERE ORDER_DT = '22-MAY-01';

no rows selected

There's no output. Surprised? Although you know there are orders on 22-MAY-01, this query didn't return any rows. The reason is that ORDER_DT is a DATE column, and contains time as well as date information. On the other hand, the date literal '22-MAY-01' doesn't contain any time information. When you don't specify the time portion in a date literal, the time portion is assumed to be beginning of the day, i.e., 12:00:00 A.M. (or 00:00:00 in 24 hour format). In the CUST_ORDER table, the time components in the ORDER_DT column are other than 12:00:00 A.M. In this case, the correct query to print orders placed on 22-MAY-01 is:

SELECT * FROM CUST_ORDER
WHERE ORDER_DT BETWEEN TO_DATE('22-MAY-01 00:00:00','DD-MON-YY HH24:MI:SS')
AND TO_DATE('22-MAY-01 23:59:59','DD-MON-YY HH24:MI:SS');

 ORDER_NBR CUST SALES_EMP SALE_PRICE ORDER_DT  EXPECTED_ CANCELLED SHIP STATUS
---------- ---- --------- ---------- --------- --------- --------- ---- ---------
      1001    1         3         99 22-MAY-01 23-MAY-01                DELIVERED
      1005    8         3         99 22-MAY-01 24-MAY-01                DELIVERED
      1021    8         7         99 22-MAY-01 24-MAY-01                DELIVERED

The query treats the one day as a range: 22-MAY-01 00:00:00 to 22-MAY-01 23:59:59. Thus, the query returns any order placed at any time during 22-MAY-01.

Another way to solve this problem of needing to ignore the time components in a DATE column would be to truncate the date, and then compare the truncated result with the input literal:

SELECT * FROM CUST_ORDER
WHERE TRUNC(ORDER_DT) = '22-MAY-01';

 ORDER_NBR CUST SALES_EMP SALE_PRICE ORDER_DT  EXPECTED_ CANCELLED SHIP STATUS
---------- ---- --------- ---------- --------- --------- --------- ---- ---------
      1001    1         3         99 22-MAY-01 23-MAY-01                DELIVERED
      1005    8         3         99 22-MAY-01 24-MAY-01                DELIVERED
      1021    8         7         99 22-MAY-01 24-MAY-01                DELIVERED

The TRUNC function sets the time portion to the beginning of the day. Therefore, the equality comparison with the date literal '22-MAY-01' returns the expected output. The same result can be achieved by converting ORDER_DT to a character string in a format matching that of the input data.

SELECT * FROM CUST_ORDER
WHERE TO_CHAR(ORDER_DT,'DD-MON-YY') = '22-MAY-01';

The downside to the approach of using the TRUNC and TO_CHAR functions is that the resulting query cannot make use of any index that happens to be on the ORDER_DT column. This can have significant performance implications. On the other hand, the date range solution, while more complex to code, does not preclude the use of any index on the column in question.

Oracle8i and higher support the use of function-based indexes, which, if created correctly, allow for the use of indexes even when functions are applied to columns.

You can use the same techniques shown in this section to SELECT data based on any given date range, even if that range spans more than just one day.

6.3.8 Creating a Date Pivot Table

For certain types of queries, it's helpful to have a table with one row for each date over a period of time. For example, you might wish to have one row for each date in the current year. You can use the TRUNC function in conjunction with some PL/SQL code to create such a table:

CREATE TABLE DATES_OF_YEAR (ONE_DAY DATE);

Table created.

DECLARE
  I NUMBER;
  START_DAY DATE := TRUNC(SYSDATE,'YY');
BEGIN
  FOR I IN 0 .. (TRUNC(ADD_MONTHS(SYSDATE,12),'YY') - 1) - (TRUNC(SYSDATE,'YY')) 
  LOOP
    INSERT INTO DATES_OF_YEAR VALUES (START_DAY+I);
  END LOOP;
END;
/

PL/SQL procedure successfully completed.

SELECT COUNT(*) FROM DATES_OF_YEAR;

  COUNT(*)
----------
       365

The DATES_OF_YEAR table is now populated with the 365 days of the year 2001. We can now play with this table to generate various useful lists of dates.

Let's say there are two paydays where you work—the 15th of each month and the last day of each month. Use the following query against the DATES_OF_YEAR table to generate a list of all paydays in the year 2001.

SELECT ONE_DAY PAYDAY FROM DATES_OF_YEAR
WHERE TO_CHAR(ONE_DAY,'DD') = '15'
OR ONE_DAY = LAST_DAY(ONE_DAY);

PAYDAY
---------
15-JAN-01
31-JAN-01
15-FEB-01
28-FEB-01
15-MAR-01
31-MAR-01
15-APR-01
30-APR-01
15-MAY-01
31-MAY-01
15-JUN-01
30-JUN-01
15-JUL-01
31-JUL-01
15-AUG-01
31-AUG-01
15-SEP-01
30-SEP-01
15-OCT-01
31-OCT-01
15-NOV-01
30-NOV-01
15-DEC-01
31-DEC-01

24 rows selected.

Quite often you are told by a government organization that the processing of a document will take "x" number of days. When they say something like that, they usually mean "x" number of working days. Thus, in order to calculate the expected completion date, you need to count "x" days from the current date, skipping Saturdays and Sundays. Obviously, you can't use simple date arithmetic, because simple date subtraction doesn't exclude weekend days. What you can do is use the DATES_OF_YEAR table. For example:

SELECT COUNT(*) FROM DATES_OF_YEAR
WHERE RTRIM(TO_CHAR(ONE_DAY,'DAY')) NOT IN ('SATURDAY', 'SUNDAY')
AND ONE_DAY BETWEEN '&d1' AND '&d2';

Enter value for d1: 18-FEB-01
Enter value for d2: 15-MAR-01
old   3: AND ONE_DAY BETWEEN '&d1' AND '&d2'
new   3: AND ONE_DAY BETWEEN '18-FEB-01' AND '15-MAR-01'

  COUNT(*)
----------
        19

This query counts the number of days between the two dates you enter, excluding Saturdays and the Sundays. The TO_CHAR function with the 'DAY' format converts each candidate date (from the DATES_OF_YEAR table) to a day of the week, and the NOT IN operator excludes the days that are Saturdays and Sundays. Notice the use of the RTRIM function with TO_CHAR. We used RTRIM because TO_CHAR produces the DAY as a nine-character string, with blank padded to the right. RTRIM eliminates those extra spaces.

There could be holidays between two dates, and the queries shown in this section don't deal with that possibility. To take holidays into account, you need another table (perhaps named HOLIDAYS) that lists all the holidays in the year. You can then modify the previous query to exclude days listed in the HOLIDAYS table.

6.3.9 Summarizing by a DATE/Time Element

Let's say you want to print a quarterly summary of all your orders. You want to print the total number of orders and total sale price for each quarter. The order table is as follows:

SELECT * FROM CUST_ORDER;

 ORDER_NBR CUST SALES PRICE ORDER_DT  EXPECTED_ CANCELLED SHIP STATUS
---------- ----- ----------- --------- --------- --------- --- -----------
      1001     1      3   99 22-MAY-01 23-MAY-01               DELIVERED
      1000     1      4      19-JAN-01 24-JAN-01 21-JAN-01     CANCELLED
      1002     5      6      12-JUL-01 25-JUL-01 14-JUL-01     CANCELLED
      1003     4      5   56 16-NOV-01 26-NOV-01               DELIVERED
      1004     4      4   34 18-JAN-01 27-JAN-01               PENDING
      1005     8      3   99 22-MAY-01 24-MAY-01               DELIVERED
      1006     1      8      22-JUL-01 28-JUL-01 24-JUL-01     CANCELLED
      1007     5      1   25 20-NOV-01 22-NOV-01               PENDING
      1008     5      1   25 21-JAN-01 23-JAN-01               PENDING
      1009     1      5   56 18-MAY-01 22-MAY-01               DELIVERED
      1012     1      2   99 22-JAN-01 23-JAN-01               DELIVERED
      1011     1      3      19-NOV-01 24-NOV-01 21-NOV-01     CANCELLED
      1015     5      3      12-NOV-01 25-NOV-01 14-NOV-01     CANCELLED
      1017     4      1   56 16-MAY-01 26-MAY-01               DELIVERED
      1019     4      9   34 18-NOV-01 27-NOV-01               PENDING
      1021     8      7   99 22-MAY-01 24-MAY-01               DELIVERED
      1023     1      1      22-NOV-01 28-NOV-01 24-NOV-01     CANCELLED
      1025     5      3   25 20-MAY-01 22-MAY-01               PENDING
      1027     5      1   25 21-NOV-01 23-NOV-01               PENDING
      1029     1      5   56 18-MAY-01 22-MAY-01               DELIVERED

20 rows selected.

There is no quarter column in the CUST_ORDER table. You have to manipulate the ORDER_DT column to generate the quarter. The following SQL statement does this using the TO_CHAR function along with a date format. In addition to being used in the SELECT list, notice that TO_CHAR is used in the GROUP BY clause to group the results by quarter.

SELECT 'Q'||TO_CHAR(ORDER_DT, 'Q') QUARTER, COUNT(*), SUM(NVL(SALE_PRICE,0))
FROM CUST_ORDER
GROUP BY 'Q'||TO_CHAR(ORDER_DT, 'Q');

QU   COUNT(*) SUM(NVL(SALE_PRICE,0))
-- ---------- ----------------------
Q1          4                    158
Q2          7                    490
Q3          2                      0
Q4          7                    140

Using this same technique, you can summarize data by week, month, year, hour, minute, or any other date/time unit that you choose.

    Team LiB   Previous Section   Next Section