Betrifft: Oracle9i New Features – SQL und PL/SQL Autor: Patrick Malcherek ([email protected]) Art der Info: Technische Background Info – Teil 3 (April 2002) Quelle: Aus dem NF9i-Kurs und NF9i-Techno-Circle der Trivadis Einleitung Im ersten und zweiten Teil haben wir neue SQL-Funktionen, die ANSI kompatible Join Syntax und das Sub-Query Factoring als neue Features der Version 9i kennen gelernt. In diesem dritten Teil werden wir nun untersuchen, welche neuen Möglichkeiten sich uns bei Hierarchischen Abfragen und CURSOR Expressions mit Oracle 9i bieten. Hierarchische Abfragen Die hierarchischen Abfragen hat Oracle in der neuesten Version 9i überarbeitet. So galten bisher die folgenden Einschränkungen: eine Sortierung mit der ORDER BY-Klausel übersteuerte die gefundene Hierarchie ein JOIN oder eine VIEW, die einen JOIN beinhaltete, konnte nicht verwendet werden eine Master-Detail-Beziehung musste demzufolge in der gleichen Tabelle abgelegt sein eine Sub-Query war nicht erlaubt JOIN Beziehung Einen JOIN in einer CONNECT BY-Klausel zu verwenden ist nun problemlos möglich. SQL> SELECT LPAD('/', 2 * level,'-') || ename "NAME" 2 , deptno 3 , loc 4 , LEVEL 5 FROM emp NATURAL JOIN dept 6 START WITH mgr IS NULL 7 CONNECT BY PRIOR empno = mgr; NAME -------------- DEPTNO LOC -------- ---------- LEVEL ------- -/KING ---/JONES -----/SCOTT -------/ADAMS -----/FORD -------/SMITH ---/CLARK -----/MILLER ---/BLAKE -----/ALLEN ... 10 20 20 20 20 20 10 10 30 30 NEW YORK DALLAS DALLAS DALLAS DALLAS DALLAS NEW YORK NEW YORK CHICAGO CHICAGO 1 2 3 4 3 4 2 3 2 3 Aufgrund dieser neuen Option ist auch eine Master-Detail Beziehung in unterschiedlichen Tabellen machbar. Mit CREATE TABLE AS SELECT wird eine Hilfstabelle erstellt, welche den Detail-Schlüssel mgr enthält. SQL> CREATE TABLE t_mgr AS SELECT empno, mgr FROM emp; Die entsprechende Abfrage mit einer zusammengesetzten Master-Detail Beziehung lautet dann: SQL> SELECT LPAD('/', 2 * LEVEL,'-') || e.ename "NAME" 2 , e.deptno 3 , LEVEL 4 FROM emp e JOIN t_mgr m ON (e.empno = m.empno) 5 START WITH m.mgr IS NULL 6 CONNECT BY PRIOR e.empno = m.mgr; NAME -------------/KING ---/CLARK -----/MILLER DEPTNO -------10 10 10 LEVEL ------1 2 3 Selbst eine CONNECT BY-Klausel auf eine VIEW, welche einen JOIN beinhaltet, kann nun verwendet werden. Hierzu wird zuerst eine entsprechende VIEW erstellt: SQL> CREATE VIEW v_emp_dept 2 (empno, ename, mgr, job, deptno, loc) 3 AS 4 SELECT empno 5 , ename 6 , mgr 7 , job 8 , deptno 9 , loc 10 FROM emp NATURAL JOIN dept; View wurde angelegt. SQL> SELECT LPAD('/', 2 * LEVEL,'-') || ename "NAME" 2 , job 3 , loc 4 , LEVEL 5 FROM v_emp_dept 6 START WITH mgr IS NULL 7 CONNECT BY PRIOR empno = mgr; NAME --------------/KING ---/JONES -----/SCOTT -------/ADAMS -----/FORD -------/SMITH ---/CLARK -----/MILLER ---/BLAKE -----/ALLEN -----/WARD -----/JAMES -----/TURNER -----/MARTIN JOB -----------PRESIDENT MANAGER ANALYST CLERK ANALYST CLERK MANAGER CLERK MANAGER SALESMAN SALESMAN CLERK SALESMAN SALESMAN LOC ---------NEW YORK DALLAS DALLAS DALLAS DALLAS DALLAS NEW YORK NEW YORK CHICAGO CHICAGO CHICAGO CHICAGO CHICAGO CHICAGO LEVEL ------1 2 3 4 3 4 2 3 2 3 3 3 3 3 Sortierung Ein weiteres Problem stellte bisher dar, dass Resultate einer hierarchischen Anweisung mittels ORDER BY nicht sinnvoll sortiert werden konnten. Wie das Beispiel zeigt, wird dadurch die hierarchische Form zerstört: SQL> SELECT LPAD('/', 2 * LEVEL,'-') || ename "NAME" 2 , job 3 , LEVEL 4 FROM emp 5 START WITH mgr IS NULL 6 CONNECT BY PRIOR empno = mgr 7 ORDER BY ename; NAME ---------------------/ADAMS -----/ALLEN JOB ---------CLERK SALESMAN LEVEL ------4 3 ---/BLAKE ---/CLARK -----/FORD -----/JAMES ---/JONES -/KING -----/MARTIN -----/MILLER -----/SCOTT -------/SMITH -----/TURNER -----/WARD MANAGER MANAGER ANALYST CLERK MANAGER PRESIDENT SALESMAN CLERK ANALYST CLERK SALESMAN SALESMAN 2 2 3 3 2 1 3 3 3 4 3 3 Bisher mussten die Daten mit einem CREATE TABLE AS SELECT & ORDER BY Befehl bearbeitet werden, um darüber die CONNECT BY-Klausel laufen zu lassen. In Oracle 9i steht uns nun die ORDER SIBLINGS BY-Klausel zum Sortieren zur Verfügung. Das Schlüsselwort SIBLINGS bewirkt, dass beim Sortiervorgang die vorhandene Hierarchie berücksichtigt wird: SQL> SELECT LPAD('/', 2 * level,'-') || ename "NAME" 2 , job 3 , LEVEL 4 FROM emp 5 START WITH mgr IS NULL 6 CONNECT BY PRIOR empno = mgr 7 ORDER SIBLINGS BY ename; NAME --------------/KING ---/BLAKE -----/ALLEN -----/JAMES -----/MARTIN -----/TURNER -----/WARD ---/CLARK -----/MILLER ---/JONES -----/FORD -------/SMITH -----/SCOTT -------/ADAMS JOB ---------PRESIDENT MANAGER SALESMAN CLERK SALESMAN SALESMAN SALESMAN MANAGER CLERK MANAGER ANALYST CLERK ANALYST CLERK LEVEL ------1 2 3 3 3 3 3 2 3 2 3 4 3 4 SYS_CONNECT_BY_PATH Neu ist auch die SYS_CONNECT_BY_PATH-Klausel. Sie veranschaulicht den hierarchischen Weg zum Root-Element. Der zweite Parameter gibt das Trennzeichen zwischen den verschiedenen Stufen an. SQL> SELECT SYS_CONNECT_BY_PATH(ename,'/') "NAME" 2 , job 3 , LEVEL 4 FROM emp 5 START WITH mgr IS NULL 6 CONNECT BY PRIOR empno = mgr 7 ORDER SIBLINGS BY ename; NAME -----------------------/KING /KING/BLAKE /KING/BLAKE/ALLEN /KING/BLAKE/JAMES /KING/BLAKE/MARTIN /KING/BLAKE/TURNER /KING/BLAKE/WARD /KING/CLARK /KING/CLARK/MILLER /KING/JONES /KING/JONES/FORD /KING/JONES/FORD/SMITH /KING/JONES/SCOTT /KING/JONES/SCOTT/ADAMS JOB ---------PRESIDENT MANAGER SALESMAN CLERK SALESMAN SALESMAN SALESMAN MANAGER CLERK MANAGER ANALYST CLERK ANALYST CLERK LEVEL ------1 2 3 3 3 3 3 2 3 2 3 4 3 4 Sub-Query Eine Sub-Query in der CONNECT BY-Klausel, wie im folgenden Beispiel ersichtlich, ist weiterhin nicht erlaubt. SQL> SELECT ename 2 , empno 3 , mgr 4 FROM emp 5 START WITH mgr IS NULL 6 CONNECT BY PRIOR empno = (SELECT mgr 7 FROM emp 8 WHERE ename = 'KING'); from emp * FEHLER in Zeile 2: ORA-01473: Unterabfragen in CONNECT BY-Klausel nicht zulässig Neu ist nur die Möglichkeit eine Sub-Query in der FROM Klausel anzusprechen, da seit 9i JOIN-Beziehungen erlaubt sind. SQL> 2 3 4 5 6 7 8 9 SELECT LPAD('/', 2 * level,'-') || e.ename "NAME" , e.deptno , dname , LEVEL FROM emp e, (SELECT dname, deptno FROM dept ) d WHERE d.deptno = e.deptno START WITH mgr IS NULL CONNECT BY PRIOR empno = mgr; CURSOR Expressions Cursor Expressions werden schon seit Oracle 8i in SQL unterstützt. Neu ist, dass sie nun auch in PL/SQL verarbeitet werden können. Es besteht jetzt die Möglichkeit, eine Cursor Expression als REF CURSOR Argument an eine Funktion zu übergeben. Der Rückgabewert der Cursor Expression ist ein Nested Cursor. Jede Row des Result-Sets enthält einerseits skalare Werte und andererseits Cursoren, welche über Sub-Queries produziert werden, die wiederum von der Hauptquery abhängig sind. Das Result-Set kann über Nested Loops abgearbeitet werden. Zuerst werden die äußeren Rows gelesen und für jede Column, die eine Cursor Expression enthält, wird ein weiterer Loop durchgeführt. PL/SQL unterstützt Queries mit Cursor Expressions, als einen Teil der Cursor-, REF CURSOR Deklaration und REF CURSOR Variablen. Auch können die Cursor Expressions in Dynamic SQL Queries verwendet werden. CURSOR (subquery) Der Nested Cursor wird automatisch von Oracle geöffnet, sobald die den Cursor enthaltene Row aus dem Parent Cursor genommen wird. Geschlossen wird der Nested Cursor, wenn er vom Benutzer explizit geschlossen wird der Parent Cursor wiederholt ausgeführt wird der Parent Cursor geschlossen wird eine Exception in der Cursor-Verarbeitung auftritt Das folgende Beispiel zeigt eine Cursor Expression in PL/SQL. Die SELECT- Klausel in der Cursor- Deklaration enthält wiederum einen Cursor. SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 DECLARE TYPE refcursor IS REF CURSOR; /* Deklaration der CURSOR Expression */ CURSOR c_dept IS SELECT d.deptno , d.dname , d.loc , CURSOR (SELECT empno , ename , job , hiredate FROM emp e WHERE e.deptno = d.deptno) FROM dept d; /* Deklaration der REF_CURSOR Variablen welche später das Ergebnis des Nested Cursors enthält */ emps refcursor; v_deptno dept.deptno%TYPE; v_dname dept.dname%TYPE; v_empno emp.empno%TYPE; v_ename emp.ename%TYPE; v_job emp.job%TYPE; v_hiredate emp.hiredate%TYPE; v_loc dept.loc%TYPE; BEGIN /* Öffnen des Parent Cursors */ OPEN c_dept; LOOP /* Fetchen der Columns c_dept.v_deptno, c_dept.v_dname … danach folgt ein Loop durch das Result-Set des Nested Cursors */ FETCH c_dept INTO v_deptno, v_dname, v_loc, emps; EXIT WHEN c_dept%NOTFOUND; DBMS_OUTPUT.put_line ('Department : ' || v_dname); LOOP /* einholen der Daten vom Nested Cursor, dieser muss nicht mehr geöffnet werden */ FETCH emps INTO v_empno, v_ename, v_job, v_hiredate; EXIT WHEN emps%NOTFOUND; DBMS_OUTPUT.put_line ('-- Employee : '|| v_ename); END LOOP; END LOOP; /* schließen des Parent Cursors, Nested Cursor muss nicht extra geschlossen werden */ CLOSE c_dept; END; Department : ACCOUNTING -- Employee : CLARK -- Employee : KING -- Employee : MILLER Department : RESEARCH -- Employee : SMITH -- Employee : JONES ... Das nächste Beispiel zeigt eine Funktion, welche ein Result-Set (Query mittels REFCURSOR) entgegen nimmt. Die Funktion berechnet die Summe von Gehältern gemäß den Attributen DEPTNO und JOB, wobei jeweils mehr als eine DEPTNO und mehr als ein JOB vorkommen können. SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 CREATE FUNCTION f_complexSum(p_cursor SYS_REFCURSOR) /*das REF CURSOR Argument muss als SYS_REFCURSOR in einer Funktion deklariert werden */ RETURN NUMBER IS BEGIN DECLARE v_deptno emp.deptno%TYPE; v_job emp.job%TYPE; v_sumSal NUMBER; v_totSal NUMBER := 0; BEGIN LOOP FETCH p_cursor INTO v_deptno, v_job; EXIT WHEN (p_cursor%NOTFOUND); SELECT SUM(sal) INTO v_sumSal FROM emp WHERE job = v_job AND deptno = v_deptno; v_totSal := v_totSal + v_sumSal; END LOOP; CLOSE p_cursor; RETURN(v_totSal); END; END; Die einfachste Form des Aufrufs: SQL> SELECT f_complexSum(CURSOR ( 2 SELECT 10, 'PRESIDENT' FROM dual)) “f_complexSum” 3 FROM dual; f_complexSum -----------5000 Der Aufruf eines Result-Sets mit mehreren Rows: SQL> SELECT f_complexSum(CURSOR( 2 SELECT deptno, job FROM emp 3 WHERE comm IS NOT NULL)) “f_complexSum” 4 FROM dual; f_complexSum -----------22400 Hier die Möglichkeit die Funktion mit unterschiedlichen Queries aufzurufen. Dadurch wird ermittelt: die Summe der Angestellten mit dem gleichem Job in anderen Abteilungen die Summe der Angestellten mit anderem Job in der gleichen Abteilung SQL> SELECT e.deptno 2 , e.ename 3 , e.job 4 , e.sal 5 , f_complexSum(CURSOR ( 6 SELECT DISTINCT od.deptno, od.job 7 FROM emp od 8 WHERE od.deptno != e.deptno 9 AND od.job = e.job)) "my job other depts" 10 , f_complexSum(CURSOR ( 11 SELECT DISTINCT oj.deptno, oj.job 12 FROM emp oj 13 WHERE oj.deptno = e.deptno 14 AND oj.job != e.job)) "my dept other jobs" 15 FROM emp e; DEPTNO -----20 30 30 20 ENAME -----SMITH ALLEN WARD JONES JOB SAL my job other depts my dept other j… -------- ----- ------------------ ----------------CLERK 800 2250 8975 SALESMAN 1600 0 3800 SALESMAN 1250 0 3800 MANAGER 2975 5300 7900 30 30 10 20 10 30 20 30 20 10 MARTIN BLAKE CLARK SCOTT KING TURNER ADAMS JAMES FORD MILLER SALESMAN MANAGER MANAGER ANALYST PRESIDENT SALESMAN CLERK CLERK ANALYST CLERK 1250 2850 2450 3000 5000 1500 1100 950 3000 1300 0 5425 5825 0 0 0 2250 3200 0 2850 3800 6550 6300 4875 3750 3800 8975 8450 4875 7450 Zur Veranschaulichung des vorangegangenen Aufrufs, folgt ein Aufruf ohne die Funktion: SQL> SELECT e.deptno 2 , e.ename 3 , e.job 4 , e.sal 5 , (CURSOR (SELECT DISTINCT od.deptno, od.job 6 FROM emp od 7 WHERE od.deptno != e.deptno 8 AND od.job = e.job)) "my job other depts" 9 , (CURSOR (SELECT DISTINCT oj.deptno, oj.job 10 FROM emp oj 11 WHERE oj.deptno = e.deptno 12 AND oj.job != e.job)) "my dept other jobs" 13 FROM emp e; ... 10 MILLER CLERK 1300 CURSOR STATEMENT: 5 CURSOR STATEMENT: 6 CURSOR STATEMENT : 5 Æ Other departments DEPTNO -----20 30 JOB --------CLERK CLERK CURSOR STATEMENT : 6 Æ Other jobs DEPTNO -----10 10 ... JOB --------MANAGER PRESIDENT Welche Einschränkungen gibt es ? Eine Cursor Expression kann nicht in einem impliziten Cursor verwendet werden. Cursor Expressions können an folgenden Stellen verwendet werden: - Ein SELECT Statement (jedoch nicht geschachtelt in einer weiteren Query Expression) oder in der Sub- Query einer Cursor Expression. Als Argumente zu Table Functions, in der FROM Klausel eines SELECT Statements. Cursor Expressions können nur in der äußersten SELECT Liste der QuerySpezifikation erscheinen Cursor Expressions können nicht in einer View verwendet werden. Es ist nicht möglich BIND und EXECUTE Operationen an Cursor Expressions auszuführen. Fazit Wie wir sehen konnten gibt es einige neue Features, die sehr nützlich für hierarchische Abfragen sind und dort die Arbeit z.B. bei gewünschten Sortierungen sehr vereinfachen. Auch die neuen Möglichkeiten im Bereich der Cursor Expressions ermöglichen es, komplexe Programmieraufgaben sehr kompakt zu lösen. Alles in allem können wir sagen, dass mit der Version 9i einige Neuerungen kamen, die sehr gut in der Praxis anwendbar sind und zum Teil durchaus in ihrer Einfachheit bestechen. Jetzt liegt es nur noch an uns, sie auch zu unserem Vorteil einzusetzen. Falls Sie noch mehr über die New Features von 9i erfahren wollen, würde es uns freuen, Sie in einem unserer 9i-Kurs (NF9i, AI9-A, AI9-B) begrüssen zu dürfen. Patrick Malcherek Trivadis AG Cityforum im Eichsfeld Ferdinand-Stuttmann-Str. 13 D-65428 Rüsselsheim Mail: [email protected] Tel: +49 6142 210 18 0 Fax: +49 6142 210 18 29 Internet: http://www.trivadis.com