Professional Documents
Culture Documents
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
*
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
6 rows selected.
*
SELECT empno, ename, sal, comm
, sal / NULLIF ( comm,
comm, 0 ) AS comp
FROM emp
WHERE deptno = 30;
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
6 rows selected.
NVL ( comm,
comm, 0 )
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.
COUNT(COMM) SUM(COMM)
----------- ----------
0
1 row selected. 1 row 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;
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.
COMM COUNT(*)
-------------------------------- ----------
Commission 3
None 11
2 rows selected.
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
Default
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' );
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')
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;
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
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.
2 rows selected.
*
SELECT ROWNUM
, empno, ename
FROM train.emp
WHERE ROWNUM = 2 ;
no rows selected.
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
*
*
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
1 row selected.
* 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.
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.
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);
3 rows selected.
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);
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
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
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;
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 ;
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
Dan Stober
dan.stober@imail.org
Twitter @dstober