Everything you wanted to know about INTERVAL datatype but was too guru to ask

Interval Shirt

Since I’m into reusing information (long description for “lazy”) I will give you the definition of the INTERVAL datatype that comes in the manuals:

“An interval is defined as the difference between two dates and times. Intervals are expressed in one of two different ways. One is a year-month interval that expresses intervals in terms of years and an integral number of months. The other is a day-time interval that expresses intervals in terms of days, minutes, and seconds. These two types of intervals are distinct and cannot be mixed, because months can have varying numbers of days.”

So aside from beeing a record label, in SQL the datatype INTERVAL is a way of storing time intervals.

But why having two kinds? Probably because one is for storing the time coupples stay married these days, and the other for the time TV shows stay on the air (exception made for ER and SNL ;-) ).

Since every SQL geek only gives value to a blog post if it contains SQL examples, let’s do it.

First we’ll create a table with a INTERVAL YEAR TO MONTH column, meaning the period a certain employee stayed in the company:

SQL> create table emp (empno number, hiredperiod interval year to month, empname varchar2(100));

Table created.

Then let’s try to insert a row, producing the interval range out of date subtraction:

SQL> insert into emp values (1,sysdate-(sysdate-99),'Luigi');
insert into emp values (1,sysdate-(sysdate-99),'Luigi')
 *
ERROR at line 1:
ORA-00932: inconsistent datatypes: expected INTERVAL YEAR TO MONTH got DATE JULIAN

Seems like SYSDATE is no good, let’s try the other function SYSTIMESTAMP:

SQL> insert into emp values (1,SYSTIMESTAMP-(SYSTIMESTAMP-99),'Luigi');
insert into emp values (1,SYSTIMESTAMP-(SYSTIMESTAMP-99),'Luigi')
 *
ERROR at line 1:
ORA-00932: inconsistent datatypes: expected NUMBER got INTERVAL DAY TO SECOND

What just happened here? Where is the guy that wrote the code for message ORA-00932? He’s a bit confused because a subtraction of dates it’s not DATE JULIAN, it’s plain and simple NUMBER! The number of days between the two dates. But the weirdness does not stop here.
The function SYSTIMESTAMP seems even more confused since the subtraction of timestamps is a timestamp and he says exactly the opposite: expecting NUMBER and got INTERVAL. Has he been drinking? Or the guy who wrote the ORA-00932 variations wanted to obscure even more this date-timestamp-interval mess?

Let’s sched some light here. The way you must refer to this datatype is by using the reserved word INTERVAL followed by the range you want to define and in the end the kind of interval: year to month or day to second.

Watch this:

SQL> insert into emp values (1,interval '0-3' year to month,'Luigi');

1 row created.

What just happened? The answer is: we’ve inserted an interval of hiring for employee “Luigi” of 0 years and 3 months…

My shortest job ever!

Let’s select from it:

SQL> select * from emp;

 EMPNO HIREDPERIO EMPNAME
---------- ---------- ------------------------------
 1 +00-03     Luigi

SQL> insert into emp values (2,interval '1-2' year to month,'Rod');

1 row created.

SQL>  insert into emp values (3,interval '0-13' year to month,'Jill');
 insert into emp values (3,interval '0-13' year to month,'Jill')
 *
ERROR at line 1:
ORA-01843: not a valid month

SQL> --oopsie!
SQL>
SQL> insert into emp values (3,interval '0-12' year to month,'Jill');
insert into emp values (3,interval '0-12' year to month,'Jill')
 *
ERROR at line 1:
ORA-01843: not a valid month

SQL> -- makes sense
SQL>
SQL> insert into emp values (3,interval '0-11' year to month,'Jill');

1 row created.

The third time is the right one!

Now let’s check who’s been hired for more than 1 year. Simple query:

SQL> select * from emp
 2  where hiredperiod > interval '1-0' year to month;

 EMPNO HIREDPERIO EMPNAME
---------- ---------- ------------------------------
 2 +01-02     Rod

If one year and two months is our record, the boss must be real nasty!

The same principles of INTERVAL YEAR TO MONTH apply to INTERVAL DAY TO SECOND.

Hope it helped to clear some minds out.

LMC.

How many times have I told not to create objects in the SYS schema?

SQL> show user
USER is “SYS”
SQL> create table xyz (n number);

Table created.

SQL> drop table xyz;

Table dropped.

SQL> flashback table xyz to before drop;
flashback table xyz to before drop
*
ERROR at line 1:
ORA-38305: object not in RECYCLE BIN

SQL> show parameter recycle

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
buffer_pool_recycle                  string
db_recycle_cache_size                big integer 0
recyclebin                           string      on

oooopsie dasy! Looks like though I have recycle bin enabled I can’t have the feature, just because it’s in the SYS schema. Is it?

Let’s take a deeper look:

SQL> create table xyz (n number) tablespace users;

Table created.

SQL> drop table xyz;

Table dropped.

SQL> flashback table xyz to before drop;

Flashback complete.

SQL> desc sys.xyz
 
Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 N                                                  NUMBER

So the problem it’s not the SYS schema, it’s the SYSTEM tablespace. Whatever object lands there, it won’t be able to come back after droping it.

Small stuff that sometimes makes the difference.

LMC.

Conditional function LNNVL: the bullsh*t detector

The Oracle conditional function LNNVL is one of those urban-myths everyone likes. It has been around for years – it’s birth date is actually another mystery, some say Oracle 7.3 others 7.2, but rumor has it that it might had been around since PL/SQL creation.

But if you go around the docs of those times, the LNNVL function it’s not documented until Oracle 10g, when it sneaked into the official books of Oracle Documentation. Another fact to add more fog into the midst is that it’s said that this function was used internally until it became public in Oracle 10g.

But what is so special about this function?

In one sentence: It’s a conditional boolean function that returns TRUE whenever the condition passed on as argument is unkown or FALSE.

In other words is a bullsh*t detector! If the condition is 100% sure thing, absolute true, the function LNNVL throws FALSE, otherwise will ring the bell and give out TRUE.

Beeing a boolean function you might think that you couldn’t use it in SQL but actually you can, but only in the WHERE clause, because it’s the only place that can handles boolean values.

Let’s give out two examples to enlight about the true usefullness of this function:

LNNVL Function in SQL

SQL> select '>> This is bull <<'
 2  from dual
 3  where null = null;

no rows selected

Hey fellows! Looks like null it’s not null! Where is the bull? It’s time to call in the BULLSH*T DETECTOR!

SQL> select '>> This is bull <<'
 2   from dual
 3   where LNNVL(null = null);  

'>>THISISBULL<<'
------------------
>> This is bull <<

Looks like it worked because LNNVL throws TRUE if the condition is FALSE (which is not the case) or UNKOWN. In this case the condition is UNKOWN simply because the NULL status it’s not comparable through the equality operator (“=”), but with the “IS” operator. Let’s proove it:

SQL> select '>> This is not bull this time <<' Result
 2  from dual
 3  where null is null;  

RESULT
--------------------------------
>> This is not bull this time <<

LNNVL Function in PL/SQL

In PL/SQL it’s easier to produce bull! Let’s use the classic example of a non-initialized variable and use it in the condition.

SQL> r
 1  declare
 2   mynum  number;
 3   intovar  number:=0;
 4  begin
 5   select 1 into intovar
 6   from dual
 7   where lnnvl(mynum=0);
 8   -- The Test
 9   if intovar = 1 then
 10      dbms_output.put_line('The Bullsh*t detector done it again! Damn! He is good!');
 11   else
 12      dbms_output.put_line('Looks fine but its not');
 13   end if;
 14* end;

The Bullsh*t detector done it again! Damn! He is good!

PL/SQL procedure successfully completed.

My deep thought on this: If you’re to use this function it’s because you’re expecting bull. Try to work out not getting bull at all!

LMC.

Oracle 11gR2 feature of the Day!

I did this on a CentOS 5 box with Oracle 11.2.0.1 and it was to show the latest new hint that you can use when transporting lines from one table to another, and there are colisions due to unique/primary keys.

The example is hilarious!

SQL> create table exemplo (
 2  col1 number primary key,
 3  col2 varchar2(100));

Table created.

SQL> insert into exemplo values (1,'Primeira Linha');
1 row created.

SQL> insert into exemplo values (2,'Segunda Linha');
1 row created.

SQL> commit;
Commit complete.

SQL> — NOW HERE IT COMES THE 11gR2 TWEAK
SQL> — Create a new table based on EXEMPLO

SQL> create table exemplo2 as select * from exemplo;
Table created.

SQL> — Insert an extra line in this new table

SQL> insert into exemplo2 values (3,'Terceira Linha');
1 row created.

SQL> commit;
Commit complete.

SQL> — And NOW if we insert all the lines from the 2nd table into the first

SQL> — The line 1 and 2 will colide

SQL> insert into exemplo select * from exemplo2;
insert into exemplo select * from exemplo2
*
ERROR at line 1:
ORA-00001: unique constraint (LMC.SYS_C0014102) violated

SQL> — Buahhhhh But I wanted Oracle to be smart without me having the need to learn PL/SQL…
SQL> — What? It is possible now?
SQL> — How?
SQL> — Like this:

SQL> insert --+IGNORE_ROW_ON_DUPKEY_INDEX
 2  into exemplo select * from exemplo2;
insert --+IGNORE_ROW_ON_DUPKEY_INDEX
*
ERROR at line 1:
ORA-00001: unique constraint (LMC.SYS_C0014102) violated

SQL> — What did I did wrong?
SQL> — See …

SQL> insert --+IGNORE_ROW_ON_DUPKEY_INDEX(exemplo(col1))
 2  into exemplo select * from exemplo2;
1 row created.

SQL> commit;
Commit complete.

SQL> select * from exemplo;

 COL1 COL2
---------- ------------------------------
 1 Primeira Linha
 2 Segunda Linha
 3 Terceira Linha

SQL> — The 3rd line just went in and the colisions ignored!!
SQL> — Damn I’m good!

Turn off CASE Sensitiveness in passwords in Oracle11g

In case you’re wandering looking for a way to turn off that sexy feature of Oracle 11g that puts your passwords sensitive to case, it’s very simple, because it’s a spfile parameter:

SQL> show parameter case
NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
sec_case_sensitive_logon             boolean     TRUE

So if you want to turn off the case sensitiveness just type:

SQL> alter system set sec_case_sensitive_logon = false scope=both;

Attention: If you create a mixed case password during the “off” period, you must remember that when you decide to turn it back on.

A new paradigm: Oracle 11g’s feature for context objects invalidation (COI)

Attention: revolution has come to Oracle RDBMS!

How many times you’ve had modified a table and just because of that simple change, all objects (views, subprograms, etc.) dependent on that table get invalidated?
It’s probably the best known Oracle “feature” for developers!

Well guys, this all is about to change taking a 180 degrees spin! Only the changes that afect dependent objects will invalidate them. This will decrease 90% of total invalidations.

Let’s use a simple, very simple scenario:

1 – Table with one column
2- View based on this table but created with “SELECT *”
3- We add a new column to the table
4- The view not only doesn’t invalidates, but discards the new column from the “SELECT *”

Wow!!! This is freaky! It is a new paradigm for developers.

Here’s the code for who need to see it to understand it:

SQL*Plus: Release 11.1.0.7.0 - Production on Thu Mar 5 00:37:25 2009

Copyright (c) 1982, 2008, Oracle.  All rights reserved.
SQL> conn hr/hr
Connected.
SQL> create table inva (n number);

Table created.

SQL> create or replace view invaview as select * from inva;

View created.

SQL> select count(*) from user_objects where status='INVALID';

  COUNT(*)
----------
         0
SQL> alter table inva add (n2 number);

Table altered.

SQL> select count(*) from user_objects where status='INVALID';

  COUNT(*)
----------
         0

SQL> insert into inva values (1,1);

1 row created.

SQL> select * from  invaview;

         N
----------
         1

Oracle 11g new System Partitioning feature

Oracle 11g is an exciting new release in terms of new features. There are tons of new features when it comes to partitioning. From virtual column partitioning, to new combinations of subpartitioning.

One of those new partition features is system partitioning that enables you to create a table that in practice it’s dynamically partitioned. You just have to select the names and number of partitions and then at the DML stage you decide.

Let’s take it slowly. First create the table with two partitions, each in a different tablespace. Here we take two default tablespaces, but you should use “real” permanent tablespaces:

SQL> create table SMALL_PARTS_ISOLATED
  2  (ncol number, xar varchar2(80))
  3  PARTITION BY SYSTEM
  4  (partition P1 tablespace users,
  5   partition P2 tablespace example);

Table created.

The key is that DML is done in a different fashion, because you have to decide in which partition you’ll put your rows into. Let’s imagine you don’t know this is a system partitioned table and you’ll do a “normal” insert. In this case you get the following error:

SQL> insert into SMALL_PARTS_ISOLATED values (1,'Nomeansno');
insert into SMALL_PARTS_ISOLATED values (1,'Nomeansno')
            *
ERROR at line 1:
ORA-14701: partition-extended name or bind variable must be used for DMLs on
tables partitioned by the System method

Wow! This error message wasn’t produced at the regular shop where Oracle uses to make them! Why? Because it’s crystal clear.
And now the right way to do DML in a system partitioned table by using the PARTITION keyword:

SQL> insert into SMALL_PARTS_ISOLATED PARTITION (p2) values (1,'YesMan');

1 row created.

SQL> insert into SMALL_PARTS_ISOLATED PARTITION (p1) values (2,'TwiceIsCheaper');

1 row created.

When you select from it this is a regular table like any other:

SQL> select * from small_parts_isolated;

      NCOL XAR
---------- --------------------------------------------------------------------------------
         2 TwiceIsCheaper
         1 YesMan

But how the heck to we know the name of the partition in order to insert it into the right place? This last table had partition P1 and P2, so it should be right to assume that it might have a partition called “P3″:

SQL> insert into SMALL_PARTS_ISOLATED PARTITION (p3) values (30,'NonExistentPartition');
insert into SMALL_PARTS_ISOLATED PARTITION (p3) values (30,'NonExistentPartition')
                                            *
ERROR at line 1:
ORA-02149: Specified partition does not exist

Oopsy dasy… Looks like our assumption was wrong…

The way I see this, Oracle Corp. should have had the idea to incorporate partition’s names at the DESCRIBE command, but this is asking too much from those folks. So describing the table won’t help a thing:

SQL> desc small_parts_isolated
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 NCOL                                               NUMBER
 XAR                                                VARCHAR2(80)

Looks like we’re going to have a trip to the dreadfull Oracle data dictionary. For this we used a query from 24th June 1997:

SQL> select partition_name
  2  from user_tab_partitions
  3  where table_name = 'SMALL_PARTS_ISOLATED';

PARTITION_NAME
------------------------------
P1
P2

Two in one: Creating and Granting privileges/roles in one shot

Most of the times in training, developing, or testing DBs you need to create users with standard roles like CONNECT, RESOURCE or DBA. To do so you need two different statements: one for creating the user and other to grant the roles.

Well if you have 10g or higher, and you have setup your default temporary and permanent tablespaces you can create and grant your roles in only one statement just by doing so:

GRANT CONNECT, RESOURCE, DBA to myuser IDENTIFIED BY mypassword;

This will grant the 3 roles to an account that will also be created in the process. Of course you have to have

Granting ALL Privileges in One Statement

Imagine you are USER01 and you wish to grant all the privileges on your XYZ table to USER02. What’s the statement?

It should be something like this:

GRANT SELECT, INSERT, UPDATE, DELETE ON XYZ TO USER02;

But if you want to make it all in one go you can put it all in one word: “ALL”. Check it out:

GRANT ALL ON XYZ TO USER02;

If instead of USER02 you would want to give it everyone else at the database, make it public:

GRANT ALL ON XYZ TO PUBLIC;

GROUP BY clause ordering after Oracle 10g

Up until Oracle 10g the GROUP BY clause in a SELECT statement was all you needed in order to get your output orderered.

But after Oracle 10g a new behaviour took place! GROUP BY clause no longer gave you an ordered set of groups.

So: Oracle 9i and backwards GROUP BY was enough; after Oracle 10g GROUP BY has to be used together with ORDER BY.

Bottomline: Always use ORDER BY with GROUP BY even if you’re in 9i databases, because the extra sort you introduce in 9i may well pay off the smashing bug of having your application scramble up your data after an upgrade from 9i to 10g.

Regular Expressions

Introduced in Oracle10g this is finally the POSIX standard applied into Oracle databases.

Just a simple example:

Replace an unknow number of space characters by a single space in a string:

select regexp_replace(<my_string>,’( ){2,}’,’ ‘) from … ;

“ADB                 DSDA        DA                    DADDD              KFFF”

turns into:

“ADB DSDA DA DADDD KFFF”

The two parenthesis with a space in the middle represent the space character.. The number two followed by a comma within braces means that the space character has to occur 2 or more times.
The REGEXP_REPLACE is in charge of replacing any string that matches the criteria of 2 or more consecutive space characters with just one space (the last argument of the function).

Cool isn’t it?

LMC.