Sayantan's Blog On Oracle

Sayantan's blogs on Oracle

Learn Oracle with Sayantan

WITH Clause in Oracle

WITH Clause in Oracle

WHAT IS THE USE OF WITH CLAUSE IN ORACLE:

WITH clause was introduced in Oracle 9i Release 2. Inside WITH clause we can define multiple query block and provide a name to each query block which is also called sub-query refactoring. Then in the main query we can use each query block as a data source. WITH clause helps us to write complex queries in a simple manner. This method is often used to improve query speed for complex sub-queries.

Syntax of SQL queries having WITH clause is little bit confusing as the query starts with WITH clause rather than SELECT clause. We have to define the each query block first and finally at the end the main query is constructed where the above query blocks are used as a data source or temp table.

EXAMPLES OF WITH CLAUSE IN ORACLE:

EXAMPLE 1:
--WITH Clause in Oracle: Example

WITH
LOCATION_DTLS
AS
 (SELECT A.LOCATION_ID, 
         A.STREET_ADDRESS, 
         A.POSTAL_CODE, 
         A.CITY, 
         A.STATE_PROVINCE, 
         A.COUNTRY_ID,
         B.COUNTRY_NAME,
         C.REGION_NAME
  FROM LOCATIONS A,
       COUNTRIES B,
       REGIONS C
  WHERE A.COUNTRY_ID = B.COUNTRY_ID
  AND B.REGION_ID = C.REGION_ID
 ),
DEPARTMENT_DTLS
AS
 (SELECT A.DEPARTMENT_ID,
         A.DEPARTMENT_NAME, 
         A.MANAGER_ID,
         A.LOCATION_ID,
         COUNT(B.EMPLOYEE_ID) NO_OF_EMPLOYEES,
         MAX(B.SALARY) MAX_SALARY,
         MIN(B.SALARY) MIN_SALARY,
         SUM(B.SALARY) TOTAL_SALARY,
         MAX(B.HIRE_DATE) MAX_HIRE_DATE,
         MIN(B.HIRE_DATE) MIN_HIRE_DATE
  FROM DEPARTMENTS A,
       EMPLOYEES B
  WHERE A.DEPARTMENT_ID = B.DEPARTMENT_ID
  GROUP BY A.DEPARTMENT_ID,
           A.DEPARTMENT_NAME, 
           A.MANAGER_ID, 
           A.LOCATION_ID
 ),
EMPLOYEE_DTLS
AS
 (SELECT A.EMPLOYEE_ID,
         A.FIRST_NAME, 
         A.LAST_NAME, 
         A.EMAIL, 
         A.PHONE_NUMBER, 
         A.HIRE_DATE, 
         A.JOB_ID, 
         A.SALARY,
         A.DEPARTMENT_ID,
         (SELECT FIRST_NAME||' '||LAST_NAME
          FROM EMPLOYEES
          WHERE EMPLOYEE_ID = A.MANAGER_ID
          AND ROWNUM = 1
         ) MANAGER_NAME
  FROM EMPLOYEES A
  ORDER BY EMPLOYEE_ID
 )
SELECT P.EMPLOYEE_ID EMPID,
       P.FIRST_NAME||' '||P.LAST_NAME EMP_NAME, 
       P.EMAIL, 
       P.PHONE_NUMBER PHONE, 
       P.HIRE_DATE, 
       P.JOB_ID, 
       P.SALARY,
       Q.DEPARTMENT_NAME DEPARTMENT,
       R.STREET_ADDRESS STREET, 
       R.POSTAL_CODE ZIPCODE, 
       R.CITY, 
       R.STATE_PROVINCE STATE,
       R.COUNTRY_NAME COUNTRY,
       R.REGION_NAME REGION,
       P.MANAGER_NAME MANAGER,
       Q.NO_OF_EMPLOYEES NO_OF_EMP,
       Q.MAX_SALARY MAX_SAL, 
       Q.MIN_SALARY MIN_SAL, 
       Q.TOTAL_SALARY DEPT_TOTAL, 
       Q.MAX_HIRE_DATE, 
       Q.MIN_HIRE_DATE
FROM EMPLOYEE_DTLS P,
     DEPARTMENT_DTLS Q,
     LOCATION_DTLS R
WHERE P.DEPARTMENT_ID = Q.DEPARTMENT_ID
AND Q.LOCATION_ID = R.LOCATION_ID
ORDER BY P.EMPLOYEE_ID;
WITH Clause in Oracle: Example Output
WITH Clause in Oracle: Output
WITH Clause in Oracle: Example Output
WITH Clause in Oracle: Example Output

In the above example, we have defined 3 query blocks named LOCATION_DTLS, DEPARTMENT_DTLS and EMPLOYEE_DTLS. Then in the main query we have used those 3 query blocks as data source to build the query.

EXAMPLE 2:

We can use one query block in other query blocks before using them in the main query.

--WITH Clause in Oracle: Example

WITH
LOCATION_DTLS
AS
 (SELECT A.LOCATION_ID, 
         A.STREET_ADDRESS, 
         A.POSTAL_CODE, 
         A.CITY, 
         A.STATE_PROVINCE, 
         A.COUNTRY_ID,
         B.COUNTRY_NAME,
         C.REGION_NAME
  FROM LOCATIONS A,
       COUNTRIES B,
       REGIONS C
  WHERE A.COUNTRY_ID = B.COUNTRY_ID
  AND B.REGION_ID = C.REGION_ID
 ),
DEPARTMENT_DTLS
AS
 (SELECT A.DEPARTMENT_ID,
         A.DEPARTMENT_NAME, 
         A.MANAGER_ID,
         A.LOCATION_ID,
         P.STREET_ADDRESS,
         P.POSTAL_CODE,
         P.CITY,
         P.STATE_PROVINCE,
         P.COUNTRY_NAME,
         P.REGION_NAME,
         COUNT(B.EMPLOYEE_ID) NO_OF_EMPLOYEES,
         MAX(B.SALARY) MAX_SALARY,
         MIN(B.SALARY) MIN_SALARY,
         SUM(B.SALARY) TOTAL_SALARY,
         MAX(B.HIRE_DATE) MAX_HIRE_DATE,
         MIN(B.HIRE_DATE) MIN_HIRE_DATE
  FROM DEPARTMENTS A,
       EMPLOYEES B,
       LOCATION_DTLS P
  WHERE A.DEPARTMENT_ID = B.DEPARTMENT_ID
  AND A.LOCATION_ID = P.LOCATION_ID
  GROUP BY A.DEPARTMENT_ID,
           A.DEPARTMENT_NAME, 
           A.MANAGER_ID, 
           A.LOCATION_ID,
           P.STREET_ADDRESS,
           P.POSTAL_CODE,
           P.CITY,
           P.STATE_PROVINCE,
           P.COUNTRY_NAME,
           P.REGION_NAME
 ),
EMPLOYEE_DTLS
AS
 (SELECT A.EMPLOYEE_ID,
         A.FIRST_NAME, 
         A.LAST_NAME, 
         A.EMAIL, 
         A.PHONE_NUMBER, 
         A.HIRE_DATE, 
         A.JOB_ID, 
         A.SALARY,
         A.DEPARTMENT_ID,
         (SELECT FIRST_NAME||' '||LAST_NAME
          FROM EMPLOYEES
          WHERE EMPLOYEE_ID = A.MANAGER_ID
          AND ROWNUM = 1
         ) MANAGER_NAME
  FROM EMPLOYEES A
  ORDER BY EMPLOYEE_ID
 )
SELECT P.EMPLOYEE_ID EMPID,
       P.FIRST_NAME||' '||P.LAST_NAME EMP_NAME, 
       P.EMAIL, 
       P.PHONE_NUMBER PHONE, 
       P.HIRE_DATE, 
       P.JOB_ID, 
       P.SALARY,
       Q.DEPARTMENT_NAME DEPARTMENT,
       Q.STREET_ADDRESS STREET, 
       Q.POSTAL_CODE ZIPCODE, 
       Q.CITY, 
       Q.STATE_PROVINCE STATE,
       Q.COUNTRY_NAME COUNTRY,
       Q.REGION_NAME REGION,
       P.MANAGER_NAME MANAGER,
       Q.NO_OF_EMPLOYEES NO_OF_EMP,
       Q.MAX_SALARY MAX_SAL, 
       Q.MIN_SALARY MIN_SAL, 
       Q.TOTAL_SALARY DEPT_TOTAL, 
       Q.MAX_HIRE_DATE, 
       Q.MIN_HIRE_DATE
FROM EMPLOYEE_DTLS P,
     DEPARTMENT_DTLS Q
WHERE P.DEPARTMENT_ID = Q.DEPARTMENT_ID
ORDER BY P.EMPLOYEE_ID;
WITH Clause in Oracle: Example Output
WITH Clause in Oracle: Example Output
WITH Clause in Oracle: Example Output
WITH Clause in Oracle: Example Output

In the above example, LOCATION_DTLS query block is used in DEPARTMENT_DTLS block.

EXAMPLE 3:

WITH CLAUSE ENHANCEMENT IN ORACLE 12c:

In oracle 12c the declarative function of the WITH clause can be used to define a PLSQL functions and procedures.

USING FUNCTION INSIDE WITH CLAUSE:

EXAMPLE 1: SINGLE FUNCTION IN WITH CLAUSE
--WITH Clause in Oracle: Example : SINGLE FUNCTION IN WITH CLAUSE 

WITH
  FUNCTION FUN_GET_MAX_SAL(P_DEPTID DEPARTMENTS.DEPARTMENT_ID%TYPE)
  RETURN NUMBER
  IS
    V_MAX_SAL EMPLOYEES.SALARY%TYPE := 0;
  BEGIN
    SELECT MAX(SALARY)
    INTO V_MAX_SAL
    FROM EMPLOYEES
    WHERE DEPARTMENT_ID = P_DEPTID;
    --
    RETURN V_MAX_SAL;
  EXCEPTION
    WHEN OTHERS THEN
      V_MAX_SAL := 0;
  END;  
  --
  SELECT A.EMPLOYEE_ID,
         A.FIRST_NAME, 
         A.LAST_NAME, 
         A.EMAIL, 
         A.PHONE_NUMBER, 
         A.HIRE_DATE, 
         A.JOB_ID, 
         A.SALARY,
         A.DEPARTMENT_ID,
         (SELECT FIRST_NAME||' '||LAST_NAME
          FROM EMPLOYEES
          WHERE EMPLOYEE_ID = A.MANAGER_ID
          AND ROWNUM = 1
         ) MANAGER_NAME,
         FUN_GET_MAX_SAL(DEPARTMENT_ID) MAX_DEPT_SAL
  FROM EMPLOYEES A
  WHERE A.DEPARTMENT_ID IN (20, 30);
WITH Clause in Oracle: Example Output
EXAMPLE 2: MULTIPLE FUNCTIONS IN WITH CLAUSE
--WITH Clause in Oracle: Example : MULTIPLE FUNCTIONS IN WITH CLAUSE

WITH
  FUNCTION FUN_GET_MAX_SAL(P_DEPTID DEPARTMENTS.DEPARTMENT_ID%TYPE)
  RETURN NUMBER
  IS
    V_MAX_SAL EMPLOYEES.SALARY%TYPE := 0;
  BEGIN
    SELECT MAX(SALARY)
    INTO V_MAX_SAL
    FROM EMPLOYEES
    WHERE DEPARTMENT_ID = P_DEPTID;
    --
    RETURN V_MAX_SAL;
  EXCEPTION
    WHEN OTHERS THEN
      V_MAX_SAL := 0;
  END;  
  --
  FUNCTION FUN_GET_AVG_SAL(P_DEPTID DEPARTMENTS.DEPARTMENT_ID%TYPE)
  RETURN NUMBER
  IS
    V_AVG_SAL EMPLOYEES.SALARY%TYPE := 0;
  BEGIN
    SELECT AVG(SALARY)
    INTO V_AVG_SAL
    FROM EMPLOYEES
    WHERE DEPARTMENT_ID = P_DEPTID;
    --
    RETURN V_AVG_SAL;
  EXCEPTION
    WHEN OTHERS THEN
      V_AVG_SAL := 0;
  END;
  --   
  SELECT A.EMPLOYEE_ID,
         A.FIRST_NAME, 
         A.LAST_NAME, 
         A.EMAIL, 
         A.PHONE_NUMBER, 
         A.HIRE_DATE, 
         A.JOB_ID, 
         A.SALARY,
         A.DEPARTMENT_ID,
         (SELECT FIRST_NAME||' '||LAST_NAME
          FROM EMPLOYEES
          WHERE EMPLOYEE_ID = A.MANAGER_ID
          AND ROWNUM = 1
         ) MANAGER_NAME,
         FUN_GET_MAX_SAL(DEPARTMENT_ID) MAX_DEPT_SAL,
         FUN_GET_AVG_SAL(DEPARTMENT_ID) AVG_DEPT_SAL
  FROM EMPLOYEES A
  WHERE A.DEPARTMENT_ID IN (20, 30);
WITH Clause in Oracle: Example Output

USING PROCEDURE INSIDE WITH CLAUSE:

You can only define a procedure in the declaration section of WITH clause:

EXAMPLE1:
--WITH Clause in Oracle: Example: USING PROCEDURE INSIDE WITH CLAUSE

WITH
  PROCEDURE SP_GET_MAX_SAL(P_DEPTID DEPARTMENTS.DEPARTMENT_ID%TYPE)
  IS
    V_MAX_SAL EMPLOYEES.SALARY%TYPE := 0;
  BEGIN
    SELECT MAX(SALARY)
    INTO V_MAX_SAL
    FROM EMPLOYEES
    WHERE DEPARTMENT_ID = P_DEPTID;
    --
    DBMS_OUTPUT.PUT_LINE('MAXIMUM SALARY OF DEPARTMENT '||P_DEPTID||' IS '||V_MAX_SAL);
  EXCEPTION
    WHEN OTHERS THEN
      V_MAX_SAL := 0;
  END;  
  --  
  SELECT A.EMPLOYEE_ID,
         A.FIRST_NAME, 
         A.LAST_NAME, 
         A.EMAIL, 
         A.PHONE_NUMBER, 
         A.HIRE_DATE, 
         A.JOB_ID, 
         A.SALARY,
         A.DEPARTMENT_ID,
         (SELECT FIRST_NAME||' '||LAST_NAME
          FROM EMPLOYEES
          WHERE EMPLOYEE_ID = A.MANAGER_ID
          AND ROWNUM = 1
         ) MANAGER_NAME
  FROM EMPLOYEES A
  WHERE A.DEPARTMENT_ID IN (20, 30);
WITH Clause in Oracle: Example Output

But if we want to use procedure inside the WITH clause then we have to create a function as well and the procedure will be called from that function.

EXAMPLE:
--WITH Clause in Oracle: Example

WITH
  PROCEDURE SP_GET_MAX_SAL(P_DEPTID  IN DEPARTMENTS.DEPARTMENT_ID%TYPE,
                           P_MAX_SAL OUT EMPLOYEES.SALARY%TYPE)
  IS
    V_MAX_SAL EMPLOYEES.SALARY%TYPE := 0;
  BEGIN
    SELECT MAX(SALARY)
    INTO V_MAX_SAL
    FROM EMPLOYEES
    WHERE DEPARTMENT_ID = P_DEPTID;
    --
    P_MAX_SAL := V_MAX_SAL;
    DBMS_OUTPUT.PUT_LINE('MAXIMUM SALARY OF DEPARTMENT '||P_DEPTID||' IS '||V_MAX_SAL);
  EXCEPTION
    WHEN OTHERS THEN
      V_MAX_SAL := 0;
  END;  
  --  
  FUNCTION FUN_GET_AVG_SAL(P_DEPTID DEPARTMENTS.DEPARTMENT_ID%TYPE)
    RETURN VARCHAR2
  IS
    V_AVG_SAL EMPLOYEES.SALARY%TYPE := 0;
    V_MAX_SAL EMPLOYEES.SALARY%TYPE := 0;
  BEGIN
    SELECT AVG(SALARY)
    INTO V_AVG_SAL
    FROM EMPLOYEES
    WHERE DEPARTMENT_ID = P_DEPTID;
    --
    SP_GET_MAX_SAL(P_DEPTID, V_MAX_SAL);
    RETURN 'MAXIMUM SALARY -> '||V_MAX_SAL||' AND AVERAGE SALARY -> '||V_AVG_SAL;
  EXCEPTION
    WHEN OTHERS THEN
      V_AVG_SAL := 0;
  END;   
  --
  SELECT A.EMPLOYEE_ID,
         A.FIRST_NAME, 
         A.LAST_NAME, 
         A.EMAIL, 
         A.PHONE_NUMBER, 
         A.HIRE_DATE, 
         A.JOB_ID, 
         A.SALARY,
         A.DEPARTMENT_ID,
         (SELECT FIRST_NAME||' '||LAST_NAME
          FROM EMPLOYEES
          WHERE EMPLOYEE_ID = A.MANAGER_ID
          AND ROWNUM = 1
         ) MANAGER_NAME,
         FUN_GET_AVG_SAL(DEPARTMENT_ID) SALARY_INFO
  FROM EMPLOYEES A
  WHERE A.DEPARTMENT_ID IN (20, 30);
WITH Clause in Oracle: Example Output

PLSQL SUPPORT FOR WITH CLAUSE:

PLSQL does not support WITH clause with function or procedure. Attempting to do that will cause compilation error.

EXAMPLE:
--WITH Clause in Oracle: Example

SET SERVEROUTPUT ON;
BEGIN
  FOR I IN (WITH
            FUNCTION FUN_GET_MAX_SAL(P_DEPTID DEPARTMENTS.DEPARTMENT_ID%TYPE)
              RETURN NUMBER
            IS
              V_MAX_SAL EMPLOYEES.SALARY%TYPE := 0;
            BEGIN
              SELECT MAX(SALARY)
              INTO V_MAX_SAL
              FROM EMPLOYEES
              WHERE DEPARTMENT_ID = P_DEPTID;
              --
              RETURN V_MAX_SAL;
            EXCEPTION
              WHEN OTHERS THEN
                V_MAX_SAL := 0;
            END;  
            --
            SELECT A.EMPLOYEE_ID,
                   A.FIRST_NAME, 
                   A.LAST_NAME, 
                   A.EMAIL, 
                   A.PHONE_NUMBER, 
                   A.HIRE_DATE, 
                   A.JOB_ID, 
                   A.SALARY,
                   A.DEPARTMENT_ID,
                   (SELECT FIRST_NAME||' '||LAST_NAME
                    FROM EMPLOYEES
                    WHERE EMPLOYEE_ID = A.MANAGER_ID
                    AND ROWNUM = 1
                   ) MANAGER_NAME,
                   FUN_GET_MAX_SAL(DEPARTMENT_ID) MAX_DEPT_SAL
            FROM EMPLOYEES A
            WHERE A.DEPARTMENT_ID IN (20, 30)      
          )
  LOOP
    DBMS_OUTPUT.PUT_LINE('MAX SALARY INFO -> '||I.MAX_DEPT_SAL);
  END LOOP;
END;
/
WITH Clause in Oracle: Example Output

Error report -
ORA-06550: line 3, column 22:
PL/SQL: ORA-00905: missing keyword
ORA-06550: line 2, column 13:
PL/SQL: SQL Statement ignored
ORA-06550: line 6, column 51:
PLS-00103: Encountered the symbol ";" when expecting one of the following:

   loop
06550. 00000 -  "line %s, column %s:\n%s"
*Cause:    Usually a PL/SQL compilation error.
*Action:

Dynamic sql can provide a work around solution.

EXAMPLE:
--WITH Clause in Oracle: Example

SET SERVEROUTPUT ON;
DECLARE
  V_SQL    VARCHAR2(32767);
  V_CURSOR SYS_REFCURSOR;
  TYPE T_VALUE IS TABLE OF VARCHAR2(32767) INDEX BY PLS_INTEGER;
  V_VALUE T_VALUE;
BEGIN
  V_SQL := 'WITH
            FUNCTION FUN_GET_MAX_SAL(P_DEPTID DEPARTMENTS.DEPARTMENT_ID%TYPE)
              RETURN NUMBER
            IS
              V_MAX_SAL EMPLOYEES.SALARY%TYPE := 0;
            BEGIN
              SELECT MAX(SALARY)
              INTO V_MAX_SAL
              FROM EMPLOYEES
              WHERE DEPARTMENT_ID = P_DEPTID;
              --
              RETURN V_MAX_SAL;
            EXCEPTION
              WHEN OTHERS THEN
                V_MAX_SAL := 0;
            END;  
            --
            SELECT ''EMPLOYEE_ID -> ''||A.EMPLOYEE_ID||'''||' NAME -> ''||A.FIRST_NAME||'' ''||A.LAST_NAME||'' -> DEPARTMENTID -> ''||A.DEPARTMENT_ID||'' -> MAX_DEPT_SAL -> '||'''||FUN_GET_MAX_SAL(A.DEPARTMENT_ID) MAX_DEPT_SAL
            FROM EMPLOYEES A
            WHERE A.DEPARTMENT_ID IN (20, 30)';      
  OPEN V_CURSOR FOR V_SQL;
  FETCH V_CURSOR BULK COLLECT INTO V_VALUE;
  CLOSE V_CURSOR;
  FOR I IN 1..V_VALUE.COUNT
  LOOP
    DBMS_OUTPUT.PUT_LINE('CURSOR VALUE -> '||V_VALUE(I));
  END LOOP;
END;
/
WITH Clause in Oracle: Example :

RELATED TOPICS:

Leave a Comment

Your email address will not be published.