You are on page 1of 69

*

Dan Stober
Intermountain Healthcare
*

* Intermountain Healthcare
* Enterprise Data Warehouse (EDW)
* California State University Fresno
* Working in Oracle databases since 2001
* Oracle certified SQL Expert
* Past Presenter at many Oracle conferences
* Oracle Open World, UTOUG, IOUG, OAUG, NYOUG
*
* Learn some intricacies about the SQL works
* Discover a function/keyword that you’ve never seen before
* Gain thorough understanding

* This should help you to…


* Be more productive in your work
* Write code with fewer bugs
* Amaze your co-workers
* Bore your spouse
SELECT * FROM EMP
*
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
----- ------ --------- ---- ----------- ---- ---- ------
7782 CLARK MANAGER 7839 6/9/1981 2450 10
7839 KING PRESIDENT 11/17/1981 5000 10
7934 MILLER CLERK 7782 1/23/1982 1300 10
7369 SMITH CLERK 7902 12/17/1980 800 20
7566 JONES MANAGER 7839 4/2/1981 2975 20
7788 SCOTT ANALYST 7566 12/9/1982 3000 20
7876 ADAMS CLERK 7788 1/12/1983 1100 20
7902 FORD ANALYST 7566 12/3/1981 3000 20
7499 ALLEN SALESMAN 7698 2/20/1981 1600 300 30
7521 WARD SALESMAN 7698 2/22/1981 1250 500 30
7654 MARTIN SALESMAN 7698 9/28/1981 1250 1400 30
7698 BLAKE MANAGER 7839 5/1/1981 2850 30
7844 TURNER SALESMAN 7698 9/8/1981 1500 0 30
7900 JAMES CLERK 7698 12/3/1981 950 30

SELECT * FROM DEPT


DEPTNO DNAME LOC
---------- ------------------------- -------------------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
*
* NULL issues
* NVL2, NULLIF
* DECODE
* CASE statements
* ROWNUM
* Scoping
* ADD_MONTHS and MONTHS_BETWEEN
* DUMP
* Undocumented Functions
*
*
Empno: 7521
Employee Name: WARD 1250 ÷ 500 = 2.5

Salary: 1250 Salary is 2.5 times commission


Commission: 500

SELECT empno, ename, sal, comm


, sal/comm AS comp
FROM emp
WHERE deptno = 30;

*
Error at line 1
ORA-01476: divisor is equal to zero
Empno: 7844
Employee Name: TURNER

Salary: 1500
Commission: 0
*
SELECT empno, ename, sal, comm
, sal/comm AS comp The record for TURNER
FROM emp
WHERE deptno = 30 has been removed…
AND comm <> 0;
0

EMPNO ENAME SAL COMM COMP


---------- ---------- ---------- ---------- ---------- …but others records
7499 ALLEN 1600 300 5.33333333
7521 WARD 1250 500 2.5 have been omitted,
7654 MARTIN 1250 1400 .892857143 too: There are six emps
3 rows selected. in DEPTNO 30

SELECT empno, ename, sal, comm


All six records , CASE WHEN comm <> 0 THEN sal/
sal/comm END AS comp
FROM emp
returned WHERE deptno = 30;

EMPNO ENAME SAL COMM COMP


---------- ---------- ---------- ---------- ----------
7499 ALLEN 1600 300 5.33333333
…and no troubles with 7521 WARD 1250 500 2.5
division by zero! 7654 MARTIN 1250 1400 .892857143
7698 BLAKE 2850
7844 TURNER 1500 0
7900 JAMES 950

6 rows selected.
*
SELECT empno, ename, sal, comm
, sal / NULLIF ( comm,
comm, 0 ) AS comp
FROM emp
WHERE deptno = 30;

EMPNO ENAME SAL COMM COMP


---------- ---------- ---------- ---------- ----------
7499 ALLEN 1600 300 5.33333333
7521 WARD 1250 500 2.5
7654 MARTIN 1250 1400 .892857143
7698
7844
7900
BLAKE
TURNER
JAMES
2850
1500
950
0 NULLIF
is one of the most
6 rows selected. convenient tools in
the toolkit to avoid
NULLIF ( comm,
comm, 0 ) zero_divide errors

If this value… …equals this value… …then return NULL instead

Equivalent to…
CASE WHEN comm = 0 THEN NULL ELSE comm END
*
In the first solution, the entire division expression is NULLed out…
CASE WHEN comm <> 0 THEN sal/comm END

…but, In the elegant solution, only the zero is NULLed out:


sal / NULL ( comm, 0)

Any addition, subtraction, multiplication, or division


involving a NULL, always results in NULL
*
sal + comm = ttl_comp

SELECT empno, ename, sal, comm


, sal + comm AS ttl_comp In this case, the ZERO
FROM emp
WHERE deptno = 30; commission does not
present a problem
EMPNO ENAME SAL COMM TTL_COMP
---------- ---------- ---------- ---------- ----------
7499 ALLEN 1600 300 1900
7521 WARD 1250 500 1750
7654 MARTIN 1250 1400 2650
7698 BLAKE 2850 However, there is no SUM
7844 TURNER 1500 0 1500 for the records with NULL
7900 JAMES 950
commissions
6 rows selected.

NULL in addition, subtraction,


multiplication, and division
ALWAYS results in NULL
*
SELECT empno, ename, sal, comm
, sal + NVL( comm,
comm, 0 ) AS ttl_comp
FROM emp
WHERE deptno = 30;

EMPNO ENAME SAL COMM TTL_COMP


---------- ---------- ---------- ---------- ----------
7499 ALLEN 1600 300 1900
7521 WARD 1250 500 1750
7654 MARTIN 1250 1400 2650
7698 BLAKE 2850 2850
7844 TURNER 1500 0 1500
7900 JAMES 950 950

6 rows selected.

NVL ( comm,
comm, 0 )

If this value is NULL… …then replace it with this value…

Equivalent to…
CASE WHEN comm IS NULL THEN 0 ELSE comm END
*
SELECT COUNT(*)
, COUNT ( comm ) AS c
, SUM ( comm ) AS s
, AVG ( comm ) AS a
FROM emp
WHERE deptno = 30;

COUNT(*) C S A
---------- ---------- ---------- ----------
6 4 2200 550
1 row selected.

* Number + NULL = NULL


* SUM ( numbers and NULL(s) ) returns the SUM
of the NOT NULL values
*
SELECT COUNT ( comm ) SELECT SUM ( comm )
FROM emp FROM emp
WHERE deptno = 20; WHERE deptno = 20;

COUNT(COMM) SUM(COMM)
----------- ----------
0
1 row selected. 1 row selected.

SELECT SUM ( comm ) DECLARE


v_sal_sum NUMBER;
FROM emp
BEGIN
WHERE deptno = 9999;
9999 SELECT SUM ( comm )
INTO v_sal_sum
SUM(COMM) FROM emp
---------- WHERE deptno = 9999;
EXCEPTION
WHEN no_data_found THEN
1 row selected.
RAISE invalid_dept;
END;

This exception will NEVER be raised by ... Program continutes


this query
*
SELECT SUM ( comm )
FROM emp GROUP BY
WHERE deptno = 9999
GROUP BY deptno;
deptno; using the single field specified in
the WHERE clauseb
no rows selected.

SELECT SUM ( comm )


FROM emp
WHERE deptno = 9999 GROUP BY
GROUP BY ( );
with empty parentheses
no rows selected.

DECLARE
v_sal_sum NUMBER;
BEGIN
SELECT SUM ( comm )
INTO v_sal_sum
FROM emp
WHERE deptno = 9999
GROUP BY ();
()
EXCEPTION
WHEN no_data_found THEN
RAISE invalid_dept;
END;

... Program continutes


*
* Count how many employees have a commission
value and how many do not?
SELECT deptno, COUNT(*) SELECT comm, COUNT(*)
FROM emp FROM emp
GROUP BY deptno; GROUP BY comm;

DEPTNO COUNT(*) COMM COUNT(*)


---------- ---------- ---------- ----------
10 3 10
20 5 1400 1
30 6 500 1
300 1
3 rows selected. 0 1

5 rows selected.
*
SELECT NVL2( comm, 'Commission','None') comm
, COUNT(*)
FROM emp
GROUP BY NVL2( comm, 'Commission','None');

COMM COUNT(*)
-------------------------------- ----------
Commission 4
None 10

2 rows selected.

NVL2 ( comm,
comm, 'Commission','None
'Commission','None')
Commission','None')
…If it is NULL, then
If this value is NOT NULL… …then return this…
return this.

Equivalent to…
CASE WHEN comm IS NOT NULL THEN 'Commission'
ELSE 'None' END
*
Dealing with the record with COMM = 0.

What if it should not be counted with the commissions?

SELECT NVL2( NULLIF (comm


(comm,
comm, 0), 'Commission','None') comm
, COUNT(*)
FROM emp
GROUP BY NVL2( NULLIF (comm, 0), 'Commission','None');

COMM COUNT(*)
-------------------------------- ----------
Commission 3
None 11

2 rows selected.

Using NULLIF allows us to NULL out the zero value and


then have it counted with the “None” commission records
*
* NVL2 can be used in place of NVL anywhere that NVL is used.

SELECT empno, ename, sal, comm


, NVL (comm, 0) n1
, NVL2 ( comm, comm, 0) n2
, sal + NVL2( comm, comm, 0 ) AS ttl_comp
FROM emp
WHERE deptno = 30;

EMPNO ENAME SAL COMM N1 N2 TTL_COMP


---------- ---------- ---------- ---------- ---------- ---------- ----------
7499 ALLEN 1600 300 300 300 1900
7521 WARD 1250 500 500 500 1750
7654 MARTIN 1250 1400 1400 1400 2650
7698 BLAKE 2850 0 0 2850
7844 TURNER 1500 0 0 0 1500
7900 JAMES 950 0 0 950

6 rows selected.
*
Old-school function
*
* Matches values and provides substitutes
* Values and substitutes are in pairs
Test value
Value and substitute pairs
or field

DECODE ( attendance, 'P','Present','E','Excused','A','Absent')

Default

DECODE ( attendance, 'P','Present','E','Excused','A','Absent','Unknown' )


*
Get a breakdown of the counts of ANALYSTs
versus other employees Trick: DECODE a
value to itself

SELECT DECODE ( job, 'ANALYST', 'ANALYST', 'OTHERS' ) job


, COUNT(*)
FROM emp
GROUP BY DECODE ( job, 'ANALYST', 'ANALYST', 'OTHERS' )
;

JOB COUNT(*)
-------------------------------- ----------
OTHERS 12
ANALYST 2

2 rows selected.
Another breakdown...
*
SELECT DECODE ( job, 'ANALYST', 'ANALYST'
, 'CLERK', 'CLERK'
, 'OTHERS' ) AS jobgroup
, COUNT(*)
FROM train.emp
GROUP BY DECODE ( job, 'ANALYST', 'ANALYST', 'CLERK', 'CLERK', 'OTHERS' );

JOBGROUP COUNT(*) In these cases, it’s more


-------------------------------- ----------
ANALYST 2 common to DECODE
CLERK 4 back to the field value
OTHERS 8

3 rows selected.
SELECT DECODE ( job, 'ANALYST', job
, 'CLERK', job
, 'OTHERS' ) AS jobgroup
, COUNT(*)
FROM train.emp
GROUP BY DECODE ( job, 'ANALYST', job
, 'CLERK', job, 'OTHERS' );

JOBGROUP COUNT(*)
-------------------------------- ----------
ANALYST 2
CLERK 4
OTHERS 8

3 rows selected.
*
Creating a pseudo-group...
* Count the office staff versus the others.
SELECT DECODE ( job, 'ANALYST', 'OFFICE
OFFICE'
OFFICE
, 'CLERK', 'OFFICE
OFFICE'
OFFICE
, 'OTHERS' ) AS jobgroup
, COUNT(*)
FROM emp
GROUP BY DECODE ( job, 'ANALYST', 'OFFICE'
, 'CLERK', 'OFFICE'
, 'OTHERS' );

JOBGROUP COUNT(*)
-------------------------------- ---------- The ANALYSTs and the
OFFICE 6
OTHERS 8 CLERKs have been
grouped together
2 rows selected.
*
Use it to understand the relationship between
values in two fields
SELECT DECODE ( comm, NULL, 'NULL', 'NOT NULL') comm
, DECODE ( mgr, NULL, 'NULL', 'NOT NULL') mgr In DECODE,
, COUNT(*) NULL matches
FROM emp
GROUP BY DECODE ( comm, NULL, 'NULL', 'NOT NULL')
NULL
, DECODE ( mgr, NULL, 'NULL', 'NOT NULL')

COMM MGR COUNT(*)


---------------------- ---------------------- ----------
NULL NOT NULL 9
NOT NULL NOT NULL 4
NULL NULL 1

3 rows selected.
*
DECODE is very compact when used
in the PARTITION BY for analytic SQL
SELECT ename, job, sal
, SUM ( sal ) OVER ( PARTITION BY DECODE(job,'ANALYST',1,'CLERK',1,2
DECODE(job,'ANALYST',1,'CLERK',1,2)
',1,'CLERK',1,2) )
AS sum_grp_sal
FROM train.emp
WHERE deptno <30;

ENAME JOB SAL SUM_GRP_SAL


---------- --------- ---------- -----------
FORD ANALYST 3000 9200
SCOTT ANALYST 3000 9200
ADAMS CLERK 1100 9200
MILLER CLERK 1300 9200
SMITH CLERK 800 9200
CLARK MANAGER 2450 10425
JONES MANAGER 2975 10425
KING PRESIDENT 5000 10425

8 rows selected.
*
*
SELECT empno, comm,
CASE comm
WHEN 300 THEN 'CCC'
WHEN 500 THEN 'L'
WHEN NULL THEN 'Empty'
ELSE 'Other'
END AS text_fld
FROM emp ...but, TEXT_FLD
WHERE empno IN ( 7499, 7521, 7900 ); does not contain
the value for NULL
(“Empty”)
EMPNO COMM TEXT_FLD
---------- ---------- --------
7499 300 CCC
7521 500 L
7900 Other

3 rows selected.

COMM is NULL...
*
SELECT empno, comm
, CASE comm WHEN 300 THEN 'CCC'
WHEN 500 THEN 'L'
WHEN NULL THEN 'Empty'
ELSE 'Other'
END AS text_fld
FROM emp
This Searched CASE statement
SELECT empno, comm
, CASE WHEN comm = 300 THEN 'CCC' is equivalent to the Matched
WHEN comm = 500 THEN 'L' CASE above
WHEN comm = NULL THEN 'Empty'
ELSE 'Other' This is why the NULL doesn’t
END AS text_fld
FROM emp
match
comm = NULL
SELECT comm,
DECODE ( comm, 300, 'CCC'
, 500, 'L' DECODE behaves differently. It
, NULL, 'Empty'
,'Other') AS text_fld
does match on NULL
FROM emp (as we’ve seen already)
*
*
Objective: KING 5000
SELECT the two highest salaries... FORD 3000
SCOTT 3000
SELECT ename, sal Use ORDER BY to sort JONES 2975
FROM emp the records and then
WHERE ROWNUM <= 2 BLAKE 2850
ORDER BY sal DESC; ROWNUM to get the CLARK 2450
first two. ALLEN 1600
ENAME SAL
---------- ---------- TURNER 1500
ALLEN 1600 Is this right? WARD 1250
SMITH 800
MILLER 1300
2 rows selected. MARTIN 1250
No. In a query,
ADAMS 1100
ROWNUM gets assigned
JAMES 950
to the records before
SMITH 800
the ORDER BY
*
SELECT ROWNUM SELECT ROWNUM
, ename, mgr, sal , empno, ename, mgr
FROM emp FROM emp
; ORDER BY sal DESC;
DESC

ROWNUM ENAME SAL ROWNUM ENAME SAL


---------- ---------- ---------- ---------- ---------- ----------
1 SMITH 800 9 KING 5000
2 ALLEN 1600 13 FORD 3000
3 WARD 1250 8 SCOTT 3000
4 JONES 2975 4 JONES 2975
5 MARTIN 1250 6 BLAKE 2850
6 BLAKE 2850 7 CLARK 2450
7 CLARK 2450 2 ALLEN 1600
8 SCOTT 3000 10 TURNER 1500
9 KING 5000 14 MILLER 1300
10 TURNER 1500 3 WARD 1250
11 ADAMS 1100 5 MARTIN 1250
12 SALES 950 11 ADAMS 1100
13 FORD 3000 12 SALES 950
14 MILLER 1300 1 SMITH 800

14 rows selected. 14 rows selected.

Records are selected in database order.


ROWNUM is assigned based on that.
*
SELECT *
FROM ( SELECT ename, sal
FROM emp Using a subquery, we can sort
ORDER BY sal DESC ) the records before the
WHERE ROWNUM <= 2; ROWNUM is applied
ENAME SAL
---------- ----------
KING 5000
SCOTT 3000

2 rows selected. SELECT ROWNUM, ename, sal


FROM ( SELECT ename, sal
FROM emp
ORDER BY sal DESC )
WHERE ROWNUM <= 2;

ROWNUM ENAME SAL


---------- ---------- ----------
1 KING 5000
2 SCOTT 3000

2 rows selected.
*
SELECT *
FROM ( SELECT ROWNUM, ename, sal
FROM emp
ORDER BY sal DESC ) sub
WHERE ROWNUM <= 2; The WHERE clause selected
ROWNUM < 2,
ROWNUM ENAME SAL but values in the query
---------- ---------- ----------
9 KING 5000 are 8 and 9.
8 SCOTT 3000

2 rows selected.

SELECT ROWNUM, sub.*


FROM ( SELECT ROWNUM, ename, sal
FROM emp
ORDER BY sal DESC ) sub
WHERE ROWNUM <= 2;

ROWNUM ROWNUM_1 ENAME SAL


---------- ---------- ---------- ----------
1 9 KING 5000
2 8 SCOTT 3000

2 rows selected.
*
SELECT ROWNUM
, empno, ename
FROM train.emp
WHERE ROWNUM = 2 ;

no rows selected.

You cannot select ROWNUM 2 unless


ROWNUM 1 is also selected

ROWNUM 2 does not exist until after


ROWNUM 1 is assigned
*
* ROWNUM is a pseudocolumn
* Not found in the data
* Assigned as the records are selected
* At the end of the WHERE clause
* This means that ROWNUM can be used in the WHERE clause
*
SELECT ename, job
*
FROM emp
WHERE job IN ( SELECT job FROM dept);

ENAME JOB
---------- --------- Why every record?
ADAMS CLERK There is no JOB field in
ALLEN SALESMAN
BLAKE MANAGER the DEPT table
CLARK MANAGER
FORD ANALYST The query is equivalent
JAMES CLERK
JONES MANAGER to this one:
KING PRESIDENT SELECT ename, job
MARTIN SALESMAN FROM emp
MILLER CLERK WHERE job = job
SCOTT ANALYST
SMITH CLERK
TURNER SALESMAN
WARD SALESMAN
SELECT ename, job
14 rows selected. FROM emp e
WHERE e.job IN ( SELECT d.job FROM dept d);
*
Error at line 3
ORA-
ORA-00904: "D"."JOB": invalid identifier
*
*

* ADD_MONTHS: Returns a DATE some number of months


away (forward or back) from a date supplied

* MONTHS_BETWEEN: Returns the number of months


between two dates.

* There are special considerations due to the fact that not


all months are the same length
SELECT MONTHS_BETWEEN
( DATE '2015-01-01', DATE '2015-03-01')
FROM DUAL; Oddity?
MONTHS_BETWEEN(DATE'2015-01-01',DATE'2015-03-01')
-------------------------------------------------
-2 Annoyance!
1 row selected.

SELECT MONTHS_BETWEEN
( DATE '2015-03-01', DATE '2015-01-01')
FROM DUAL;

MONTHS_BETWEEN(DATE'2015-01-01',DATE'2015-03-01')
-------------------------------------------------
2

1 row selected.
*
SELECT ADD_MONTHS
( DATE '2015-01-01', 2 )
FROM DUAL;

ADD_MONTHS(DATE'2015-01-01',2)
------------------------------
01-MAR-15

1 row selected.

SELECT ADD_MONTHS
( DATE '2015-01-01', 2.5 )
FROM DUAL;

ADD_MONTHS(DATE'2015-01-01',2.5)
--------------------------------
01-MAR-15 ADD_MONTHS adds whole
months only.
1 row selected. If you provide a fractional
month, the value is truncated
before the calculation
*
SELECT ADD_MONTHS
( ADD_MONTHS (DATE '2015-01-27', 1 ), -1)
FROM DUAL;

ADD_MONTHS(ADD_MONTHS(DATE'2015-01-27',1),-1)
---------------------------------------------
27-JAN-15

1 row selected.

SELECT ADD_MONTHS
( ADD_MONTHS (DATE '2015-01-28', 1 ), -1)
FROM DUAL;

ADD_MONTHS(ADD_MONTHS(DATE'2015-01-28',1),-1)
---------------------------------------------
31-JAN-15
ADD_MONTHS, when called
1 row selected. from the last day of the
month,
always returns the last day
of the target month
* SELECT MONTHS_BETWEEN
( DATE '2015-03-31', DATE '2015-02-28' )
FROM DUAL;

MONTHS_BETWEEN(DATE'2015-03-31',DATE'2015-02-28')
-------------------------------------------------
1
Feb 28 to Mar 31 31 days
1 row selected.

SELECT MONTHS_BETWEEN
( DATE '2015-03-29', DATE '2015-02-28' )
FROM DUAL;

MONTHS_BETWEEN(DATE'2015-03-29',DATE'2015-02-28')
-------------------------------------------------
Feb 28 to Mar 29 29 days 1.03225806
1 row selected.

SELECT MONTHS_BETWEEN
( DATE '2015-03-28', DATE '2015-02-28' )
FROM DUAL;

MONTHS_BETWEEN(DATE'2015-03-28',DATE'2015-02-28')
-------------------------------------------------
Feb 28 to Mar 28 28 days 1
1 row selected.
*
* ADD_MONTHS
* If starting point is the last day of the month…
* …then ADD_MONTHS always returns the last day of the
target month
* Otherwise, ADD_MONTHS returns the day with the same
number in the target month

* MONTHS_BETWEEN
* If starting point (second param) is the last day of the
month…
* …then it always results in an integer number of months to
the last day of a month
* Otherwise, the decimal portion is 1/31 times the number of
days in the remainder

Feb 28 to Mar 31 31 days


BOTH
Feb 28 to Mar 28 28 days are one month apart
*
SELECT MONTHS_BETWEEN
( DATE '2015-03-02', DATE '2015-03-01' ) AS mar
, MONTHS_BETWEEN
( DATE '2015-04-02', DATE '2015-04-01' ) AS apr
, MONTHS_BETWEEN
( DATE '2015-02-02', DATE '2015-02-01' ) AS feb15
, MONTHS_BETWEEN
( DATE '2016-02-02', DATE '2016-02-01' ) AS feb16
, 1/ 31 AS math_calc
FROM DUAL;

MAR APR FEB15 FEB16 MATH_CALC


---------- ---------- ---------- ---------- ----------
.032258065 .032258065 .032258065 .032258065 .032258065

1 row selected.

Regardless of the actual


length of the month,
MONTHS_BETWEEN always
returns 1/31
*
12/31/2014 01/28/2015 0.903225806 28 / 31
12/31/2014 01/29/2015 0.935483871 29 / 31
12/31/2014 01/30/2015 0.967741935 30 / 31
12/31/2014 01/31/2015 1.000000000 31 / 31

03/31/2015 04/27/2015 0.870967742 27 / 31


03/31/2015 04/28/2015 0.903225806 28 / 31
03/31/2015 04/29/2015 0.935483871 29 / 31 2/31
difference
03/31/2015 04/30/2015 1.000000000 31 / 31 for one day
*
Calculating AGE!

age = TRUNC ( SYSDATE – birthdate ) / 365.25 )

For someone born on March 1, 2012...


As of date Age days Divided by 365.25 TRUNC
03/01/2013 365 0.99931553730217 0
03/01/2014 730 1.99863107460643 1
03/01/2015 1095 2.99794661190965 2
03/01/2016 1461 4.00000000000000 4

365 / 365.25 <1


*
Calculating AGE!
age = TRUNC ( MONTHS_BETWEEN ( SYSDATE, birthdate ) / 12)

For someone born on March 1, 2012...


As of date MONTHS_BETWEEN Divided by 12 TRUNC
03/01/2013 12 1 1
03/01/2014 24 2 2
03/01/2015 36 3 3
03/01/2016 48 4 4

* The only scenario where this method will return the incorrect
age (possibly) is people born on February 29.
* In years without a Leap Year, it reports birthday on Feb 28
* 1 in 1461 people ( 0.068%) …in 3 out of 4 years
* Fractional ages could suffer the same oddities seen previously
* But whole ages always land in the same month as the start date
*
One of my favorite functions
*
* DUMP is a data checking function
* Returns the contents of a field, character by character
* Including non-printable (invisible) characters
* Best used when troubleshooting
SELECT ename
, DUMP ( ename )
FROM emp
WHERE deptno = 10;

ENAME DUMP(ENAME)
---------- --------------------------------------------------
KING Typ=1 Len=4: 75,73,78,71
CLARK Typ=1 Len=5: 67,76,65,82,75
MILLER Typ=1 Len=6: 77,73,76,76,69,82

3 rows selected.

Datatype Length ASCII value of each character


*
Code Datatype
1 VARCHAR, NVARCHAR2
2 NUMBER, FLOAT
8 DATE
12 LONG
96 CHAR, NCHAR
*
SELECT ename
, DUMP ( ename,17 )
FROM emp
WHERE deptno = 10;

ENAME DUMP(ENAME,17)

---------------------------------------
KING Typ=1 Len=4: K,I,N,G
CLARK Typ=1 Len=5: C,L,A,R,K Now, each character is
MILLER Typ=1 Len=6: M,I,L,L,E,R
represented as itself
3 rows selected.

But…watch what happens with


non-printable characters!
*
SELECT business_unit
, account
, deptid
, posted_total_amt_txt
FROM erp_ledger_insert
WHERE deptid IN ( 10011, 11503, 14010);

BUSINESS_UNIT ACCOUNT DEPTID POSTED_TOTAL_AMT_TXT


------------- ---------- ---------- ------------------------------
4321 123456 10011 216.57

4321 123456 11503 1089.66

4321 123456 14010 4555.11

3 rows selected. SELECT business_unit


, account
, deptid
, TO_NUMBER ( posted_total_amt_txt )
FROM erp_ledger_insert
WHERE deptid IN ( 10011, 11503, 14010);

Error at line 4
ORA-01722: invalid number
*
SELECT business_unit
, account
, deptid
, posted_total_amt_txt
, DUMP ( posted_total_amt_txt )
FROM erp_ledger_insert
WHERE deptid IN ( 10011, 11503, 14010);

BUSINESS_UNIT ACCOUNT DEPTID POSTED_T DUMP(POSTED_TOTAL_AMT_TXT)


------------- ------- ------- -------- ----------- ----------------------------------
4321 123456 10011 216.57 Typ=1 Len=8: 50,49,54,46,53,55,32,13
4321 123456 11503 1089.66 Typ=1 Len=9: 49,48,56,57,46,54,54,32,13
4321 123456 14010 4555.11 Typ=1 Len=9: 52,53,53,53,46,49,49,32,13

3 rows selected.

And here is the ASCII


SELECT business_unit
, account number
, deptid
, posted_total_amt_txt
, DUMP ( posted_total_amt_txt, 17 ) Here is the
FROM erp_ledger_insert
WHERE deptid IN ( 10011, 11503, 14010); offending character
BUSINESS_UNIT ACCOUNT DEPTID POSTED_T DUMP(POSTED_TOTAL_AMT_TXT)
------------- ------- ------- -------- ----------- ---------------------------------
4321 123456 10011 216.57 Typ=1 Len=8: Typ=1 Len=8: 2,1,6,.,5,7, ,^M
4321 123456 11503 1089.66 Typ=1 Len=9: Typ=1 Len=9: 1,0,8,9,.,6,6, ,^M
4321 123456 14010 4555.11 Typ=1 Len=9: Typ=1 Len=9: 4,5,5,5,.,1,1, ,^M

3 rows selected.
*
The character 13 is REPLACEd out
of the string

SELECT business_unit
, account
, deptid
, TO_NUMBER ( REPLACE ( posted_total_amt_txt, CHR(13) ))
FROM erp_ledger_insert
WHERE deptid IN ( 10011, 11503, 14010);

BUSINESS_UNIT ACCOUNT DEPTID TO_NUMBER(REPLACE(POSTED_TOTAL_AMT_TXT,CHR(13)))


------------- ---------- ---------- ------------------------------------------------
4321 123456 10011 216.57
4321 123456 11503 1089.66
4321 123456 14010 4555.11

3 rows selected.
*Another example…
SELECT SUBSTR ( company, 1, INSTR ( company, ' ')) AS ticker
, djia.*
FROM djia;
SUBSTR ( company, 1, INSTR ( company, ' '))
TICKER COMPANY PRICE
-------------------- ----------------------------------------- ----------
MMM 3M MMM 3M Co 137.72
AXP American AXP American Express Co 87.77
T AT&T T AT&T Inc 35.43
BA Boeing BA Boeing Co 128.33
CAT Caterpillar CAT Caterpillar Inc 105.18
CVX Chevron CVX Chevron Corp 126.57
CSCO Cisco CSCO Cisco Systems Inc 23.18
DD E DD E I du Pont de Nemours and Co 67.01
XOM Exxon XOM Exxon Mobil Corp 101.63
GE General GE General Electric Co 26.69
GS Goldman
HD Home
INTC Intel
IBM International
HD Home Depot Inc
INTC Intel Corp
*
GS Goldman Sachs Group Inc

IBM International Business Machines Co...


157.92
79.57
26.5
195.47
JNJ Johnson JNJ Johnson & Johnson 101.2
JPM JPMorgan JPM JPMorgan Chase and Co 56.02
MCD McDonald's MCD McDonald's Corp 101.54
MRK Merck MRK Merck & Co Inc 58.42
MSFT Microsoft MSFT Microsoft Corp 40.6
NKE Nike NKE Nike Inc 72.55
PFE Pfizer PFE Pfizer Inc 31.65
PG Procter PG Procter & Gamble Co Full DJIA82.55
query in slide note
KO The KO The Coca-Cola Co 40.53
SELECT INSTR ( company, ' ') AS space_loc INSTR ( company, ' ')
, djia.*
FROM djia;

SPACE_LOC COMPANY PRICE


---------- ----------------------------------------- ----------
7 MMM 3M Co 137.72
13 AXP American Express Co 87.77
7 T AT&T Inc 35.43
10 BA Boeing Co 128.33
16 CAT Caterpillar Inc 105.18
12 CVX Chevron Corp 126.57
11 CSCO Cisco Systems Inc 23.18 So….
5 DD E I du Pont de Nemours and Co 67.01 What is
10 XOM Exxon Mobil Corp 101.63
11 GE General Electric Co 26.69 going on?
11
8
11
18
GS Goldman Sachs Group Inc
HD Home Depot Inc
INTC Intel Corp
*
IBM International Business Machines Co...
157.92
79.57
26.5
195.47
12 JNJ Johnson & Johnson 101.2
13 JPM JPMorgan Chase and Co 56.02
15 MCD McDonald's Corp 101.54
10 MRK Merck & Co Inc 58.42
15 MSFT Microsoft Corp 40.6
9 NKE Nike Inc 72.55
11 PFE Pfizer Inc 31.65
11 PG Procter & Gamble Co 82.55
7 KO The Coca-Cola Co 40.53
*
SELECT company
, DUMP ( company)
FROM djia; DUMP ( company )
COMPANY DUMP(COMPANY)
----------------------------------------- --------------------------------------------------------
MMM 3M Co Typ=1 Len=9: 77,77,77,160
160,51,77,32
160 32,67,111
32
AXP American Express Co Typ=1 Len=23: 65,88,80,160
160,65,109,101,114,105,99,97,110,
160
T AT&T Inc Typ=1 Len=10: 84,160
160,65,84,38,84,32
160 32,73,110,99
32
BA Boeing Co Typ=1 Len=12: 66,65,160
160,66,111,101,105,110,103,32
160 32,67,111
32
CAT Caterpillar Inc Typ=1 Len=19: 67,65,84,160
160,67,97,116,101,114,112,105,108
160
CVX Chevron Corp Typ=1 Len=16: 67,86,88,160
160,67,104,101,118,114,111,110,
160
CSCO Cisco Systems Inc Typ=1 Len=22: 67,83,67,79,160
160,67,105,115,99,111,32
160 32,83,12
32
DD E I du Pont de Nemours and Co Typ=1 Len=32: 68,68,160
160,69,32
160 32,73,32
32 32,100,117,32
32 32,80,111,11
32
XOM Exxon Mobil Corp Typ=1 Len=20: 88,79,77,160
160,69,120,120,111,110,32
160 32,77,111,
32
GE General Electric Co Typ=1 Len=22: 71,69,160
160,71,101,110,101,114,97,108,32
160 32
GS Goldman Sachs Group Inc Typ=1 Len=26: 71,83,160
160,71,111,108,100,109,97,110,32
160 32
HD Home Depot Inc Typ=1 Len=17: 72,68,160
160,72,111,109,101,32
160 32,68,101,112,111
32
INTC Intel Corp Typ=1 Len=15: 73,78,84,67,160
160,73,110,116,101,108,32
160 32
IBM International Business Machines Co... Typ=1 Len=41: 73,66,77,160
160,73,110,116,101,114,110,97,116
160
JNJ Johnson & Johnson Typ=1 Len=21: 74,78,74,160
160,74,111,104,110,115,111,110,
160
JPM JPMorgan Chase and Co Typ=1 Len=25: 74,80,77,160
160,74,80,77,111,114,103,97,110,
160
MCD McDonald's Corp Typ=1 Len=19: 77,67,68,160
160,77,99,68,111,110,97,108,100,3
160
MRK Merck & Co Inc Typ=1 Len=18: 77,82,75,160
160,77,101,114,99,107,32
160 32,38,
32
MSFT Microsoft Corp Typ=1 Len=19: 77,83,70,84,160
160,77,105,99,114,111,115,111,
160
NKE Nike Inc Typ=1 Len=12: 78,75,69,160
160,78,105,107,101,32
160 32,73,110,99
32
PFE Pfizer Inc Typ=1 Len=14: 80,70,69,160
160,80,102,105,122,101,114,32,73,
160
PG Procter & Gamble Co Typ=1 Len=22: 80,71,160
160,80,114,111,99,116,101,114,32,38,
160
KO The Coca-Cola Co Typ=1 Len=19: 75,79,160
160,84,104,101,32
160 32,67,111,99,97,45,67
32
TRV Travelers Companies Inc Typ=1 Len=27: 84,82,86,160
160,84,114,97,118,101,108,101,114
160
UTX United Technologies Corp Typ=1 Len=28: 85,84,88,160
160,85,110,105,116,101,100,32
160 32
SELECT
SELECT company
company
, DUMP ( company, 17)
FROM
FROM djia;
djia; DUMP ( company, 17 )
COMPANY DUMP(COMPANY,17)
----------------------------------------- ---------------------------------------------------------
AXP American Express Co Typ=1 Len=23: A,X,P, ,A,m,e,r,i,c,a,n, ,E,x,p,r,e,s,s, ,C
BA Boeing Co Typ=1 Len=12: B,A, ,B,o,e,i,n,g, ,C,o
CAT Caterpillar Inc Typ=1 Len=19: C,A,T, ,C,a,t,e,r,p,i,l,l,a,r, ,I,n,c
CSCO Cisco Systems Inc Typ=1 Len=22: C,S,C,O, ,C,i,s,c,o, ,S,y,s,t,e,m,s, ,I,n,c
CVX Chevron Corp Typ=1 Len=16: C,V,X, ,C,h,e,v,r,o,n, ,C,o,r,p
DD E I du Pont de Nemours and Co Typ=1 Len=32: D,D, ,E, ,I, ,d,u, ,P,o,n,t, ,d,e, ,N,e,m,o
DIS Walt Disney Co Typ=1 Len=18: D,I,S, ,W,a,l,t, ,D,i,s,n,e,y, ,C,o
GE General Electric Co Typ=1 Len=22: G,E, ,G,e,n,e,r,a,l, ,E,l,e,c,t,r,i,c, ,C,o
GS Goldman Sachs Group Inc Typ=1 Len=26: G,S, ,G,o,l,d,m,a,n, ,S,a,c,h,s, ,G,r,o,u,p
HD Home Depot Inc Typ=1 Len=17: H,D, ,H,o,m,e, ,D,e,p,o,t, ,I,n,c
IBM International Business Machines Co... Typ=1 Len=41: I,B,M, ,I,n,t,e,r,n,a,t,i,o,n,a,l, ,B,u,s,i
INTC Intel Corp Typ=1 Len=15: I,N,T,C, ,I,n,t,e,l, ,C,o,r,p
JNJ Johnson & Johnson Typ=1 Len=21: J,N,J, ,J,o,h,n,s,o,n, ,&, ,J,o,h,n,s,o,n
JPM JPMorgan Chase and Co Typ=1 Len=25: J,P,M, ,J,P,M,o,r,g,a,n, ,C,h,a,s,e, ,a,n,d
KO The Coca-Cola Co Typ=1 Len=19: K,O, ,T,h,e, ,C,o,c,a,-,C,o,l,a, ,C,o
MCD McDonald's Corp Typ=1 Len=19: M,C,D, ,M,c,D,o,n,a,l,d,',s, ,C,o,r,p
MMM 3M Co Typ=1 Len=9: M,M,M, ,3,M, ,C,o
MRK Merck & Co Inc Typ=1 Len=18: M,R,K, ,M,e,r,c,k, ,&, ,C,o, ,I,n,c
MSFT Microsoft Corp Typ=1 Len=19: M,S,F,T, ,M,i,c,r,o,s,o,f,t, ,C,o,r,p
NKE Nike Inc Typ=1 Len=12: N,K,E, ,N,i,k,e, ,I,n,c
PFE Pfizer Inc Typ=1 Len=14: P,F,E, ,P,f,i,z,e,r, ,I,n,c
PG Procter & Gamble Co Typ=1 Len=22: P,G, ,P,r,o,c,t,e,r, ,&, ,G,a,m,b,l,e, ,C,o
T AT&T Inc Typ=1 Len=10: T, ,A,T,&,T, ,I,n,c
TRV Travelers Companies Inc Typ=1 Len=27: T,R,V, ,T,r,a,v,e,l,e,r,s, ,C,o,m,p,a,n,i,e
UNH UnitedHealth Group Inc Typ=1 Len=26: U,N,H, ,U,n,i,t,e,d,H,e,a,l,t,h, ,G,r,o,u,p
*
SELECT
CASE
WHEN company LIKE 'VZ%'
OR company LIKE 'KO%' THEN ASCII ( SUBSTR ( company, 3, 1))
WHEN company LIKE 'MMM%'
OR company LIKE 'AXP%' THEN ASCII ( SUBSTR ( company, 4, 1))
WHEN company LIKE 'CSCO%'
OR company LIKE 'INTC%' THEN ASCII ( SUBSTR ( company, 5, 1))
END AS mystery_char
, company
FROM djia
WHERE company LIKE 'VZ%'
OR company LIKE 'KO%'
Using the SUBSTR
OR company LIKE 'MMM%' function to isolate
OR company LIKE 'AXP%'
OR company LIKE 'CSCO%'
just one character
OR company LIKE 'INTC%';
'INTC%‘; from the string
MYSTERY_CHAR COMPANY
------------ -----------------------------
160 MMM 3M Co
160 AXP American Express Co
160 CSCO Cisco Systems Inc
160 INTC Intel Corp
160 KO The Coca-Cola Co
160 VZ Verizon Communications Inc

6 rows selected.
*

*DUMP:
DUMP Returns the ASCII value of every character in the
string

*ASCII:
ASCII Returns the ASCII value of just one character
*
*
* Returns the characters from a string in reverse order

* Cannot be called on numbers


SELECT ename SELECT empno
, REVERSE ( ename ) rev , REVERSE ( empno ) rev
FROM emp FROM emp
WHERE deptno = 30; WHERE deptno = 30;
ENAME REV *
---------- ---------- Error at line 2
ALLEN NELLA ORA-00932: inconsistent datatypes:
BLAKE EKALB expected CHAR got NUMBER
JAMES SEMAJ
MARTIN NITRAM
TURNER RENRUT
WARD DRAW

6 rows selected.
*
* Compares characters from two strings
* If the characters in the same position from the
two strings match, it returns the character
* If they do not match, function returns “B” in
that character’s place
* Usefulness? Zero…
SELECT ename, job
, merge$actions ( ename, job )
FROM emp
WHERE comm IS NOT NULL;

ENAME JOB MERGE$ACTIONS(ENAME,JOB)


---------- --------- ------------------------
ALLEN SALESMAN BBLEB
MARTIN SALESMAN BABBBB
TURNER SALESMAN BBBBBB
WARD SALESMAN BABB

4 rows selected.
*
* Returns:
* 0 if two values are identical
* 1 if they are not
* Overloads for numbers, characters, and dates
WITH dta AS (
SELECT ename, job
, LEAD ( job ) OVER ( ORDER BY empno ) job1
FROM emp
WHERE deptno = 30
)
SELECT ename, job, job1
, SYS_OP_DISTINCT ( job, job1) f
FROM dta ;

ENAME JOB JOB1 F


---------- --------- --------- ----------
ALLEN SALESMAN SALESMAN 0
BLAKE MANAGER SALESMAN 1
JAMES CLERK 1
MARTIN SALESMAN MANAGER 1
TURNER SALESMAN CLERK 1
WARD SALESMAN SALESMAN 0

6 rows selected.
*
SELECT DENSE_RANKM( ename ) OVER (ORDER BY deptno, sal) FROM emp;

ERROR at line 1:
ORA-03113: end-of-file on communication channel

* Don’t try this one at home!

* I ran it in TOAD, too.


* It caused system resource issues
* I had to shut down TOAD and reopen it
*
* NULL issues
* NVL2, NULLIF
* DECODE
* CASE statements
* ROWNUM
* Scoping
* ADD_MONTHS and MONTHS_BETWEEN
* DUMP
* Undocumented Functions
*

Dan Stober

dan.stober@imail.org
Twitter @dstober

You might also like