SQL advanced in Oracle

From Training Material
Jump to navigation Jump to search


SQL advanced in Oracle Training Materials

IMPORTANT NOTE for trainers only: 
Please DO NOT change these materials, especially the EXERCISES, without direct permission from: GRZEGORZ PRUSZCZYNSKI or LUKASZ SOKOLOWSKI.


Standard SQL vs Oracle SQL

  • Oracle was the first company which implemented SQL standard in commercial db server
  • Oracle db supports the whole core of SQL standards
  • All additional parts of the official standards are mostly supported by Oracle's own inventions
  • More here
--- https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Oracle-and-Standard-SQL.html#GUID-330DEBBB-006E-4B35-A516-5C0AEFFE06B9

Recap of basics

  • Connect with "sys" user
    • Create db for exercises
  • Create mike_recap.sql worksheet
  • Discussion

Connection for 'sys' user

SysOracleSetup.png

Connection for 'mike' user

Do this one only after "DDL setup" from below SqlOraMike.png

Unlocking HR example with sys user

  • From command line cli or via sql developer
DB SETUP:

connect sys/asdf@localhost:1521/XEPDB1 as SYSDBA

ALTER USER HR IDENTIFIED BY asdf ACCOUNT UNLOCK;

/* Before executing fix all the necessary paths in the script 'hr_main.sql' */

@?\DEMO\SCHEMA\HUMAN_RESOURCES\HR_MAIN.SQL;
(or this way @\D:\app\lsokolowski\product\18.0.0\dbhomeXE\demo\schema\human_resources\hr_main.sql )

connect hr/asdf@localhost:1521/XEPDB1;

show user;  

DDL part

DROP USER mike CASCADE;

CREATE USER mike IDENTIFIED BY mike;
ALTER USER mike DEFAULT TABLESPACE system
              QUOTA UNLIMITED ON system;
ALTER USER mike TEMPORARY TABLESPACE temp;

GRANT CREATE SESSION, CREATE VIEW, ALTER SESSION, CREATE SEQUENCE TO mike;
GRANT CREATE SYNONYM, CREATE DATABASE LINK, RESOURCE TO mike;


CONNECT mike/mike
ALTER SESSION SET NLS_LANGUAGE=English;
ALTER SESSION SET NLS_TERRITORY=Poland;



DROP TABLE EMP;
DROP TABLE DEPT;
DROP TABLE SALGRADE;


CREATE TABLE DEPT (
 DEPTNO              NUMBER(2) NOT NULL,
 DNAME               VARCHAR(14),
 LOC                 VARCHAR(13),
 CONSTRAINT DEPT_PRIMARY_KEY PRIMARY KEY (DEPTNO));

/*
INSERT INTO dept(DEPTNO, DNAME, LOC)
VALUES (10, 'ACCOUNTING', 'NEW YORK');
INSERT INTO dept(DEPTNO, DNAME, LOC)
VALUES (20, 'RESEARCH', 'DALLAS');
INSERT INTO dept(DEPTNO, DNAME, LOC)
VALUES (30, 'SALES', 'CHICAGO');
INSERT INTO dept(DEPTNO, DNAME, LOC)
VALUES (40, 'OPERATIONS', 'BOSTON');
COMMIT;
*/

INSERT INTO dept(DEPTNO, DNAME, LOC)
VALUES (10, 'ACCOUNTING', 'NEW YORK');
INSERT INTO dept(DEPTNO, DNAME, LOC)
VALUES (20, 'RESEARCH', 'LONDON');
INSERT INTO dept(DEPTNO, DNAME, LOC)
VALUES (30, 'SALES', 'PARIS');
INSERT INTO dept(DEPTNO, DNAME, LOC)
VALUES (40, 'OPERATIONS', 'BERLIN');


CREATE TABLE EMP (
 ID               NUMBER(4) NOT NULL,
 ENAME               VARCHAR(10),
 JOB                 VARCHAR(9),
 MGR                 NUMBER(4) CONSTRAINT EMP_SELF_KEY REFERENCES EMP (ID),
 HIREDATE            DATE,
 SAL                 NUMBER(7,2),
 COMM                NUMBER(7,2),
 DEPTNO              NUMBER(2) NOT NULL,
 CONSTRAINT EMP_FOREIGN_KEY FOREIGN KEY (DEPTNO) REFERENCES DEPT (DEPTNO),
 CONSTRAINT EMP_PRIMARY_KEY PRIMARY KEY (ID));

INSERT INTO emp   (ID, ENAME, JOB, HIREDATE, SAL, DEPTNO)
 VALUES   (7839, 'BUSH', 'PRESIDENT', TO_DATE('11/17/1981 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 5000, 10);
INSERT INTO emp    (ID, ENAME, JOB, MGR, HIREDATE, SAL, DEPTNO)
 VALUES    (7566, 'PUTIN', 'MANAGER', 7839, TO_DATE('04/02/1981 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 2975, 20);
INSERT INTO emp   (ID, ENAME, JOB, MGR, HIREDATE, SAL, DEPTNO)
 VALUES   (7788, 'CARNEGIE', 'ANALYST', 7566, TO_DATE('12/09/1982 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 3000, 20);
 INSERT INTO emp   (ID, ENAME, JOB, MGR, HIREDATE, SAL, DEPTNO)
 VALUES   (7876, 'FORD', 'CLERK', 7788, TO_DATE('01/12/1983 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 1100, 20);
INSERT INTO emp   (ID, ENAME, JOB, MGR, HIREDATE, SAL, DEPTNO)
 VALUES   (7902, 'TOOSK', 'ANALYST', 7566, TO_DATE('12/03/1981 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 3000, 20);
INSERT INTO emp    (ID, ENAME, JOB, MGR, HIREDATE, SAL, DEPTNO)
 VALUES    (7369, 'THATCHER', 'CLERK', 7902, TO_DATE('12/17/1980 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 800, 20);
INSERT INTO emp   (ID, ENAME, JOB, MGR, HIREDATE, SAL, DEPTNO)
 VALUES   (7698, 'BLAIR', 'MANAGER', 7839, TO_DATE('05/01/1981 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 2850, 30);
INSERT INTO emp    (ID, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO)
 VALUES    (7499, 'BAROSSO', 'SALESMAN', 7698, TO_DATE('02/20/1981 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 1600, 300, 30);
INSERT INTO emp    (ID, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO)
 VALUES    (7521, 'WALTON', 'SALESMAN', 7698, TO_DATE('02/22/1981 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 1250, 500, 30);
INSERT INTO emp   (ID, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO)
 VALUES   (7654, 'CHIRACK', 'SALESMAN', 7698, TO_DATE('09/28/1981 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 1250, 1400, 30);
INSERT INTO emp   (ID, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO)
 VALUES   (7844, 'GATES', 'SALESMAN', 7698, TO_DATE('09/08/1981 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 1500, 0, 30);
INSERT INTO emp   (ID, ENAME, JOB, MGR, HIREDATE, SAL, DEPTNO)
 VALUES   (7900, 'BUFFETT', 'CLERK', 7698, TO_DATE('12/03/1981 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 950, 30);
INSERT INTO emp   (ID, ENAME, JOB, MGR, HIREDATE, SAL, DEPTNO)
 VALUES   (7782, 'MERKEL', 'MANAGER', 7839, TO_DATE('06/09/1981 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 2450, 10);
INSERT INTO emp   (ID, ENAME, JOB, MGR, HIREDATE, SAL, DEPTNO)
 VALUES   (7934, 'ELISON', 'CLERK', 7782, TO_DATE('01/23/1982 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 1300, 10);
COMMIT;

/* Shorter way, just for the comparison's sake
INSERT INTO EMP VALUES (7839,'BUSH','PRESIDENT',NULL,'1981-11-17',5000,NULL,10);
INSERT INTO EMP VALUES (7698,'BLAIR','MANAGER',7839,'1981-05-01',2850,NULL,30);
INSERT INTO EMP VALUES (7782,'MERKEL','MANAGER',7839,'1981-06-09',2450,NULL,10);
INSERT INTO EMP VALUES (7566,'PUTIN','MANAGER',7839,'1981-04-02',2975,NULL,20);
INSERT INTO EMP VALUES (7654,'CHIRACK','SALESMAN',7698,'1981-09-28',1250,1400,30);
INSERT INTO EMP VALUES (7499,'BAROSSO','SALESMAN',7698,'1981-02-20',1600,300,30);
INSERT INTO EMP VALUES (7844,'GATES','SALESMAN',7698,'1981-09-08',1500,0,30);
INSERT INTO EMP VALUES (7900,'BUFFETT','CLERK',7698,'1981-12-03',950,NULL,30);
INSERT INTO EMP VALUES (7521,'WALTON','SALESMAN',7698,'1981-02-22',1250,500,30);
INSERT INTO EMP VALUES (7902,'TOOSK','ANALYST',7566,'1981-12-03',3000,NULL,20);
INSERT INTO EMP VALUES (7369,'THATCHER','CLERK',7902,'1980-12-17',800,NULL,20);
INSERT INTO EMP VALUES (7788,'CARNEGIE','ANALYST',7566,'1982-12-09',3000,NULL,20);
INSERT INTO EMP VALUES (7876,'FORD','CLERK',7788,'1983-01-12',1100,NULL,20);
INSERT INTO EMP VALUES (7934,'ELISON','CLERK',7782,'1982-01-23',1300,NULL,10);
*/


CREATE TABLE SALGRADE (
 GRADE               NUMBER,
 LOSAL               NUMBER,
 HISAL               NUMBER);

INSERT INTO salgrade (GRADE, LOSAL, HISAL)
VALUES (1, 700, 1200);
INSERT INTO salgrade (GRADE, LOSAL, HISAL)
VALUES (2, 1201, 1400);
INSERT INTO salgrade(GRADE, LOSAL, HISAL)
VALUES (3, 1401, 2000);
INSERT INTO salgrade (GRADE, LOSAL, HISAL)
VALUES (4, 2001, 3000);
INSERT INTO salgrade(GRADE, LOSAL, HISAL)
VALUES (5, 3001, 9999);
COMMIT;


/* Shorter way, just for the comparison's sake
INSERT INTO salgrade VALUES (1,700,1200);
INSERT INTO salgrade VALUES (2,1201,1400);
INSERT INTO salgrade VALUES (3,1401,2000);
INSERT INTO salgrade VALUES (4,2001,3000);
INSERT INTO salgrade VALUES (5,3001,9999);
*/

DDL con't examples

--- https://livesql.oracle.com
----- https://livesql.oracle.com/apex/livesql/file/tutorial_D39T3OXOCOQ3WK9EWZ5JTJA.html

--triggers, indexes, etc

Examples

---- Links for related documentation
-- https://docs.oracle.com/en/database/oracle/oracle-database/19/development.html
-- https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/index.html
-- https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlqr/index.html


Escape in Like

--- Escape string in LIKE clause
SELECT
    dname, loc
FROM
    dept
WHERE
    loc LIKE '%\_YORK' ESCAPE '\'
;


Conditional expressions

CASE expression

  • Example
 
---- Case examples
-- Simple vs Searched 
SELECT DISTINCT job, sal,
  ---- Simple - same condition
  (CASE grade 
    WHEN 5 THEN 'Bossy'
    WHEN 4 THEN 'CEO monies'
    ELSE 'Peanuts' END) AS How_Much,
  ---- Searched - multiple conditions
  (CASE
    WHEN sal BETWEEN 500 AND 1200 THEN 'Rice'
    WHEN ((sal BETWEEN 1250 AND 1800) AND comm IS NULL ) THEN 'Beans'
    ELSE 'Coconuts' END) AS "Who's rich?"
  FROM emp JOIN salgrade
    ON sal BETWEEN losal AND hisal
  ORDER BY 2;
---------------------------------
---- https://docs.oracle.com/cd/B19306_01/server.102/b14200/expressions004.htm
  • Exercise - Calculate report per job, like below
    • summarize salary bigger than 2000, otherwise put 0
    • average of commissions lower than 1000, otherwise 0
    • how many people
-- 5 rows
----------------------------------------------------------------------
Job        Summarized Salary    Average Commission    Number of people
----------------------------------------------------------------------
ANALYST	   6000                 0                     2
CLERK	   0                    0                     4
SALESMAN   0	                200                   4
MANAGER	   8275                 0                     3
PRESIDENT  5000                 0                     1
----------------------------------------------------------------------

DECODE expression

  • Example
 
SELECT id, ename,
  DECODE (grade, 1, 'Basic',
                 2, 'Common',
                 3, 'Medium',
                 4, 'High',
                 5, 'Extreme',
                    'Sky') "Monies"
  FROM emp JOIN salgrade
    ON sal BETWEEN losal AND hisal
  ORDER BY grade DESC
;
--------------------------------
---- https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions040.htm
  • Exercise - make this Prima Aprilis report
    • differentiate concatenated answers accordingly to 'deptno'
Login        Descr
------------------------------------
BAROSSO	     Lovely Food
BLAIR	     Lovely Food
BUFFETT	     Lovely Food
BUSH	     ACCOUNTING Cheater
CARNEGIE     Posh ladie from LONDON
CHIRACK      Lovely Food
ELISON       ACCOUNTING Cheater
FORD         Posh ladie from LONDON
GATES        Lovely Food
MERKEL       ACCOUNTING Cheater
PUTIN        Posh ladie from LONDON
THATCHER     Posh ladie from LONDON
TOOSK        Posh ladie from LONDON
WALTON       Lovely Food
------------------------------------

JOIN the Party and make new Union

Gathering data from many tables

Exercise - report such story (-;

  • take only those complaining subordinates, who have boss earning less than 3000
  • boss should complain only about his inferior earning more than 1500
-- 14 rows
-----------------------------------------------------------------------------------------
Blamers for blamed, The Histerical History
-----------------------------------------------------------------------------------------
BAROSSO from SALES in PARIS blames his boss BLAIR for his very low salary grade 3
BUFFETT from SALES in PARIS blames his boss BLAIR for his very low salary grade 1
Boss BLAIR blames his slave BAROSSO from SALES in PARIS for his very lazy attitude
Boss BUSH blames his slave BLAIR from SALES in PARIS for his very lazy attitude
Boss BUSH blames his slave MERKEL from ACCOUNTING in NEW YORK for his very lazy attitude
Boss BUSH blames his slave PUTIN from RESEARCH in LONDON for his very lazy attitude
Boss PUTIN blames his slave CARNEGIE from RESEARCH in LONDON for his very lazy attitude
Boss PUTIN blames his slave TOOSK from RESEARCH in LONDON for his very lazy attitude
CARNEGIE from RESEARCH in LONDON blames his boss PUTIN for his very low salary grade 4
CHIRACK from SALES in PARIS blames his boss BLAIR for his very low salary grade 2
ELISON from ACCOUNTING in NEW YORK blames his boss MERKEL for his very low salary grade 2
GATES from SALES in PARIS blames his boss BLAIR for his very low salary grade 3
TOOSK from RESEARCH in LONDON blames his boss PUTIN for his very low salary grade 4
WALTON from SALES in PARIS blames his boss BLAIR for his very low salary grade 2
-----------------------------------------------------------------------------------------

Aggregation

  • Querying data grouped before it's filtered
  • GROUP BY
  • Oracle supports aggregation with clauses and built-in functions
---- https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Aggregate-Functions.html#GUID-62BE676B-AF18-4E63-BD14-25206FEA0848

GROUPING, ROLLUP, CUBE, GROUPING SETS

  • GROUPING function distinguishes superaggregate rows from regular grouped rows
  • ROLLUP and CUBE produce superaggregate rows - set of all values is represented by null
    • ROLLUP(n) - gives n+1 groupings; Usage examples: with SUM(), COUNT() gives subtotal values
    • CUBE(n) - gives 2n groupings; Usage example: cross-tabulation values
  • GROUPING SETS - multiple groupings of data, efficient aggregation by pruning the aggregates we don't need; UNION ALL
    • composite columns - within parentheses, db treats them as a unit while computing ROLLUP or CUBE
    • concatenated grouping sets - separate multiple grouping sets, ROLLUP, and CUBE operations with commas
      • db combines them into a single GROUP BY clause (cross-product of groupings from each grouping set)
------ EXAMPLES - GROUPING, ROLLUP, CUBE
---- Exercises
-- 1. Show also empty department, does it give the true answer?
-- 2. Change order of ROLLUP arguments, what is the difference? (Hide empty dept first)
-- 3. Replace ROLLUP with CUBE and compare the results - what can you see?
----
SELECT
    DECODE(GROUPING(dname), 1, 'ALL DEPARTMENTS', dname)
      AS department,
    DECODE(GROUPING(job), 1, 'All Jobs', job) AS job,
    COUNT(*) "Total Empl",
    round(AVG(sal) * 12) AS "Average Sal"
  FROM emp e, dept d
  WHERE d.deptno = e.deptno
  GROUP BY ROLLUP (dname, job)
  ORDER BY department, job;
-----------------------------------------
------ EXAMPLE - GROUPING SETS
---- Exercises
-- 1. Compare it with simple grouping (undo the comment)
-- 2. Wrap '(deptno, job)' set with CUBE and later ROLLUP 
-- 3. Improve it - group also by 'grade' and count it
----
select deptno, job, sum(sal)
  from emp
  where deptno in (20, 30)
  -- group by deptno, job
  group by GROUPING SETS( 
      (deptno, job), 
      (deptno), 
      (job) ) 
  order by deptno, job
;
  • More exercises
    1. Solve Ex.14 from here: SQL_Exercises. Do it without set operator and no subqueries allowed (-:
    2. Join all 3 tables and roll it up - aggregate with dname and grade. Sort it by Department and Total_Empl.
-- 15 rows
-------------------------------------------------------
Department       Grade        Total_Empl    Average_Sal
-------------------------------------------------------
ACCOUNTING	 4	      1	            29400
ACCOUNTING	 2	      1	            15600
ACCOUNTING	 5	      1	            60000
ACCOUNTING	 All GRADES   3	            35000
ALL DEPARTMENTS	 All GRADES   14	    24964
OPERATIONS	 (null)	      0	            (null)
OPERATIONS	 All GRADES   0	            (null)
RESEARCH	 1	      2	            12000
RESEARCH	 4	      3	            35900
RESEARCH	 All GRADES   5	            26340
SALES	         4	      1	            34200
SALES	         1	      1	            11400
SALES	         3	      2	            18600
SALES	         2	      2	            15000
SALES	         All GRADES   6	            18800
-------------------------------------------------------

Subqueries

  • Nested SELECT clause
  • Useful for example in improving aggregation results or with correlated data
---- Sub-queries 
--- https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Using-Subqueries.html#GUID-53A705B6-0358-4E2B-92ED-A83DE83DFD20
-- Most common places in SELECT clause, where the sub-clause can appear
SELECT
    SUBQ.cid,
    cname,
    ( select  'regid' from dual ) as "REGID",
    ( select count("REGION_ID") from HR."COUNTRIES" where region_id = 3 ) as "HowMany"
FROM
    (select
        "COUNTRY_ID" as cid,
        "COUNTRY_NAME" as cname,
        "REGION_ID" as regid
      from HR."COUNTRIES"
      where
        "COUNTRY_NAME" like '%a'
        and
        REGION_ID = 3
    ) SUBQ
WHERE
    cid LIKE '%N'
    AND
    (select
        avg("REGION_ID")
      from
        HR."COUNTRIES"
      where
        SUBQ.regid = "REGION_ID"
    ) < 10
;

Subq Exercises

Exercises

Hierarchical Query

---- Hierarchical queries examples
SELECT
    id
    , ename
    , mgr
--    , LEVEL
   FROM emp
--   START WITH id = 7839
   CONNECT BY PRIOR id = mgr
--   ORDER SIBLINGS BY ename
;

--- CONNECT_BY_ROOT examples
--1 name of each employee in department 10, 
--1 each manager at the highest level above that employee in the hierarchy,
--1 the number of levels between manager and employee,
--1 and the path between the two
SELECT ename "Employee", CONNECT_BY_ROOT ename "Manager",
   LEVEL-1 "Pathlen", SYS_CONNECT_BY_PATH(ename, '/') "Path"
   FROM emp
   WHERE LEVEL > 1 and deptno = 10
   CONNECT BY PRIOR id = mgr
   ORDER BY "Employee", "Manager", "Pathlen", "Path";

--2 returns the total salary of each employee in department 10 and all employees above that employee in the hierarchy
SELECT name, SUM(sal) "Total_Salary" FROM (
   SELECT CONNECT_BY_ROOT ename as name, sal
      FROM emp
      WHERE deptno = 10
      CONNECT BY PRIOR id = mgr)
      GROUP BY name
   ORDER BY name, "Total_Salary";

HQ Exercises

1. Show all employees in hierarchical order. Begin with the employee whose job is Analyst.

  • Filter it by Manager instead
  • Select only the first two levels of the management hierarchy
Organogram      ID      MGR     JOB
-------------  ------  ------  ----------
CARNEGIE         7788    7566  ANALYST
  FORD           7876    7788  CLERK
TOOSK            7902    7566  ANALYST
  THATCHER       7369    7902  CLERK

2. Solve Exercise nr 41 from here: SQL_Programming

  • Use Hierarchical Query

3. Improve Blamers exercise with HQ - show bosses department name and location

  • "...his boss BLAIR from SALES in PARIS..."
  • "Boss BUSH from ACCOUNTING in NEW YORK blames..."
  • And then show only bosses who work in a different department than their subordinates

Regular Expressions

---- Regexp examples
SELECT
    *
FROM
    emp
---- Name starts with B or T
-- WHERE REGEXP_LIKE(ename, '^(B|T).*$')
---- Name contains double adjacent consonants 
-- WHERE REGEXP_LIKE (ename, '([bcdfghjklmnpqrstvwxz])\1', 'i')
ORDER BY
    ename
;
---- Exercises
--- Job ends with K or R
--- Manager's id contains double adjacent digit


---- REGEXP_INSTR , REGEXP_REPLACE , and REGEXP_SUBSTR
-- https://livesql.oracle.com/apex/livesql/docs/sqlrf/regexp_instr/find-location.html
-- https://livesql.oracle.com/apex/livesql/docs/sqlrf/regexp_replace/match-replace1.html


---- REGEXP_REPLACE examples
--- Replaces two or more spaces with one space
SELECT
    REGEXP_REPLACE('37-450      Stalowa Wola,       Podkarpackie,  Poland', '( ){2,}', ' ') "REGEXP_REPLACE Example"
FROM DUAL;

--- Converts 'camel case' to 'underscore':
WITH functions_names as (
    SELECT 'GetAllTheBookingForms' f FROM dual union all
    SELECT 'PrepareDelegatesDetails' f FROM dual union all
    SELECT 'HelperFunction' f FROM dual
)
SELECT
    f "Function name",
    lower( regexp_replace(f, '([A-Z0-9])', '_\1', 2) )
    as "FN after"
FROM functions_names;

---- Exercise
---* Convert the format of a hiredate from '2019-01-01' into '01.01.2019'

Casting Data Types

---- https://docs.oracle.com/javadb/10.8.3.0/ref/rrefsqlj33562.html

Functions

  • Oracle db has plenty of built in functions
  • Allows also to create user-define functions
---- https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Functions.html#GUID-D079EFD3-C683-441F-977E-2C9503089982

Date and time functions

---- https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Single-Row-Functions.html#GUID-5652DBC2-41C7-4F07-BEDD-DAF620E35F3C

Analytic Functions

Example 1

  • Find the 2 highest paid employees in each department
SELECT deptno, ename, sal
FROM
(
  SELECT
    deptno, ename, sal,
    ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY sal desc) rn
  FROM emp
)
WHERE rn <= 2
ORDER BY deptno, sal DESC;
-----------------------------
---- https://docs.oracle.com/cd/E11882_01/server.112/e41084/functions004.htm#SQLRF06174
-- 6 rows
-----------------------
DEPTNO  ENAME     SAL
-----------------------
10	BUSH	  5000
10	MERKEL	  2450
20	CARNEGIE  3000
20	TOOSK	  3000
30	BLAIR	  2850
30	BAROSSO	  1600
-----------------------

Exercise 1

  • Count median of salaries by manager
    • only for people from departments 10 and 30
    • sort it by hierarchy and id
-- 9 rows
-------------------------------------
MGR     ID      SAL     Median_by_Mgr
-------------------------------------
7698    7499	1600	1250
7698    7521	1250	1250
7698    7654	1250	1250
7698    7844	1500	1250
7698    7900	950	1250
7782    7934	1300	1300
7839    7698	2850	2650
7839    7782	2450	2650
(null)  7839	5000	5000
-------------------------------------

Example 2

------ LAG vs LEAD example - no need to use 'self-join'
--- For each Clerk find the salary of the employee hired just before
SELECT hiredate, ename, sal,
  LAG(sal, 1, 0) OVER (ORDER BY hiredate) AS prev_sal
  FROM emp
  WHERE job = 'CLERK'
  ORDER BY hiredate;
----------------------
--- For each employee in Department 30 find the hire date of the employee hired just after
SELECT hiredate, ename,
  LEAD(hiredate, 1) OVER (ORDER BY hiredate) AS "NextHired"
  FROM emp
  WHERE deptno = 30
  ORDER BY hiredate;

Example 3

  • Ten-bucket histogram on the sal column
    • Employees with sal >= to the max value are assigned to the overflow bucket, 11
SELECT id, ename, sal,
  WIDTH_BUCKET(sal, 500, 3500, 10) "Sal Group"
  FROM emp
  ORDER BY 4;
-- 14 rows
------------------------------------
ID      ENAME       SAL    Sal Group
------------------------------------
7369	THATCHER    900    2
7900	BUFFETT     950    2
7654	CHIRACK     1250   3
7934	ELISON      1300   3
7521	WALTON      1250   3
7876	FORD        1100   3
7499	BAROSSO     1600   4
7844	GATES       1500   4
7782	MERKEL      2450   7
7698	BLAIR       2850   8
7788	CARNEGIE    3000   9
7566	PUTIN       2975   9
7902	TOOSK       3000   9
7839	BUSH        5000   11
------------------------------------

Example 4

---- FIRST vs LAST example
/* Calculate:
- the minimum salary among the employees who make the lowest commission
- the maximum salary among the employees who make the highest commission
- for each employee within the department */
SELECT ename, deptno, sal,
  MIN(sal) KEEP (DENSE_RANK FIRST ORDER BY comm)
    OVER (PARTITION BY deptno) "Worst",
  MAX(sal) KEEP (DENSE_RANK LAST ORDER BY comm)
    OVER (PARTITION BY deptno) "Best"
FROM emp
ORDER BY deptno, sal;

More exercises

Additional examples and exercises

City Examples

Use script citiPriceTbl.sql in mike session

  • Median of prices
    • For each paper and date, after the 1st of Apr but before the 6th (both inclusive):
      • count prices median
      • filter it by paper 5 and 6
      • roll it up
      • sort it with paper ID and date
-- 15 rows
-------------------------------------------
Market_ID         PriceDate    Median_Price
-------------------------------------------
ALL Market IDs    All DATES    112,5
ISIN_5            21/04/01     220
ISIN_5            21/04/02     222
ISIN_5            21/04/03     229
ISIN_5            21/04/04     223
ISIN_5            21/04/05     215
ISIN_5            21/04/06     212
ISIN_5            All DATES    221
ISIN_6            21/04/01     11
ISIN_6            21/04/02     11
ISIN_6            21/04/03     12
ISIN_6            21/04/04     11
ISIN_6            21/04/05     13
ISIN_6            21/04/06     10
ISIN_6            All DATES    11
-------------------------------------------
  • WIDTH_BUCKET histogram
    • For prices of papers calculate ten-bucket report
      • sort it by the bucket
      • use range of [10, 220)
      • use regular expression to filter papers between 1-4
-- 40 rows
---------------------------------------------
MARKET_ID    PDATE       PRICE    Price Group
---------------------------------------------
ISIN_3       21/04/05    30       1
ISIN_2       21/04/01    30       1
ISIN_2       21/04/04    30       1
ISIN_4       21/04/01    14       1
(...)
---------------------------------------------
  • REAL issue from City
/*
From a bigger table with stocks/bonds prices per day:
-- Get last 5 security valuations (this part is already done - PRICE_TABLE, but try to think how to manage that part of the problem in separate)
-- Check if the prices has changed in the last 5 days
---- Flag it with 'Y' if price didn't change, otherwise with 'N'
*/
-- comment only, don't show it       4/6/2021  4/7/2021  4/8/2021  4/9/2021  4/10/2021	

Market_ID    Security description    Date-4    Date-3    Date-2    Date-1    Date    IS_Stale_Price
---------    --------------------    ------    ------    ------    ------    ----    --------------
   ISIN_1	        Security1    48        52	 49        48        51      N
   ISIN_2	        Security2    33        33        33        33        33      Y
   ISIN_3	        Security3    31        34        32        34        33      N
   ISIN_4	        Security4    200       208       214       226       209     N
   ISIN_5	        Security5    212       212       212       212       212     Y
   ISIN_6	        Security6    10        10        11        14        10      N
   ISIN_7	        Security7    12        13        12        13        10      N
   ISIN_8	        Security8    73        74        73        69        71      N
  • Row Pattern Matching
    • Usually useful to query stock price data
    • V-shape example - find all cases where stock prices dipped to a bottom price and then rose
    • Let's do the v-shape for prices in Citi table
SELECT *
FROM price_table MATCH_RECOGNIZE (
     PARTITION BY market_id
     ORDER BY pdate
     MEASURES STRT.pdate AS start_pdate,
              LAST(DOWN.pdate) AS bottom_pdate,
              LAST(UP.pdate) AS end_pdate
     ONE ROW PER MATCH
     AFTER MATCH SKIP TO LAST UP
     PATTERN (STRT DOWN+ UP+)
     DEFINE
        DOWN AS DOWN.price < PREV(DOWN.price),
        UP AS UP.price > PREV(UP.price)
     ) MR
ORDER BY MR.market_id, MR.start_pdate;
Exercise - homework: try to solve previous issue (last 5 days stock prices) with 'row pattern matching' method (-:

PL/SQL Fundamentals

PL/SQL Fundamentals