4 Dynamisches Open SQL Adrian Görler und Ulrich Koch ABAP zeichnet sich insbesondere dadurch aus, dass Datenbankzugriffe über Open SQL bereits in die Sprache eingebettet sind. Diese enge Integration führt zu einer optimalen Performance, gewährleistet die Plattformunabhängigkeit und macht eine Syntaxprüfung schon während der Kompilation möglich. Dennoch können Probleme auftreten, die nicht mit statischem Open SQL gelöst werden können, wie z.B. generische Datenbankabfragen. Das liegt daran, dass nicht alle Datenbanktabellen, Tabellenfelder oder logischen Bedingungen bereits während der Kompilation bekannt sein müssen. Solchen Situationen kann man zwar begegnen, indem man ein ABAP-Programm dynamisch generiert (siehe auch Kapitel 2) und ausführt, doch dieser Ansatz birgt die folgenden schwer wiegenden Nachteile: 왘 Für wenig erfahrene Programmierer ist es meist ebenso schwierig, einen Quell- textgenerator zu schreiben, wie ihn anschließend daraufhin zu überprüfen, ob er den eigenen Erwartungen entsprechend funktioniert. 왘 Was die Rechenzeit angeht, ist das persistente Generieren eines ABAP-Pro- gramms für dynamische Zwecke eine äußerst aufwändige Operation. 왘 Wenn der generierte Quelltext durch die Verwendung der Anweisung GENERATE SUBROUTINE POOL nur transient vorhanden ist, kann die Fehleranalyse relativ schwierig werden. Dieses Kapitel beschäftigt sich mit dynamischem Open SQL, einer Variante von Open SQL, die eine wesentlich bessere Art und Weise darstellt, Projekte zu verwirklichen, bei denen ein dynamischer oder semidynamischer Datenbankzugriff nötig ist, weil manche Teile von SQL-Anweisungen erst zur Laufzeit angegeben werden können. Dynamisches Open SQL wird nicht durch die Nachteile der Quelltextgenerierung behindert, sondern stattdessen werden alle möglichen Syntaxprüfungen schon während der Kompilation durchgeführt. Der erhöhte Aufwand für die Syntaxanalyse der dynamischen Komponenten der Anweisung zur Laufzeit ist minimal, und das Debugging des Programms gestaltet sich einfach, da kein zwischengeschalteter Quelltext generiert wird. Im Verlauf der Entwicklung von ABAP wurde der Funktionsumfang von dynamischem Open SQL von Release 3.0 bis Release 6.40 kontinuierlich erweitert. Insbesondere zu ABAP Release 6.10 wurde die Funktionspalette noch einmal deutlich umfassender. Falls nicht anders angegeben, bezieht sich dieses Kapitel auf die seit Release 4.6 in ABAP verfügbaren Techniken. Die ABAP-Beispiele dieses Kapi- Dynamisches Open SQL 135 tels, die ein höheres Release voraussetzen, sind als solche gekennzeichnet. Sie sollen dazu dienen, Ihnen einige der neueren Möglichkeiten bis einschließlich Release 6.40 vorzustellen. Wir zeigen Ihnen, wie die Namen von Datenbanktabellen zur Laufzeit angegeben und wie Daten in eine dynamisch angelegte interne Tabelle ausgelesen werden können. Dem schließt sich eine Erläuterung der dynamischen WHERE-, SELECT-, GROUP BY-, HAVING- und FROM-Klauseln an. Darüber hinaus werden die Ausnahmebehandlung und dynamische Änderungsoperationen behandelt. Dies alles geschieht mithilfe einiger leicht nachvollziehbarer Programme, die Sie mit der Funktionsweise von dynamischem Open SQL vertraut machen. 4.1 Allgemeines Konzept Dynamisches Open SQL ist eine Variante von Open SQL, die es erlaubt, Teile von SQL-Anweisungen erst zur Laufzeit anzugeben; die dazugehörige Technik ist die in Kapitel 2 beschriebene dynamische Token-Angabe. Eine SQL-Anweisung wird dabei nicht vollständig dynamisch angegeben: Die Teile der Anweisung, die statisch bekannt sind, können wie gewohnt als statischer ABAP-Code geschrieben werden. Anstelle der Teile des Open-SQL-Befehls, die erst zur Laufzeit bekannt sind, steht im Quelltext ein eingeklammertes ABAP-Datenobjekt als Platzhalter. Das Programm muss Anweisungen enthalten, die den ABAP-Quelltext für die dynamischen Fragmente erzeugen und im entsprechenden Datenobjekt speichern. Zur Laufzeit wird der ABAP-Quelltext der dynamischen Fragmente analysiert und in die statischen Teile der Anweisung eingefügt. Dynamische und statische Teile werden als eine einzige SQL-Anweisung zur Datenbank geschickt. Dieser Prozess ist für die Datenbank vollständig unsichtbar. 4.1.1 Tabellennamen zur Laufzeit angeben Unser erstes, sehr einfaches Beispiel in Listing 4.1 veranschaulicht das Arbeiten mit einem dynamisch angegebenen Tabellennamen. Das Programm zeigt eine Liste mit den Namen von drei Datenbanktabellen an. Über einen Doppelklick kann der Benutzer eine Tabelle in der Liste auswählen, woraufhin das Programm die Anzahl der Einträge in der ausgewählten Tabelle anzeigt. 1 PROGRAM dynamic_sql_1. 2 3 DATA: tabname TYPE tabname, 4 count TYPE i. 5 6 START-OF-SELECTION. 7 WRITE: / 'SPFLI', / 'SFLIGHT', / 'SBOOK'. 136 Dynamisches Open SQL 8 9 AT LINE-SELECTION. 10 READ CURRENT LINE LINE VALUE INTO tabname. 11 12 SELECT COUNT(*) FROM (tabname) INTO count. 13 14 WRITE: / 'The table', tabname(7), 'contains', 15 count, 'entries.'. Listing 4.1 Dynamisch angegebener Tabellenname Beim Ereignis AT LINE-SELECTION wird der Inhalt der ausgewählten Zeile in das Feld tabname eingelesen. In der Anweisung SELECT wird der Name der Datenbanktabelle nicht statisch angegeben, stattdessen gibt die FROM-Klausel (tabname) an, dass der Name der Datenbanktabelle aus der Variablen tabname eingelesen und in die SELECT-Anweisung eingesetzt werden soll (Zeile 12). Bei der Ausführung der SELECT-Anweisung wird das Feld tabname darauf geprüft, ob es einen gültigen Namen einer Datenbanktabelle enthält. Ist das nicht der Fall, wird eine Ausnahme ausgelöst. In ABAP Release 4.6 und früher musste ein dynamischer Tabellenname in Großbuchstaben angegeben werden. Diese Einschränkung ist seit ABAP Release 6.10 aufgehoben. Zumeist geht es beim Arbeiten mit Open SQL darum, Datensätze aus der Datenbank zu beschaffen. Als problematisch erweist sich bei der dynamischen Angabe von Datenbanktabellen die Wahl eines geeigneten Arbeitsbereiches, der die selektierten Daten aufnehmen soll. Nahe liegend und praktikabel wäre ein Container, der groß genug ist, die Daten aus der dynamisch angegebenen Tabelle aufzunehmen. Auf die Felder der abgerufenen Datensätze könnte dann zugegriffen werden, indem auf den Container über einen geeigneten Typ zugegriffen wird, z.B. durch Zuweisung an ein entsprechend typisiertes Feldsymbol. Leider hat dieser Ansatz zwei Nachteile: 왘 Es muss ein Container ausgewählt werden, der für die Aufnahme einer beliebi- gen Datenbanktabelle ausreichend dimensioniert ist.1 왘 Der Ansatz erfordert einen flachen Container, also ein Container ohne Strings, interne Tabellen oder Referenzen, da für tiefe Container kein uneingeschränktes Casting möglich ist. 1 Außerdem muss der Container passend zur Struktur der Datenbanktabelle ausgerichtet sein, was z.B. durch die Aufnahme eines Felds vom Typ f in eine Rahmenstruktur des Containers erreicht werden kann. Allgemeines Konzept 137 Obgleich die Wahl eines sehr großen Containers üblicherweise zu einer Ressourcenvergeudung führt, kann dies in der Praxis häufig toleriert werden. Die Notwendigkeit eines flachen Containers ist die schwerer wiegende der beiden Einschränkungen, da sie die Ausführung des Programms mit Tabellen, die Strings enthalten, was in ABAP seit Release 6.10 möglich ist, verhindert.2 Mit dem Befehl CREATE DATA, der seit Release 4.6 verfügbar ist, gibt es eine bessere Alternative, einen geeigneten Arbeitsbereich anzulegen. Mit ihm können Datenobjekte eines zur Laufzeit angegebenen Typs dynamisch erzeugt werden. Mit dieser Technik kann bereits ein einfaches Werkzeug für generelle Abfragen geschrieben werden. Im folgenden Beispiel (siehe Listing 4.2) kann der Benutzer eine von drei Datenbanktabellen aus einer Liste durch Doppelklick auswählen, woraufhin das Programm deren Inhalt anzeigt. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 PROGRAM dynamic_sql_2. DATA: dref TYPE REF TO data, tabname TYPE tabname. FIELD-SYMBOLS: <row> TYPE any, <component> TYPE any. START-OF-SELECTION. WRITE: / 'SPFLI', / 'SFLIGHT', / 'SBOOK'. AT LINE-SELECTION. READ CURRENT LINE LINE VALUE INTO tabname. * dynamically create appropriate internal table CREATE DATA dref TYPE (tabname). ASSIGN dref->* TO <row>. * fetch the data SELECT * FROM (tabname) UP TO 20 ROWS INTO <row>. * display the result NEW-LINE. DO. 2 Wenn in der Datenbankstruktur Spalten vom Typ SSTRING, STRING oder RAWSTRING vorkommen, muss der Arbeitsbereich kompatibel zur Datenbankstruktur sein. 138 Dynamisches Open SQL 26 27 28 29 30 31 32 33 ASSIGN COMPONENT sy-index OF STRUCTURE <row> TO <component>. IF sy-subrc <> 0. EXIT. " no more components ENDIF. WRITE: <component>. ENDDO. ENDSELECT. Listing 4.2 Dynamischer Arbeitsbereich Zunächst wird die Referenzvariable dref mit dem Typ REF TO DATA deklariert, der eine Referenz auf ein beliebiges Datenobjekt enthalten kann (Zeile 3). Anschließend wird ein Arbeitsbereich mit dem Zeilentyp tabname durch den ABAP-Befehl CREATE DATA in Zeile 16 dynamisch erzeugt. Nach der Ausführung dieser Anweisung verweist die Datenreferenz dref auf diesen Arbeitsbereich, auf den nicht direkt zugegriffen werden kann, da ein mit CREATE DATA erzeugter Arbeitsbereich im Gegensatz zu mit DATA deklarierten Arbeitsbereichen ein so genanntes anonymes Datenobjekt ist, auf das nicht über einen Namen zugegriffen werden kann. Stattdessen wird der Arbeitsbereich, auf den dref verweist, dem Feldsymbol <row> zugewiesen (Zeile 17). Nun können die Daten aus der Tabelle tabname sicher in <row> eingelesen werden, da gewährleistet ist, dass die Datenbanktabelle mit dem Arbeitsbereich typkompatibel ist. Die Ergebnismenge wird auf 20 Zeilen begrenzt: 20 21 22 33 SELECT * FROM (tabname) UP TO 20 ROWS INTO <row>. [...] ENDSELECT. Alle Komponenten von <row> werden in den Zeilen 25 bis 32 in einer Schleife durchlaufen und die abgerufenen Daten werden in Listenform angezeigt. 4.1.2 Dynamische interne Tabelle Seit ABAP Release 6.10 können nicht nur Strukturen eines dynamisch angegebenen Typs, sondern auch interne Tabellen eines dynamisch angegebenen Zeilentyps dynamisch erzeugt werden; ab Release 6.40 ist es sogar möglich, Struktur- bzw. Zeilentyp nicht nur dynamisch anzugeben, sondern sogar dynamisch zu konstruieren (siehe Abschnitt 4.8). Mit dieser Möglichkeit können Sie im Beispielcode die Allgemeines Konzept 139 SELECT-ENDSELECT-Schleife durch einen so genannten Array-Fetch ersetzen (siehe Listing 4.3). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 PROGRAM dynamic_sql_3. DATA: tabname TYPE tabname, dref TYPE REF TO data. FIELD-SYMBOLS <itab> TYPE STANDARD TABLE. START-OF-SELECTION. WRITE: / 'SPFLI', / 'SFLIGHT', / 'SBOOK'. AT LINE-SELECTION. READ CURRENT LINE LINE VALUE INTO tabname. CREATE DATA dref TYPE TABLE OF (tabname). ASSIGN dref->* TO <itab>. SELECT * FROM (tabname) UP TO 20 ROWS INTO TABLE <itab>. CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY' EXPORTING i_structure_name = tabname TABLES t_outtab = <itab>. Listing 4.3 Dynamische interne Tabelle (ab Release 6.10) In der Anweisung CREATE DATA erzeugt der von der Anweisung DATA her bekannte Zusatz TABLE OF eine anonyme interne Tabelle. Diese Tabelle wird dem Feldsymbol <itab> zugewiesen und hinter SELECT als Zielbereich verwendet (Zeile 14 bis 19). Der Array-Fetch verkürzt die Ausführungsdauer, weil die Datenbankschnittstelle nur noch einmal für alle Zeilen der Ergebnismenge und nicht mehrfach für jede selektierte Zeile einzeln aufgerufen wird. Anstatt die Ergebnismenge als Liste anzuzeigen, wird sie in diesem Beispiel über ein ALV-Grid-Control dargestellt. Dazu wird der Funktionsbaustein REUSE_ALV_ GRID_DISPLAY gerufen, dem der dynamisch angegebene Tabellenname tabname 140 Dynamisches Open SQL und die Ergebnismenge <itab> übergeben werden (Zeile 21 bis 25). Die passenden Spaltenüberschriften mit Quick-Info-Text werden automatisch erzeugt. 4.2 Dynamische WHERE-Klausel Eine sehr nützliche Eigenschaft von dynamischem Open SQL ist es, dass die WHERE-Klausel dynamisch angegeben werden kann. Listing 4.4 veranschaulicht ihren Gebrauch. Das Programm fragt den Benutzer nach den Namen der Abflugund Ankunftsorte eines Fluges. Der Benutzer kann wählen, ob Datensätze gesucht werden sollen, für die beide Bedingungen gleichzeitig erfüllt werden müssen (op_and), oder ob es genügt, dass eine Bedingung erfüllt ist (op_or). Das Programm zeigt anschließend eine Liste aller entsprechenden Flüge. 1 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 PROGRAM dynamic_sql_4. PARAMETERS: depart arrive op_and op_or TYPE spfli-cityfrom, TYPE spfli-cityto, RADIOBUTTON GROUP 1 DEFAULT 'X', RADIOBUTTON GROUP 1. DATA: where_tab source_line operator carrid connid TABLE OF edpline, edpline, c LENGTH 3 VALUE 'AND', spfli-carrid, spfli-connid. TYPE TYPE TYPE TYPE TYPE IF op_or = 'X'. operator = 'OR'. ENDIF. CONCATENATE 'cityfrom = ''' depart '''' INTO source_line. APPEND source_line TO where_tab. APPEND operator TO where_tab. CONCATENATE 'cityto = ''' arrive '''' INTO source_line. APPEND source_line TO where_tab. SELECT carrid connid cityfrom cityto FROM spfli INTO (carrid, connid, depart, arrive) Dynamische WHERE-Klausel 141 29 WHERE (where_tab). 30 WRITE: / carrid, connid, depart, arrive. 31 ENDSELECT. Listing 4.4 Dynamische WHERE-Klausel Zunächst gilt es, die interne Tabelle where_tab zu deklarieren, die den Quelltext der dynamischen WHERE-Klausel aufnehmen soll. Dies geschieht in den Zeilen 8 und 9. Daraufhin wird der Quelltext für die WHERE-Klausel erzeugt. Sie soll die Werte der Datenbankspalten cityfrom bzw. cityto auf die Werte der Parameter depart bzw. arrive beschränken. Während die beteiligten Feldnamen statisch bekannt sind, ist der Operator (AND oder OR), der die beiden logischen Bedingungen verbindet, erst zur Laufzeit bekannt. Zum Anlegen des Quelltextes der WHERE-Klausel werden statische und dynamische Teile verkettet und an where_tab angefügt (Zeile 18 bis 24). In der Verkettung werden doppelte Hochkommata verwendet, um ein Hochkomma in einem Textfeldliteral darzustellen. Seit Release 6.10 könnten statt Textfeldliteralen auch in Backquotes (`) eingeschlossene Stringliterale verwendet werden, in denen ein einzelnes Hochkomma angegeben werden kann – und umgekehrt (siehe Abschnitt 4.3). Angenommen, ein Benutzer hat die Werte »FRANKFURT« und »NEW YORK« für die Parameter depart bzw. arrive eingegeben und die Option op_and gewählt, dann enthält where_tab an dieser Stelle die folgenden Zeilen: cityfrom = 'FRANKFURT' AND cityto = 'NEW YORK' Bei der Ausführung der SELECT-Anweisung wird die WHERE-Klausel aus der internen Tabelle where_tab gelesen, dynamisch analysiert und anschließend ausgeführt (Zeile 26 bis 31). Es ist möglich, statische und dynamische Teile einer WHERE-Klausel zu kombinieren. Wenn z.B. die Auswahl stets auf Flüge der Fluggesellschaft Lufthansa (carrid = 'LH') beschränkt werden sollte, könnte die obige SELECT-Anweisung wie folgt geändert werden: SELECT carrid connid cityfrom cityto FROM spfli INTO (carrid, connid, depart, arrive) WHERE carrid = 'LH' AND (where_tab). 142 Dynamisches Open SQL [...] ENDSELECT. Die statischen und dynamischen Teile der WHERE-Klausel werden zur Laufzeit kombiniert und als eine Klausel zur Datenbank gesendet. Es sind beliebige Kombinationen statischer und dynamischer Teile zulässig, solange jedes einzelne dynamische Fragment eine logische Bedingung darstellt. Die dynamische WHERE-Klausel wurde seit Release 4.6 kontinuierlich verbessert. In Abschnitt 4.3 werden die Änderungen zu Release 6.10 beschrieben. Zusätzlich ist seit Release 6.20 die Auswertung einer hinter FOR ALL ENTRIES angegebenen internen Tabelle möglich, und seit Release 6.40 können Selektionstabellen mit IN ausgewertet werden. 4.3 Neue Möglichkeiten mit dynamischem Open SQL Dynamisches Open SQL folgt dem grundlegenden Konzept, dass der Quelltext einer dynamisch angegebenen Klausel oder logischen Bedingung in einem ABAPDatenobjekt bereitgestellt wird. Mit ABAP Release 6.10 wurden einige Beschränkungen aufgehoben, die zuvor für die Angabe dynamischer Quelltexte galten. Zur Veranschaulichung dieser »neuen« Möglichkeiten mag das bereits bekannte Beispiel aus Listing 4.4 dienen, wobei wir jetzt allerdings von den praktischeren Möglichkeiten Gebrauch machen (siehe Listing 4.5). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 PROGRAM dynamic_sql_5. PARAMETERS: depart arrive op_and op_or TYPE spfli-cityfrom, TYPE spfli-cityto, RADIOBUTTON GROUP 1 DEFAULT 'X', RADIOBUTTON GROUP 1. DATA: where_clause operator carrid connid TYPE TYPE TYPE TYPE string, c LENGTH 3 VALUE 'AND', spfli-carrid, spfli-connid. CONSTANTS tabname TYPE tabname VALUE 'spfli'. IF op_or = 'X'. operator = 'OR'. ENDIF. Neue Möglichkeiten mit dynamischem Open SQL 143 19 CONCATENATE ` cityfrom = depart ` 20 operator 21 ` cityto = '` arrive `'` 22 INTO where_clause. 23 24 SELECT carrid connid cityfrom cityto 25 FROM (tabname) 26 INTO (carrid, connid, depart, arrive) 27 WHERE (where_clause). 28 WRITE: / carrid, connid, depart, arrive. 29 ENDSELECT. Listing 4.5 Dynamischer Quelltext ab Release 6.10 Bis ABAP Release 4.6D waren ABAP-Programmierer an zwei Typen von Datenobjekten gebunden, die Quelltextinformationen der dynamischen Teile einer OpenSQL-Anweisung aufnehmen konnten: 왘 Dynamisch angegebene Tabellennamen mussten in Variablen vom Typ c bereitgestellt werden. 왘 Der Quelltext aller anderen Klauseln musste in einer internen Tabelle mit dem Zeilentyp c und einer Zeilenbreite von 72 Zeichen bereitgestellt werden; ein passender Zeilentyp für die interne Tabelle ist der im ABAP Dictionary definierte Typ edpline. Mit ABAP Release 6.10 sind die gültigen Datentypen vielfältiger geworden und es stehen die folgenden vier Datentypen für alle dynamischen Klauseln zur Verfügung: 왘 c, beliebige Länge 왘 Zeichenstring string 왘 interne Tabelle mit dem Zeilentyp c, beliebige Zeilenbreite 왘 interne Tabelle mit dem Zeilentyp string In unserem modifizierten Beispiel in Listing 4.5 verwenden wir in Zeile 6 den Zeichenstring where_clause, um die dynamische WHERE-Klausel aufzunehmen und so die statischen und dynamischen Teile direkt in der Zeichenkette where_ clause verketten zu können, ohne dass sie in einem zweiten Schritt an eine interne Tabelle angefügt werden müssten: 19 CONCATENATE ' cityfrom = depart ' [...] 22 INTO where_clause. 144 Dynamisches Open SQL Wenn ein Zeichenstring anstelle einer internen Tabelle mit Zeilen fester Länge den Quelltext der Klausel aufnimmt, bringt das einen weiteren Vorteil mit sich: Sie brauchen sich wegen der Überschreitung der Zeilenbreite der internen Tabelle keine Gedanken zu machen. Allerdings ist der Quelltext der generierten Klausel eventuell nicht so einfach zu lesen wie eine interne Tabelle, was sich bei einem eventuellen Debugging negativ auswirken kann. Deshalb mag für lange dynamische Abschnitte eine interne Tabelle mit dem Zeilentyp string das Mittel der Wahl sein. Bis ABAP Release 4.6D war die rechte Seite einer Vergleichsoperation in einer dynamischen WHERE-Klausel auf ein Literal oder den Namen einer Spalte einer der Datenbanktabellen aus der FROM-Klausel beschränkt. Seit ABAP Release 6.10 ist diese Beschränkung aufgehoben, sodass es auch möglich ist, eine Hostvariable in Form eines ABAP-Datenobjekts anzugeben. Ein Beispiel ist die ABAP-Variable depart, die direkt in den Quelltext der dynamischen WHERE-Klausel in Zeile 19 eingefügt wurde. Bei der Ausführung des Programms wird die WHERE-Klausel analysiert. Das Laufzeitsystem sucht nach einem ABAP-Datenobjekt mit dem Namen depart und ermittelt dessen Wert. Diese Suche des Wertes eines Datenobjekts anhand seines Namens verursacht einen geringen zusätzlichen Rechenaufwand. Deshalb empfehlen wir Ihnen, dass dynamische Klauseln nur dann ABAP-Datenobjekte enthalten sollten, wenn die dynamischen Klauseln nur einmal erzeugt und dann mehrmals mit verschiedenen Werten der Variablen ausgeführt werden sollen. In diesem Fall kann die Ersparnis durch die nur einmal erfolgte Erzeugung des Quelltextes den Aufwand für die dynamische Suche des Datenobjekts ausgleichen. Wie das Beispiel in Listing 4.5 zeigt, enthält der Quelltext einer dynamischen Klausel häufig Text in einfachen Hochkommata. Beim Schreiben der Klauseln gilt es allerdings Folgendes zu beachten: 3 왘 Alle einfachen Anführungszeichen ('), die Textfeldliterale umgeben, müssen verdoppelt werden. 왘 Sie müssen beachten, dass schließende Leerzeichen bei den meisten Zuwei- sungen aus Textfeldliteralen entfernt werden. Diese Bedingungen beeinträchtigen nicht nur die Lesbarkeit der Quelltextgenerierung, sondern erschweren zudem eventuell anfallenden Pflegeaufwand. Die 3 Bei einem Textfeldliteral handelt es sich um eine mit einfachen Hochkommata (') umgebene Zeichenfolge. Textfeldliterale weisen den ABAP-Typ c auf. Abschließende Leerzeichen werden in den meisten Operationen ignoriert. Neue Möglichkeiten mit dynamischem Open SQL 145 Verwendung von Stringliteralen4, die ab ABAP Release 6.10 möglich ist, beseitigt diese Beeinträchtigungen, denn ein einfaches Hochkomma in einem Stringliteral muss nicht mit einem Fluchtsymbol versehen werden. Darüber hinaus werden abschließende Leerzeichen nicht entfernt, ein Vergleich der Zeilen 16 bis 22 in Listing 4.4 mit den Zeilen 19 bis 22 in Listing 4.5 macht deutlich, wie vorteilhaft sich die Verwendung von Stringliteralen auswirkt. Ab ABAP Release 6.10 empfehlen wir Ihnen, prinzipiell mit Strings bzw. Stringliteralen zu arbeiten, wenn Sie den Quelltext einer dynamischen Klausel schreiben. Seit diesem Release sind auch Konstanten vom Typ string möglich. Da das Beispiel ab Release 6.10 ausführbar sein soll, kann die dynamische Angabe der Tabelle sowohl in Groß- als auch in Kleinschreibung erfolgen. Auch in dieser Klausel waren vor Release 6.10 nur Textfelder möglich und die Tabellen mussten in Großbuchstaben angegeben werden. 4.4 Dynamische SELECT-, GROUP BY- und HAVINGKlauseln Bisher haben wir die dynamischen FROM- und WHERE-Klauseln besprochen, die sich auf die Auswahl der Datenbanktabellen und der zu lesenden Zeilen beziehen. Die dynamischen SELECT-, GROUP BY- und HAVING-Klauseln gestatten es, Projektionen und Aggregationen ausgewählter Zeilen zur Laufzeit anzugeben. Die Funktionsweise dieser drei Klauseln ist Gegenstand der folgenden Abschnitte. 4.4.1 Die dynamischen SELECT- und GROUP BY-Klauseln In allen Beispielen dieses Kapitels sind bislang vollständige Datenzeilen aus der Datenbank ausgelesen worden. Wenn jedoch nur Werte bestimmter Spalten in der Datenbank von Interesse sind, stellt eine solche Vorgehensweise eine Verschwendung von Datenbankressourcen dar. In dynamischem Open SQL wird dieses Problem mithilfe einer dynamischen SELECT-Klausel angegangen. Wie für alle dynamischen Klauseln können Sie den Quelltext einer SELECT-Liste in einem ABAP-Datenobjekt bereitstellen. Das Datenobjekt kann die gleichen Angaben wie die statische Syntax enthalten, also auch Aggregatfunktionen und Aliasnamen. Wenn eine SELECT-Liste Aggregatfunktionen enthält, erfordert der SQL-Standard, dass alle Spalten in der SELECT-Liste, die nicht aggregiert werden, in der GROUP BY-Klausel aufgeführt werden. 4 Bei einem Stringliteral handelt es sich um eine mit einfachen Backquotes (`) umgebene Zeichenfolge. Stringliterale haben den ABAP-Typ string auf. Abschließende Leerzeichen werden in keiner Operation entfernt. 146 Dynamisches Open SQL Um diese Regel einhalten zu können, kann der Benutzer – wenn eine dynamische SELECT-Klausel benutzt wird – auch ein Datenobjekt als dynamische GROUP BYKlausel angeben, das die erforderliche Liste von Spaltenbezeichnern zum Inhalt hat. Das folgende Beispiel (siehe Listing 4.6) veranschaulicht die Verwendung der dynamischen SELECT- und GROUP BY-Klauseln. Der Benutzer kann beliebige Spalten aus der Tabelle SPFLI auswählen, das Programm bestimmt die Projektion von SPFLI für die ausgewählten Spalten. Alle unterschiedlichen Zeilen dieser Projektion und die Häufigkeit ihres Vorkommens in der Projektion bilden die Ergebnismenge, die als Liste angezeigt wird. So kann beispielsweise die Anzahl der Flüge für jede Verbindung zwischen zwei Orten in SPFLI ausgegeben werden. 1 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 PROGRAM dynamic_sql_6. TYPE-POOLS abap. PARAMETERS: lt RADIOBUTTON GROUP 1 DEFAULT 'X', gt RADIOBUTTON GROUP 1, value TYPE i. DATA: BEGIN OF wa, count TYPE i. INCLUDE TYPE spfli. DATA END OF wa. DATA: checked name lines descr_ref sel_list group_list having TYPE TYPE TYPE TYPE TYPE TYPE TYPE abap_bool, fieldname, i, REF TO cl_abap_structdescr, TABLE OF edpline, TABLE OF edpline, string. FIELD-SYMBOLS: <fs> TYPE ANY, <comp_wa> TYPE abap_compdescr. START-OF-SELECTION. SET PF-STATUS 'MAIN'. * get all components of table 'SPFLI' Dynamische SELECT-, GROUP BY- und HAVING-Klauseln 147 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 148 descr_ref ?= cl_abap_typedescr=>describe_by_name( 'SPFLI' ). LOOP AT descr_ref->components ASSIGNING <comp_wa>. name = <comp_wa>-name. WRITE: / checked AS CHECKBOX, name. ENDLOOP. lines = LINES( descr_ref->components ). AT USER-COMMAND. * determine selected columns CLEAR: sel_list, group_list. APPEND 'count(*) as count' TO sel_list. DO lines TIMES. READ LINE sy-index FIELD VALUE checked. IF checked = 'X'. READ LINE sy-index FIELD VALUE name. APPEND name TO: sel_list, group_list. ENDIF. ENDDO. * determine operator IF gt = 'X'. having = 'count(*) > value'. ELSE. having = 'count(*) < value'. ENDIF. SELECT (sel_list) FROM spfli UP TO 20 ROWS INTO CORRESPONDING FIELDS OF wa GROUP BY (group_list) HAVING (having). " remove in Release 4.6 * write all components to list WRITE / wa-count. LOOP AT group_list INTO name. ASSIGN COMPONENT name OF STRUCTURE wa TO <fs>. WRITE <fs>. Dynamisches Open SQL 70 71 ENDLOOP. ENDSELECT. Listing 4.6 Dynamische SELECT-, GROUP BY- und HAVING-Klausel Im Beispielprogramm in Listing 4.6 werden in Zeile 18 und 19 die beiden internen Tabellen sel_list und group_list deklariert, die den Quelltext der dynamischen SELECT- und GROUP BY-Klauseln aufnehmen sollen. Der in den Zeilen 9 bis 12 deklarierte Arbeitsbereich wa soll die ausgewählten Daten aufnehmen. Beim Ereignis START-OF-SELECTION wird eine Liste aller Spalten der Tabelle SPFLI mit Checkboxen angezeigt, aus denen der Benutzer die gewünschten auswählen kann. Wenn der Benutzer die Eingabetaste drückt, wird das Ereignis AT USER-COMMAND ausgelöst. Für eine erfolgreiche Ausführung muss das Programm den GUI-Status MAIN besitzen, der eine Drucktaste mit dem Funktionscode ENTER definiert. Beim Ereignis USER-COMMAND wird count(*) der SELECT-Liste als erstes Feld hinzugefügt. Der Alias count stimmt mit dem Namen der Komponente count des Arbeitsbereiches wa überein. Als Nächstes werden die Werte der Checkboxen aus der Liste extrahiert, um die ausgewählten Spalten zu bestimmen. Diese Aufgabe erfüllt eine Schleife (Zeile 44 bis 50). Sie durchläuft die Liste und fügt die Namen der ausgewählten Spalten den internen Tabellen sel_list und group_list hinzu. Da die SELECT-Liste die Aggregatfunktion count(*) enthält, müssen die Namen aller Spalten, über die nicht aggregiert wird, in der GROUP BY-Klausel aufgeführt werden. Die Daten der in sel_list angegebenen Spalten werden aus der Datenbank SPFLI abgerufen und gemäß group_list gruppiert: 59 60 61 62 71 SELECT (sel_list) FROM spfli UP TO 20 ROWS INTO CORRESPONDING FIELDS OF wa GROUP BY (group_list) [...] ENDSELECT. Alle bisher vorgestellten Techniken stehen bereits in ABAP Release 4.6 zur Verfügung. Um das Beispiel unter Release 4.6 auszuführen, müssen Sie allerdings Zeile 63 auskommentieren und die Anweisung in Zeile 62 mit einem Punkt (.) beenden. 4.4.2 Die dynamische HAVING-Klausel Die in einer WHERE-Klausel angegebene logische Bedingung bestimmt eine Ergebnismenge, die gemäß den Angaben einer GROUP BY-Klausel weiter gruppiert wer- Dynamische SELECT-, GROUP BY- und HAVING-Klauseln 149 den kann, wobei eine Zwischenergebnismenge gebildet wird. Da die WHERE-Klausel angewendet wird, bevor die Daten aggregiert und gruppiert werden, darf sie keine Aggregatfunktionen enthalten. Mittels einer logischen Bedingung in der HAVING-Klausel können die gruppierten Daten jedoch weiter reduziert werden. Eine HAVING-Klausel wird im Wesentlichen wie eine WHERE-Klausel angegeben, kann aber zusätzlich Aggregatfunktionen als Argumente enthalten. Eine dynamische HAVING-Klausel wird exakt wie eine dynamische WHERE-Klausel angegeben. Seit ABAP Release 6.10 ist es möglich, in dynamischen HAVING-Klauseln auch Aggregatfunktionen anzugeben. Die Angabe einer dynamischen HAVING-Klausel ist zwar schon mit Release 4.6 möglich, sie darf dort aber noch keine Aggregatfunktionen enthalten, weshalb das Beispiel aus Listing 4.6 ohne die zuvor beschriebenen Änderungen (Auskommentierung von Zeile 63 und Abschluss von Zeile 62 mit einem Punkt) erst ab Release 6.10 ablauffähig ist. Das vollständige Beispiel aus Listing 4.6 demonstriert die Verwendung einer dynamischen HAVING-Klausel, die eine Aggregatfunktion enthält. Der Benutzer wird aufgefordert, einen Wert einzugeben, der count(*) einschränkt. Er kann anschließend entscheiden, ob value ein oberer (lt) oder unterer (gt) Grenzwert von count sein soll. Der Quelltext der dynamischen HAVING-Klausel wird, abhängig vom Wert des Parameters gt, einfach aus einem von zwei Literalen gewählt. Die HAVING-Klausel enthält die Aggregatfunktion count(*) und die ABAP-Variable value. Beide Möglichkeiten stehen seit ABAP Release 6.10 zur Verfügung (Zeile 53 bis 57). In der SELECT-Anweisung wird in Zeile 63 die dynamisch angegebene HAVINGKlausel nun zum Filtern der sich aus der GROUP BY-Klausel ergebenden Zwischendaten verwendet. Schließlich wird die Ergebnismenge wie zuvor spaltenweise angezeigt. Bitte beachten Sie, dass es in einigen Fällen nicht – wie vielleicht zu erwarten wäre – zu einer Ausnahme führt, wenn dynamische Klauseln leer, also mit initialen Datenobjekten angegeben werden (siehe Abschnitt 4.5). Das System interpretiert leere Klauseln vielmehr nach besonderen Regeln: 왘 Eine leere WHERE- oder HAVING-Klausel entspricht einem logischen Ausdruck, der immer wahr ist. 왘 Bei einer leeren 150 SELECT-Klausel werden alle Spalten ausgewählt. Dynamisches Open SQL