In Oracle reporting it's not uncommon for the end-users to requests
comparison of two point-in-time measures (i.e. compare June 1, 2015 with June 1 2015), but it gets more complex when you need to compare values for two ranges
of dates (e.g. compare June 2015 sales with June 2015 sales).
How do we formulate SQL to compare two ranges of date values?
Traditional SQL might formulate this query: "Show me the difference in March
sales between 2004 and 2005, by city, by region".
Comparing date ranges with Oracle SQL
Comparing date ranges can be done in a variety of ways, each with different
performance.
|
There is a significant run-time overhead of
converting and testing each date in a multi-million row table while
maintaining sub-second response time!
This range comparison query would require complex
date transformation at the SQL level, where the transaction requires one
date group to be compared with another date range.
This range-based self-join has notoriously poor
performance. It's easy to write SQL to compare values within two date
ranges, but it's more difficult to write SQL that compares two date
ranges with fast performance. |
The query to compare two date ranges might look something
like this, using a pair of greater-than and less-than operators:
select
sum(sales)
from
transaction_table mar2004,
where
trans_date >= to_date('1-mar-2004')
and
trans_date < to_date('31-mar-2004')
group by trans_date
MINUS
select
sum(sales)
from
transaction_table mar2005,
where
trans_date >= to_date('1-mar-2005')
and
trans_date < to_date('31-mar-2005')
group by trans_date;
Comparing ranges of values within he same table (even with partitioning) can
generate very long-running queries, as in the date range comparison SQL above.
There are several approaches to this range date time constraint issue when
comparing two date ranges.
- Add a redundant day-month-year column
- Add a date lookup table
- Use the new SQL "overlaps" operator
Approach one for comparing date ranges: Add redundant
day-month-year
One approach is to slice-out the day-month-year into
separate columns in the transaction table:
trans_year trans_month
trans_day trans_date . . . .
2005 04 25 2005-04-25 17:25:43
2005 04 25 2005-04-25 17:25:43
2005 04 25 2005-04-25 17:25:43
This redundancy greatly simplifies range comparisons and
performance. Because the redundant date slice-off columns (trans_year,
trans_month, trans_day) become separate dimensions, it simplifies the OLAP
model.
It also improves query performance (psuedocode below-
may not be syntactically accurate):
select
sum(mar2004.sales) - sum(mar2005.sales)
from
transaction_table mar2004,
transaction_table mar2005
where
mar2004.trans_year = 2004
and
mar2004.trans_month = 3
and
mar2004.trans_year = 2005
and
mar2004.trans_month = 3;
Approach two for comparing date ranges: Add a date lookup
table
Michael Armstrong-Smith, author of the bestselling Oracle
Press book
Oracle Discoverer Handbook notes his approach to the issue:
What works best is to create a routine that preloads
calendar dates, along with their corresponding fiscal quarters, months and
years into a single master date table. Then inside Discoverer we would join
the transactional dates to this table.
The secret is to fully de-normalize the master table
such that every date would have corresponding month, quarter and year data.
Thus for any given date, in any table, we could look up the corresponding
month, quarter and year. We should also add start and end dates.
Approach 3 for comparing date ranges
- Use the Oracle "overlaps" and "interval" SQL predicates
David Aldridge shows a demonstration of the Oracle SQL "Overlaps" predicate
and how it can be used to easily compare two ranges of dates:
SQL> select * from dual
2 where (date ?2007-01-01′, date ?2008-01-01′)
3 overlaps (date ?2005-01-01′, date ?2006-01-01′);
David also introduces the "interval" predicate and
demonstrates how it performs with the "overlaps" predicate to compare two date
ranges:
SQL> select * from dual
2 where (date ?2007-01-01′, interval ?5′ year)
3 overlaps (date ?2005-01-01′, interval ?10′ year;