Category Archives: sql

random statements

I read the ultimate excuse database and checked if I can use model to do generate random statements:


with t as (
  select '%E and %E are travelling to %L.' fmt 
  from dual union all
  select 'About %N employees live in %L.' 
  from dual)
select str from t
model
reference
  dept on (
    select loc,count(*) over () c,rownum r 
    from dept)
  dimension by (r) measures (loc,c)
reference
  emp on (
    select ename,count(*) over () c, rownum r 
    from emp)
  dimension by (r) measures (ename, c) 
partition by (fmt)
dimension by (1 x) 
measures (cast(fmt as varchar2(4000)) str)
rules 
  iterate (1000) 
  until str[1] not like '%/%%' escape '/'
(str[1]=substr(str[1],1,
  decode(instr(str[1],'%'),0,4000,
    instr(str[1],'%')-1))||
  decode(substr(str[1],nullif(
    instr(str[1],'%'),0)+1,1),
    'N',to_char(trunc(
      dbms_random.value(0,1+emp.c[1]))),
    'E',initcap(emp.ename[trunc(
      dbms_random.value(1,1+emp.c[1]))]),
    'L',initcap(dept.loc[trunc(
      dbms_random.value(1,1+dept.c[1]))]))||
    substr(str[1],nullif(
      instr(str[1],'%'),0)+2))
/
STR
----------------------------------------
James and Ward are travelling to Dallas.
About 1 employees live in Chicago.
/
STR
--------------------------------------------
King and Blake are travelling to New York.
About 3 employees live in Dallas.

What is ROWNUM=1 ?

Is rownum=1 the first row returned? well, it depends :


select rownum, ename 
from emp 
order by ename;
    ROWNUM ENAME
---------- ----------
        11 ADAMS
         2 ALLEN
         6 BLAKE
         7 CLARK
        13 FORD
        12 JAMES
         4 JONES
         9 KING
         5 MARTIN
        14 MILLER
         8 SCOTT
         1 SMITH
        10 TURNER
         3 WARD

The ROWNUM is evaluated before the order by

select rownum 
from emp 
group by rownum 
having rownum between 3 and 6;
    ROWNUM
----------
         6
         5
         3
         4

the rownum is selected before the GROUP BY and before the HAVING.

select 
   rownum, 
   sal, 
   median(sal) over () 
from emp;
    ROWNUM        SAL MEDIAN(SAL)OVER()
---------- ---------- -----------------
         1        800              1550
        12        950              1550
        11       1100              1550
         3       1250              1550
         5       1250              1550
        14       1300              1550
        10       1500              1550
         2       1600              1550
         7       2450              1550
         6       2850              1550
         4       2975              1550
         8       3000              1550
        13       3000              1550
         9       5000              1550

The optimiser may chose to resort the result, here as a WINDOW SORT operation for the MEDIAN analytic function

OR aggregate

you want to BIT_OR multiple rows. For example you have a table with 3 rows that you want to aggregate with BIT_OR


1010 (10)
1100 (12)
0110 (6)
=========
1110 (14)

Let’s try


with t as (
  select 10 n from dual union all 
  select 12 from dual union all 
  select 6 from dual) 
select 
  utl_raw.cast_to_binary_integer(
    sys.mvaggrawbitor(
      utl_raw.cast_from_binary_integer(
        n
      )
    )
  ) N 
from t;
  N
---
 14

It is that easy !

disclaimer: mvaggrawbitor is not documented

NOT IN and NULL

I posted about Unexpected results in June 2005. Here are more results from NOT IN and NULL

select * from dual
WHERE (1) NOT IN (SELECT NULL FROM DUAL);
no rows selected

select * from dual
WHERE (1,1) NOT IN (SELECT NULL,1 FROM DUAL);
no rows selected

However, and this surprised me,
select * from dual
WHERE (1,1) NOT IN (SELECT NULL,2 FROM DUAL);

D
-
X

:roll:

Probably the expression
where (a,b) not in (select c,d from t)
is translated into
where (a!=c or b!=d) — first row of t
and (a!=c or b!=d) — second row of t
— and …

MIN(DISTINCT X)

What is doing the MIN(DISTINCT X) call? Basically, every distinct value of X is passed to the MIN function. Well, it is probably of very little interest as the MIN function is very fast and processing less rows than MIN(X) should not boost the performance because of the overhead of sorting distinct values.

However, if you write your own aggregate, distinct may be interesting!

create type myudag_type as object
(
  myudag INTEGER,
  static function ODCIAggregateInitialize(
    sctx IN OUT myudag_type)
    return number,
  member function ODCIAggregateIterate(
    self IN OUT myudag_type,
    value IN INTEGER)
    return number,
  member function ODCIAggregateTerminate(
    self IN myudag_type,
    returnValue OUT INTEGER,
    flags IN number)
    return number,
  member function ODCIAggregateMerge(
    self IN OUT myudag_type,
    ctx2 IN myudag_type)
    return number
);
/

create or replace type body myudag_type is
static function ODCIAggregateInitialize(
  sctx IN OUT myudag_type)
  return number is
begin
  sctx := myudag_type(0);
  return ODCIConst.Success;
end;
member function ODCIAggregateIterate(
  self IN OUT myudag_type,
  value IN INTEGER)
  return number is
begin
  -- doing nothing will cost you a lot !!!
  for i in 1..1000000 loop null; end loop; 
  return ODCIConst.Success;
end;
member function ODCIAggregateTerminate(
  self IN myudag_type,
  returnValue OUT INTEGER,
  flags IN number) return number is
begin
  returnValue := self.myudag;
  return ODCIConst.Success;
end;
member function ODCIAggregateMerge(
  self IN OUT myudag_type,
  ctx2 IN myudag_type)
  return number is
begin
  return ODCIConst.Success;
end;
end;
/

CREATE FUNCTION myudag (
  input INTEGER)
  RETURN INTEGER
  AGGREGATE USING myudag_type;
/

SQL> select myudag(deptno) from emp;

MYUDAG(DEPTNO)
--------------
             0

Elapsed: 00:00:00.57
SQL> select myudag(distinct deptno) from emp;

MYUDAG(DISTINCTDEPTNO)
----------------------
                     0

Elapsed: 00:00:00.13

What is bigger than infinity?

Nan


select
  BINARY_DOUBLE_INFINITY INF, 
  BINARY_DOUBLE_NAN NAN,
  greatest(BINARY_DOUBLE_INFINITY, BINARY_DOUBLE_NAN) GRE
from t;

INF NAN GRE
--- --- ---
Inf Nan Nan

Nan means not a number. It could be square root of -1, log of -1, 0/0, acos(1000), Inf-Inf, etc…


select
  SQRT(-1d),
  LN(-1d),
  0/0d,
  acos(1000d),
  BINARY_DOUBLE_INFINITY-BINARY_DOUBLE_INFINITY
from t;
SQR LN- 00D ACO BIN
--- --- --- --- ---
Nan Nan Nan Nan Nan

According to the doc, it is greater than any value, inclusive positive infinity.

To check if a value is nan, it could be compared to BINARY_DOUBLE_NAN.
where :z = BINARY_DOUBLE_NAN
There is a function NANVL(:z, :y) which evaluates to :y when :z is equal Nan. if :z is not equal to Nan and :y is not null, then it evaluates to :z. NANVL evaluates to NULL when :z or :y is null.


select NANVL(1,null) from dual;
NANVL
------
[null]

Display a blob

I have a table with a blob


create table t(b blob);
insert into t values ('585858');

In 11g sql*plus, I can display raw data

select b from t;
B
------
585858

Ok, but if I want to display XXX (the character content)


select utl_raw.cast_to_varchar2(b) from t;
UTL
--- 
XXX

However, in sql, a raw cannot be more than 2000 bytes long.

Another way to print your blob content is to use DBMS_LOB.CONVERTTOCLOB


var c clob
set autoprint on
declare
  b blob;
  dest_offset integer := 1 ;
  src_offset  integer := 1 ;
  lang_context integer:= 1 ;
  warning integer;
begin
  select b into b from t for update;
  dbms_lob.createtemporary(:c,true);
  dbms_lob.converttoclob(
    :c, b, DBMS_LOB.LOBMAXSIZE,
    dest_offset, src_offset,
    1, lang_context, warning);
end;
/
C
---
XXX

On associativity, transitivity and reflexivity

Addition is supposed to be associative.
a+(b+c)=(a+b)+c

This may be wrong in Oracle when dealing with months and days


with t as (select
 interval '1' month a,
 date '2007-09-26' b,
 interval '7' day c
from dual)
select a+(b+c),(a+b)+c 
from t;

A+(B+C)     (A+B)+C
----------- -----------
03-NOV-2007 02-NOV-2007

The equality is supposed to be transitive
if (a=b and b=c) then (a=c)
However, in Oracle the comparison operator equal may imply some transformation


with t as (select '.0' a, 0 b, '0.' c from dual) 
select 
  case when a=b then 'YES' else 'NO ' end "A=B",
  case when b=c then 'YES' else 'NO ' end "B=C",
  case when a=c then 'YES' else 'NO ' end "A=C"
from t;
A=B B=C A=C
--- --- ---
YES YES NO

The equality operator is also supposed to be reflexive
a=a

This is unfortunately wrong with null

with t as (select null a from dual) 
select case when a=a then 'YES' else 'NO ' end "A=A" 
from t;
A=A
---
NO

:mrgreen:

Please RTFOM !

Today I opened two SR about flashback archive in 11g. In one of them, I complained that user SCOTT was not allowed to create a flashback archive. In the doc that I downloaded a few weeks ago I read :
Prerequisites
You must have the FLASHBACK ARCHIVE ADMINISTER system privilege to create a flashback data archive. This privilege can be granted only by a user with DBA privileges. In addition, you must have the CREATE TABLESPACE system privilege to create a flashback data archive, as well as sufficient quota on the tablespace in which the historical information will reside.

So as I was getting an ORA-55611, I opened a SR. The support engineer pointed me to the online documentation where I was astonished to read :
Prerequisites
You must have the FLASHBACK ARCHIVE ADMINISTER system privilege to create a flashback data archive. In addition, you must have the CREATE TABLESPACE system privilege to create a flashback data archive, as well as sufficient quota on the tablespace in which the historical information will reside. To designate a flashback data archive as the system default flashback data archive, you must be logged in as SYSDBA.

Well, Read The Fine Online Manual !!!

The second tar is related to long retention (about the age of the earth)


SQL> alter flashback archive fba01 
  modify retention 4106694757 year;

Flashback archive altered.

SQL> select retention_in_days
  from DBA_FLASHBACK_ARCHIVE;
RETENTION_IN_DAYS
-----------------
                1

:mrgreen:

isNumber in sql

I tried this in 11g
TABLE T

X
123
-1.2e-3
abc


select x, 
  to_number(
    xmlquery('number($X)' 
      passing x as x 
      returning content)) n 
from t;
X                N
------- ----------
123            123
-1.2e-3     -.0012
abc

it is quite a common task to extract numbers from varchar2 and to dig out poor quality data.

select x, to_number(x) from t;
ERROR:
ORA-01722: invalid number

A well-known PL/SQL approach would be to use exception. Ex:


create or replace function f(x varchar2) 
return number is 
begin return to_number(x); 
exception when others then return null; 
end;
/
select x, f(x) n from t;
X                N
------- ----------
123            123
-1.2e-3     -.0012
abc

another approach in plain sql could involve CASE and REGEXP

select x,
case when
regexp_like(x,
‘^-?(&#92+&#92.?|&#92d*&#92.&#92d+)([eE][+-]&#92d+)?$’)
then to_number(x)
end n
from t;
X N
——- ———-
123 123
-1.2e-3 -.0012
abc

Oracle Database 11g: The Top Features for DBAs and Developers

I am always delighted to read the top features by Arup Nanda.

He started his 11g series : Oracle Database 11g: The Top Features for DBAs and Developers

There are many partitioning enhancements. The most exciting feature for me is the INTERVAL partitioning. A huge cause of downtime and waste of storage is the range partitioning. In 10g and before, a partitioning by dates required that the partition are defined before values are inserted.

Now we have automatic partition creation :-D


create table t(d date) 
partition by range(d) 
interval(interval '1' month) 
(partition p1 values less than (date '0001-01-01'));

One partition must be created manually, here the partition will contain all dates from 1-JAN-4712BC to 31-DEC-0000 (which is not a legal date by the way)

There is also new syntax to query the partition

SQL> insert into t values (date '2000-01-10');

1 row created.

SQL> insert into t values (date '2000-01-20');

1 row created.

SQL> insert into t values (date '2000-03-30');

1 row created.

SQL> select * from t partition for (date '2000-01-01');
D
-------------------
10.01.2000 00:00:00
20.01.2000 00:00:00

Note the syntax can be used in any form of partitioning. Here in a list-list composite


SQL> create table t(x number, y number) 
  partition by list(x) 
  subpartition by list(y) 
    subpartition template (
      subpartition sp1 values(1),
      subpartition sp2 values(2)) 
  (partition values(1), partition values(2));

Table created.

SQL> insert into t values(1,2); 
1 row created.

SQL> select * from t subpartition for (1,2);
         X          Y
---------- ----------
         1          2

Ok, one more feature Arup introduced is the REF partitioning, where you have a schema with both the parent and child tables partitioned, and you want to partition on a column of the parent table that is not in the child table (as you had bitmap join indexes, you have now ref partitions). Check it on his site.

Finally Arup explained SYSTEM partitioning, which is not inconceivable, but will hardly be used.

Imagine you have a table containing just one single LOB column, and a LOB cannot be used as a partition key.

SQL> create table t(x clob)
  partition by system (
    partition p1, 
    partition p2, 
    partition p3, 
    partition p4);

Table created.

So far this seems fine. So what the problem? You cannot insert in that table!

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

so you must define in which partition you want to add data. For example round robin. Or random. Whatever.


SQL> insert into t partition (P1)  values ('x');

1 row created.

SQL> insert into t partition (P2)  values ('y');

1 row created.

If you want to use bind variable, you can use dataobj_to_partition


SQL> select object_id 
  from user_objects 
  where object_name='T' 
    and subobject_name is not null;
 OBJECT_ID
----------
     55852
     55853
     55854
     55855

SQL> var partition_id number
SQL> exec :partition_id := 55852

PL/SQL procedure successfully completed.

SQL> insert into t 
  partition (dataobj_to_partition("T",:partition_id))
  values ('x');

1 row created.
SQL> exec :partition_id := 55853

PL/SQL procedure successfully completed.

SQL> insert into t 
  partition (dataobj_to_partition("T",:partition_id))
  values ('x');

1 row created.

Actually, SYSTEM partitioning is misleading, YOU are responsible for choosing the partition in which you want to insert, not the system :mrgreen:

flashback archive table

One of the problem with flashback queries in 10g and before is that you never know if it will works, especially you cannot expect to have flashback queries working for very old tables.

Let’s imagine you want to export your CUSTOMER as of 30/6/2007. No chance in 10g…

Well, with 11g, you can create a flashback archive, and it will save all change until end of retention (many years if you want).

Here it is :

SQL> connect / as sysdba
Connected.
SQL> create tablespace s;

Tablespace created.

SQL> create flashback archive default fba01 tablespace s 
  retention 1 month;

Flashback archive created.

SQL> connect scott/tiger
Connected.
SQL> create table t(x number) flashback archive;

Table created.

SQL> host sleep 10

SQL> insert into t(x) values (1);

1 row created.

SQL> commit;

Commit complete.

SQL> SELECT dbms_flashback.get_system_change_number FROM dual;
GET_SYSTEM_CHANGE_NUMBER
------------------------
                  337754

SQL> update t set x=2;

1 row updated.

SQL> commit;

Commit complete.

SQL> select * from t as of scn 337754;
         X
----------
         1

SQL> alter table t no flashback archive;

Table altered.

SQL> drop table t;

Table dropped.

SQL> select FLASHBACK_ARCHIVE_NAME, RETENTION_IN_DAYS,
  STATUS from DBA_FLASHBACK_ARCHIVE;
FLASHBACK_ARCHIVE_NAME RETENTION_IN_DAYS STATUS
---------------------- ----------------- -------
FBA01                                 30 DEFAULT

SQL> connect / as sysdba
Connected.
SQL> drop flashback archive fba01;

Flashback archive dropped.

SQL> drop tablespace s;

Tablespace dropped.

note that a month is 30 days. If you try to create a flashback archive in a non-empty tablespace you may get
ORA-55603: Invalid Flashback Archive command
which is not a very helpful message

select*from”EMP”where’SCOTT’=”ENAME”…

What is wrong with this query?


select*from"EMP"where'SCOTT'="ENAME"and"DEPTNO"=20;
     EMPNO ENAME      JOB              MGR HIREDATE     
---------- ---------- --------- ---------- ---------
      7788 SCOTT      ANALYST         7566 13-JUL-87

It is a zero-space query 8-)

You could write it as


select
    *
from
    "EMP"
where
    'SCOTT'="ENAME"
    and
    "DEPTNO"=20;

personnaly, I would write it as


select *
from emp
where ename='SCOTT'
  and deptno=20;

Formatting is very important, it makes your code nice to read and indentation make the blocks visualable.

Auto-formatting is also fine, but I like to decide myself if the line is too long, or if I want to have FROM and EMP on the same line.

Have a look at the free online SQL Formatter SQLinForm

positive infinity

I have read a long long time ago the following note on positive infinity http://www.ixora.com.au/notes/infinity.htm

Today I finally succeeded in inserting positive infinity in a number field


create table t as select 
  STATS_F_TEST(cust_gender, 1, 'STATISTIC','F') f
from (
  select 'M' cust_gender from dual union all 
  select 'M' from dual union all 
  select 'F' from dual union all 
  select 'F' from dual)
;

I am so happy :-D

Let’s try a few queries


SQL> desc t
 Name              Null?    Type
 ----------------- -------- ------
 F                          NUMBER

SQL> select f from t;

         F
----------
         ~

SQL> select f/2 from t;
select f/2 from t
        *
ERROR at line 1:
ORA-01426: numeric overflow

SQL> select -f from t;

        -F
----------
        -~

SQL> select cast(f as binary_double) from t;

CAST(FASBINARY_DOUBLE)
----------------------
                   Inf

SQL> select * from t
  2  where cast(f as binary_double) = binary_double_infinity;

         F
----------
         ~

Now expect a lot of bugs with your oracle clients 8-)

Toad 9 for example returns


SQL>  select f from t
select f from t
              *
Error at line 1
OCI-22065: number to text translation for the given 
format causes overflow

on delete cascade

The use of a referential integrity constraint is to enforce that each child record has a parent.


SQL> CREATE TABLE DEPT
  2    (DEPTNO NUMBER PRIMARY KEY,
  3    DNAME VARCHAR2(10)) ;

Table created.

SQL> CREATE TABLE EMP
  2    (EMPNO NUMBER PRIMARY KEY,
  3    ENAME VARCHAR2(10),
  4    DEPTNO NUMBER
  5      CONSTRAINT EMP_DEPT_FK
  6      REFERENCES DEPT(deptno));

Table created.

SQL> INSERT INTO DEPT(deptno,dname) VALUES
  2    (50,'CREDIT');

1 row created.

SQL> INSERT INTO EMP(EMPNO,ENAME,DEPTNO) VALUES
  2    (9999,'JOEL',50);

1 row created.

SQL> COMMIT;

Commit complete.

SQL> DELETE DEPT WHERE DEPTNO=50;
DELETE DEPT WHERE DEPTNO=50
*
ERROR at line 1:
ORA-02292: integrity constraint (SCOTT.EMP_DEPT_FK) violated
 - child record found

I cannot delete this department, because the department is not empty. Fortunately :!:

Let’s redefine the constraint with a DELETE CASCADE clause


SQL> alter table emp drop constraint emp_dept_fk;

Table altered.

SQL> alter table emp add constraint emp_dept_fk
  2  foreign key (deptno) references dept(deptno)
  3  on delete cascade;

Table altered.

SQL> DELETE DEPT WHERE DEPTNO=50;

1 row deleted.

SQL> select * from emp where ename='JOEL';

no rows selected

Note the line 1 row deleted. This is evil :evil: I have deleted a department, and there were employees in it, but I got no error, no warning and no feedback about the DELETE EMP.

Instead of improving the data quality, the ON DELETE CASCADE foreign key constraint here silently deleted rows. Joel will once phone you and ask why he has been deleted…

There is one more clause of the foreign key which sets the refering column to null


SQL> INSERT INTO DEPT(deptno,dname) VALUES
  2    (60,'RESTAURANT');

1 row created.

SQL> INSERT INTO EMP(EMPNO,ENAME,DEPTNO) VALUES
  2    (9998,'MARC',60);

1 row created.

SQL> alter table emp drop constraint emp_dept_fk;

Table altered.

SQL> alter table emp add constraint emp_dept_fk
  2  foreign key (deptno) references dept(deptno)
  3  on delete set null;

Table altered.

SQL> DELETE DEPT WHERE DEPTNO=60;

1 row deleted.

SQL> select * from emp where ename='MARC';

     EMPNO ENAME          DEPTNO
---------- ---------- ----------
      9998 MARC

Marc has no department, because his department has been deleted. Again, no feedback, no warning, no error.

Instead of improving the data quality, the ON DELETE SET NULL foreign key constraint here silently updated rows columns to NULL. Marc will wonder why he get no invitation to the department meetings.

What could be worse???

Triggers of course! Triggers not only removes rows in child tables, but triggers can also do very weird things, like updating another table, changing the values you are trying to insert, outputing a message, etc.

Also triggers are programmed by your colleagues, so they must be full of bugs :twisted:

You cannot imagine the number of problems that are caused by triggers and revealed only when tracing.

I once had something like

SQL> CREATE INDEX I ON T(X);

P07431B processed

Well, after enabling the trace, I discover one trigger fired on any ddl and the trigger was doing nothing else than this distracting dbms_output for “debugging” purpose. Guess google and metalink for the message did not help much…

errorlogging in 11g

This is a very neat feature in 11g.

I have a script called foo.sql


create table t(x number primary key);
insert into t(x) values (1);
insert into t(x) values (2);
insert into t(x) values (2);
insert into t(x) values (3);
commit;

It is eyes-popping that this script will return an error, but which one?

Let’s errorlog !


SQL>set errorl on
SQL> @foo

Table created.

1 row created.

1 row created.

insert into t(x) values (2)
*
ERROR at line 1:
ORA-00001: unique constraint (SCOTT.SYS_C004200) violated

1 row created.

Commit complete.

SQL> set errorl off
SQL> select timestamp,script,statement,message from sperrorlog;
TIMESTAMP  SCRIPT  STATEMENT
---------- ------- ---------------------------
MESSAGE
---------------------------------------------------------
11:18:56   foo.sql insert into t(x) values (2)
ORA-00001: unique constraint (SCOTT.SYS_C004200) violated

There is also a huge bonus :-D

You can use it with 9i and 10g databases too! Only the client must be 11g. To download the 11g client only, go to Oracle E-Delivery Website

Even small, this is one of my favorite new features!

keep dense_rank with multiple column

create table t(
  deptno number, 
  firstname varchar2(10), 
  lastname varchar2(10), 
  hiredate date);

insert into t values (
  10,'Jo','Smith',date '2001-01-01');

insert into t values (
  10,'Jack','River',date '2002-02-02');

to get the latest hiredate per department

select deptno, 
  max(hiredate) hiredate
from t 
group by deptno;

    DEPTNO HIREDATE 
---------- ---------
        10 02-FEB-02

if you want to get the name of the employee at that date, you could by mistake believe the following works


select deptno, 
  max(firstname) keep (dense_rank last 
    order by hiredate) firstname,
  max(lastname) keep (dense_rank last 
    order by hiredate) lastname,
  max(hiredate) hiredate
from t
group by deptno;

    DEPTNO FIRSTNAME  LASTNAME   HIREDATE 
---------- ---------- ---------- ---------
        10 Jack       River      02-FEB-02

This will produce wrong result if hiredate is not unique


insert into t values (10,'Bob','Zhong', date '2002-02-02');
select deptno, 
  max(firstname) keep (dense_rank last 
    order by hiredate) firstname,
  max(lastname) keep (dense_rank last 
    order by hiredate) lastname,
  max(hiredate) hiredate
from t
group by deptno;

    DEPTNO FIRSTNAME  LASTNAME   HIREDATE 
---------- ---------- ---------- ---------
        10 Jack       Zhong      02-FEB-02

of course there is Jack Zhong.

To get a consistent record, it is possible to add all the columns in the order by


select deptno, 
  max(firstname) keep (dense_rank last 
    order by hiredate,firstname,lastname) firstname,
  max(lastname) keep (dense_rank last 
    order by hiredate,firstname,lastname) lastname,
  max(hiredate) hiredate
from t
group by deptno;

    DEPTNO FIRSTNAME  LASTNAME   HIREDATE 
---------- ---------- ---------- ---------
        10 Jack       River      02-FEB-02

get Nth column of a table

I answered this question twice, once on otn forums and once on developpez.net

Here is the latest to get the third column of emp

select
column_name as name,
extractvalue(column_value,’/ROW/’||column_name) as value
from table(xmlsequence(cursor(select * from emp))),
user_tab_columns
where COLUMN_ID=3 and table_name=’EMP’
;


NAME VALUE     
---- ----------
JOB  CLERK     
JOB  SALESMAN  
JOB  SALESMAN  
JOB  MANAGER   
JOB  SALESMAN  
JOB  MANAGER   
JOB  MANAGER   
JOB  ANALYST   
JOB  PRESIDENT 
JOB  SALESMAN  
JOB  CLERK     
JOB  CLERK     
JOB  ANALYST   
JOB  CLERK     

probably useless, but fun ;)

how to spell 999999999 ?


begin
  dbms_output.put_line(
    to_char(
      timestamp '9999-12-31 23:59:59.999999999',
      'FF9SP'));
end;
/ 
NINE HUNDRED NINETY-NINE MILLION NINE HUNDRED NINETY-NINE 
THOUSAND NINE HUNDRED NINETY-NINE

Unfortunately, I could not get this in sql/10.2.0.2


select 
  to_char(
    timestamp '9999-12-31 23:59:59.999999999',
    'FF9SP') X
from
  dual;
ORA-01877: string is too long for internal buffer

Well, since the string is too long, let’s try with a LONGER string :twisted:


select 
  regexp_substr(
    to_char(
      timestamp '9999-12-31 23:59:59.999999999',
'FF9SP/FMDAY MONTH DDTHSP YYYYSP A.D. HH24SP MISP SSSP')
    ,'[^/]+')X
from
  dual;

X                                                                                          
---------------------------------------------------------
----------------------------------
NINE HUNDRED NINETY-NINE MILLION NINE HUNDRED NINETY-NINE 
THOUSAND NINE HUNDRED NINETY-NINE

SQL Expert?

I have attended the sql certified expert beta exam this morning. There were a lot of errors in it, I added in the comment that they have to groundly review their regular expressions questions. There were a lot of rubbish question, but hardly any challenge, it is more like detecting the incorrect syntax. So I am deceived. I have to wait 3 months for the result, but I expect no more than 90% 8-)

They even have an exhibit with a table containing many columns with the same name (!)

Well, I hope they will improve with the comments I made when the production release will come out.

The time to answer the question is sufficient. I had only 139 questions in 3 hours, and I needed only 2 hours actually with plenty of time to review.

ORA-01466: unable to read data – table definition has changed

I re-edited this post and it is unresolved yet. I thought it was related to system time, but apparently not :o


SQL> create table t(x number);

Table created.

SQL> set transaction read only ;

Transaction set.

SQL> select * from t;
select * from t
              *
ERROR at line 1:
ORA-01466: unable to read data - table definition has changed

If I wait one minute after my create table statement, it works


SQL> drop table t;

Table dropped.

SQL> create table t(x number);

Table created.

SQL> host sleep 60

SQL> set transaction read only;

Transaction set.

SQL> select * from t;

no rows selected

:twisted:

avoid unnecessary updates

I do update t set x=:a;

If I do it twice, I am doing a lot of unnecessary updates. This is true in an update, and also in the update clause of a MERGE.

I need to take care of null, I can update null with something, or something with null, but update null with null is also unnecessary.


SQL> update t set x=:a;

4977 rows updated.

Elapsed: 00:00:00.34
SQL> update t set x=:a;

4977 rows updated.

Elapsed: 00:00:00.32
SQL> update t set x=:a
  2  where x!=:a
  3  or (x is null and :a is not null)
  4  or (x is not null and :a is null);

0 rows updated.

Elapsed: 00:00:00.04

v$session_longops in 10gR2

I have read an excellent overview of DBMS_APPLICATION_INFO in Andy Campbell blog on If only…

I have written a procedure P which is executing 5 statements. I want to add the name of the procedure P as a target. I do not want to hardcode the name however… So I can use $$PLSQL_UNIT in 10gR2 (documented in Using Predefined Inquiry Directives With Conditional Compilation)

Ok, here I go


create or replace procedure p is
   rindex binary_integer;
   slno binary_integer;
   sofar number;
   totalwork number;
   target number;
begin
   totalwork := 5;
   sofar := 0;
   select object_id 
   into target 
   from user_objects 
   where object_name = $$PLSQL_UNIT;
   DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS(
     rindex,slno,'Executing...',target,null,
     sofar,totalwork,null,'Statement');
   -- do something 
   sofar:=sofar+1;
   DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS(
     rindex,slno,'Executing...',target,null,
     sofar,totalwork,null,'Statement');
   -- do something 
   sofar:=sofar+1;
   DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS(
     rindex,slno,'Executing...',target,null,
     sofar,totalwork,null,'Statement');
   -- do something 
   sofar:=sofar+1;
   DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS(
     rindex,slno,'Executing...',target,null,
     sofar,totalwork,null,'Statement');
   -- do something 
   sofar:=sofar+1;
   DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS(
     rindex,slno,'Executing...',target,null,
     sofar,totalwork,null,'Statement');
   -- do something 
   sofar:=sofar+1;
   DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS(
     rindex,slno,'Executing...',target,null,
     sofar,totalwork,null,'Statement');
end;
/

Now I execute it


SQL> exec p

PL/SQL procedure successfully completed.

and monitor it


SQL> select sid,opname,target,sofar,totalwork,units
  2   from v$session_longops;
 SID OPNAME       TARGET   SOFAR TOTALWORK UNITS    
---- ------------ -------- ----- --------- ---------
 538 Executing... SCOTT.P      5         5 Statement

rename column_value


SQL> create or replace type t is 
 2  table of varchar2(12);
 3  /
Type created.

SQL> create or replace function f return t is 
 2  begin return t('foo'); end;
 3  /
Function created.

SQL> select * from table(f);

COLUMN_VALUE
------------
foo         

What is this column_value field? It is a pseudo-column. But you may want to have an user-defined column name.


SQL> create or replace type o is 
 2  object(BAR varchar2(12));
 3  /
Type created.

SQL> create or replace type t is 
 2  table of o;
 3  /
Type created.

SQL> create or replace function f return t is 
 2  begin return t(o('foo')); end;
 3  /
Function created.
SQL> select * from table(f);

BAR
------------
foo         

to divide or to multiply

warning, this test is cpu intensive, do not try on your productive server

One user on the developpez.net French forums asked today about rewritting a division in a multiplication for tuning. Like select avg(sal)/2 from emp; in select avg(sal)*.5 from emp;.

Well, I had to test this ! I execute 41055 divisions in a plsql loop. To avoid incrementation, I divide by 1.014… and multiply by 0.986… in a way that the result keep the same all over the loop.


SQL> 
SQL> var z number
SQL> var y number
SQL> exec :z := power(2,102)*2e-31;
SQL> exec :y := 1e125;
SQL> set timi on
SQL> exec while (:y>1e-125) loop :y:=:y/:z; end loop
Elapsed: 00:00:00.42
SQL> set timi off
SQL> print y

                               Y
--------------------------------
9.9879215917842541374103299E-126

SQL> exec :z := power(2,-104)*2e31;
SQL> exec :y := 1e125;
SQL> set timi on
SQL> exec while (:y<1e-125) loop :y:=:y*:z; end loop
Elapsed: 00:00:00.28
SQL> set timi off
SQL> print y

                               Y
--------------------------------
9.9879215917842541374103299E-126

It has been difficult to find an example with clear difference and not too weird :twisted:

The operation divide is slower than multiply, probably to catch divide by zero errors...

Column qualification best practice

Lazyness at the development can have dramatic costs in production and maintenance. I want to summarize why and where you should always qualify your columns.

Why? when you select or modify data from a table, you must qualify the columns you are using so if the order of the column change, or if one column is added, renamed or removed, so long this column is not related to your query your code should keep working. A typical example is when the dba add a LAST_MODIFICATION_DATE and a trigger to automatically fill that column for auditing purpose. It should never make your application fail.

0) sample table


create table t1(x number, y number);
create table t2(a number, b number);
create table t3(x number, z number);

1) insert into table
bad:insert into t1 values(1,2);
good:insert into t1(x,y) values(1,2);

bad:

insert into t1
select * from t2;

good:
insert into t1(x,y)
select t2.a,t2.b from t2;

bad:

insert all
when a>0 then into t1
select * from t2;

good:
insert all
when a>0 then into t1(x,y)
select t2.a,t2.b from t2;

2) merge
bad:

merge into t1
using t2 on (x=a)
when matched then
  update set y=b
  where (b>y)
delete
  where (b>y)
when not matched then
  insert values(a,b)
  where (b>0);

good:
merge into t1
using t2 on (t1.x=t2.a)
when matched then
  update set y=t2.b
  where (t2.b>t1.y)
delete
  where (t2.b>t1.y)
when not matched then
  insert (x,y) values(t2.a,t2.b)
  where (t2.b>0);

Not prefixing the column here will bug as soon as a column a or b is added to table t1

2) joins

just never use natural join in production

bad:

select *
from t1 natural join t3;

good:
select x, t1.y, t3.z
from t1 join t3 using (x);

bad:

select *
from t1,t2
where x=a;

good:
select t1.x,t1.y,t2.a,t2.b
from t1,t2
where t1.x=t2.a;

It is not about good looking code, it is about data quality and stability 8-)

select from a comma-separated string

This is one question I solved today with XML on the developpez.net/forums

I have a table T

Un,Trois,Cinq,Six
Un,Deux,Quatre
Trois
Sept,Huit
Un,Six

I want to select un,deux,trois,quatre,cinq,six,sept,huit.

I could well have written a plsql function like


create or replace type c as object (value varchar2(40));
/
create or replace type t_c as table of c;
/
sho err
create or replace function f(
  list varchar2, 
  sep varchar2 default ',') 
return t_c pipelined is
  i number:=1;
begin
  loop
    if (instr(list,sep,1,i)>0)
    then
      if (i=1) then
        pipe row(c(substr(list, 
          1, instr(list,sep)-1)));
      else
        pipe row(c(substr(list, 
          instr(list,sep,1,i-1)+1,
          instr(list,sep,1,i)-
          instr(list,sep,1,i-1)-1)));
      end if;
    else
      if (i=1) then
        pipe row(c(list));
      else
        pipe row(c(substr(list, 
          instr(list,sep,1,i-1)+1)));
      end if;
      return;
    end if;
    i:=i+1;
  end loop;
end;
/
select distinct value
from t,
  table(f(c))
;

or anything using recursion or whatever.

I just tried with XML


SQL> select distinct extractvalue(column_value,’/x’)
2 from t,
3 table(xmlsequence(extract(xmltype(
4 ‘<list><x>’||replace(
5 c,’,’,'</x><x>’)||
6 ‘</x></list>’),
7 ‘/list/x’)));

EXTRACTVALUE(COLUMN_VALUE,’/X’)
——————————-
Trois
Un
Quatre
Cinq
Six
Deux
Sept
Huit

8 rows selected.