Oracle DB – Fallstricke für Entwickler: Zeilenausgabe limitieren

09.11.2015

Als Softwareentwickler stolpern wir immer wieder über Applikationsfehler, deren Ursache in der Datenbankschicht liegen. Oft sind es sehr banale Fehler, die sich bereits mit geringen Grundkenntnissen über die Datenbank leicht vermeiden lassen.
Daher möchte ich in meinen Beiträgen alltägliche Fallstricke aufzeigen, über die Softwareentwickler nur allzu gerne stolpern.

In diesem Beitrag soll es um die Limitierung von Zeilenausgaben gehen. Viele RDBM-Systeme (relationale Datenbankmanagementsysteme) erlauben bereits mit einfacher SQL Syntax die Limitierung der Ergebnismenge. Bei Oracle erfolgt dies mit Hilfe eines Tricks, und genau hier verbirgt sich der erste Fallstrick…

Die Aufgabe:
Die Geschäftsführung unseres fiktiven Unternehmens „ClearCode GmbH“ möchte im Rahmen der alljährlichen Weihnachtsfeier die vermeidlich produktivsten Softwareentwickler küren. Hierfür sollen die drei Entwickler mit den meisten geschriebenen Codezeilen ermittelt werden.
Die Datenbank Tabelle T_DEV_PRODUCTIVITY enthält bereits alle Entwickler mit deren Anzahl programmierten Codezeilen

SELECT DEV_PRODUCTIVITY_ID,
   DEVELOPER_NAME,
   TO_CHAR(LINES_OF_CODE,'999G999') LINES_OF_CODE
FROM T_DEV_PRODUCTIVITY;

ROWNUM_Screen1

Im ersten Schritt verwenden wir nun die für Oracle übliche vorgehensweiße zur Limitierung der Zeilen mittels der Pseudospalte ROWNUM.
Die Oracle Dokumentation beschreibt die Funktion der Spalte wie folgt:
“For each row returned by a query, the ROWNUM pseudocolumn returns a number indicating the order in which Oracle selects the row from a table or set of joined rows. The first row selected has a ROWNUM of 1, the second has 2, and so on.
You can use ROWNUM to limit the number of rows returned by a query, as in this example:
SELECT * FROM employees WHERE ROWNUM < 11;”
(siehe ROWNUM Pseudocolumn – Oracle Documentation)

Somit gibt folgende Abfrage nur die ersten 3 Zeilen der ermittelten Treffer zurück:

SELECT 
   DEV_PRODUCTIVITY_ID,
   DEVELOPER_NAME,
   TO_CHAR(LINES_OF_CODE,'999G999') LINES_OF_CODE,
   ROWNUM
FROM T_DEV_PRODUCTIVITY
WHERE ROWNUM < = 3
;

ROWNUM_Screen2

Um nun die drei Entwickler mit den meist geschriebenen Codezeilen zu ermitteln, verändern wir nun die Reihenfolge der Ausgabe. Wir beginnen mit der höchsten Anzahl an produzierten Codezeilen. Dies wird mittels ORDER BY in absteigender (DESC) Reihenfolge der Spalte LINES_OF_CODE umgesetzt:

SELECT 
   DEV_PRODUCTIVITY_ID,
   DEVELOPER_NAME,
   TO_CHAR(LINES_OF_CODE,'999G999') LINES_OF_CODE,
   ROWNUM
FROM T_DEV_PRODUCTIVITY
WHERE ROWNUM < = 3
ORDER BY LINES_OF_CODE DESC;

Diese Abfrage liefert nun folgendes Ergebnis:

ROWNUM_Screen3

Wie wir sehen können wird nun die Ausgabe in absteigender Reihenfolge der programmierten Codezeilen sortiert. Auch die Einschränkung auf drei Entwickler ist weiterhin gegeben.
Doch halt, irgendetwas passt hier nicht. Betrachten wir hier im Vergleich die Einträge in der Tabelle T_DEV_PRODUCTIVITY. Sollten hier nicht die Entwickler „Hugo Hacker“, "Tim Trainee“ und „Debbie Debug“ angezeigt werden? Wieso erhalten wir hier ein falsches Ergebnis?
Um die besinnliche Weihnachtsfeier nicht zu gefährden sollten wir uns unser Statement noch einmal genauer ansehen.
Hier hat uns die Datenbank einen Streich gespielt, da wir ein wichtiges Detail übersehen haben: Die Sortierung der ermittelten Zeilen mittels ORDER BY erfolgt erst NACHDEM das komplette Ergebnis des SELECT Statements, inklusive der Vergabe der ROWNUM-Werte, generiert wurde. Somit erhält der erste Datensatz weiterhin die ROWNUM 1, der zweite Datensatz die ROWNUM 2, … und erst im Anschluss erfolgt die Sortierung dieser ermittelten Zeilen mittels ORDER BY.

Um dieses Problem zu umgehen muss die Sortierung mittels ORDER BY vor der Vergabe der ROWNUM geschehen. Dies lässt sich wie folgt formulieren:

SELECT dev.*, ROWNUM
FROM
   (SELECT 
      DEV_PRODUCTIVITY_ID,
      DEVELOPER_NAME,
      TO_CHAR(LINES_OF_CODE,'999G999') LINES_OF_CODE,
      ROWNUM inner_rownum
   FROM T_DEV_PRODUCTIVITY
   ORDER BY LINES_OF_CODE DESC
   ) dev
WHERE ROWNUM < =3;

Hiermit erhalten wir nun folgendes Ergebnis:

ROWNUM_Screen4

Nun konnten wir tatsächlich die drei Entwickler mit den meisten Zeilen an erstelltem Code ermitteln und den Frieden der Weihnachtsfeier damit retten.
Die Verschachtelung der SELECT-Abfragen lässt sich hier leider nicht vermeiden. Hier bietet Oracle zur Limitierung von Zeilen keine LIMIT-OFFSET-Klausel an, wie es bei anderen Datenbanken der Fall ist. Daher muss diese etwas umständliche Formulierung mit der Verschachtelung gewählt werden.
Erst mit Oracle 12c ist es möglich, die neu eingeführte row_limiting_clause (siehe auch Database SQL Language Reference) zu verwenden. Diese sähe dann für unser Beispiel wie folgt aus:

SELECT 
   DEV_PRODUCTIVITY_ID, 
   DEVELOPER_NAME, 
   TO_CHAR(LINES_OF_CODE,'999G999') LINES_OF_CODE 
FROM T_DEV_PRODUCTIVITY 
ORDER BY LINES_OF_CODE DESC 
FETCH FIRST 3 ROWS ONLY; 

Wem das jetzt noch nicht genug war, dem sei der Artikel “On ROWNUM and Limiting Results” von Tom Kyte empfohlen.
http://www.oracle.com/technetwork/issue-archive/2006/06-sep/o56asktom-086197.html

Ausblick:
Die Limitierung ist eine von mehreren Möglichkeiten die gegebene Aufgabenstellung zu lösen. Eine etwas „fortgeschrittenere“-Variante zur Ermittlung der Entwickler mit der höchsten Anzahl an programmiertem Code wäre mittels der Oracle Funktion RANK zu realisieren. Doch dazu mehr in einem anderen Blogbeitrag.

Zurück zur Übersicht

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*Pflichtfelder

*