Schmeling Datenbankentwicklung mit dem
Microsoft SQL Server 2005
Holger Schmeling
Microsoft
SQL Server 2005
Der Autor: Holger Schmeling, München
Alle in diesem Buch enthaltenen Informationen, Verfahren und Darstellungen wurden nach bestem Wissen zusammengestellt und mit Sorgfalt getestet. Dennoch sind Fehler nicht ganz auszuschließen. Aus diesem Grund sind die im vorliegenden Buch enthaltenen Informationen mit keiner Verpflichtung oder Garantie irgendeiner Art verbunden. Autoren und Verlag übernehmen infolgedessen keine juristische Verantwortung und werden keine daraus folgende oder sonstige Haftung übernehmen, die auf irgendeine Art aus der Benutzung dieser Informationen oder Teilen davon entsteht. Ebenso übernehmen Autoren und Verlag keine Gewähr dafür, dass beschriebene Verfahren usw. frei von Schutzrechten Dritter sind. Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Buch berechtigt deshalb auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften.
Bibliografische Information Der Deutschen Nationalbibliothek Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.
Dieses Werk ist urheberrechtlich geschützt. Alle Rechte, auch die der Übersetzung, des Nachdruckes und der Vervielfältigung des Buches, oder Teilen daraus, vorbehalten. Kein Teil des Werkes darf ohne schriftliche Genehmigung des Verlages in irgendeiner Form (Fotokopie, Mikrofilm oder ein anderes Verfahren) auch nicht für Zwecke der Unterrichtsgestaltung reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden.
© 2007 Carl Hanser Verlag München Lektorat: Fernando Schneider Sprachlektorat: Sandra Gottmann, Münster-Nienberge Herstellung: Monika Kraus Umschlagdesign: Marc Müller-Bremer, Rebranding, München Umschlaggestaltung: MCP Susanne Kraus GbR, Holzkirchen Datenbelichtung, Druck und Bindung: Kösel, Krugzell Ausstattung patentrechtlich geschützt. Kösel FD 351, Patent-Nr. 0748702 Printed in Germany ISBN-10: 3-446-22532-3 ISBN-13: 978-3-446-22532-9 www.hanser.de/computer
Für Gerda
Inhalt 1 1.1 1.2 1.3 1.4 1.5
Einleitung ................................................................................................................. 1 Was vermittelt dieses Buch?.................................................................................................... 1 Für wen ist dieses Buch? ......................................................................................................... 2 Wo finde ich was? ................................................................................................................... 3 Welche Voraussetzungen werden benötigt? ............................................................................ 5 Danksagung ............................................................................................................................. 6
2 2.1
SQL Server 2005-Architektur .................................................................................. 9 SQL Server 2005-Komponenten ............................................................................................. 9 2.1.1 Das SQL Server-Datenbankmodul........................................................................... 10 2.1.2 SQL Server Replication Services............................................................................. 12 2.1.3 SQL Server Notification Services............................................................................ 12 2.1.4 SQL Server Reporting Services ............................................................................... 12 2.1.5 SQL Server Analysis Services ................................................................................. 13 2.1.6 SQL Server Integration Services.............................................................................. 13 2.1.7 Verwaltungswerkzeuge............................................................................................ 14 2.1.8 Entwicklungswerkzeuge .......................................................................................... 14 2.1.9 Der SQL Server Agent............................................................................................. 14 SQL Server-Editionen ........................................................................................................... 15 Systemdatenbanken ............................................................................................................... 16 2.3.1 Die master-Datenbank ............................................................................................. 16 2.3.2 Die model-Datenbank .............................................................................................. 16 2.3.3 Die msdb-Datenbank ............................................................................................... 17 2.3.4 Die tempdb-Datenbank ............................................................................................ 17 2.3.5 Weitere Datenbanken............................................................................................... 17 Die Struktur einer SQL Server-Datenbank ............................................................................ 18 2.4.1 Datenbanken ............................................................................................................ 19 2.4.2 Datenbankobjekte .................................................................................................... 19 Zusammenfassung ................................................................................................................. 21
2.2 2.3
2.4
2.5
VII
Inhalt 3 3.1 3.2 3.3
3.4 3.5 3.6 3.7 3.8 3.9 4 4.1 4.2 4.3 4.4 4.5 4.6
4.7
4.8
4.9 4.10 4.11 4.12 4.13 4.14
VIII
SQL Server-Verwaltungswerkzeuge .................................................................... 23 SQL Server-Konfigurations-Manager ....................................................................................24 SQL Server-Oberflächenkonfiguration ..................................................................................25 SQL Server Management Studio............................................................................................27 3.3.1 Start und Anmeldung................................................................................................27 3.3.2 Bereiche des Hauptfensters.......................................................................................30 3.3.3 Datenbankprojekte erstellen .....................................................................................47 Business Intelligence Development Studio ............................................................................51 SQL Server Profiler................................................................................................................52 Datenbankmodul-Optimierungsratgeber ................................................................................58 Sqlcmd ...................................................................................................................................61 SQL Server-Onlinedokumentation.........................................................................................63 Zusammenfassung..................................................................................................................64 Transact SQL-Grundlagen.................................................................................... 65 Das Relationenmodell ............................................................................................................66 SQL mit System .....................................................................................................................67 T-SQL-Stapel (Batches) .........................................................................................................69 SQL-Datentypen ....................................................................................................................71 T-SQL-Ausdrücke und -Operatoren.......................................................................................76 Datenbanken verwalten ..........................................................................................................79 4.6.1 Datenbank erzeugen .................................................................................................79 4.6.2 Datenbank ändern.....................................................................................................82 4.6.3 Datenbank löschen....................................................................................................84 Schemas verwenden ...............................................................................................................84 4.7.1 Namensauflösung .....................................................................................................87 4.7.2 Synonyme .................................................................................................................89 Tabellen entwerfen .................................................................................................................90 4.8.1 Tabellen anlegen.......................................................................................................90 4.8.2 Tabellen ändern ........................................................................................................93 4.8.3 Identität/Primärschlüssel ..........................................................................................95 4.8.4 Weitere Einschränkungen.........................................................................................98 4.8.5 Berechnete Spalten .................................................................................................102 4.8.6 Ein Wiedersehen mit CREATE TABLE ................................................................103 4.8.7 Beziehungen festlegen............................................................................................104 4.8.8 Zusammenfassung zu Einschränkungen .................................................................107 4.8.9 Tabellen löschen .....................................................................................................107 4.8.10 Datenbankdiagramme.............................................................................................108 4.8.11 Temporäre Tabellen................................................................................................109 INSERT Daten einfügen ...................................................................................................110 SELECT Daten abfragen...................................................................................................111 4.10.1 Filtern: Projektion und Selektion............................................................................115 Informationen ausgeben mit PRINT.....................................................................................122 Ausführen von gespeicherten Prozeduren ............................................................................122 UPDATE Daten ändern.....................................................................................................123 DELETE Daten löschen ....................................................................................................125
Inhalt 4.15
4.16 4.17
4.18
4.19
4.20 4.21
4.22 5 5.1 5.2 5.3
5.4
SQL-Funktionen.................................................................................................................. 126 4.15.1 Systemfunktionen .................................................................................................. 127 4.15.2 Konfigurationsfunktionen ...................................................................................... 129 4.15.3 Metadatenfunktionen ............................................................................................. 130 4.15.4 Numerische Funktionen......................................................................................... 132 4.15.5 Funktionen zur Zeichenkettenbearbeitung............................................................. 136 4.15.6 Konvertierungsfunktionen ..................................................................................... 142 4.15.7 Datums- und Zeitfunktionen .................................................................................. 144 4.15.8 Sonstige Funktionen und Operatoren..................................................................... 149 Die Beispieldatenbank AdventureWorks............................................................................. 153 Sichten................................................................................................................................. 154 4.17.1 Ändern von Sichten ............................................................................................... 156 4.17.2 Aktualisierung von Daten ...................................................................................... 156 Daten einfügen mit SELECT............................................................................................... 158 4.18.1 INSERT und SELECT........................................................................................... 158 4.18.2 SELECT INTO ...................................................................................................... 158 Null...................................................................................................................................... 159 4.19.1 Dreiwertige Logik.................................................................................................. 160 4.19.2 Arithmetische Operationen .................................................................................... 163 4.19.3 Operationen mit Zeichenketten.............................................................................. 164 4.19.4 Hinweise für die Verwendung von NULL............................................................. 166 4.19.5 Spezielle Funktionen für NULL-Werte ................................................................. 167 Fehlerbehandlung in T-SQL................................................................................................ 169 Komplexe Abfragen mit SELECT....................................................................................... 170 4.21.1 Mengenoperationen mit SQL................................................................................. 170 4.21.2 Abfragen über mehrere Tabellen ........................................................................... 172 4.21.3 Aggregatfunktionen und GROUP BY ................................................................... 179 4.21.4 Rangfolgefunktionen ............................................................................................. 186 4.21.5 Korrelierende Unterabfragen ................................................................................. 191 4.21.6 Abgeleitete Tabellen .............................................................................................. 192 4.21.7 Allgemeine Tabellenausdrücke.............................................................................. 193 4.21.8 PIVOT und UNPIVOT .......................................................................................... 198 4.21.9 OUTPUT ............................................................................................................... 202 4.21.10 APPLY .................................................................................................................. 203 Zusammenfassung ............................................................................................................... 205 Transaktionen und Sperren ................................................................................ 207 Eigenschaften von Transaktionen........................................................................................ 207 Start und Ende einer Transaktion ........................................................................................ 210 5.2.1 Transaktionsmodi des SQL Servers....................................................................... 211 Sperren ................................................................................................................................ 213 5.3.1 Sperrbare Ressourcen ............................................................................................ 214 5.3.2 Sperrtypen.............................................................................................................. 217 5.3.3 Deadlocks .............................................................................................................. 219 Isolationsstufen.................................................................................................................... 224 5.4.1 READ UNCOMMITTED...................................................................................... 224
IX
Inhalt
5.5 5.6
5.7 5.8 6 6.1
6.2
6.3
6.4
6.5 7 7.1
7.2 7.3
X
5.4.2 READ COMMITTED ............................................................................................225 5.4.3 REPEATABLE READ...........................................................................................226 5.4.4 SNAPSHOT ...........................................................................................................227 5.4.5 SERIALIZABLE....................................................................................................228 5.4.6 Abschließende Betrachtungen zu Isolationsstufen..................................................228 Sperren explizit anfordern Sperrhinweise .........................................................................229 Das Transaktionsprotokoll ...................................................................................................232 5.6.1 Abschneiden des Protokolls....................................................................................233 5.6.2 Verhalten im Fehlerfall...........................................................................................234 Hinweise zur Verwendung von Transaktionen.....................................................................234 Zusammenfassung................................................................................................................236 Administration für Datenbankentwickler........................................................... 237 Datensicherung und -wiederherstellung ...............................................................................237 6.1.1 Wiederherstellungsmodelle ....................................................................................240 6.1.2 Prüfen der Konsistenz einer Datenbank..................................................................242 6.1.3 Vollständige Datenbanksicherung ..........................................................................242 6.1.4 Sicherung des Transaktionsprotokolls ....................................................................248 6.1.5 Sichern der Systemdatenbanken .............................................................................251 Wiederherstellung von Datenbanken....................................................................................252 6.2.1 Automatische Wiederherstellung bei Systemstart ..................................................255 6.2.2 Wiederherstellung der Systemdatenbanken ............................................................255 Zugriffskontrolle ..................................................................................................................256 6.3.1 SQL Server-Anmeldungen .....................................................................................257 6.3.2 Anmeldungen hinzufügen.......................................................................................259 6.3.3 Anmeldungen löschen ............................................................................................261 6.3.4 Berechtigungen vergeben .......................................................................................261 6.3.5 Vordefinierte Anmeldekonten ................................................................................262 6.3.6 Datenbankbenutzer .................................................................................................263 6.3.7 Datenbankrollen .....................................................................................................265 6.3.8 Rechte erteilen und entziehen .................................................................................266 Überwachung von SQL Server.............................................................................................270 6.4.1 Was kann überwacht werden? ................................................................................271 6.4.2 Überwachung mit dem SQL Server Management Studio.......................................272 6.4.3 Überwachung mit dem SQL Server Profiler...........................................................276 6.4.4 Überwachung mit dem Windows-Systemmonitor..................................................276 6.4.5 Verwendung von T-SQL zur Überwachung ...........................................................277 Zusammenfassung................................................................................................................286 T-SQL-Programmierung...................................................................................... 287 Variablen..............................................................................................................................287 7.1.1 Deklaration von Variablen......................................................................................287 7.1.2 Wertzuweisung an Variablen..................................................................................288 7.1.3 Besondere Variablentypen......................................................................................290 Dynamisches SQL................................................................................................................292 Ablaufsteuerung ...................................................................................................................293
Inhalt
7.4
7.5
7.6
7.7
7.8 8 8.1
8.2 8.3
8.4
7.3.1 Anweisungsblöcke ................................................................................................. 293 7.3.2 Bedingte Ausführung von Anweisungen ............................................................... 293 7.3.3 Wiederholte Ausführung von Anweisungen .......................................................... 294 Cursor.................................................................................................................................. 297 7.4.1 Cursor erzeugen ..................................................................................................... 297 7.4.2 Einen Cursor durchlaufen Daten abrufen............................................................ 300 7.4.3 Daten aktualisieren ................................................................................................ 305 7.4.4 Hinweise zur Verwendung von Cursorn ................................................................ 306 Gespeicherte Prozeduren ..................................................................................................... 306 7.5.1 Warum gespeicherte Prozeduren?.......................................................................... 307 7.5.2 Erstellung von gespeicherten Prozeduren .............................................................. 307 7.5.3 Verwenden von Parametern ................................................................................... 311 7.5.4 Der Rückgabewert einer gespeicherten Prozedur .................................................. 314 7.5.5 Die gespeicherte Systemprozedur sp_procoption .................................................. 314 7.5.6 EXECUTE und INSERT ....................................................................................... 316 7.5.7 Erweitertes Beispiel: PDF-Dateien einlesen .......................................................... 317 Benutzerdefinierte Funktionen ............................................................................................ 320 7.6.1 Skalarwertfunktionen............................................................................................. 320 7.6.2 Tabellenwertfunktionen ......................................................................................... 323 Trigger................................................................................................................................. 325 7.7.1 DML-Trigger ......................................................................................................... 326 7.7.2 Hinweise zur Verwendung von Triggern ............................................................... 332 7.7.3 DDL-Trigger.......................................................................................................... 333 Zusammenfassung ............................................................................................................... 338 XML-Integration ................................................................................................... 339 Tabellen im XML-Format darstellen: FOR XML ............................................................... 339 8.1.1 FOR XML RAW ................................................................................................... 341 8.1.2 FOR XML AUTO.................................................................................................. 342 8.1.3 FOR XML EXPLICIT........................................................................................... 344 8.1.4 FOR XML PATH .................................................................................................. 352 8.1.5 FOR XML-Optionen.............................................................................................. 355 8.1.6 Noch einmal FOR XML PATH............................................................................. 358 XML-Daten in Tabellenform abbilden: OPENXML........................................................... 367 Der XML-Datentyp ............................................................................................................. 372 8.3.1 Ungetypte XML-Daten .......................................................................................... 375 8.3.2 Getypte XML-Daten .............................................................................................. 376 8.3.3 query...................................................................................................................... 379 8.3.4 exist ....................................................................................................................... 382 8.3.5 value ...................................................................................................................... 383 8.3.6 nodes...................................................................................................................... 384 8.3.7 modify.................................................................................................................... 388 8.3.8 XML-Indizes ......................................................................................................... 391 Zusammenfassung ............................................................................................................... 395
XI
Inhalt 9 9.1
9.2
9.3 9.4 10 10.1 10.2 10.3 10.4 10.5 10.6
10.7 10.8 10.9 11 11.1 11.2 11.3
11.4 11.5
11.6
XII
SQL Server Service Broker................................................................................. 397 Architektur ...........................................................................................................................397 9.1.1 Service Oriented Architecture (SOA) .....................................................................398 9.1.2 Service Broker-Kommunikation.............................................................................399 9.1.3 Service Broker-Komponenten ................................................................................401 Erstellung von Anwendungen ..............................................................................................402 9.2.1 T-SQL-Kommandos für eine Service Broker-Kommunikation ..............................402 9.2.2 Eine Service Broker-Beispielanwendung ...............................................................403 9.2.3 Service Broker-Aktivierung....................................................................................417 Überwachung von Server- und Datenbankobjekten .............................................................418 Zusammenfassung................................................................................................................421 Datenbankentwicklung mit .NET ........................................................................ 423 SQL Server und die .NET CLR............................................................................................423 Sicherheitsaspekte ................................................................................................................425 Assemblys ............................................................................................................................426 Der Namensraum Microsoft.SqlServer.Server .....................................................................430 Datentypen der Namensraum System.Data.SqlTypes .......................................................431 Datenbankprojekte mit Visual Studio 2005 erstellen ...........................................................431 10.6.1 Erstellen eines Datenbankprojektes ........................................................................432 10.6.2 Erstellen von gespeicherten Prozeduren .................................................................434 10.6.3 Erstellen von benutzerdefinierten Funktionen ........................................................442 10.6.4 Erstellen benutzerdefinierter Typen........................................................................44 10.6.5 Erstellen von benutzerdefinierten Aggregaten........................................................45 10.6.6 Erstellen von Triggern ............................................................................................460 10.6.7 Debuggen von CLR-Objekten ................................................................................462 Noch einmal: Sicherheit .......................................................................................................46 T-SQL oder .NET?...............................................................................................................46 Zusammenfassung................................................................................................................46 Web Services mit dem SQL Server .................................................................... 469 Was sind Web Services? ......................................................................................................469 SOAP-Endpunkte.................................................................................................................470 11.2.1 Syntax zur Erstellung eines HTTP-Endpunktes......................................................471 Reservierung von HTTP-Namensräumen.............................................................................473 11.3.1 Implizite Reservierung ...........................................................................................474 11.3.2 Explizite Reservierung ...........................................................................................475 11.3.3 Löschen einer expliziten Reservierung...................................................................475 Aufbau der URL für einen Endpunkt ...................................................................................476 Einen Web Service erstellen und verwenden .......................................................................476 11.5.1 Den AdWrksServicePoint-Endpunkt erzeugen........................................................477 11.5.2 Sicherheitsaspekte ..................................................................................................478 11.5.3 Den Web Service verwenden..................................................................................481 Zusammenfassung................................................................................................................486
Inhalt 12 12.1 12.2 12.3 12.4
Optimierung von Abfragen ................................................................................. 487 Die Speicherverwaltung von SQL Server............................................................................ 488 Ausführung von Abfragen durch SQL Server ..................................................................... 489 Erstellen einer Testdatenbank.............................................................................................. 490 Verwenden von Indizes ....................................................................................................... 491 12.4.1 Tabellen ohne Index Heap .................................................................................. 491 12.4.2 Gruppierte Indizes ................................................................................................. 492 12.4.3 Nicht gruppierter Index auf einem Heap................................................................ 494 12.4.4 Nicht gruppierter Index auf einem gruppierten Index............................................ 495 12.4.5 Erzeugen von Indizes............................................................................................. 497 12.4.6 Löschen von Indizes .............................................................................................. 501 12.5 Messen der Leistung............................................................................................................ 501 12.5.1 Messen mit der Stoppuhr ....................................................................................... 502 12.5.2 Statistische Werte .................................................................................................. 503 12.5.3 Ausführungspläne.................................................................................................. 505 12.5.4 Der SQL Server Profiler ........................................................................................ 511 12.5.5 Windows-Systemmonitor ...................................................................................... 517 12.5.6 Verbindung von Ablaufverfolgung und Logfile .................................................... 518 12.6 Beispiele für die Optimierung ............................................................................................. 521 12.7 Weitere Hinweise für Indizes .............................................................................................. 528 12.7.1 Index-Selektivität................................................................................................... 528 12.7.2 Index-Fragmentierung ........................................................................................... 529 12.7.3 Fehlende Indizes .................................................................................................... 529 12.7.4 Nicht verwendete Indizes....................................................................................... 531 12.8 Der Datenbankmodul Optimierungsratgeber ....................................................................... 532 12.9 Tipps zur Abfrageoptimierung ............................................................................................ 535 12.10 Zusammenfassung ............................................................................................................... 538 13 13.1 13.2 13.3 13.4
13.5
Anwendungen mit den Notification Services erstellen .................................... 539 Einführung in SQL Server Notification Services ................................................................ 539 Architektur .......................................................................................................................... 540 Erstellen von Notification Services-Anwendungen............................................................. 543 Eine Notification Services-Beispielanwendung................................................................... 544 13.4.1 Instance Configuration File (ICF).......................................................................... 544 13.4.2 Application Definition File (ADF) ........................................................................ 547 13.4.3 Erzeugen und Ausführen der NS-Anwendung....................................................... 554 13.4.4 Überprüfen der erzeugten NS-Datenbanken und -Objekte..................................... 558 13.4.5 Erzeugen von Abonnements und Ereignissen........................................................ 559 Zusammenfassung ............................................................................................................... 572
Literatur ........................................................................................................................... 573 Register............................................................................................................................ 575
XIII
Vorwort
Mit dem SQL Server 2005 hat Microsoft die nächste Generation des SQL Server Systems herausgegeben, die sich von der Vorgängerversion in vielen Bereichen deutlich abhebt. Die Änderungen sind teilweise beträchtlich, man könnte gar versucht sein zu behaupten, dass es sich beim SQL Server 2005 eher um ein neues Produkt als um eine Weiterentwicklung der vorherigen Version(en) handelt. Die Erweiterungen erstrecken sich natürlich auch oder eben erst recht auf die Möglichkeiten und Mittel, die dem Datenbankentwickler nunmehr zur Verfügung stehen. Seien es die Sprache T-SQL, die Common Language Runtime Integration, neue Möglichkeiten durch die erweiterte XML-Integration oder die Einführung komplett neuer Module wie Service Broker oder Notification Services. All diese Bereiche sind mittlerweile umfangreich und interessant genug, um jeweils eigene Bücher zu füllen. Aber warum veröffentlicht man denn nun eigentlich noch ein Buch über die SQL Server 2005 Datenbankentwicklung, mögen Sie sich fragen. Nun, dieses Buch konzentriert sich nahezu ausschließlich auf die reine Datenbankentwicklung, also die Programmierung bzw. die Programmiermöglichkeiten des SQL Servers 2005 an sich, unter Verwendung von T-SQL und C#. Durch eine derartige Fokussierung kann in diesem Buch auch auf so interessante Bereiche wie z.B. die Erstellung von Anwendungen mit den Notifications Services, die Optimierung von Abfragen oder auch den Einsatz des Service-Brokers näher eingegangen werden, die in vergleichbaren Werken oft ausgelassen werden. Dagegen werden Sie hier kaum Erläuterungen zur Client-Entwicklung, also etwa zum Datenzugriff mittels ADO.NET, finden und wenn, dann sicherlich nur am Rande. Ich hoffe, dass dieses Buch mit der beschriebenen Fokussierung Ihnen nützlich sein wird, sowohl beim Einstieg in die Programmierung des SQL Servers 2005 als auch beim eventuellen Umstieg von der Vorgängerversion, und wünsche Ihnen viel Freude beim Lesen bzw.
XV
Vorwort Durcharbeiten. Möglicherweise möchten Sie aber auch einfach nur Anregungen für einen Vergleich der Leistungsfähigkeit des SQL Servers 2005 mit anderen Datenbanksystemen erhalten, was die Möglichkeiten der Datenbankentwicklung anbelangt. Auch dann werden Sie sicherlich nützliche Informationen finden. Abschließend noch eine Anmerkung. Alle an diesem Buch Beteiligten haben mit großer Akribie gearbeitet, um in diesem Buch bestmögliche Informationen zusammenzutragen. Sämtliche im Buch verwendeten Beispiele (und das sind eine Menge!) wurden deshalb sorgfältig entworfen und geprüft. Nichtsdestotrotz kann sich natürlich der eine oder andere (Druck-)Fehler eingeschlichen haben. Falls Sie solche Ungereimtheiten entdecken, zögern Sie bitte nicht, mich zu informieren. Auch bei auftretenden Fragen, Anregungen oder Korrekturvorschlägen würde ich mich über eine Kontaktaufnahme sehr freuen.
München, im April 2007 Holger Schmeling
[email protected]
XVI
1 Einleitung Als die Firma Microsoft im November des Jahres 2005 endlich die lang ersehnte neue Version des SQL Servers freigegeben hat, war ich sehr gespannt darauf, ob sich das mehrjährige Warten auf diese Version auch tatsächlich gelohnt hat. Immerhin ist der SQL Server 2005 mit einer Verspätung von einigen Jahren erschienen, was die Frage aufwirft, ob denn die neuen Features diese Verspätung wenigstens rechtfertigen. SQL Server 2005 enthält faszinierende und beeindruckende Erweiterungen, vor allem in den Bereichen Business Intelligence und Datenbank- bzw. Anwendungsentwicklung, aber auch wesentlich verbesserte Möglichkeiten für den Administrator. Insgesamt umfasst der SQL Server 2005 eine solche Vielzahl von Veränderungen bzw. Weiterentwicklungen gegenüber der Vorgängerversion, dass ich letztlich sogar ein wenig Verständnis für die nicht unerhebliche Verspätung seiner Auslieferung aufbringe. Aus meiner Sicht kann ich die Frage, ob sich das Warten gelohnt hat, also mit einem klaren Ja beantworten. SQL Server 2005 bietet eine sichere, zuverlässige und stabile Plattform für unternehmenskritische Anwendungen. Da Sie dieses Buch in der Hand halten, denken Sie zumindest über den Einsatz von SQL Server 2005 nach. Ich hoffe, dass Ihnen das Buch hierbei eine Hilfe und ein Wegweiser sein wird.
1.1
Was vermittelt dieses Buch? In diesem Buch werden vor allem die Möglichkeiten für die Datenbankentwicklung mit dem SQL Server 2005 ausgelotet. Hierbei wird natürlich ausführlich auf die neuen Features von SQL Server 2005 eingegangen. Das Buch beinhaltet jedoch letztlich alle Aspekte der Datenbankentwicklung. Sie können dieses Buch also verwenden, um in die Datenbankentwicklung mit SQL Server 2005 von Grund auf einzusteigen. Unabhängig von den Neuerungen im SQL Server 2005 ist die Sprache T-SQL nach wie vor die erste Wahl bei der Datenbankentwicklung mit SQL Server. Weite Teile des vorliegenden Buches konzentrieren sich daher auch auf diesen Aspekt, also die Verwendung von
1
1 Einleitung T-SQL. Darüber hinaus finden Sie hier selbstverständlich auch detaillierte Ausführungen über die .NET-Integration, die Erstellung von Web Services und die Möglichkeiten für die Verarbeitung von XML-Daten. Diese Thematiken werden jeweils in eigenen Kapiteln abgehandelt. Das Buch enthält auch eigenständige Kapitel zu den Themen SQL Server Service Broker und Notification Services. Ebenso finden Sie ein Kapitel, das sich mit der SQL ServerAdministration befasst. In diesen drei Kapiteln wird allerdings lediglich eine Einführung in die Thematik vermittelt. Anders geht es leider nicht, da der Umfang des Buches natürlich beschränkt ist. Die Kapitel sind jedoch inhaltlich so aufgebaut, dass Sie mit den dort präsentierten Informationen in die Lage versetzt werden, bei Bedarf tiefer in die jeweilige Thematik einzusteigen. Gleiches gilt auch für die Optimierung von Abfragen, die ebenfalls in einem eigenständigen Kapitel behandelt wird. Optimierung ist ein recht komplexes Thema, über das auch bereits eigene Bücher geschrieben wurden (und werden). Die an dieser Stelle zur Thematik Abfrageoptimierung dargebotenen Ideen sind dazu gedacht, Sie in die Materie einzuführen und Sie mit einer Art Framework für die Untersuchung von Abfragen und deren Optimierung auszustatten. Der Bereich Business Intelligence wird in diesem Buch nicht behandelt. Sie werden hier also keine Ausführungen zu SQL Server Integration Services, Analysis Services oder Reporting Services finden.
1.2
Für wen ist dieses Buch? Wie der Titel eindeutig verrät, wendet sich dieses Buch in erster Linie an Datenbankentwickler. Ich denke jedoch, dass sich für jeden, der einfach nur die Möglichkeiten von SQL Server 2005 ausloten möchte, ein Blick in das Buch lohnen wird. Vor allem SQL ServerAdministratoren sollten sich ebenso angesprochen fühlen. Sicherlich muss ein Administrator dieses Buch nicht unbedingt von Anfang bis Ende durcharbeiten. Der SQL Server 2005 ist jedoch kein reines Datenbank-Managementsystem mehr, sondern ebenso ein Anwendungsserver. Der Einsatz von SQL Server als Anwendungsserver stellt natürlich nicht nur eine Herausforderung für den Datenbankentwickler, sondern ebenso für den Administrator dar. Dies bezieht sich zum Beispiel auf die Möglichkeit, Web Services bereitstellen zu können, sowie auf die Komponenten Service Broker und Notification Services. Ein Administrator sollte in der Lage sein, Anwendungen, die unter Verwendung dieser Komponenten erstellt wurden, auch abzusichern. In den entsprechenden Kapiteln zu diesen Themen wird hierauf zum Teil näher eingegangen. Darüber hinaus finden Administratoren hier auch Grundlagen für die Optimierung.
2
1.3 Wo finde ich was?
1.3
Wo finde ich was? Das vorliegende Buch unterteilt sich in vier größere Abschnitte. Einführung Der erste Teil besteht aus den Kapiteln 2 und 3 und vermittelt Grundlegendes zur SQL Server-Architektur sowie zur Benutzung des Servers unter Verwendung der mitgelieferten Software für die Server-Verwaltung. Kapitel 2: SQL Server 2005-Architektur. In diesem Kapitel werden die SQL ServerKomponenten und deren Zusammenspiel behandelt. Kapitel 3: SQL Server-Verwaltungswerkzeuge. SQL Server 2005 wird mit einem komplett übererarbeiteten Satz an Verwaltungswerkzeugen ausgeliefert. Kapitel 3 erklärt die Verwendung dieser Werkzeuge mit Bezug auf Entwicklung und Administration. T-SQL-Grundlagen In diesem Teil des Buches werden sowohl die Grundlagen von T-SQL als auch die Verarbeitung von Transaktionen behandelt Kapitel 4: Transact SQL-Grundlagen. Dies ist das bei weitem umfangreichste Kapitel des Buches. Das Kapitel vermittelt eine ausführliche Einführung in die Verwendung der Sprache T-SQL. Kapitel 5: Transaktionen und Sperren. In diesem Kapitel erfahren Sie, wie SQL Server Transaktionen benutzt, um die Datenintegrität sicherzustellen, und wie Sperren verwendet werden, um parallele Zugriffe auf Server- und Datenbankressourcen zu ermöglichen. SQL Server-Administration Der dritte Teil liegt konzentriert sich auf die Administration. Kapitel 6: Administration für Datenbankentwickler. Grundkenntnisse der Administration sind auch für Datenbankentwickler interessant und erforderlich. Kapitel 6 vermittelt eine Einführung in diese Thematik. Kapitel 12: Optimierung von Abfragen. Für die Optimierung von Abfragen ist es erforderlich, die Abfrageleistung zu messen und langsame Abfragen, die überarbeitet werden sollten, herauszufinden. Welche Möglichkeiten Ihnen hierfür zur Verfügung stehen, ist unter anderem Gegenstand des Kapitels 12. Datenbankentwicklung Dieser Part ist natürlich der Schwerpunkt des Buches. Hier erfahren Sie alles über die Möglichkeiten für die Datenbankentwicklung mit dem SQL Server.
3
1 Einleitung Kapitel 7: T-SQL-Programmierung. Kapitel 7 erläutert weiterführende Konzepte für den Einsatz von T-SQL zur Datenbankprogrammierung. Hier wird erklärt, welche Sprachelemente T-SQL zu diesem Zweck zur Verfügung stellt. Das Kapitel erläutert auch die Entwicklung von gespeicherten Prozeduren, benutzerdefinierten Funktionen und Triggern. Kapitel 8: XML-Integration. Mit SQL Server 2005 wird ein nativer XML-Datentyp eingeführt. Die sich hierdurch ergebenden Möglichkeiten sind Gegenstand des Kapitels 8. Darüber hinaus erfahren Sie in diesem Kapitel, wie XML-Daten in ein Tabellenformat bzw. Tabellen in XML überführt werden können. Kapitel 9: SQL Server Service Broker. Der mit dem SQL Server 2005 eingeführte Service Broker ermöglicht eine asynchrone Datenverarbeitung. In Kapitel 9 erfahren Sie, wie Sie den Service Broker einsetzen können. Kapitel 10: Datenbankentwicklung mit .NET. Eine fantastische Neuigkeit in SQL Server 2005 ist die Möglichkeit, Datenbank- und Serverobjekte in einer .NETProgrammiersprache erstellen zu können. Kapitel 10 zeigt, wie es geht, und erklärt auch, was hierbei zu beachten ist. Kapitel 11: Web Services mit dem SQL Server. SQL Server 2005 erlaubt die Erstellung von nativen HTTP SOAP-Endpunkten und ist dadurch in der Lage, auf HTTP SOAP-Anforderungen zu antworten, ohne dass die Internet Information Services als Vermittler notwendig sind. Dies ermöglicht die Bereitstellung von Web Services direkt mit dem SQL Server. Kapitel 11 befasst sich mit dieser Thematik. Kapitel 12: Optimierung von Abfragen. Hier erfahren Sie, wie Sie als Datenbankentwickler Abfragen optimieren können. Kapitel 13: Anwendungen mit den Notification Services erstellen. Kapitel 13 befasst sich mit einer speziellen SQL Server-Komponente, den Notification Services. Die Notification Services bieten ein Anwendungs-Framework zur Erstellung von Applikationen für eine Ereignisüberwachung. In Kapitel 13 erhalten Sie hierzu eine Einführung. Das Kapitel 12 nimmt hier eine gewisse Sonderstellung ein. In diesem Kapitel wird untersucht, welche grundlegenden Möglichkeiten für die Optimierung von Abfragen SQL Server bietet. Sicher ist die Entwicklung performanter Abfragen in erster Linie Sache des Datenbankentwicklers. Das Kapitel befasst sich jedoch auch damit, auf welche Weise Abfragen, für die eine Optimierung lohnenswert oder erforderlich ist, gefunden werden können, wie man also die Abfrageleistung messen kann. Daher ist dieses Kapitel sicherlich auch für Administratoren interessant. Das Kapitel ist natürlich auch ein Beleg dafür, dass in vielen Fällen eine klare Abgrenzung zwischen Administrator und Datenbankentwickler schwierig ist.
4
1.4 Welche Voraussetzungen werden benötigt?
1.4
Welche Voraussetzungen werden benötigt? Persönliche Kenntnisse Zum Verständnis der in diesem Buch abgehandelten Thematiken sind grundlegende Kenntnisse relationaler Datenbanken nützlich, aber nicht unbedingt erforderlich. Wenn Sie wissen, wie Tabellen aufgebaut sind, so ist dies für ein Verständnis des Buchinhaltes absolut ausreichend. Falls Sie das Gefühl haben, in dieser Beziehung nähere Informationen zu benötigen, so finden Sie eine Vielzahl von Büchern, die sich mit dieser Thematik auseinandersetzen. Stellvertretend sei hier nur [STEI03] genannt. Sie benötigen weiterhin keinerlei SQL Server-Vorkenntnisse. Sicherlich ist es von Vorteil, wenn Sie bereits Gelegenheit hatten, Erfahrungen mit einer Vorgängerversion von SQL Server etwa SQL Server 2000 zu sammeln. Das Buch ist jedoch so aufgebaut, dass dies nicht erforderlich ist. Systemvoraussetzungen Microsoft empfiehlt einen Computer mit Pentium III 600 und 512 MB Hauptspeicher als Minimalvoraussetzung für die Installation des SQL Servers 2005. Für die lizenzkostenfrei verfügbare Express Edition des SQL Servers 2005 sollen sogar 192 MB als Hauptspeicher ausreichend sein. Das erscheint sicherlich etwas niedrig gegriffen. Ich kann dies jedoch bestätigen, da ich auf einem entsprechenden Computer (PIII 600, 512 MB) eine Installation der SQL Server 2005 Developer Edition im Einsatz hatte. Allerdings sollten Sie von solch einem System natürlich keine Wunder erwarten. Wenn möglich, verwenden Sie einen PC mit mehr Prozessor und vor allem mit mehr Hauptspeicher, erst dann bereitet die Arbeit mit SQL Server auch Freude. Damit Sie alle in diesem Buch verwendeten Beispiele nachvollziehen können, benötigen Sie zumindest die Standard Edition von SQL Server. Falls Sie diese Version nicht besitzen, so können Sie sich auf der Microsoft-Webseite auch eine 180-Tage-Testversion der Enterprise Edition von SQL Server herunterladen, in der dann alle SQL Server-Features zur Verfügung stehen. Die Verwendung der lizenzkostenfrei verfügbaren Express Edition ist für Experimente mit den in diesem Buch behandelten Thematiken nicht ausreichend. Insbesondere für die Beispiele in den Kapiteln 11, 12 und 13, aber auch für Teile der anderen Kapitel ist der Leistungsumfang der Express Edition zu eingeschränkt. (Kapitel 2 erklärt den Umfang der einzelnen Editionen etwas genauer.) Das Buch geht davon aus, dass eine SQL Server-Version bereits installiert ist, befasst sich also nicht mit der Installation des Servers. Um die Beispiele des Buches nachvollziehen zu können, müssen Sie außerdem lokale Administratorrechte besitzen oder zumindest für den SQL Server als Systemadministrator eingerichtet sein. In den Kapiteln 10, 11 und 13 werden C# Anwendungen bzw. -Assemblys mit Visual Studio 2005 erstellt. Wenn Sie die Beispiele in diesen Kapiteln nachvollziehen möchten, so
5
1 Einleitung benötigen Sie wenigstens die Visual Studio 2005 Express Edition. Diese Edition können Sie kostenlos im Internet beziehen. Mit der Visual Studio Express Edition sind allerdings nur lokale Verbindungen zu SQL Servern möglich. Sie benötigen dann also eine lokale Installation von SQL Server. Besser wäre es daher, wenn Sie Visual Studio 2005 wenigstens in der Standard Edition zur Verfügung haben. Die Beispieldatenbank AdventureWorks Zum Leistungsumfang aller SQL Server-Editionen gehört die AdventureWorksBeispieldatenbank. Diese Datenbank wird in der kompletten SQL Server-Dokumentation, aber auch in vielen Artikeln und Büchern zu SQL Server verwendet und ist sozusagen mittlerweile eine Art Standard für diese Zwecke. Auch in diesem Buch basieren viele Beispiele und Erklärungen auf der Datenbank AdventureWorks. Sie sollten also auf eine SQL Server-Installation mit dieser Datenbank Zugriff haben. Im vorliegenden Buch werden nicht alle Tabellen der Datenbank verwendet. In Kapitel 4 finden Sie ein Diagramm dieser Datenbank, das nur diejenigen Objekte der Datenbank darstellt, die in diesem Buch verwendet werden. Generell ist es eine gute Idee, wenn Sie sich das komplette Datenbankdiagramm dieser Datenbank einmal ansehen, um zum Beispiel auch für das Studium anderer Literatur zu SQL Server 2005 entsprechend gewappnet zu sein. Sie finden dieses Diagramm auf der Microsoft-Webseite unter dieser Adresse:
Zusätzlich wird das vollständige Datenbankdiagramm auch auf der Webseite zu diesem Buch zur Verfügung stehen.
1.5
Danksagung Ein Buch zu schreiben zumal als Erstautor ist eine echte Herausforderung! Es bedeutet zuerst einmal eine Menge Stress, gibt einem aber auch die Möglichkeit zu lernen. Der Lernprozess ist hierbei nicht nur auf das Fachliche beschränkt, sondern bezieht auch den eigentlichen Produktionsprozess ein. Das Buch, das Sie gerade in den Händen halten, ist letztlich das Resultat einer Teamarbeit. Sicherlich hat der Autor den größten Teil der Arbeit geleistet. Dafür steht ja dann auch sein Name auf dem Buchdeckel. Für mich war dies alles eine neue und ausgezeichnete Erfahrung. Wenn Sie ein Buch in die Hand nehmen, so können Sie in fast allen Fällen an dieser Stelle entsprechende Danksagungen lesen. So auch hier. Beim Lesen eines Buches werden Sie diesen Abschnitt in der Regel einfach übergehen. Aus meiner nun gesammelten Erfahrung kann Ich Ihnen aber versichern, dass diese Danksagungen allesamt durchaus ernst gemeint sind und nicht nur aus purer Höflichkeit geschrieben werden. Glauben Sie mir dies bitte!
6
1.5 Danksagung Ohne die Unterstützung einer Reihe weiterer Leute aus dem Verlag und auch dem persönlichen Umfeld wäre dieses Buch mit Sicherheit nicht möglich gewesen. In erster Linie möchte ich mich hier bei Herrn Fernando Schneider vom Hanser Verlag bedanken, der es mir überhaupt erst ermöglicht hat, dieses Buch in Angriff zu nehmen. Seine kompetente Beratung und sein Verständnis waren eine große Hilfe. Ein weiterer Dank gelten Frau Kraus vom Hanser Verlag sowie Frau Gottmann. Beide haben durch ihre Arbeit aus dem Manuskript überhaupt erst ein Buch geformt. Das letzte und zugleich wichtigste Dankeschön ist Angelika vorbehalten, die während der Zeit, die dieses Buch benötigt hat, auf vieles verzichten musste. Insbesondere ihre Unterstützung und ihre behutsame, aber doch irgendwie energische Art, mich anzutreiben, hat viel zum Gelingen dieses Buches beigetragen. Hierfür und auch für alles andere: Danke, Angelika.
7
2 SQL Server 2005-Architektur Der SQL Server 2005 ist inzwischen weit mehr als nur ein pures Datenbankmanagementsystem (DBMS). Sicherlich ist unbestreitbar, dass die Kernfunktionalität nach wie vor entscheidend durch das Datenbankmodul, das letztlich die DBMS-Funktionalität zur Verfügung stellt, bestimmt wird. Auch der Inhalt dieses Buches befasst sich weitgehend mit der Verwendung des Datenbankmoduls aus der Sicht des Datenbankentwicklers. Allerdings enthält der SQL Server auch eine Reihe weiterer Komponenten, die es ermöglichen, den SQL Server nicht nur als ein reines DBMS, sondern auch als Anwendungsserver zu betreiben. Die SQL Server-Komponenten werden im folgenden Kapitel, das sich mit der generellen Architektur des SQL Servers auseinandersetzt, zunächst vorgestellt. Im weiteren Verlauf dieses Buches werden Sie dann sehen, wie Sie einige dieser Komponenten als Datenbankentwickler einsetzen können.
2.1
SQL Server 2005-Komponenten Abbildung 2.1 zeigt die Komponenten des SQL Servers 2005. Die Abbildung verdeutlicht weiterhin die Integration von SQL Server in die Entwicklungsumgebung Visual Studio .NET und veranschaulicht, wie SQL Server-Anwendungen sich in das Gesamtsystem einfügen. Solche Anwendungen können natürlich von Ihnen selbst entwickelt werden. Auf der anderen Seite sind hierunter auch Standardanwendungen einzuordnen, wie zum Beispiel die Microsoft Office-Familie insbesondere Office 2007 oder auch der Microsoft SharePoint Portal Server. Wie bereits gesagt, wird sich dieses Buch in der Hauptsache mit den Möglichkeiten des Datenbankmoduls für die Verwaltung und Verarbeitung von relationalen Daten befassen. Es ist einfach so, dass die Datenbankentwicklung sich in der Hauptsache auf genau dieses Modul mit all seiner Komplexität konzentriert. Sie werden aber zum Beispiel auch erfahren, wie Sie Anwendungen mit den Notification Services erstellen und welche Möglichkeiten Ihnen Visual Studio .NET für die Entwicklung von Datenbankobjekten bietet.
9
2 SQL Server 2005-Architektur
Abbildung 2.1 Die Komponenten von SQL Server 2005
In den folgenden Abschnitten erhalten Sie eine Einführung in die einzelnen Bestandteile des SQL Servers.
2.1.1
Das SQL Server-Datenbankmodul
Das Datenbankmodul von SQL Server 2005 repräsentiert ein relationales Datenbankmanagementsystem (DBMS). Als ein solches System ist es verantwortlich für die Regelung aller Zugriffe auf die im System enthaltenen Datenbanken. Die relationalen Datenbanken bestehen hierbei im Wesentlichen aus Tabellen, die zueinander in Beziehung stehen. Das DBMS verarbeitet Anfragen an die Datenbanken entsprechend der vergebenen Zugriffsberechtigungen. Sie können sich das DBMS als eine Art Schutzschild um die eigentlichen Daten vorstellen. Es ist nicht möglich, dieses Schutzschild zu durchdringen. Alle Datenänderungs- und Datenleseprozesse müssen ihre Anforderungen sozusagen an die äußere Hülle richten und werden dann beantwortet oder auch abgelehnt. Durch diese Verfahrensweise werden letztlich die Datenintegrität und der Datenschutz gewährleistet. Das DBMS ist hierbei sogar in der Lage, die Daten bei einem Systemabsturz zu schützen. Die Verarbeitung von Daten wird innerhalb von Transaktionen durchgeführt. Transaktionen stellen sicher, dass alle Datenänderungen konsistent sind und dauerhaft gespeichert werden. Transaktionen sorgen auch dafür, dass mehrere Prozesse gleichzeitig auf identische Datenbereiche zugreifen können, wobei diese Prozesse innerhalb der Transaktionsverarbeitung voneinander isoliert werden. Hierdurch ermöglicht das DBMS eine parallele Datenverarbeitung. Transaktionen sind ein wesentliches Konzept von DBMS, aus diesem Grund ist dieser Thematik im vorliegenden Buch ein eigenes Kapitel (5) gewidmet.
10
2.1 SQL Server 2005-Komponenten Der Zugriff auf die Daten erfolgt über eine standardisierte Schnittstelle, unter Verwendung der Structured Query Language (SQL). Diese Abfragesprache muss jeder Datenbankentwickler beherrschen. Die Verwendung von SQL insbesondere des Microsoftspezifischen Dialektes T-SQL zieht sich daher durch alle Kapitel dieses Buches. Zusätzlich sind dieser Thematik zwei eigene Kapitel (4 und 7) gewidmet. Für die Speicherung aller Informationen, die das DBMS für die Sicherstellung der geschilderten Funktionalität benötigt, verwendet das DBMS ebenfalls Datenbanken. Diese Systemdatenbanken müssen in jeder SQL Server-Instanz existieren, damit das DBMS korrekt arbeiten kann. Das Datenbankmodul bietet die Möglichkeit, über sogenannte Systemkataloge aus diesen Systemdatenbanken Informationen über Server und Datenbanken zu erhalten. In Abschnitt 2.3 werden die Systemdatenbanken näher beleuchtet. In vielen Kapiteln dieses Buches werden Sie dann sehen, wie man die Systemkataloge abfragt. Der SQL Server-Dienst Jede SQL Server-Instanz enthält ein separates DBMS, das in einem eigenen WindowsDienst läuft. Sie können sich die SQL Server-Dienste sowie auch die Dienste der anderen SQL Server-Komponenten im Dienstmanager Ihres Computers ansehen (Abbildung 2.2).
Abbildung 2.2 Der SQL Server-Dienst der Standardinstanz
Der SQL Server Service Broker Der ebenfalls im Datenbankmodul enthaltene Service Broker ermöglicht die asynchrone Verarbeitung von Daten. Hierzu können Anfragen als Ereignisse in eine Warteschlange eingereiht und dann durch eine Anwendung abgearbeitet werden. Dies funktioniert übrigens auch über Servergrenzen hinweg, d. h., die Service Broker unterschiedlicher SQL
11
2 SQL Server 2005-Architektur Server-Instanzen können Nachrichten austauschen. Eine solche nachrichtenbasierte Datenverarbeitung kann zu einer besseren Server-Auslastung bzw. Lastverteilung beitragen. Das Einreihen von Ereignissen in eine Warteschlange benötigt zunächst nur minimale Ressourcen. Eine möglicherweise ressourcenintensive Abarbeitung dieser Ereignisse kann dann zu einem Zeitpunkt geringerer Server-Auslastung erfolgen. SQL Server ist sogar in der Lage, die Abarbeitung der Ereignisse innerhalb von Threads zu erledigen, deren Anzahl ja nach Zustand der Warteschlange automatisch erhöht oder erniedrigt wird. Der Service Broker ist Gegenstand eines eigenen Kapitels (9).
2.1.2
SQL Server Replication Services
Mit den Replication Services können verteilte SQL Server Datenbanken verwaltet werden, die eine dezentrale Verarbeitung von Daten ermöglichen. Über die Mechanismen der Replication Services ist es dann möglich, die dezentralen, an einer Replikation beteiligten Datenbestände wieder zu synchronisieren. Replication Services ermöglichen auch eine einfache Publikation von lokalen Datenänderungen für das Einspielen auf entfernten Servern bzw. Datenbanken. Hierzu müssen die entfernten Systeme nicht ständig verbunden sein, das Übertragen der Datenänderungen kann automatisch erfolgen, sobald ein System wieder online ist. Das klassische Einsatzgebiet hierfür sind sicherlich Architekturen, bei denen die Notebooks von Vertriebsmitarbeitern dezentral arbeiten und nach Verbindungsherstellung zur Zentrale automatisch auf den aktuellen Stand gebracht werden.
2.1.3
SQL Server Notification Services
Auch die Notification Services ermöglichen eine ereignisbasierte Verarbeitung von Daten. Mithilfe der Notification Services können zum Beispiel Datenänderungen überwacht und dann beim Auftreten von bestimmten Änderungen, die zuvor über ein Regelwerk definiert wurden, Benachrichtigungen an Abonnenten verschickt werden. Die Notification Services bieten ein Framework für die Entwicklung solcher Applikationen. Die Erstellung von Anwendungen mit den Notification Services wird in Kapitel 13 behandelt.
2.1.4
SQL Server Reporting Services
Die SQL Server Reporting Services sind eine serverbasierte Plattform, welche die Entwicklung und Verteilung von Berichten ermöglicht. Reporting Services stellen einen Server der mittleren Ebene zur Verfügung, der seinerseits die Internet Information Services benötigt. Über einen speziellen Berichtsmanager können Sie eine Vielzahl von Einstellungen vornehmen. Hierzu gehören zum Beispiel:
12
2.1 SQL Server 2005-Komponenten Berechtigungen. Sie können die Zugriffskontrolle für Elemente des Berichtsservers, wie zum Beispiel die Berichte selber, einstellen. Automatische Ausführungen. Ein Bericht kann automatisch zu einem bestimmten Zeitpunkt ausgeführt werden. Auslieferungen/Verteilung. Berichte können zum Beispiel auf einem Webserver bereitgestellt oder auch per E-Mail verteilt werden. Historie. Berichte können auch in Kopie auf dem Berichtsserver gespeichert werden. Auf diese Weise ist es möglich, einen Berichtsverlauf zu erzeugen. Erstellte Berichte können in einer Vielzahl von Formaten gespeichert und auch verteilt werden. Mögliche Formate sind zum Beispiel PDF, TIF, HTML, XML oder Microsoft Excel. Sie haben außerdem die Möglichkeit, den Benutzern des Systems das Generieren von Ad-hoc-Berichten auf der Basis zuvor definierter Berichtsmodelle zu gestatten.
2.1.5
SQL Server Analysis Services
Mit den Analysis Services können multidimensionale Datenbanken, OnLine Analytical Processing sowie Data Mining-Anwendungen erstellt und aktualisiert werden.
2.1.6
SQL Server Integration Services
Die Integration Services ermöglichen die Entwicklung von Extract-, Transform- und Load Paketen (ETL). Sie unterstützen die Erstellung von Datawarehouse-Anwendungen, für die eine unternehmensweite Datenintegration erforderlich ist. Integration Services Anwendungen sind in sogenannten Paketen organisiert. Innerhalb eines solchen Paketes wird in der Regel eine bestimmte Datentransformation erledigt. Hierbei können Daten aus einer Vielzahl von Datenquellen eingelesen und auch wieder an unterschiedliche Ziele ausgegeben werden. Zwischen dem Einlesen und dem Ausgeben von Daten enthält ein Paket Transformationsschritte, welche die empfangenen Daten letztlich in das erforderliche Zielformat überführen. Ein typischer Einsatzfall ist das Sammeln von Daten aus unterschiedlichen Quellen, wie zum Beispiel Excel-, Access- und Textdokumenten, sowie eventuell auch aus anderen Datenbanken und die Zusammenführung dieser Daten in einer zentralen Datenbank dem Datawarehouse. Die Komponenten Reporting Services, Analysis Services und Integration Services werden auch unter dem Begriff Business Intelligence (BI) eingeordnet. Die Thematik Business Intelligence ist relativ komplex und wird daher in der Literatur auch separat behandelt. Dieses Buch befasst sich nicht mit BI, sondern mit dem Thema Datenbankentwicklung. Falls Sie sich für die BI-Thematik interessieren, so finden Sie in [AZEV06], [SCHR06] oder [SCHU06] entsprechende Ausführungen.
13
2 SQL Server 2005-Architektur
2.1.7
Verwaltungswerkzeuge
Die Verwaltungswerkzeuge bestehen aus einer Handvoll leicht zu bedienender Programme, mit denen Sie die SQL Server-Komponenten konfigurieren und überwachen können. Das mit Abstand wichtigste Werkzeug ist sicherlich das SQL Server Management Studio, das im Verlaufe dieses Buches durchgängig zum Einsatz kommen wird. Eine gründliche Einführung in die Benutzung der einzelnen Werkzeuge sowie deren Verwendungszweck erfolgt im anschließenden Kapitel 3.
2.1.8
Entwicklungswerkzeuge
Für den Datenbankentwickler bietet der SQL Server 2005 die integrierte Entwicklungsumgebung Visual Studio 2005. Die Integration von SQL Server und Visual Studio ermöglicht die komfortable Erstellung von Anwendungen und Bibliotheken. Die integrierten Debugging-Möglichkeiten für T-SQL- und .NET-Quellcode vereinfachen die Fehlersuche ganz erheblich und können dazu beitragen, dass sich die Entwicklungszeiten verkürzen und die Fehlerquoten sinken. In Kapitel 10 werden Sie sehen, wie Visual Studio zur Erstellung von Datenbankobjekten verwendet werden kann. SQL Server kommt mit einer speziellen Edition von Visual Studio daher, dem sogenannten Business Intelligence Development Studio. Mit dieser Edition können Sie wie der Name schon verrät Business Intelligence-Anwendungen erstellen, also Reporting Services-, Analysis Services- und Integration Services-Projekte entwickeln. Das Business Intelligence Development Studio erlaubt jedoch nicht das Übersetzen von C#- oder .NETQuellcode. Für die in Kapitel 10 behandelte Datenbankprogrammierung mit .NET reicht das Business Intelligence Development Studio also nicht aus, hierfür benötigen Sie am besten die Professional Edition von Visual Studio. Im SQL Server Management Studio können Skriptsammlungen also T-SQL-Quellcode in Form von speziellen Projekten organisiert werden. Das Management Studio ermöglicht auch die Analyse von T-SQL-Befehlen in Bezug auf Ausführungsdauer und Ressourcenverbrauch. In gewisser Weise kann das Management Studio somit auch als ein Entwicklungswerkzeug angesehen werden.
2.1.9
Der SQL Server Agent
Der SQL Server Agent ist ein eigener Windows-Dienst. Jede SQL Server-Instanz bringt ihren eigenen Agenten mit, eine Ausnahme bildet hier lediglich die Express-Edition, für die kein Agent zur Verfügung steht. Der SQL Server Agent kann Ereignisse überwachen und beim Auftreten bestimmter Ereignisse Aktionen auslösen. Typischerweise wird ein zu überwachendes Ereignis das Erreichen eines bestimmten Zeitpunktes sein, an dem dann zum Beispiel eine (nächtliche) Datensicherung durchgeführt wird. Der Agent kann aber auch andere Ereignisse, wie zum Beispiel die Auslastung von Datenbanken oder das Windows-Ereignisprotokoll, überwachen und beim Überschreiten einer bestimmten Schwelle
14
2.2 SQL Server-Editionen bzw. dem Eintragen eines definierten Fehlers in das Ereignisprotokoll eine E-MailNachricht an bestimmte Personen, sogenannte Operatoren, verschicken. Der SQL Server Agent koordiniert auch die Replikation. In Kapitel 6 werden Sie sehen, wie der SQL Server Agent zum Erstellen von automatisierten Datensicherungen verwendet werden kann.
2.2
SQL Server-Editionen Die SQL Server-Produktfamilie ist in unterschiedlichen Editionen verfügbar. Diese Editionen unterscheiden sich bezüglich des angebotenen Leistungsumfangs und natürlich auch im Preis. Sie können aus den folgenden Editionen die für Sie passende auswählen: SQL Server Enterprise Edition Die Enterprise Edition bietet den kompletten Leistungsumfang der gesamten in SQL Server 2005 verfügbaren Features. SQL Server Standard Edition Die Standard Edition enthält gegenüber der Enterprise Edition Einschränkungen in Bezug auf die Skalierbarkeit, Verfügbarkeit und Business Intelligence. Ebenfalls nicht unterstützt wird die horizontale Partitionierung von Tabellen und Indizes. SQL Server Workgroup Edition Die Workgroup Edition enthält zusätzliche Einschränkungen gegenüber der Standard Edition. Die Notification Services stehen in dieser Edition nicht zur Verfügung. Ebenfalls nicht möglich ist die Erstellung von http-Endpunkten, also die Veröffentlichung von Web Services, wie dies in Kapitel 11 geschildert wird. SQL Server Developer Edition Die Developer Edition bietet den gleichen Leistungsumfang wie die Enterprise Edition. Der Einsatz dieser Edition ist aber nur für die Entwicklung gestattet. Das Lizenzmodell verbietet die Benutzung der Developer Edtion in Produktionsumgebungen. SQL Server Express Edition Die Express Edition können Sie lizenzkostenfrei benutzen. Diese Version hat gegenüber der Workgroup Edition weitere Einschränkungen. Insbesondere enthält diese SQL Server Edition nicht den SQL Server Agent. Außerdem ist die Größe einer einzelnen Datenbank auf 4 Gigabyte begrenzt.
15
2 SQL Server 2005-Architektur Der Leistungsumfang für die einzelnen Editionen kann sich im Laufe der Zeit allerdings verändern. Das ist vorrangig abhängig von der Lizenzpolitik, die ja durch eine Vielzahl von Faktoren beeinflusst werden kann. Dies betrifft insbesondere vielleicht dank MySQL die Express Edition, für die zum Beispiel ursprünglich weder das SQL Server Management Studio noch die Reporting Services verfügbar waren. Inzwischen können Sie für diese Edition das Service Pack 1 installieren, in dem nun sowohl die Reporting Services als auch das SQL Server Management Studio Express (ein spezielles Management Studio für die Express Edition) enthalten sind. Ich kann Ihnen also nur dazu raten, dass Sie sich die jeweils aktuellen und detaillierten Informationen zum Leistungsumfang der einzelnen Editionen von der Microsoft-Website holen. Dort finden Sie eine entsprechende Übersicht unter: http://www.microsoft.com/germany/sql/editionen/default.mspx
2.3
Systemdatenbanken Wie bereits weiter oben erwähnt, benutzt das Datenbankmodul für die permanente Speicherung von benötigten Informationen die sogenannten Systemdatenbanken der SQL Server-Instanz. Diese Systemdatenbanken werden für eine einwandfreie Funktion der SQL Server-Instanz benötigt, bei fehlenden oder fehlerhaften Systemdatenbanken ist eine uneingeschränkt korrekte Funktionalität nicht gewährleistet. SQL Server benötigt die folgenden Systemdatenbanken, die bei der Installation automatisch angelegt werden:
2.3.1
Die master-Datenbank
Die Datenbank master ist die zentrale Datenbank für die SQL Server-Instanz. In der master-Datenbank sind Informationen über alle im Server enthaltenen Objekte abgelegt. Hierzu zählen zum Beispiel Anmeldeinformationen, Sicherheitseinstellungen und Informationen zu Benutzerdatenbanken. Ohne diese Datenbank kann die SQL Server-Instanz nicht gestartet werden.
2.3.2
Die model-Datenbank
Die model-Datenbank ist eine Datenbank, die als Vorlage für die Erstellung neuer Datenbanken benutzt wird. Jede Datenbank, die in der SQL Server-Instanz angelegt wird, verwendet die model-Datenbank als Muster. Sie können die Datenbank model also so konfigurieren, dass jede erzeugte Datenbank bereits definierte Standardobjekte enthält. Typischerweise werden dies Benutzer oder Datentypen aber vielleicht auch bestimmte Tabellen oder gespeicherte Prozeduren sein.
16
2.3 Systemdatenbanken
2.3.3
Die msdb-Datenbank
In dieser Datenbank speichert vor allem der SQL Server Agent seine Objekte und Informationen. Hierzu zählen zum Beispiel Aufträge mit Ausführungsterminen, der Verlauf der Auftragsausführung und Benachrichtigungen. Die msdb enthält darüber hinaus auch Informationen über den Sicherungs- und Wiederherstellungsverlauf von Datenbanken.
2.3.4
Die tempdb-Datenbank
Die tempdb ist die Datenbank, in welcher der SQL Server temporäre Informationen ablegt. Sie können diese Datenbank mit dem TEMP-Verzeichnis von Windows vergleichen, in dem das Betriebssystem temporäre Informationen ablegt. Beim Start einer SQL ServerInstanz wird die tempdb stets mit der Standardgröße neu erzeugt. Während der Ausführung der Instanz wird die Größe der Datenbank dann dynamisch an die Erfordernisse angepasst. Die tempdb existiert wie alle anderen Systemdatenbanken auch pro SQL ServerInstanz einmal. Alle Prozesse, die auf die Instanz von SQL Server zugreifen, müssen sich also die tempdb teilen. Wenn nun diese Prozesse viele temporäre Objekte benötigen, so kann durch die gemeinsame Benutzung der Datenbank tempdb das Laufzeitverhalten leiden. Im Kapitel 5 werden wir hierauf noch einmal zurückkommen.
2.3.5
Weitere Datenbanken
Die einzelnen Komponenten von SQL Server nutzen ihrerseits ebenfalls die Möglichkeit, ihre Daten in einer Datenbank abzulegen. Außerdem können Sie bei der Installation der SQL Server-Instanz Beispieldatenbanken installieren. Diese Beispieldatenbanken sind die Grundlage für die meisten Codebeispiele in der SQL Server-Dokumentation und viele andere Diskussionen in Foren oder der Literatur zu SQL Server. ReportServer Die ReportServer-Datenbank ist die zentrale Datenbank für die SQL Server Reporting Services. Eine Reporting Services-Instanz speichert alle Informationen in dieser Datenbank. Hierzu zählen zum Beispiel Zugriffsberechtigungen, Berichtsdefinitionen, gespeicherte Berichte, Abonnements für die Berichtserstellung usw. ReportServer ist der Standardname für diese Datenbank, die Sie allerdings bei der Installation der Reporting Services auch anders benennen können. ReportServerTempDB Dies ist eine temporäre Datenbank für die SQL Server Reporting Services, ähnlich der tempdb für die SQL Server-Instanz. Diese Datenbank kann ebenfalls bei der Installation der Reporting Services umbenannt werden.
17
2 SQL Server 2005-Architektur AdventureWorks Die Datenbank AdventureWorks wurde bereits im einleitenden Kapitel 1 erwähnt. Diese Datenbank können Sie bei der SQL Server-Installation in einem separaten Schritt installieren. Für eine korrekte Funktion der SQL Server-Instanz wird die Datenbank AdventureWorks nicht benötigt. Auf einem Test- oder Entwicklungsserver ist die Installation dieser Datenbank allerdings zu empfehlen. Es sei an dieser Stelle nochmals daran erinnert, dass diese Datenbank die Grundlage für nahezu alle in diesem Buch verwendeten Beispiele bildet. AdventureWorksDW Auch die Datenbank AdventureWorksDW ist eine Beispieldatenbank, die Sie bei der SQL Server-Installation separat installieren müssen. Diese Datenbank ist die Basis für viele Beispiele zu den Analysis Services das DW steht hier für Datawarehouse. Im Prinzip finden Sie in der AdventureWorksDW eine stark denormalisierte Form der Datenbank AdventureWorks. Die AdventureWorksDW wird für dieses Buch nicht benötigt. Datenbanken für Notification Services-Instanzen Wenn Sie Notification Services Anwendungen erstellen, so wird jede dieser Anwendungen in einer Instanz von Notification Services ausgeführt. Eine solche Instanz kann mehrere Notification Services-Anwendungen ausführen. Jede Instanz von Notification Services besitzt eine eigene Datenbank, in der zum Beispiel Informationen über existierende Abonnenten und die Notification Services-Anwendungen, die in dieser Instanz ausgeführt werden, gespeichert werden. Datenbanken für Notification Services-Anwendungen Die einzelnen Notification Services-Anwendungen speichern ihre Informationen ebenfalls in einer zugehörigen Datenbank. Hierbei benutzt jede Anwendung ihre eigene Datenbank. In dieser Datenbank finden Sie zum Beispiel Informationen zu aufgetretenen Ereignissen und Abonnements.
2.4
Die Struktur einer SQL Server-Datenbank In diesem Abschnitt wird eine kurze Einführung in die generelle Struktur einer Datenbank im SQL Server präsentiert. Diese Einführung ist nicht umfassend, das Ziel ist lediglich, eine Grundlage für die weiteren Kapitel dieses Buches zu schaffen.
18
2.4 Die Struktur einer SQL Server-Datenbank
2.4.1
Datenbanken
Eine SQL Server-Datenbank ist aus logischer Sicht nichts anderes als einfach eine Sammlung von Datenbankobjekten, die in den folgenden Abschnitten gleich näher erläutert werden. Die SQL Server-Instanz speichert eine Datenbank in diversen Dateien des Betriebssystems dies ist sozusagen die physikalische Sicht auf die Datenbank. Dabei werden mindestens zwei Dateien benötigt, eine für die eigentlichen Daten hierin werden die erzeugten Datenbankobjekte abgelegt und eine zweite für die sogenannten Protokolleinträge. Wozu die Protokolleinträge nützlich sind, wird in Kapitel 5, das sich mit der Transaktionsverarbeitung des SQL Servers auseinandersetzt, behandelt. An dieser Stelle soll zunächst nur kurz ein Überblick über die zur Verfügung stehenden Datenbankobjekte gegeben werden.
2.4.2
Datenbankobjekte
Eine Datenbank ist natürlich nur von Nutzen, wenn sie in der Lage ist, Informationen zu speichern und diese Informationen auch wieder abgerufen werden können. Die Organisation dieser Datenspeicherung und des Abfragens von Informationen wird durch diverse Datenbankobjekte ermöglicht, die in diesem Abschnitt kurz vorgestellt werden sollen. In Kapitel 4 werden Sie dann erfahren, wie man Datenbankobjekte anlegt, ändert und auch löscht. Tabelle Das mit Abstand wichtigste Datenbankobjekt ist sicherlich die Tabelle, die letztlich die vom Benutzer eingegebenen Informationen speichert und über Abfragen auch ausgibt. Der Name Tabelle ist hierbei tatsächlich wörtlich zu nehmen, eine Datenbanktabelle besteht in der Tat aus Zeilen und Spalten. Für jede Tabelle muss bei der Erzeugung eine Struktur spezifiziert werden. Dadurch wird festgelegt, wie die einzelnen Zeilen der Tabelle aufgebaut sein müssen, also zum Beispiel wie viele Spalten die Tabelle enthält und wie die Datentypen dieser Spalten sind. Für die einzelnen Spalten können auch Einschränkungen angegeben werden, die insbesondere den Wertebereich einer Spalte begrenzen können. Jede Tabelle sollte außerdem über einen sogenannten Primärschlüssel verfügen, der eine Zeile innerhalb der Tabelle eindeutig identifiziert. Für eine Tabelle, die Kundendaten enthält, wird zum Beispiel die Kundennummer als Primärschlüssel dienen. Sicht Eine Sicht können Sie sich einfach wie eine virtuelle Tabelle vorstellen. Wie der Name schon andeutet, repräsentiert eine Sicht einen bestimmten Blick auf die eigentlichen Tabellendaten, die der Sicht zugrunde liegen. Durch die Sicht können Sie also aus Tabellen nur bestimmte Spalten oder Zeilen herausfiltern sowie Spalten im Ergebnis neu berechnen und/oder umbenennen.
19
2 SQL Server 2005-Architektur Schema Ein Schema ist letztlich nur ein Container, der andere Datenbankobjekte aufnehmen kann. Schemas werden vorrangig verwendet, um die Verwaltung von Datenbankobjekten und den Zugriff auf die im Schema enthaltenen Datenbankobjekte übersichtlicher zu gestalten. Gespeicherte Prozedur Gespeicherte Prozeduren ermöglichen die Programmierung des SQL Servers. Eine Prozedur enthält T-SQL Code, der einfach unter einem Namen, dem Prozedurnamen, in der Datenbank abgelegt werden kann. Eine Prozedur kann auch Parameter empfangen und verarbeiten. Benutzerdefinierte Funktion Auch benutzerdefinierte Funktionen ermöglichen die SQL Server-Programmierung. Wie Prozeduren, so können auch Funktionen Parameter entgegennehmen. SQL Server unterscheidet prinzipiell zwei Typen von benutzerdefinierten Funktionen: Skalarwertfunktionen. Dieser Funktionstyp gibt einen einfachen Parameter zurück. Tabellenwertfunktionen. Eine Tabellenwertfunktion gibt eine Tabelle als Ergebnis zurück. Trigger Trigger sind ebenfalls gespeicherte Prozeduren. Nur können Sie diese Prozeduren nicht direkt aufrufen. Die Ausführung eines Triggers ist immer an ein bestimmtes Ereignis gebunden, daher auch der Name. Beim Eintreten dieses Ereignisses wird der Trigger dann gefeuert. Trigger können zum Beispiel für Datenänderungsoperationen an Tabellen angelegt werden. So ist es möglich, immer dann, wenn Zeilen zu einer Tabelle hinzugefügt werden, den im Trigger enthaltenen T-SQL-Code auszuführen, der dann zum Beispiel zusätzliche Gültigkeitsprüfungen vornehmen oder Berechnungen durchführen kann. Benutzerdefinierter Datentyp Benutzerdefinierte Datentypen sind nichts anderes als Synonyme für SQL Server-eigene Datentypen. Durch die Verwendung benutzerdefinierter Datentypen können Sie für SQL Server-Datentypen Bezeichnungen aus Ihrer Anwendungsdomäne vergeben. Hierbei haben Sie auch die Möglichkeit, zusätzliche Attribute, wie zum Beispiel Genauigkeit, anzugeben. Dadurch könnten Sie in etwa einen eigenen Datentyp für E-Mail hinzufügen und spezifizieren, dass dieser eine maximale Länge von 80 Zeichen haben darf. Index Indizes können für Tabellen oder auch Sichten erzeugt werden. Über Indizes kann der Datenzugriff enorm beschleunigt werden, da bei der Verwendung von Indizes statt der an-
20
2.5 Zusammenfassung sonsten erforderlichen sequenziellen Suche die binäre Suche verwendet werden kann. In Kapitel 12 werden wir hierauf noch einmal zurückkommen. Benutzer Datenbankbenutzer ermöglichen die Verwaltung der Datenbanksicherheit. Insbesondere kann Benutzern der Zugriff auf Datenbankobjekte verwehrt oder gestattet werden. In Kapitel 6 wird darauf genauer eingegangen. Rolle Eine Rolle innerhalb einer Datenbank ist einfach ein Container, der Benutzer aufnehmen kann. Rollen können ebenfalls Berechtigungen erteilt oder entzogen werden, was natürlich entsprechende Auswirkungen auf die in der Rolle enthaltenen Benutzer hat. Dadurch erleichtern Rollen die Verwaltung von Zugriffsberechtigungen,
2.5
Zusammenfassung In diesem Kapitel haben Sie die Architektur des SQL Servers kennengelernt. Sie wissen nun, aus welchen Komponenten sich der SQL Server zusammensetzt und welche Rolle die einzelnen Komponenten übernehmen bzw. welche Aufgaben diese Komponenten erfüllen. Sie haben gesehen, welchen unterschiedlichen Leistungsumfang die verschiedenen SQL Server-Editionen bieten. Außerdem kennen Sie die Systemdatenbanken des SQL Servers. Zum Abschluss dieses Kapitels haben Sie schließlich noch eine Einführung in den generellen Aufbau von SQL Server-Datenbanken erhalten und erfahren, welche Datenbankobjekte in Datenbanken enthalten sind und welche Funktion diese Datenbankobjekte erfüllen.
21
3 SQL Server-Verwaltungswerkzeuge Bei der Installation des SQL Servers wird in der Programmgruppe Microsoft SQL Server 2005 eine Reihe von Programmen abgelegt, mit denen eine komfortable Verwaltung aller SQL Server-Komponenten möglich ist. Zusätzlich stehen Ihnen auch Werkzeuge zur Verfügung, mit denen Sie den SQL Server überwachen und konfigurieren sowie Business Intelligence-Projekte entwickeln können. Hierfür können Sie die in der folgenden Übersicht enthaltenen und kurz beschriebenen Instrumente verwenden: SQL Server-Konfigurations-Manager. Ein Werkzeug für die Konfiguration der SQL Server-Dienste und Netzwerk-Optionen. SQL Server-Oberflächenkonfiguration. Ein Programm zum Aktivieren oder Deaktivieren von SQL Server Features. SQL Server Management Studio. Die Verwaltungszentrale für alle SQL ServerKomponenten. Der enthaltene Abfrageeditor ermöglicht das komfortable Erstellen und Testen von T-SQL-Abfragen. Business Intelligence Development Studio. Die Entwicklungsumgebung für Business Intelligence-Projekte. Datenbankmodul-Optimierungsratgeber. Ein Ratgeber, der Abfragen analysiert und Vorschläge für physikalische Datenbankmodifikationen unterbereitet. SQL Server Profiler. Ein Programm zum Überwachen und Protokollieren der SQL Server-Aktivitäten. Sqlcmd. Ein Kommandozeilenprogramm für die Ausführung von Abfragen. SQL Server-Online-Dokumentation. Die umfassende Dokumentation für alle SQL Server-Komponenten. Nicht alle dieser Instrumente sind für den Datenbankentwickler gleichermaßen interessant, einige der genannten Programme werden zum Beispiel hauptsächlich für die SQL ServerAdministration benötigt. Schaden kann es jedoch nicht, wenn auch Sie als Datenbankentwickler die zur Verfügung stehenden Werkzeuge zumindest kennen wenn auch vielleicht nicht zu 100 % beherrschen. Im Rest dieses Kapitels wird daher auch für einige der Programme aus der obigen Auflistung nur eine Einführung erfolgen. Der Fokus in diesem Ka-
23
3 SQL Server-Verwaltungswerkzeuge pitel liegt eindeutig auf dem SQL Server Management Studio. Das SQL Server Management Studio ist unzweifelhaft das Instrument, das als das vorrangige Arbeitsmittel für Datenbankentwickler (und SQL Server-Administratoren) angesehen werden muss.
3.1
SQL Server-Konfigurations-Manager Den SQL Server-Konfigurations-Manager wird über das Startmenü Microsoft SQL Server 2005/Konfigurationstools/SQL Server-Konfigurations-Manager aufgerufen. Mit diesem Programm können Sie die SQL Server-Dienste verwalten und die Netzwerk-Optionen für den Server und die Clients einstellen.
Abbildung 3.1 Der SQL Server-Konfigurations-Manager
In sehen Sie die Windows-Dienste für die SQL Server-Installation, für die Sie mit dem Konfigurations-Manager genau dieselben Einstellungen vornehmen können, wie dies auch über die Systemsteuerung möglich ist. So können für die einzelnen SQL Server-Dienste zum Beispiel Startoptionen und Anmeldeinformationen spezifiziert werden. Die Abbildung zeigt auch einen Dienst NS$HAL9000-NSI_1. Dies ist der Dienst einer Notification Services-Instanz, die in Kapitel 13 entwickelt wird. Über die Auswahl von SQL Server 2005-Netzwerkkonfiguration kann angegeben werden, welche Netzwerkprotokolle der SQL Server unterstützen soll. Im Zweig SQL Native Client-Konfiguration haben Sie die Möglichkeit festzulegen, welche Netzwerkoptionen dem SQL Server-Client zur Verfügung stehen.
24
3.2 SQL Server-Oberflächenkonfiguration
3.2
SQL Server-Oberflächenkonfiguration Die SQL Server-Oberflächenkonfiguration verwenden Sie zum Aktivieren oder Deaktivieren von SQL Server Features. Die Deaktivierung von Features wird auch als Oberflächenreduzierung bezeichnet. Da nach der SQL Server-Installation aus Sicherheitsgründen eine Reihe von Features deaktiviert ist, werden Sie die Oberflächenkonfiguration sehr wahrscheinlich benötigen, um die Optionen Ihrer Installation im Nachhinein anzupassen. Zu den deaktivierten Features zählen zum Beispiel: Die Erlaubnis von Remoteverbindungen (Developer Edition). Diese Edition lässt zunächst nur lokale Verbindungen zu. CLR-Integration. Dieses Feature, das den Inhalt von Kapitel 10 dieses Buches bestimmt, ist ebenfalls nach der Installation nicht verfügbar. Die SQL Server-Oberflächenkonfiguration starten Sie ebenfalls über das Startmenü, unter Microsoft SQL Server 2005/Konfigurationstools/SQL Server-Oberflächenkonfiguration. Nach dem Start können Sie zwischen den Optionen Oberflächenkonfiguration für Dienste und Verbindungen und der Oberflächenkonfiguration für Features wählen (Abbildung 3.2).
Abbildung 3.2 SQL Server-Oberflächenkonfiguration
In diesem Fenster haben Sie auch die Möglichkeit, die Oberflächenkonfiguration für einen entfernten SQL Server durchführen zu können, wofür Sie dann natürlich auch die entsprechenden Berechtigungen haben müssen.
25
3 SQL Server-Verwaltungswerkzeuge Bei der Auswahl der Konfiguration für Dienste und Verbindungen wird ein Fenster geöffnet, in dem Sie die SQL Server-Dienste konfigurieren sowie die Optionen für Remoteverbindungen einstellen können (Abbildung 3.3).
Abbildung 3.3 Oberflächenkonfiguration für Dienste und Verbindungen
In Abbildung 3.3 sehen Sie, wie für das Datenbankmodul der lokalen Standardinstanz von SQL Server Remoteverbindungen über TCP/IP zugelassen werden. Auch hier haben Sie im Übrigen die Möglichkeit, die Startoptionen für die einzelnen Dienste zu konfigurieren, genauso wie dies auch mit dem SQL Server-KonfigurationsManager möglich ist. In der Liste der Dienste taucht wiederum der Dienst für die existierende Notification Services-Instanz, HAL9000-NSI_1, auf. Wenn Sie im Startfenster den Verweis für die Oberflächenkonfiguration für Features auswählen, öffnet sich ein anderes Fenster, ähnlich dem in Abbildung 3.4 gezeigten. In der Abbildung sehen Sie, wie ein http-Endpunkt, der einen Web Service zur Verfügung stellt, gestartet oder beendet werden kann. Der dort zu sehende Endpunkt AdWrksServicePoint wird in Kapitel 11 erstellt.
26
3.3 SQL Server Management Studio
Abbildung 3.4 Oberflächenkonfiguration für Features
3.3
SQL Server Management Studio Das SQL Server Management Studio ist das zentrale Instrument zur Konfiguration und Verwaltung aller SQL Server-Komponenten zuzusagen die Kommandozentrale von SQL Server. Dabei können nicht nur die SQL Server 2005-Komponenten, sondern auch Datenbankmodule der früheren Versionen 7.0 und 2000 mit dem Management Studio administriert werden. Für diese Module steht allerdings nur eine begrenzte Funktionalität zur Verfügung. Das Management Studio starten Sie über das Startmenü Microsoft SQL Server 2005/SQL Server Management Studio.
3.3.1
Start und Anmeldung
Nach dem Start des Management Studios werden Sie zuerst aufgefordert, eine Verbindung zu einer SQL Server-Komponente herzustellen. Hierzu wird ein Anmeldefenster geöffnet, in dem Sie diese Information eingeben können. Abbildung 3.5 zeigt dieses Fenster, wobei als Authentifizierungsmodus die Windows-Authentifizierung ausgewählt ist. Dies bedeutet, dass für die Anmeldung an die in der Combobox Servertyp ausgewählte SQL ServerKomponente derjenige Benutzer verwendet wird, der sich an das Betriebssystem angemeldet hat.
27
3 SQL Server-Verwaltungswerkzeuge
Abbildung 3.5 Anmeldung an das SQL Server Management Studio mit Windows-Authentifizierung
Für den Fall, dass Sie im Moment nicht genau wissen, was mit dem Authentifizierungsmode gemeint ist, haben Sie bitte einfach noch etwas Geduld. In Kapitel 6 wird hierauf näher eingegangen. Sie können für die Anmeldung auch die SQL Server-eigene Authentifizierung verwenden. In diesem Fall müssen Sie einen Benutzer und ein Kennwort für die Anmeldung am SQL Server angeben. Ein Beispiel für ein solches Anmeldefenster zeigt Abbildung 3.6.
Abbildung 3.6 Anmeldung an das SQL Server Management Studio mit SQL Server-Authentifizierung
Das SQL Server Management Studio kann alle SQL Server-Komponenten verwalten. In der Combobox Servertyp können Sie daher die Komponente auswählen, mit der Sie sich verbinden möchten (Abbildung 3.7).
Abbildung 3.7 Auswahl einer Komponente für die Anmeldung
28
3.3 SQL Server Management Studio Beachten Sie hierbei bitte, dass der Dienst für die entsprechende Komponente gestartet sein muss, ansonsten erhalten Sie beim Verbindungsversuch einen Fehler. In diesem Buch werden im Management Studio grundsätzlich nur Verbindungen zum SQL Server-Datenbankmodul hergestellt. Dies ist auch die Voreinstellung für die Combobox Servertyp. Falls Sie SQL Server 2005 auf Ihrem Computer installiert haben und auf diesem Computer auch lokale Windows-Administratorprivilegien besitzen, so können Sie im Anmeldefenster auch die Voreinstellung Windows-Authentifizierung für den Authentifizierungsmodus übernehmen. Im Feld Servername ist jeweils der Name des Servers voreingestellt, zu dem zuletzt eine Verbindung hergestellt wurde. Möchten Sie eine Verbindung zur Standardinstanz auf Ihrem (lokalen) Computer herstellen, so können Sie hier einfach localhost, (local) oder auch . (einen Punkt) angeben. Wenn Sie einmal erfolgreich eine Verbindung mit der Windows-Authentifizierung hergestellt haben, wird das Anmeldefenster beim nächsten Start von SQL Server Management Studio schon Voreinstellungen enthalten, die es Ihnen erlauben, einfach nur über den Button Verbinden die Verbindung herzustellen. Nach dem erfolgreichen Verbinden zum Datenbankmodul öffnet sich dann das Hauptfenster des SQL Server Management Studios (Abbildung 3.8).
Abbildung 3.8 Das Hauptfenster des SQL Server Management Studios
In den folgenden Abschnitten werden die einzelnen Bereiche des Hauptfensters nun näher erklärt.
29
3 SQL Server-Verwaltungswerkzeuge
3.3.2
Bereiche des Hauptfensters
Ein Hinweis vorweg: Sofern die im Folgenden beschriebenen Fenster in Ihrem Management Studio nicht sichtbar sind, können Sie diese Fenster über das Menü Ansicht öffnen. Registrierte Server Links oben wird das Fenster mit den im Management Studio registrierten Servern angezeigt (Abbildung 3.9). Über die Menüleiste dieses Fensters können Sie weitere Server registrieren. Die unterstützten Servertypen sind: SQL Server-Datenbankmodul SQL Server Analysis Services SQL Server Reporting Services SQL Server Mobile. Dies ist eine spezielle Version des SQL Servers für PDA und Pocket PC SQL Server Integration Services Innerhalb dieses Buches werden nur Verbindungen zum SQL Server-Datenbankmodul benötigt.
Abbildung 3.9 Registrierte Server
Objekt-Explorer Über den Objekt-Explorer können Sie Eigenschaften von SQL Server-Objekten abfragen und verändern. Es ist auch möglich, Objekte hinzuzufügen oder zu löschen. Der ObjektExplorer zeigt die Objekthierarchie des SQL Servers, so wie Sie dies bereits vom Windows Explorer und dem Dateisystem kennen. Oberster Knoten ist hierbei der Server selber, für den im Knoten die Information über den Servernamen, die Version des Servers und den angemeldeten Benutzer ausgegeben wird. In Abbildung 3.8 können Sie im Ordner Datenbanken alle Datenbanken des Servers HAL9000 sehen. Der Ordner Systemdatenbanken ist geöffnet, wodurch Sie auch die im vorigen Kapitel erwähnten Systemdatenbanken des Servers erkennen können.
30
3.3 SQL Server Management Studio Enthalten sind weiterhin die beiden Beispieldatenbanken AdventureWorks und AdventureWorksDW sowie die von den SQL Server Reporting Services verwendeten Datenbanken ReportServer und ReportServerTempDB. Schließlich sehen Sie in der Abbildung noch die Datenbanken für eine existierende Notification Services-Instanz (HAL9000-NSI_1NSMain) und die in dieser Instanz ausgeführte Notification ServicesAnwendung (HAL9000-NSI_1KonzertTermine). Sie können auch erkennen, dass die Datenbank Book sich im Offline-Status befindet, also derzeit keine Verbindungen zulässt. Im Ordner Sicherheit können vor allem SQL Server-Anmeldungen verwaltet werden. Hierzu werden Sie in Kapitel 6 mehr erfahren. Der Ordner Serverobjekte enthält Sicherungsmedien, SQL Server-Endpunkte (zum Beispiel HTTP-Endpunkte für Web Services, siehe Kapitel 11), Informationen für Verbindungen zu anderen Servern und Trigger, die beim Auftreten von Server-Ereignissen gefeuert werden. (Solche Trigger werden in Kapitel 7 entworfen.) Im Zweig Replikation können Publikationen und Abonnements konfiguriert und überwacht werden. Replikation wird in diesem Buch nicht behandelt. In der Mappe Verwaltung finden Sie zum Beispiel die SQL Server-Protokolle, in denen Information über SQL Server-Ereignisse abgelegt werden. In diesem Ordner hat der Administrator die Möglichkeit, SQL Server-Wartungsaufträge zu konfigurieren und die Aktivitäten der aktuellen SQL Server-Verbindungen zu überwachen. Der Notification Services-Ordner enthält einen Eintrag für jede konfigurierte Notification Services-Instanz. In Abbildung 3.8 ist eine solche Instanz mit dem Namen HAL9000-NSI_1 vorhanden. Der Zweig SQL Server-Agent ermöglicht die Konfiguration des Agenten und seiner zugehörigen Komponenten. Hier können Operatoren, Aufträge und Warnungen konfiguriert und kontrolliert werden. In Kapitel 6 wird der Agent verwendet, um zyklische Datensicherungen durchzuführen. Filter verwenden Filter sind eine nützliche Möglichkeit, um Objekte bestimmten Namens zu suchen bzw. die Liste der angezeigten Objekte zu reduzieren. Wenn Sie zum Beispiel den Ordner Systemsichten in der Datenbank master öffnen, so werden einige hundert Elemente angezeigt (Abbildung 3.10).
31
3 SQL Server-Verwaltungswerkzeuge
Abbildung 3.10 Einen Filter setzen
In einem solchen Fall kann es sehr hilfreich sein, wenn Sie das Ergebnis einschränken. Hierzu können Sie einfach auf das Filter-Symbol in der Menüleiste des Objekt-Explorers klicken (Abbildung 3.10), nachdem Sie den Ordner ausgewählt haben, in dem Sie den Filter anwenden möchten. Dadurch wird ein Fenster geöffnet, in dem Sie die Einstellungen für den Filter setzen können. Um zum Beispiel nur alle Objekte anzuzeigen, in deren Name die Zeichenkette memory vorkommt, geben Sie für die Eigenschaft Name den Operator Enthält an, und tragen Sie bei Wert memory ein, so wie in Abbildung 3.11 gezeigt.
Abbildung 3.11 Filtereinstellungen konfigurieren
32
3.3 SQL Server Management Studio Nach dem Bestätigen der vorgenommenen Einstellungen wird die Liste der angezeigten Objekte dann entsprechend der Filterung reduziert. Sie können im Objekt-Explorer Verbindungen zu weiteren Servern hinzufügen. Auch hier gibt es wieder die Möglichkeit, eine SQL Server-Komponente auszuwählen (Abbildung 3.12).
Abbildung 3.12 Eine Verbindung zum Objekt-Explorer hinzufügen
Nach der Angabe der Verbindungsinformation wird der ausgewählte Server dann ebenfalls im Objekt-Explorer angezeigt, und Sie können die Objekte dieses Servers überwachen und konfigurieren.
Abbildung 3.13 Das Eigenschaftsfenster der Datenbank AdventureWorks
33
3 SQL Server-Verwaltungswerkzeuge Objekt-Eigenschaften verwalten Für jedes dargestellte Objekt kann über das Kontextmenü ein Eigenschaftsfenster geöffnet werden, das sowohl Informationen zu diesem Objekt anzeigt als auch die Änderung von Eigenschaften gestattet. Um zum Beispiel Informationen zur Datenbank AdventureWorks zu erhalten, öffnen Sie das Kontextmenü für diese Datenbank, und wählen Sie den Eintrag Eigenschaften. Es öffnet sich ein Fenster, in dem Sie die Eigenschaften der Datenbank sehen und verändern können (Abbildung 3.13). Skripterstellung Falls Sie Eigenschaften verändern, so können Sie diese Änderungen durch den Button OK sofort übernehmen. Alle Eigenschaftsfenster bieten aber auch die Möglichkeit, dass Sie die vorgenommenen Änderungen nicht unmittelbar ausführen, sondern in einem T-SQL-Skript ablegen. Dieses Skript enthält die entsprechenden Anweisungen, um die Änderungen durchzuführen, und kann zu einem späteren Zeitpunkt ausgeführt oder auch für die wiederholte Ausführung gespeichert werden. Um ein solches Skript zu erstellen, bieten die Eigenschaftsfenster in der Menüleiste den Button Skript. Beim Klick auf diesen Button öffnet sich ein Untermenü, aus dem Sie Optionen für die Skripterzeugung auswählen können (Abbildung 3.14).
Abbildung 3.14 Skripterstellung für durchgeführte Änderungen
Das Menü bietet die folgenden Möglichkeiten zur Skripterzeugung: Skript für Aktion in Fenster Neue Abfrage schreiben. Hierbei wird im Abfrageeditor ein neues Fenster mit dem T-SQL-Skript geöffnet. Skript für Aktion in Datei schreiben. Sie werden zunächst aufgefordert, einen Dateinamen einzugeben. In dieser Datei wird das Skript abgelegt. Skript für Aktion in Zwischenablage schreiben. Das Skript wird in die Zwischenablage geschrieben und kann von dort in beliebige Dokumente eingefügt werden.
34
3.3 SQL Server Management Studio Skript für Aktion in Auftrag schreiben. Es wird ein SQL Server-Agent-Auftrag erstellt, der die Ausführung des Skriptes als Auftragsschritt beinhaltet. Für diesen Auftrag können Sie einen Zeitplan erstellen, um die periodische Ausführung des Skriptes anzustoßen. In Kapitel 6 werden Sie erfahren, wie dies funktioniert. Ich möchte Ihnen an dieser Stelle dazu raten, dass Sie von der Möglichkeit der Skripterstellung zu Beginn Ihrer Arbeit mit SQL Server regen Gebrauch machen. Es gibt kaum eine bessere Möglichkeit, sich mit T-SQL vertraut zu machen. Wirklich alle Eigenschaftsfenster bieten die Möglichkeit zur Skripterzeugung. Sie können dadurch Änderungen sozusagen nur antäuschen, indem Sie Eigenschaften verändern und dann aber die Änderungen nicht ausführen, sondern sich einfach nur ein Skript erstellen lassen. Dieses Skript mit den fiktiven Änderungen bietet wirklich eine hervorragende Möglichkeit, T-SQL zu lernen. Hinzu kommt, dass Sie ebenfalls Skripte für alle Datenbankobjekte wie Tabellen, Sichten, gespeicherte Prozeduren und auch für die Datenbanken selber erstellen können. Abbildung 3.15 zeigt dies am Beispiel einer Tabelle, für die ein Skript zur Erzeugung in einem neuen Abfrage-Editorfenster erstellt werden soll.
Abbildung 3.15 Tabellenerzeugungsskript erstellen
Das SQL Server Management Studio bietet sogar einen Assistenten für die Erzeugung von Skripten. Diesen Assistenten starten Sie zum Beispiel über das Kontextmenü der Datenbank, so wie in Abbildung 3.16 gezeigt.
35
3 SQL Server-Verwaltungswerkzeuge
Abbildung 3.16 Skript-Assistenten starten
Der Assistent führt Sie Schritt für Schritt durch die für die Skripterstellung zu verwendenden Optionen. So können Sie zum Beispiel auswählen, welche Datenbankobjekte in das Skript aufgenommen und ob für diese Objekte Erzeugungs- bzw. Löschanweisungen erstellt werden sollen. Abbildung 3.17 zeigt den Assistenten in Aktion.
Abbildung 3.17 Der Skript-Assistent
36
3.3 SQL Server Management Studio Zusammenfassung Der Bereich Zusammenfassung (bitte schauen Sie sich nochmals Abbildung 3.8 an) enthält den eigentlichen Arbeitsbereich, in dem zum Beispiel Abfragen eingegeben und ausgeführt werden können. Wie Sie mit Abfragen arbeiten, erfahren Sie etwas weiter unten. In der Zusammenfassung können Sie sich ebenfalls Informationen zu Datenbankobjekten anzeigen lassen. Hierzu stehen Ihnen diverse Berichte zur Verfügung, die je nach dem im Objekt-Explorer ausgewählten Objekttyp variieren. So existiert zum Beispiel für eine Datenbank ein anderer Satz von Berichten als für den SQL Server insgesamt. Eine Liste der vorhandenen Berichte bekommen Sie, indem Sie im Fenster Zusammenfassung auf den Button Berichte klicken. Abbildung 3.18 zeigt die verfügbaren Berichte für eine Datenbank.
Abbildung 3.18 Auswahl eines Berichtes
Wählen Sie den Bericht zur Datenträgerverwendung, so erhalten Sie ein Ergebnis wie in Abbildung 3.19 gezeigt.
37
3 SQL Server-Verwaltungswerkzeuge
Abbildung 3.19 Datenträgerverwendung in der Datenbank AdventureWorks
Projektmappen-Explorer SQL Server Management Studio bietet Ihnen die Möglichkeit, Datenbankprojekte anzulegen. Wenn Sie hiervon Gebrauch machen, werden Ihre Projekte und die in diesen Projekten enthaltenen Elemente im Projektmappen-Explorer verwaltet. Weiter unten werden Sie sehen, wie Sie Datenbankprojekte verwenden. Abfrageeditor Im Abfrageeditor werden Abfragen eingegeben und ausgeführt. Der Abfrageeditor enthält einen Ergebnisbereich, in dem außer dem eigentlichen Ergebnis der Abfrage auch weitergehende Informationen ausgegeben werden. Ein neues Abfragefenster können Sie einfach durch einen Klick auf den Button Neue Abfrage links oben in der Menüleiste öffnen (Abbildung 3.20).
38
3.3 SQL Server Management Studio
Abbildung 3.20 Ein neues Abfragefenster öffnen
Ist bereits eine Abfrage geöffnet, so werden für die Verbindungsinformationen der neuen Abfrage automatisch die Verbindungsinformationen der gerade geöffneten Abfrage verwendet, ansonsten müssen die Verbindungsinformationen wie in Abbildung 3.5 bzw. Abbildung 3.6 gezeigt angegeben werden. Abbildung 3.21 zeigt eine Abfrage und deren Ergebnistabelle. Bitte stören Sie sich im Moment nicht daran, wenn Sie die SQL-Anweisung nicht verstehen. In den Kapiteln 4 und 7 wird T-SQL noch sehr ausführlich behandelt. An dieser Stelle ist zunächst nur wichtig, dass Sie verstehen, wie man das SQL Server Management Studio verwendet, um Abfragen zu entwerfen. Wie diese Abfragen strukturiert sind, ist hier vorerst belanglos.
Abbildung 3.21 Ausführen einer Abfrage im Abfrageeditor
39
3 SQL Server-Verwaltungswerkzeuge Der Abfrageeditor kann übrigens nicht nur SQL-Abfragen für Datenbankmodule des SQL Servers ausführen. Sie können im Abfrageeditor auch Abfragen für SQL Server Analysis Services entwerfen. Hierfür stehen Ihnen MDX-, DMX- und XMLA-Abfragen zur Verfügung. Das ist aber nicht Gegenstand dieses Buches, wir werden hier nur mit T-SQL arbeiten. Sicherlich ist die Ausführung von Abfragen und das Auswerten der Abfrageergebnisse das hauptsächliche Einsatzgebiet für den Abfrageeditor. Dieses Instrument kann jedoch erheblich mehr. Für die zur Verfügung stehenden Optionen und Kommandos können Sie in den meisten Fällen einfach die Menüleiste verwenden, die so aufgebaut ist, wie in Abbildung 3.22 zu sehen.
Abbildung 3.22 Die Elemente der Menüleiste des Abfrageeditors
Im Folgenden werden die einzelnen Elemente dieser Menüleiste näher erklärt. Hierbei werden Sie auch zusätzliche Hintergrundinformationen erhalten und sicherlich feststellen, dass der Abfrageeditor ein enorm nützliches Werkzeug ist. Verbinden. Der Abfrageeditor erfordert für den Entwurf von Abfragen nicht zwingend eine Verbindung zu einem SQL Server. Falls der Abfrageeditor offline ist, können Sie über diesen Button eine Verbindung herstellen. Sie können Ihre Abfragen offline entwerfen, wenn Sie jedoch eine Abfrage ausführen, muss natürlich eine Verbindung existieren. Verbindung ändern. Betätigen Sie diesen Button, wenn Sie die Verbindungsinformationen ändern möchten. Dadurch wird die bestehende Verbindung beendet, und Sie werden aufgefordert, sich neu anzumelden. Dies ist zum Beispiel nützlich, wenn Sie eine entwickelte Abfrage unter einem anderen Sicherheitskontext ausführen möchten, um zu sehen, wie sich die existierenden Berechtigungen auf die Ausführung Ihrer Abfrage auswirken.
40
3.3 SQL Server Management Studio Trennen. Über diesen Button können Sie eine bestehende Verbindung beenden. Datenbank auswählen. Jede Abfrage beginnt auf einer Datenbank, die Sie in dieser Combobox festlegen können. Oftmals erhalten Sie bei der Abfrageausführung Fehlermeldungen, die besagen, dass die in der Abfrage verwendeten Objekte nicht existieren. In einem solchen Fall kontrollieren Sie bitte zunächst, ob Ihre Abfrage auch auf der richtigen Datenbank ausgeführt wird, dies ist eine häufige Ursache für einen solchen Fehler. Abfrage ausführen. Die eingegebene Abfrage wird ausgeführt. Wenn die Abfrage fehlerhaft ist, erhalten Sie im Meldungsfenster des Ausgabebereiches eine entsprechende Fehlermeldung. Ansonsten wird das Ergebnis der Abfrage vorausgesetzt natürlich, die Abfrage gibt ein Ergebnis zurück im Ergebnisbereich ausgegeben. Abfrage analysieren. Über diesen Button haben Sie die Möglichkeit, eine Syntaxprüfung der eingegebenen Abfrage vorzunehmen, ohne dass die Abfrage tatsächlich ausgeführt wird. Allerdings wird wirklich nur eine Überprüfung der Syntax der SQLAnweisungen vorgenommen. Es wird nicht kontrolliert, ob die in der Abfrage enthaltenen Objekte auch tatsächlich existieren. So wird zum Beispiel eine Abfrage
den Syntaxtest erfolgreich bestehen, obwohl die in der Abfrage verwendete Tabelle nicht existiert. Abfrageausführung abbrechen. Insbesondere bei langen Abfragen kann es nützlich sein, die Abfrage einfach abzubrechen. Wenn Sie zum Beispiel versehentlich eine Abfrage abgeschickt haben, die Millionen von Zeilen zurückliefern wird, so wird dies Server, Netzwerk und auch Client unnötig belasten. Eine solche Abfrage können Sie dann einfach stoppen. Geschätzten Ausführungsplan anzeigen. Ausführungspläne geben Auskunft darüber, wie der Abfrageoptimierer des Datenbankmoduls SQL Anweisungen physikalisch verarbeitet. Diese Ausführungspläne sind eine sehr große Hilfe bei der Abfrageoptimierung. Wenn Sie diesen Button betätigen, so analysiert der Abfrageoptimierer die Anweisungen und gibt einen geschätzten Ausführungsplan in grafischer Form zurück ohne allerdings die Abfrage auch auszuführen. Ein solcher Plan enthält wie gesagt Informationen zur internen Verarbeitung der Abfrage im SQL Server und sieht zum Beispiel so aus:
41
3 SQL Server-Verwaltungswerkzeuge
In diesem Buch werden Ihnen Ausführungspläne nochmals in Kapitel 12, das sich mit der Optimierung von Abfragen auseinandersetzt, begegnen. DTA Analyse. Der Database Tuning Advisor, oder zu Deutsch der DatenbankmodulOptimierungsratgeber, kann Abfragen analysieren und Hinweise für die Optimierung des physikalischen Datenbankentwurfs geben. Über diesen Button können Sie den im Editorfenster enthaltenen Abfragetext an den Datenbankmodul-Optimierungsratgeber übergeben und dann dort eine Analyse starten. Weiter hinten in diesem Kapitel werden Sie noch eine kurze Einführung in den Datenbankmodul-Optimierungsratgeber bekommen. In Kapitel 12 wird der Datenbankmodul-Optimierungsratgeber dann zum Einsatz kommen. Abfrageeditor öffnen. Über diesen Button können Sie einen grafischen Abfrageeditor öffnen und auf eine komfortable Art und Weise Abfragen entwerfen. Wenn Sie bereits Erfahrung mit Microsoft Access haben, so wird Ihnen dieser Editor sicherlich bekannt vorkommen:
42
3.3 SQL Server Management Studio Beim Klick auf OK wird die Abfrage nicht ausgeführt, sondern der Abfragetext nur in das Bearbeitungsbereich des standardmäßigen Abfrageeditors eingetragen. Vorlagenparameter ersetzen. Im SQL Server Management Studio können Sie TSQL-Code aus Vorlagen erzeugen. Solche Vorlagen existieren bereits für eine Vielzahl von Einsatzfällen, Sie können die vorhandenen Vorlagen aber auch durch Ihre eigenen Vorlagen ergänzen. Vorlagen finden Sie im Vorlagen-Explorer, den Sie über das Menü Ansicht/Vorlagen-Explorer öffnen. Die existierenden Vorlagen werden dann angezeigt, und Sie können aus den thematisch geordneten Vorlagen eine auswählen. Eine Vorlage zum Sichern einer Datenbank können Sie beispielsweise so öffnen:
Viele Vorlagen müssen allerdings noch angepasst werden. Hierzu werden in den Vorlagen Parameter verwendet. Dies sind einfach Platzhalter in einem bestimmten Format, die durch konkrete Werte ersetzt werden müssen, damit aus einer Vorlage ein ausführbares Skript wird. Über den Button Vorlagenparameter ersetzen können Sie diese Ersetzung vornehmen. Es öffnet sich ein Fenster, in dem alle Parameter aufgelistet sind und dann mit korrekten Werten versehen werden können. Für die ausgewählte Vorlage zum Sichern einer Datenbank müssen die beiden Parameter für die zu sichernde Datenbank und das Verzeichnis, in dem die Sicherung erstellt werden soll, spezifiziert werden:
43
3 SQL Server-Verwaltungswerkzeuge
Ausführungsplan einschließen. Wenn Sie diese Option auswählen, dann wird der tatsächliche Ausführungsplan zum Ergebnisbereich hinzugefügt. Dies passiert immer dann, wenn die Abfrage ausgeführt wird, und zwar so lange, bis Sie diese Option wieder ausschalten. Der erzeugte Ausführungsplan sieht genauso aus wie weiter oben bereits gezeigt. Clientstatistiken. Bei der Auswahl dieser Option werden zusätzliche Statistiken aus der Sicht des Clients, also des Absenders der Abfrage, im Ausgabebereich angezeigt. Diese Statistiken geben Auskunft über die Abfrage selber, enthalten aber auch Informationen zur Netzwerkauslastung und Ausführungszeit:
Sqlcmd-Modus. Sqlcmd ist ein Kommandozeilenprogramm zur Ausführung von Abfragen. Am Ende dieses Kapitels wird dieses Programm noch genauer erklärt. Der Abfrageeditor arbeitet im Sqlcmd-Emulationsmodus, wenn Sie diese Option einschalten.
44
3.3 SQL Server Management Studio Ergebnis in Text. Das Ergebnis der Abfrage wird in Textform ausgegeben. Über das Menü Abfrage/Abfrageoptionen
können Sie festlegen, wie die einzelnen Spalten voneinander getrennt werden sollen:
Ergebnis in Raster. Durch die Auswahl dieser Einstellung wird das Abfrageergebnis in ein Raster eingetragen, so wie in Abbildung 3.21 zu sehen ist. In diesem Raster können Sie die Breite der einzelnen Spalten verändern. Ergebnis in Datei. Wenn Sie diese Option wählen, werden Sie beim Ausführen einer Abfrage aufgefordert, einen Dateinamen anzugeben. Das Abfrageergebnis wird dann in diese Daten geschrieben. Das Format dieser Datei ist so wie die bei der Option Ergebnis in Text. Auskommentieren. Dieser Befehl fügt Zeilenkommentare (--) am Anfang der markierten Zeilen ein. Dadurch werden diese Zeilen nicht ausgeführt. Kommentierung aufheben. Durch diesen Befehl werden die Zeilenkommentare am Zeilenanfang wieder entfernt. Das Entfernen der Kommentare wird für die markierten Zeilen vorgenommen. Einzug verkleinern. Wenn die ausgewählten Zeilen eingerückt sind, wird der Einzug verkleinert. Einzug vergrößern. Die ausgewählten Zeilen werden weiter eingerückt, also nach rechts verschoben. Über das Menü Extras/Optionen
können Sie die Schrittweite einstellen, um die der Einzug bei jedem Klick auf den Button vergrößert wird. Der Standardwert ist 4:
45
3 SQL Server-Verwaltungswerkzeuge
Abfrageoptionen festlegen Etwas weiter oben haben Sie bereits den Dialog für die Einstellung der Abfrageoptionen gesehen. In diesem Dialog, der über das Menü Abfrage/Abfrageoptionen
geöffnet wird, können Sie weitere Optionen festlegen, welche die Darstellung des Abfrageergebnisses und des Abfragetextes sowie auch das Verhalten der Abfrage beeinflussen. Im Verlaufe dieses Buches wird auf einige dieser Optionen noch näher eingegangen. Teile einer Abfrage ausführen Der Abfrageeditor ermöglicht es auch, eine Abfrage nur teilweise auszuführen. Hierzu markieren Sie einfach den Teil der Abfrage, der ausgeführt werden soll. Wenn Sie nun die Abfrage starten, wird nur der markierte Teil der Abfrage ausgeführt. Abfrageergebnis speichern Sie wissen bereits, dass Sie das Abfrageergebnis über den Button Ergebnisse in Datei aus der Menüleiste direkt in eine Datei umleiten können. Es ist auch möglich, das Abfrageergebnis nach der Ausführung einer Abfrage in einer Datei zu speichern. Hierzu können Sie das Kontextmenü für den Ergebnisbereich öffnen, wie in Abbildung 3.23 gezeigt.
46
3.3 SQL Server Management Studio
Abbildung 3.23 Abfrageergebnis in einer Datei speichern
Alternativ können Sie das Ergebnis auch über das Menü Datei/Ergebnisse speichern unter
in einer Datei ablegen. Das Format der gespeicherten Datei hängt hierbei vom Abfrageergebnis ab. Ist das Ergebnis im Rasterformat ausgegeben worden, so werden die einzelnen Spalten in der Datei durch ein Semikolon voneinander getrennt. Ein Abfrageergebnis im Textformat wird einfach genau so in die Datei übernommen. Ein Raster können Sie auch komplett in die Zwischenablage kopieren und dann in ein anderes Dokument einfügen. In diesem Fall werden die einzelnen Spalten durch einen Tabulator voneinander getrennt. Dadurch können Sie zum Beispiel das Abfrageergebnis direkt in Microsoft Excel kopieren. Excel erkennt den Tabulator als Spaltentrennzeichen und erzeugt dadurch eine Tabelle, die dem Raster aus dem Abfrageergebnis entspricht. Abfragetexte öffnen und speichern Falls Sie Abfragetexte gespeichert haben, so können Sie diese im Abfrageeditor öffnen. Hierzu wählen Sie im Menü Datei/Öffnen/Datei
aus und laden die Datei in den Editor. Normalerweise haben diese Dateien die Endung .sql und sind auch bereits mit dem Abfrageeditor des Management Studios verknüpft, können also auch einfach durch Doppelklick im Abfrageeditor geöffnet werden. Es ist ebenfalls möglich, entworfene Abfragen als Datei zu speichern. Hierzu klicken Sie einfach irgendwo in das Fenster mit dem Abfragetext und wählen in der Menüleiste oder über das Menü Datei den Eintrag ... speichern aus. Natürlich funktioniert hier auch die bekannte Tastenkombination Strg+S.
3.3.3
Datenbankprojekte erstellen
SQL Server Management Studio bietet die Möglichkeit, Datenbankprojekte zu erstellen. In solchen Projekten können Sie Ihre T-SQL-Abfragen organisieren. Sie können eigene Skriptsammlungen erstellen, in denen Sie T-SQL-Skripte für wiederkehrende Aufgaben einordnen oder auch einfach nur interessante Skripte, die Sie zum Beispiel im Internet ge-
47
3 SQL Server-Verwaltungswerkzeuge funden haben, in eigenen Sammlungen ablegen. Durch die Einbindung von Datenbankprojekten in Microsoft Visual SourceSafe können Sie für diese Projekte auch eine Versionskontrolle ermöglichen. Abbildung 3.8 zeigt im rechten Teil den Projektmappen-Explorer. Nach jedem Start von SQL Server Management Studio wird automatisch eine neue Projektmappe mit Namen Solution 1 erstellt, sobald Sie die erste Abfrage hinzufügen. Zu dieser Projektmappe können Sie Datenbankprojekte hinzufügen. Hierfür wählen Sie entweder im Kontextmenü für die Projektmappe Hinzufügen/Neues Projekt
oder aus dem Menü Datei/Neu/Projekt
. In beiden Fällen öffnet sich ein Fenster, in dem eine Projektvorlage ausgewählt werden kann (Abbildung 3.24).
Abbildung 3.24 Ein SQL Server-Projekt
Es existiert hier auch die Möglichkeit, Skriptsammlungen für SQL Server Analysis Services oder SQL Server Mobile zu erzeugen. Für ein Projekt, das SQL Server-Skripte enthalten soll, wählen Sie den Projekttyp SQL Server Skripts und geben im Feld Name einen Namen für das Projekt und ein Verzeichnis an, im Beispiel heißt das Projekt Utilities1 und wird im Verzeichnis C:\SQL2005\Utilities1 abgelegt. Wenn der Dialog mit OK bestätigt wird, existiert das Projekt nun in der Projektmappe Solution 1 (Abbildung 3.25). Sie können die Projektmappe über das Kontextmenü auch umbenennen, wenn Ihnen der Name Solution 1 nicht gefällt. Sobald Sie die Projektmappe speichern, kann ebenfalls ein geeigneter Name für die Projektmappe angegeben werden.
48
3.3 SQL Server Management Studio
Abbildung 3.25 Das erzeugte Projekt Utilities1
Die im Projekt Utilities1 enthaltenen Abfragen sind im Ordner Abfragen enthalten. Im Moment ist dieser Ordner noch leer, über das Kontextmenü können Sie eine Abfrage hinzufügen (Abbildung 3.26).
Abbildung 3.26 Eine Abfrage zum Projekt hinzufügen
Durch das Hinzufügen einer Abfrage wird eine Datei mit einem automatisch vergebenen Namen SQLQuery_.sql im Projektverzeichnis erzeugt (der Unterstrich steht hierbei für eine automatisch vergebene Nummer) und im Abfrageeditor geöffnet. Sie können den Namen dieser Datei anschließend ändern und Ihre Abfrage entwerfen. Es ist auch möglich, eine als Datei vorhandene Abfrage zum Projekt hinzuzufügen. Dies kann einfach über Drag und Drop erledigt werden. Dadurch wird die Datei in das Projektverzeichnis kopiert und in das Projekt aufgenommen. Für den Fall, dass Sie eine Abfrage entworfen haben, die noch nicht gespeichert wurde und Sie diese Anfrage nun in ein Projekt der Projektmappe aufnehmen möchten, können Sie im Menü Datei/
verschieben in/
auswählen ( steht hier für den Namen eines Projektes). Um zum Beispiel die aktuell im Abfrageeditor enthaltene Ab-
49
3 SQL Server-Verwaltungswerkzeuge frage in das Projekt Utilities1 aufzunehmen, können Sie so vorgehen, wie in der folgenden Abbildung 3.27 gezeigt. Die Abfrage wird dadurch nachdem ein Dateiname angegeben wurde in den Ordner Abfragen des Projektes aufgenommen.
Abbildung 3.27 Eine Abfrage in das Projekt verschieben
Im Projektordner Sonstiges können übrigens beliebige Dateien wie zum Beispiel Textoder HTML-Dokumente abgelegt werden, die im Projekt verwaltet werden sollen. Der Projektordner Verbindungen enthält eine Liste aller Datenbankverbindungen, die von Ihren Abfragen verwendet werden. Diese Verbindungen werden nur angezeigt und können nicht geändert werden. Für eine Versionskontrolle Ihrer Skripte kann das Projekt zur Quellcodeverwaltung über das Kontextmenü der Projektmappe hinzugefügt werden (Abbildung 3.28).
Abbildung 3.28 Hinzufügen der Projektmappe zur Quellcodeverwaltung
50
3.4 Business Intelligence Development Studio Es ist übrigens möglich, mehrere Projekte in einer Projektmappe zu verwalten. Über das Kontextmenü der Projektmappe können jederzeit weitere Projekte angelegt und zur Mappe hinzugefügt werden Falls bereits Projekte existieren, ist es auch möglich, diese zu einer bestehenden Projektmappe zu addieren. Die Dateitypen für Projekte und Projektmappen werden bei der Installation mit dem SQL Server Management Studio verknüpft. Dadurch können bestehende Projekte und auch Projektmappen einfach durch Doppelklick im Management Studio geöffnet werden. Dies betrifft Dateien mit diesen Endungen: ssmssln. Die Dateiendung ist die Abkürzung für SQL Server Management Studio Solution. Diese Datei enthält eine Projektmappe. ssmssqlproj. Dies ist die Abkürzung für SQL Server Management Studio SQL Project. In einer Datei dieses Typs ist eine Projektbeschreibung gespeichert.
3.4
Business Intelligence Development Studio Das Business Intelligence Development Studio ist eine Entwicklungsumgebung zur Erstellung von Business Intelligence(BI)-Lösungen. Das Programm kann über das Startmenü Microsoft SQL Server 2005/ Business Intelligence Development Studio gestartet werden. In Abbildung 3.29 sehen Sie die beim Anlegen eines Projektes zur Verfügung stehenden Projektvorlagen.
Abbildung 3.29 Anlegen eines Business Intelligence-Projektes
51
3 SQL Server-Verwaltungswerkzeuge Ein Business Intelligence-Projekt kann unter Verwendung einer der folgenden Vorlagen erstellt werden: Analysis Services-Projekt. In diesem Projekttyp werden Analysis ServicesDatenbanken entwickelt und verwaltet. Solche OLAP-Datenbanken sind für den schnellen Lesezugriff optimiert. Ein Analysis Services-Projekt kann auch Data MiningModelle enthalten. In [SCHRO06] finden Sie hierzu weitergehende Informationen. Analysis Services 9.0-Datenbank importieren. Falls bestehende SQL Server 2000oder SQL Server 7.0-Analysis Services-Datenbanken in eine SQL Server 2005Analysis Services-Datenbank überführt werden sollen, kann dieser Projekttyp verwendet werden. Integration Services-Projekt. Ein Integration Services-Projekt ist ein Projekt zur Integration von Daten. In einem solchen Projekt können zum Beispiel Daten aus unterschiedlichen Quellen eingelesen, bearbeitet (transformiert) und zentral gespeichert werden. Berichtsserverprojekt. In einem Berichtsserverprojekt werden Berichte für SQL Server Reporting Services erstellt. Hierzu steht ein Berichtsdesigner zur Verfügung, mit dem Datenzugriffe und Berichtslayouts entworfen werden. Berichtsmodellprojekt. Ein Berichtsmodell enthält eine Geschäftssicht auf die zugrunde liegenden Daten. Diese Sicht können Benutzer des Modells verwenden, um mit dem Berichtsgenerator Ad-hoc-Berichte zu erstellen. Berichtsserverprojekt-Assistent. Bei der Auswahl dieses Projekttyps wird ein Berichtsserverprojekt unter Verwendung eines Assistenten erstellt. Das erstellte Projekt kann dann anschließend manuell bearbeiten werden. Die Thematik Business Intelligence ist nicht Bestandteil dieses Buches, das Business Intelligence Development Studio wird daher hier auch nicht zum Einsatz kommen.
3.5
SQL Server Profiler Der SQL Server Profiler ist ein Instrument zur Überwachung und Protokollierung der SQL Server-Aktivität. Das erzeugte Protokoll wird auch als Ablaufverfolgung bezeichnet. Eine Ablaufverfolgung kann einfach in einer Datei im nativen Format des Profilers abgelegt werden. Das Protokoll kann aber auch in einer Datei im XML-Format oder in einer Datenbanktabelle gespeichert werden. Für eine Ablaufverfolgung müssen Ereignisse und Ereignisdaten (auch Ereignisspalten genannt), die in die Ablaufverfolgung aufgenommen werden sollen, angegeben werden. Den Profiler starten Sie über das Startmenü Microsoft SQL Server 2005/ Leistungstools/SQL Server Profiler. Alternativ ist der Aufruf auch aus dem SQL Server Management Studio über das Menü Extras/SQL Server Profiler möglich (Abbildung 3.30).
52
3.5 SQL Server Profiler
Abbildung 3.30 Start des SQL Server Profilers aus dem Management Studio
Nach dem Start des Profilers kann über die Menüleiste eine neue Ablaufverfolgung erzeugt werden (Abbildung 3.31).
Abbildung 3.31 Eine neue Ablaufverfolgung konfigurieren
Beim Erzeugen einer neuen Ablaufverfolgung erscheint zunächst der schon aus der Anmeldung an das Management Studio bekannte Anmeldedialog. Nach der erfolgreichen Anmeldung wird ein Dialog geöffnet, in dem die zu protokollierenden Ereignisse ausgewählt werden können. Es existiert eine Vielzahl von Ereignissen, mit denen Sie Ihre Ablaufverfolgung ganz speziell an die jeweiligen Erfordernisse anpassen können. Die Auswahl geeigneter Ereignisse ist keinesfalls trivial. Grundsätzlich wird das Erstellen einer Ablaufverfolgung zu einer Verschlechterung des Laufzeitverhaltens von SQL Server führen, da die Protokollierung ja auch Ressourcen auf dem Server benötigt. Für die Ereignisauswahl sollte deshalb stets der Grundsatz so wenig wie möglich, so viel wie nötig gelten. Glücklicherweise existieren für viele Einsatzfälle bereits vordefinierte Sammlungen von zu protokollierenden Ereignissen, die in Vorlagen zusammengefasst sind. Beim Konfigurieren der Ablaufverfolgungseigenschaften ist es oftmals ausreichend, einfach eine solche Vorlage zu verwenden (Abbildung 3.32). Es ist auch möglich, bestehende Vorlagen zu verändern oder eigene Vorlagen zu entwerfen.
53
3 SQL Server-Verwaltungswerkzeuge
Abbildung 3.32 Vorlagen für zu protokollierende Ereignisse
Tabelle 3.1 enthält eine Kurzübersicht der zur Verfügung stehenden Vorlagen. Tabelle 3.1 Vorlagen für die Ablaufverfolgung Vorlage
Beschreibung
SP_Counts
Geeignet für die Protokollierung der Ausführung gespeicherter Prozeduren.
Standard (Standard)
Protokollierung gespeicherter Prozeduren und T-SQL-Stapel.
TSQL
Protokolliert T-SQL-Anweisungen und deren Startzeit.
TSQL_Duration
Protokolliert T-SQL-Anweisungen und deren Ausführungszeit.
TSQL_Grouped
Protokolliert T-SQL-Anweisungen und deren Startzeit und gruppiert das Protokoll nach dem ausführenden Benutzer.
TSQL_Replay
Protokolliert Informationen, die für einen erneutes Einspielen der Ablaufverfolgung erforderlich sind.
TSQL_SPs
Protokolliert ebenfalls Informationen zur Ausführung von gespeicherten Prozeduren.
Tuning
Erstellt eine Ablaufverfolgung, die als Eingabe für den DatenbankmodulOptimierungsratgeber verwendet werden kann.
Durch die Auswahl einer Vorlage sind bereits die entsprechenden Ereignisse für die Protokollierung ausgewählt. Auf der Seite Ereignisauswahl können die Ereignisse aber auch noch modifiziert werden (Abbildung 3.33).
54
3.5 SQL Server Profiler
Abbildung 3.33 Manuelle Auswahl der Protokollereignisse
Wichtig ist, dass Sie die beiden Optionen Alle Ereignisse anzeigen und Alle Spalten anzeigen auswählen, anderenfalls bekommen Sie nur die bereits ausgewählten Ereignisse angezeigt. Wie gesagt: Die zur Verfügung stehenden Ereignisse sind zahlreich und sollten sorgfältig ausgewählt werden. Dies erleichtert die Zusammenstellung der zu protokollierenden Ereignisse natürlich nicht gerade. Glücklicherweise erhalten Sie aber im Dialog für die Ereignisauswahl eine Kurzbeschreibung für Ereignisse und die Ereignisspalten. Sobald der Mauszeiger sich über einem Ereignis bzw. einer Ereignisspalte befindet, wird die Beschreibung im unteren Teil des Fensters angezeigt. Tabelle 3.2 gibt einen Überblick über einige häufig verwendete Ereignisse. Tabelle 3.2 Einige häufig benötigte Ereignisse Bereich
Ereignis
Beschreibung
Stored Procedures
SP:Starting
Tritt auf, wenn eine gespeicherte Prozedur gestartet wird.
SP:Completed
Tritt auf, wenn eine gespeicherte Prozedur beendet wurde.
SP:StmtStarting
Tritt auf, wenn innerhalb einer Prozedur eine Anweisung beginnt.
SP:StmtCompleted
Tritt auf, wenn innerhalb einer Prozedur eine Anweisung abgeschlossen wurde.
SQL:BatchStarting
Tritt auf, wenn ein T-SQL-Stapel beginnt.
SQL:BatchCompleted
Tritt auf, wenn ein T-SQL-Stapel beendet wurde.
TSQL
55
3 SQL Server-Verwaltungswerkzeuge Bereich
Ereignis
Beschreibung
TSQL
SQL:StmtStarting
Tritt auf, wenn eine Anweisung in einem T-SQLStapel beginnt.
SQL:StmtCompleted
Tritt auf, wenn eine Anweisung in einem T-SQLStapel beendet wurde.
Audit:Login
Tritt auf, wenn eine Clientverbindung zum SQL Server hinzugefügt wurde.
Audit:Logout
Tritt auf, wenn eine Clientverbindung beendet wurde.
Security Audit
Es stehen übrigens nicht alle Ereignisspalten für alle Ereignisse zur Verfügung. Möchten Sie zum Beispiel die Dauer einer Abfrage in die Ablaufverfolgung aufnehmen, so muss die Spalte Duration hinzugefügt werden. Dies ist aber nur möglich, wenn auch das Ereignis zum Beenden einer Abfrage protokolliert wird. Zusätzlich können die protokollierten Zeilen über Spaltenfilter eingeschränkt werden. Hierzu klicken Sie in der Ereignisauswahl auf den Button Spaltenfilter. Es öffnet sich ein Dialog, in dem die Filterbedingungen eingestellt werden können. Abbildung 3.34 zeigt die Konfiguration einer Filterbedingung, die bewirkt, dass nur Ereignisse für die Datenbank AdventureWorks protokolliert werden.
Abbildung 3.34 Spaltenfilter konfigurieren
Speichern von Ablaufverfolgungen Beim Konfigurieren der Ablaufverfolgungseigenschaften (Abbildung 3.32) kann auch festgelegt werden, wie das erzeugte Protokoll gespeichert werden soll. Prinzipiell ist es möglich, ein Protokoll in einer Datei oder in einer Datenbanktabelle zu speichern. Die Speicherung in einer Tabelle hat natürlich einige Vorteile, zum Beispiel kann eine solche
56
3.5 SQL Server Profiler Tabelle relativ einfach unter Verwendung von SQL durchsucht werden, was bei Dateien nicht möglich ist. Allerdings ist die Speicherung in einer Tabelle auch langsamer als die Dateivariante. Es gibt jedoch eine Möglichkeit, eine Ablaufverfolgungsdatei in eine entsprechende Tabelle zu konvertieren. In Kapitel 12 werden Sie erfahren, wie dies funktioniert. Es ist auch jederzeit möglich, eine Ablaufverfolgung über das Menü Datei/Speichern oder Datei/Speichern unter in einer Datei zu speichern. Ist die Auswahl der Ereignisse abgeschlossen, kann die konfigurierte Ablaufverfolgung schließlich über den Button Ausführen gestartet werden. Abbildung 3.35 zeigt den Profiler mit einer laufenden Ablaufverfolgung.
Abbildung 3.35 Der SQL Server Profiler in Aktion
Über die Menüleiste kann die Ablaufverfolgung auch angehalten bzw. gestoppt und fortgesetzt bzw. neu gestartet werden. Es ist auch möglich, die Ereignisse für eine Ablaufverfolgung zu verändern, dafür muss die Ablaufverfolgung aber zumindest angehalten werden (Abbildung 3.36).
Abbildung 3.36 Die Menüleiste des Profilers
57
3 SQL Server-Verwaltungswerkzeuge Der SQL Server Profiler ist übrigens ein ganz hervorragendes Instrument zum Debuggen. Im Profiler können Sie genau verfolgen, wie Ihre vom Client abgeschickten SQLAnweisungen letztlich im SQL Server ankommen. Dies ist oftmals eine große Hilfe bei der Fehlersuche. Seien Sie aber bitte vorsichtig beim Einsatz des Profilers auf Produktionsservern. Denken Sie stets daran, dass die Protokollierung ihrerseits ebenfalls SQL Server-Ressourcen bindet. Dieser Umstand kann das Laufzeitverhalten eines Servers ganz erheblich beeinflussen. Der Profiler wird Ihnen in Kapitel 12 zur Abfrageoptimierung nochmals begegnen. Dort werden Sie auch sehen, wie man serverseitige Ablaufverfolgungen erstellt, die möglichst wenig Einfluss auf das SQL Server Laufzeitverhalten haben.
3.6
Datenbankmodul-Optimierungsratgeber Der Datenbankmodul-Optimierungsratgeber (englisch: Database Engine Tuning Advisor oder kurz DTA) kann sogenannte Arbeitsauslastungen analysieren und Vorschläge zum Ändern des physikalischen Datenbankentwurfs unterbreiten. Ein Analyseergebnis kann Vorschläge für die Änderung von: Indizes Gruppierten Indizes Indizierten Sichten Partitionen enthalten. Arbeitsauslastungen, also die Eingabe für den DTA, können sowohl T-SQL-Skripte als auch gespeicherte SQL Server Profiler-Ablaufverfolgungen sein. Ablaufverfolgungen, die als Eingabe für den DTA verwendet werden sollen, müssen bestimmte Informationen erhalten, damit eine Analyse durchgeführt werden kann. Am einfachsten erreichen Sie dies, indem Sie die Ablaufverfolgung mit der Vorlage Tuning erstellen (schauen Sie sich bitte nochmals Abbildung 3.32 an, dort sehen Sie diese Vorlage als letzte in der Liste). Diese Vorlage enthält bereits alle notwendigen Ereignisse und Ereignisspalten. Den DTA starten Sie über das Startmenü Microsoft SQL Server 2005/Leistungstools/ Datenbankmodul-Optimierungsratgeber oder über das Menü Extras/DatenbankmodulOptimierungsratgeber (siehe Abbildung 3.30) im Management Studio. Beim Start werden Sie zunächst den schon vertrauten Anmeldedialog sehen. Nach der Anmeldung wird sofort eine neue Sitzung geöffnet. Über die Menüleiste kann aber auch jederzeit manuell eine Sitzung hinzugefügt werden (Abbildung 3.37).
58
3.6 Datenbankmodul-Optimierungsratgeber
Abbildung 3.37 Eine DTA-Sitzung hinzufügen
Nach der Erzeugung einer neuen Sitzung können dann die für die Analyse erforderlichen Parameter eingegeben werden. Hierzu zählen der Name für die Sitzung sowie die Datenbanken und Tabellen für die Optimierung. Außerdem benötigt der DTA eine Arbeitsauslastung, welche die Basis für die Analyse bildet Diese Arbeitsauslastung kann in einer Datei stehen und ein T-SQL-Skript bzw. eine Profiler-Ablaufverfolgung enthalten. Da eine Ablaufverfolgung ebenso in einer Tabelle einer Datenbank gespeichert werden kann, ist es hier auch möglich, eine solche Tabelle als Arbeitsauslastung anzugeben. In Abbildung 3.38 sehen Sie eine Beispielkonfiguration.
Abbildung 3.38 Sitzungsparameter festlegen
Auf der Registerkarte Optimierungsoptionen (Abbildung 3.39) kann festgelegt werden, welche Objekttypen optimiert und welche bei der Optimierung nicht berücksichtigt werden sollen. Hier ist es auch möglich anzugeben, dass gewisse physikalische Strukturen beibe-
59
3 SQL Server-Verwaltungswerkzeuge halten werden sollen. Die Analyse untersucht die entsprechenden Datenbanken auch auf überflüssige Elemente, wie zum Beispiel Indizes. Wenn in Ihrer Arbeitsauslastung solche Elemente nicht benötigt werden, so wird der DTA vorschlagen, diese zu entfernen. Falls Sie dies vermeiden möchten etwa weil Sie wissen, dass die Arbeitsauslastung nicht repräsentativ ist , so können Sie festlegen, dass keine Vorschläge für das Entfernen vorhandener Elemente unterbereitet werden sollen.
Abbildung 3.39 Optionen für die Optimierung festlegen
Beim Einstellen der Optionen ist es auch möglich, dass Sie die Zeit für die Optimierung einschränken. Dies ist sicherlich dann sinnvoll, wenn Sie sehr umfangreiche Ablaufverfolgungen verwenden, deren Analyse eventuell zu lange dauern würde. Haben Sie alle Optionen festgelegt, kann die Analyse schließlich gestartet werden. Hierzu klicken Sie einfach auf den Button Analyse in der Menüleiste. Auch der Datenbankmodul-Optimierungsratgeber wird nochmals in Kapitel 12 auftauchen. Dort wird dann auch ein Analyseergebnis präsentiert.
60
3.7 Sqlcmd
3.7
Sqlcmd Sqlcmd ist ein Befehlszeilenprogramm zur Ausführung von SQL-Skripten. Hierbei kann Sqlcmd wahlweise einzelne Anweisungen von der Kommandozeile entgegennehmen, aber auch komplette T-SQL-Skripte aus einer Datei verarbeiten. Das Ergebnis kann dann ebenso entweder ausgegeben oder in einer Datei abgelegt werden. Wie bei Kommandozeilenprogrammen üblich, wird die Ausführung von Sqlcmd über Kommandozeilenparameter gesteuert. Eine Übersicht über die zur Verfügung stehenden Parameter erhalten Sie durch den Aufruf:
Abbildung 3.40 zeigt das Ergebnis des Programmaufrufs.
Abbildung 3.40 Parameter für Sqlcmd
Wenn Sie beim Start von Sqlcmd keine Verbindungsinformation angeben, so wird standardmäßig versucht, eine Verbindung zum lokalen SQL Server unter Verwendung der Windows-Authentifizierung aufzubauen, wobei dann der an Windows angemeldete Benutzer verwendet wird. Sqlcmd kann sehr schön zur Automatisierung innerhalb von Stapelverarbeitungsprogrammen verwendet werden. Schauen Sie sich die obigen Parameter einmal an und versuchen dann, den folgenden Programmaufruf zu verstehen:
61
3 SQL Server-Verwaltungswerkzeuge Über den Parameter Q wird eine Abfrage ausgeführt. Das Ergebnis dieser Abfrage wird dann in der durch den Parameter -o angegebenen Datei gespeichert. Da keine Verbindungsinformationen angegeben wurden, wird der lokale Server mit der WindowsAnmeldeinformation verwendet. Eine interessante Möglichkeit ist die Übergabe von Parametern über die Option v:
Auf einen derart übergebenen Parameter kann im SQL-Code folgendermaßen zugegriffen werden:
Es ist auch möglich, Umgebungsvariablen abzufragen. Um zum Beispiel den Computernamen zu verwenden, existiert die Umgebungsvariable COMPUTERNAME, die so verwendet wird:
Auf die gleiche Weise haben Sie auch Zugriff auf die Kommandozeilenparameter. Hierzu existieren vordefinierte Variablen. Tabelle 3.3 zeigt die Verbindung zwischen Kommandozeilenparameter und Variable. Tabelle 3.3 Variablen für Kommandozeilenparameter Variable
Kommandozeilenparameter
SQLCMDUSER
-U
SQLCMDSERVER
-S
SQLCMDWORKSTATION
-H
SQLCMDDBNAME
-d
SQLCMDLOGINTIMEOUT
-l
SQLCMDSTATTIMEOUT
-t
SQLCMDHEADERS
-h
SQLCMDCOLSEP
-s
SQLCMDCOLWIDTH
-w
SQLCMDPACKETSIZE
-a
SQLCMDERRORLEVEL
-m
SQLCMDMAXVARTYPEWIDTH
-y
SQLCMDMAXFIXEDTYPEWIDTH
-Y
Eine Besonderheit ist der Kommandozeilenparameter A. Durch die Angabe dieses Parameters wird eine dedizierte Verbindung zum Server aufgebaut. Diese Verbindung wird sogar dann funktionieren, wenn die Serverauslastung derart hoch ist, dass normalerweise
62
3.8 SQL Server-Onlinedokumentation keine Verbindungen mehr möglich sind. Ein Administrator kann dadurch eine solche Situation analysieren und eventuell Abhilfe schaffen. Diese Möglichkeit muss jedoch zuvor durch die Oberflächenkonfiguration für Features aktiviert werden. In Abbildung 3.4 sehen Sie links den Eintrag DAC (für Dedicated Administrator Connection). Hierüber kann diese Aktivierung vorgenommen werden.
3.8
SQL Server-Onlinedokumentation Die Onlinedokumentation wird zum Beispiel über das Startmenü Microsoft SQL Server 2005/Dokumentation und Lernprogramme/SQL Server-Onlinedokumentation aufgerufen. Ebenso kann die Dokumentation über das Menü Hilfe im Management Studio gestartet werden. Es ist auch möglich, im Abfrageeditor SQL Schlüsselwörter zu markieren und dann die Dokumentation über die Tastenkombination UMSCHALT+F1 zu aktivieren. Dadurch erhalten Sie gleich die Hilfe zum markierten Schlüsselwort. Abbildung 3.41 zeigt die Hilfe für die Anweisung CREATE DATABASE, die zu Beginn des nächsten Kapitels verwendet wird.
Abbildung 3.41 Onlinedokumentation für CREATE DATABASE
63
3 SQL Server-Verwaltungswerkzeuge In der Onlinedokumentation finden Sie nicht nur die Referenzen zu SQL-Anweisungen, sondern auch Lernprogramme und Beispiele. Die Dokumentation enthält Informationen zum SQL Server-Setup und auch ein Handbuch für den Datenbankentwickler, kurzum: Die Dokumentation ist eine schier unerschöpfliche Informationsquelle für alle Aspekte von SQL Server 2005.
3.9
Zusammenfassung In diesem Kapitel haben Sie die Verwaltungswerkzeuge des SQL Servers kennengelernt. Sie wissen nun, welche Möglichkeiten für die Konfiguration und Überwachung es gibt und welche Instrumente Sie hierfür zur Verfügung haben. Ein besonderer Schwerpunkt war in diesem Kapitel das SQL Server Management Studio mit der Möglichkeit, Server- und Datenbankobjekte zu überwachen und zu konfigurieren. Insbesondere ist nun klar, wie Sie den im Management Studio integrierten Abfrageeditor benutzen, um Ihre Abfragen an den Server zu senden. Auch wenn Sie zu diesem Zeitpunkt noch nicht so genau wissen, welche Möglichkeiten SQL-Abfragen bieten und wie Sie solche Abfragen entwerfen, sollten Sie sich nun mit der Bedienung des Abfrageeditors einigermaßen auskennen. Der Abfrageeditor wird Sie durch alle verbleibenden Kapitel dieses Buches hindurch permanent begleiten. Sie haben erfahren, wie Sie Skriptsammlungen anlegen und in Datenbankprojekten organisieren können, und haben ein grobes Verständnis für den Einsatz des Business Intelligence Development Studios. Zum Schluss dieses Kapitels haben Sie eine Einführung in die Benutzung des SQL Server Profilers und des Datenbankmodul-Optimierungsratgebers erhalten. Schließlich kennen Sie nun auch das Programm Sqlcmd, das die Verarbeitung von Abfragen von der Kommandozeile ermöglicht, und wissen, wie Sie die Onlinedokumentation verwenden.
64
4 Transact SQL-Grundlagen Dieses Kapitel vermittelt Ihnen eine umfassende Einführung in die Structured Query Language (SQL), wobei der spezielle Microsoft-Dialekt Transact SQL im Vordergrund stehen wird. Sie erfahren, wie man mit SQL Datenbanken und Datenbankobjekte anlegt, verändert und löscht. Weiterhin werden Sie lernen, wie Sie SQL-Kommandos zur Datenabfrage und Datenänderung verwenden. In den 70er-Jahren des vorigen Jahrhunderts erblickte die Sprache SQL als Implementierungssprache für ein relationales Datenbankmodell in einem IBM-Forschungslabor das Licht der Welt. Seit diesem Zeitpunkt hat sich SQL als Standardsprache für relationale Datenbank-Management-Systeme (DBMS) weitgehend durchgesetzt und ist mittlerweile Bestandteil aller gängigen DBMS darunter auch Microsoft SQL Server. Die starke Verbreitung von SQL hat auch dazu geführt, dass mittlerweile internationale Standards existieren, die den Sprachumfang definieren. Diese Standards haben zum Ziel, dass alle DBMS nach Möglichkeit denselben SQL Sprachumfang unterstützen, sodass es für die Entwickler von Datenbanklösungen letztlich keine Rolle spielt, auf welchem System ihre Lösungen installiert werden. Auch wenn die existierenden Standards von den Herstellern der unterschiedlichen DBMS in unterschiedlichen Ausbaustufen erfüllt werden und somit sicherlich ein gewisser Standardisierungsgrad gegeben ist, so unterscheiden sich die bestehenden DBMS teilweise doch beträchtlich in ihren SQL-Dialekten. Die bestehenden Standardisierungsvorschriften des American National Standards Institute (ANSI), die später von der International Standards Organization (ISO) übernommen wurden, beschränken sich in der Hauptsache auf die grundlegenden SQL-Kommandos und eröffnen den DBMS-Herstellern somit ein weites Feld für eigene Erweiterungen. Von diesen Möglichkeiten machen alle DBMSAnbieter regen Gebrauch auch die Firma Microsoft hat mit der Sprache Transact SQL ihren eigenen SQL-Dialekt entwickelt. Dieser Dialekt hält sich natürlich in allen wesentlichen Punkten an bestehende Standards, dort, wo es die Standardisierung erlaubt, wird der SQL-Sprachumfang allerdings auch erweitert bzw. angepasst.
65
4 Transact SQL-Grundlagen
4.1
Das Relationenmodell Um erfolgreich mit SQL arbeiten zu können, ist zumindest ein gewisses Grundverständnis des Relationenmodells erforderlich. Dieses Kapitel wird aber keine vollständige Beschreibung der zugrunde liegen Theorie liefern, sondern nur eine kurze Einführung geben, die zum weiteren Verständnis der Sprache SQL unerlässlich ist. Für den theoretischen Aspekt des Datenbankentwurfs und den für einen robusten, performanten und beständigen Entwurf notwendigen Normalisierungsprozess gibt es eigene Bücher, siehe z. B. [STEI03]. Der mathematisch gebildete Leser kann sich durchaus auch an das Original heranwagen: A Relational Model of Data for Large Shared Data Banks. von Erfinder des Relationenmodells Dr. E. F. Codd selbst verfasst. Für den Beginn ist es ausreichend, wenn Sie verstehen, dass die Daten in sogenannten Relationen gespeichert werden. Relationen sind in der Theorie nichts weiter als Mengen. Die Repräsentation einer Relation aus dem relationalen Modell ist im Datenbanksystem eine Tabelle, also eine zweidimensionale Anordnung aus Zeilen und Spalten. Eine Menge von Tabellen wird dann in einer relationalen Datenbank, die wir im Folgenden auch nur kurz als Datenbank bezeichnen wollen, zusammengefasst. Und das ist etwas vereinfacht auch schon alles. Wenn Sie weiterführende Literatur über den Datenbankentwurf und das Relationenmodell studieren, werden Sie mit einer Reihe von Begriffen konfrontiert, die wir in diesem Buch, das ja keine theoretischen Abhandlungen, sondern Praxiswissen liefern soll, nicht weiter verwenden werden. Daher folgt an dieser Stelle auch nur eine kurze Erläuterung der wichtigsten Begriffe: Relation Wie bereits erwähnt ist eine Relation eine zweidimensionale Anordnung von Zeilen und Spalten, also eine Tabelle. Wir verwenden hier nur den Begriff Tabelle. Tupel Ein Tupel ist einfach eine Menge von Feldwerten also eine Zeile in einer Relation (Tabelle). Wir werden im weiteren Verlauf nicht von Tupeln, sondern immer nur von Zeilen reden. Attribut Als Attribut wird eine einzelne Spalte in einem Tupel (also in einer Tabellenzeile) bezeichnet. Schlüssel Schlüssel dienen zur Identifikation von Tupeln. Durch einen Schlüssel muss eine Zeile innerhalb einer Tabelle eindeutig identifizierbar sein, sich also von allen anderen Zeilen unterscheiden.
66
4.2 SQL mit System Die Theorie zum Relationenmodell befasst sich mit der Abbildung, also der Überführung, der Realität1 in ein relationales Modell, das ein Datenmodell aus Tabellen, die zueinander in Beziehung stehen, beschreibt. Das Relationenmodell ist mengenorientiert, hier ist stets die Rede von Mengen, Teilmengen, Schnittmengen etc. SQL als Abfragesprache für relationale Datenbanken ist daher eine mengenorientierte Sprache. Es ist sehr wichtig, dass Sie sich dies einprägen wir werden im weiteren Verlauf dieses Buches noch mehrfach darauf zurückkommen. Eine SQL-Abfrage gibt also genau genommen keine Tabelle, sondern eine Ergebnismenge zurück. Sie sollten sich also das Ergebnis einer Abfrage eher so vorstellen, wie in Abbildung 4.1 in der Ellipse dargestellt.
Abbildung 4.1 Ergebnismenge einer Abfrage
4.2
SQL mit System SQL ist eine faszinierende Sprache. Für das Verständnis einer einfachen SELECT-Anweisung benötigt jemand, der bereits mit einem Tabellenkalkulationsprogramm in Berührung gekommen ist, vielleicht ungefähr zwei Minuten. Um die Sprache allerdings zu beherrschen, zu meistern, sind wahrscheinlich sogar einige Jahre Praxiserfahrung erforderlich. Dabei ist die Sprache SQL an sich gar nicht so besonders umfangreich. Im Prinzip gibt es nur eine Handvoll Anweisungen, die allerdings oftmals viele Klauseln und Optionen enthalten können und dadurch letztlich umfangreich und komplex werden. Diese Aussage trifft insbesondere auf die SELECT-Anweisung zu. SELECT-Anweisungen können sich im Ausdruck durchaus über eine, manchmal sogar über mehrere DIN-A4-Seite(n) erstrecken. Sie können ineinander geschachtelt sein, Tabellen auf verschiedene Arten miteinander verknüpfen, Berechnungen durchführen und umfangreiche Filterbedingungen verwenden.
1
Der Begriff Realität steht hier in Anführungszeichen, weil es so etwas wie eine objektive Realität sicher nicht gibt. Die Interpretation der realen Welt hängt von subjektiven Einflüssen und der Wahrnehmung, Kultur etc. des Einzelnen ab, aber das ist mehr ein philosophisches Thema. Fest steht allerdings, dass unterschiedliche Datenbankdesigner dieselbe Problematik letztlich auch in verschiedene Designs übersetzen werden eben darum, weil die Problemanalyse (auch) durch ihre subjektive Sicht beeinflusst wird.
67
4.3 T-SQL-Stapel (Batches)
4.3
T-SQL-Stapel (Batches) T-SQL-Anweisungen werden vom SQL Server in sogenannten Stapeln ausgeführt. Hierbei können in einem Stapel mehrere Anweisungen enthalten sein, die jeweils voneinander getrennt sein müssen. Auch der Stapel selber muss vom nachfolgenden Stapel getrennt, also mit einem Stapeltrennzeichen abgeschlossen werden. Stellen Sie sich einen T-SQL-Stapel wie ein Paket vor, das als Einheit zum Server gesendet und ausgeführt wird. Trennung von Anweisungen Wie aus vielen anderen Programmiersprachen (zum Beispiel Java, C# oder C++) bekannt, ist das Semikolon das Zeichen für das Ende einer Anweisung. Ein Anweisungsende wird aber auch durch das Auftreten eines SQL-Schlüsselworts im Stapel erkannt. In diesem Fall wird automatisch eine neue Anweisung gestartet, ohne dass hierfür ein Semikolon angegeben werden muss. Die meisten aber eben nicht alle T-SQL-Anweisungen zählen zu diesen Schlüsselwörtern, zum Beispiel auch die in Abbildung 4.2 aufgeführten. Daher wird Ihnen sehr oft T-SQL-Code begegnen, in dem keine explizite Trennung von Anweisungen durch ein Semikolon enthalten ist. Es gibt T-SQL-Anweisungen, die in einem Stapel an erster Stelle stehen müssen. Hierzu zählt zum Beispiel das Kommando CREATE VIEW (siehe Abschnitt 4.17). Eine solche Anweisung muss also entweder am Beginn des gesamten Skripts stehen oder unmittelbar nach dem Stapeltrennzeichen. Stapeltrennzeichen Das Standard-Stapeltrennzeichen ist die Zeichenkette GO. Durch diese Zeichenfolge wird ein T-SQL-Stapel abgeschlossen. Die nächste Anweisung beginnt dann einen neuen Stapel. Es ist möglich, über das Menü Abfrage/Abfrageoptionen ein alternatives Stapeltrennzeichen festzulegen (Abbildung 4.3). Dadurch wird GO allerdings nicht deaktiviert, das so definierte Stapeltrennzeichen steht Ihnen zusätzlich zur Verfügung. Bitte bedenken Sie aber, dass diese Einstellung temporär ist und nur für den lokalen Abfrageeditor gilt. Das Stapeltrennzeichen ist keine SQL-Anweisung, es wird also nicht an den Server übertragen. Nur der Abfrageeditor verwendet das Trennzeichen, um ein Skript in Stapel zu zerlegen, die dann als einzelne Pakete zum Server übertragen werden. Ein alternatives Stapeltrennzeichen wird nur von der lokalen Instanz des Abfrageeditors korrekt als solches interpretiert. Wenn Sie in Ihrem Skript ein alternatives Stapeltrennzeichen verwenden, so wird dieses Skript auch nur im aktuellen Abfrageeditor fehlerfrei funktionieren. Sobald Sie das SQL Server Management Studio beenden und neu starten, ist die Einstellung für das alternative Stapeltrennzeichen verloren und muss erneut vorgenommen werden. Ansonsten wird ein Skript, welches das alternative Stapeltrennzeichen verwendet, nicht laufen. Andere Clients, die T-SQL-Stapel verarbeiten, werden in der Regel davon ausgehen, dass GO als Stapeltrennzeichen verwendet wird, und ein Skript mit einem alternativen Stapeltrennzeichen daher nicht verarbeiten können. Sqlcmd, zum Beispiel, verwendet GO als stan-
69
4 Transact SQL-Grundlagen dardmäßiges Stapeltrennzeichen und kann ein Skript mit einem alternativen Trennzeichen nur verarbeiten, wenn dieses alternative Trennzeichen über den Kommandozeilenparameter -c angegeben wird.
Abbildung 4.3 Festlegen eines alternativen Stapeltrennzeichens
Im Stapeltrennzeichen kann mit einer zusätzlichen Option angegeben werden, wie oft der Stapel an den Server gesendet werden soll. Dieses Feature ist allerdings undokumentiert. Um zum Beispiel einen T-SQL-Stapel zehn Mal auszuführen, kann man schreiben:
Kommentare T-SQL-Quellcode kann Kommentarzeilen enthalten. Solche Zeilen sind zur Dokumentation des Codes gedacht, sie werden bei der Ausführung von Stapeln ignoriert. Unterschieden wird zwischen Zeilen- und Blockkommentar. Ein Zeilenkommentar wird durch zwei Bindestriche () in einer Zeile begonnen und kommentiert dadurch den Rest dieser Zeile aus. Ein Blockkommentar wird durch die Zeichenfolge begonnen und durch wieder beendet. Der zwischen diesen beiden Markierungen liegende Text ist der Kommentar, der sich also auch über mehrere Zeilen erstrecken kann:
70
4.4 SQL-Datentypen
4.4
SQL-Datentypen Durch einen Datentyp werden sowohl die Art der enthaltenen Daten als auch deren Länge festgelegt. Bei numerischen Datentypen bestimmt der Datentyp außerdem die Genauigkeit und die Anzahl der Dezimalstellen. Neben den Datentypen für numerische Werte kennt SQL Server Datentypen für Zeichenketten, Datum und Zeit sowie zur Speicherung binärer Daten. SQL Server unterstützt auch einen nativen XML-Datentyp und einige weitere Datentypen, die in keine der aufgezählten Kategorien eingeordnet werden können. Eine Besonderheit von SQL ist, dass Variablen und Spaltenwerte auch leer sein dürfen. SQL kennt hierzu den Wert NULL, der Spalten bzw. Variablen zugewiesen werden kann. Die Besonderheiten und Probleme mit solchen NULL-Werten werden in Abschnitt 4.15 erläutert. Numerische Datentypen SQL Server unterstützt die in Tabelle 4.2 genannten numerischen Datentypen. Tabelle 4.2 SQL Server: numerische Datentypen Datentyp
Wertebereich
Erklärung
2
63
2
31
smallint
2
15
tinyint
0 bis 255
Eine ganze Zahl mit einer Länge von 1 Byte
bit
0 oder 1
SQL Server kennt keinen nativen booleschen Datentypen für WAHR oder FALSCH. Hierfür kann der Datentyp bit verwendet werden, der nur die Werte 0 (für FALSCH) und 1 (für WAHR) enthalten kann und der auch entsprechende Konvertierungen unterstützt. Datentypkonvertierungen werden weiter unten erklärt.
decimal(p,s) dec(p,s) numeric(p,s)
1038 + 1 bis 1038 1
Eine Zahl mit einer festen Anzahl von Dezimalstellen. Für diesen Datentypen kann die Präzision p und die Anzahl der Dezimalstellen s angegeben werden.
bigint int
bis 2
63
1
Eine ganze Zahl mit einer Länge von 8 Byte
bis 2
31
1
Eine ganze Zahl mit einer Länge von 4 Byte
bis 2
15
1
Eine ganze Zahl mit einer Länge von 2 Byte
Der Parameter p für die Präzision gibt die Gesamtanzahl von zu speichernden Stellen an. Der Wert für diesen Parameter muss zwischen 1 und 18 liegen. Der Parameter ist optional, der Standardwert für diesen Parameter ist 18. Der Parameter s für die Dezimalstellen gibt die Stellen rechts vom Komma an. Auch dieser Parameter ist optional, der Standardwert ist 0.
71
4 Transact SQL-Grundlagen Datentyp
Wertebereich
Erklärung Beispiel: Mit wird eine Zahl deklariert, die insgesamt fünf Stellen enthält, zwei davon als Nachkommastellen. Damit bleiben drei Stellen vor dem Komma, die Zahl hat also das Format:
308
308
float(n)
1,79 e
bis 1,79 e
real
3,4 e bis 3,4 e
Speichert eine Gleitkommazahl.
money
922.337.203.685.477,5808 bis 922.337.203.685.477,5807
Speichert einen Währungswert mit vier Nachkommastellen.
smallmoney
214.748,3648 bis 214.748,3647
Speichert einen Währungswert mit vier Nachkommastellen.
38
38
Speichert eine Gleitkommazahl. Über den Parameter n kann angegeben werden, wie viele Bits zum Speichern benutzt werden sollen. Die Angabe von n ist optional, der Standardwert ist 53.
Zeichenketten Tabelle 4.3 listet die zur Verfügung stehenden Datentypen für Zeichenketten auf. Für alle Zeichenketten-Datentypen gibt es auch eine Unicode-Variante. Dies ist jeweils der Datentyp, der mit dem Buchstaben n beginnt (siehe unten). Im T-SQL-Code können Sie Zeichenketten-Literale verwenden. Hierzu wird der betreffende Text in einfache Anführungszeichen eingeschlossen, also etwa so:
Tabelle 4.3 Datentypen für Zeichenketten
72
Datentyp
Wertebereich
Erklärung
char(n) nchar(n)
Maximal 8000 Zeichen (char) oder 4000 Zeichen (nchar)
Speichert eine Zeichenkette fester Länge. Der Parameter n gibt die Länge der Zeichenkette an und ist optional. Der Standardwert für eine Deklarationsanweisung ist 1. Für die Verwendung des Datentyps in einer Konvertierungsfunktion ist der Standardwert 30. Für char kann n eine maximale Größe von 8000 annehmen, für nchar 4000.
text ntext
Maximal 2.147.483.647 Zeichen (text) oder1.073.741.823 Zeichen (ntext)
Kann Textdaten variabler Länge enthalten. Die Verwendung dieser Datentypen ist relativ kompliziert, für Neuentwicklungen sollten Sie besser die Datentypen varchar(max) bzw. nvarchar(max) verwenden.
4.4 SQL-Datentypen Datentyp
Wertebereich
Erklärung
varchar(n) nvarchar(n)
Maximal 8000 Zeichen (char) oder 4000 Zeichen (nchar)
Speichert eine Zeichenkette variabler Länge. Der Parameter n gibt die maximale Länge der Zeichenkette an und ist auch hier optional. Der Standardwert für eine Deklarationsanweisung ist wiederum 1 für Deklarationen und 30 für Konvertierungsfunktionen. Das Maximum von n ist 8000 für varchar bzw. 4000 für nvarchar.
varchar(max) nvarchar(max)
Maximal 2.147.483.647 Zeichen (varchar) oder1.073.741.823 Zeichen (nvarchar)
Speichert Textdaten variabler Länge.
Falls Sie internationale Zeichen speichern müssen, sollten Sie jeweils den Unicode-Datentyp verwenden. Dieser Datentyp bereitet weniger Probleme bei der Zeichenkonvertierung, hat allerdings auch den doppelten Speicherplatzbedarf, da ein Unicode-Zeichen immer in zwei Byte abgelegt wird im Gegensatz zu einem normalen Zeichen, für das ein Byte ausreicht. Der in der Deklaration eines Datentyps angegebene Wert für n bezeichnet immer die Länge der Zeichenkette und nicht die Anzahl der Bytes, die zur Speicherung benötigt werden. Eine Deklaration steht für eine Zeichenkette von 100 Zeichen Länge und 100 Byte Speicherbedarf. In einer Variablen oder einer Spalte vom Typ kann ebenfalls eine Zeichenkette mit einer maximalen Länge 100 gespeichert werden, hier werden allerdings 200 Byte Speicherplatz benötigt. Bei der Deklaration von Zeichenketten ist auch zu bedenken, dass für die Datentypen und unterschiedliche Mechanismen für die Speicherverwaltung verwendet werden. Für einen -Datentypen wird immer der über den Parameter n angegebene Speicher benötigt, unabhängig davon, wie viele Zeichen tatsächlich gespeichert werden. Für den Datentyp erfolgt die Anforderung von Speicher dynamisch, der tatsächlich benötigte Speicher richtet sich also jeweils nach der Länge der zu speichernden Zeichenkette. Abbildung 4.4 verdeutlicht dies für eine maximale Länge von 15 Zeichen.
Abbildung 4.4 Speicherbedarf für char(15) und varchar(15)
Der Datentyp ist zu bevorzugen, wenn die Länge der gespeicherten Daten in einer Spalte jeweils gleich ist. Ein Beispiel hierfür wäre vielleicht eine Kunden- oder Rechnungsnummer. In so einem Fall kann die bei erforderliche dynamische Speicherverwaltung vermieden werden. Wenn sich die Dateneinträge in der Größe unterscheiden, ist die bessere Wahl.
73
4 Transact SQL-Grundlagen Datum und Zeit SQL Server kennt die in Tabelle 4.4 genannten Datentypen für Datum und Zeit. Es gibt für Datum und Uhrzeit keine separaten Datentypen, Zeit und Datum werden stets zusammen gespeichert. Tabelle 4.4 Datentypen für Datum und Zeit Datentyp
Wertebereich
Erklärung
datetime
01.01.1753 bis 31.12.9999
Der Zeitanteil wird mit einer Genauigkeit von 3,33 Millisekunden gespeichert.
smalldatetime
01.01.1900 bis 06.06.2079
Der Zeitanteil wird mit einer Genauigkeit von einer Minute gespeichert.
Binäre Datentypen In Tabelle 4.5 finden Sie die verfügbaren Datentypen zur Speicherung binärer Daten. Tabelle 4.5 Binäre Datentypen Datentyp
Wertebereich
Erklärung
binary(n)
1 bis 8000 Byte
Speichert binäre Daten fester Länge.
image
Maximal 2.147.483.647 Byte
Der Datentyp image sollte für Neuentwicklungen nicht mehr verwendet werden. Nehmen Sie stattdessen varbinary(max).
varbinary(n)
1 bis 8000 Byte
Speichert binäre Daten variabler Länge.
varbinary(max)
Maximal 2.147.483.647 Byte
Speichert ebenfalls binäre Daten variabler Länge.
Das bereits weiter oben für die Zeichenketten-Datentypen und bezüglich der dynamischen Speicherverwaltung Gesagte gilt ebenso für und . Verwenden Sie , wenn die Länge der gespeicherten Daten in einer Spalte gleich ist. Ist die Länge unterschiedlich, so ist der Datentyp zu bevorzugen. Weitere Datentypen SQL Server enthält noch einige weitere Datentypen, die in keine der bislang besprochenen Kategorien eingeordnet werden können. Tabelle 4.6 listet diese Datentypen auf. Tabelle 4.6 Sonstige Datentypen Datentyp
Erklärung
cursor
Dieser Datentyp kann nicht in Tabellen verwendet werden. Er steht lediglich für Variablen zur Verfügung. Cursor werden in Kapitel 7 behandelt.
sql_variant
Die Bezeichnung wurde in Anlehnung an den Visual Basic-Datentyp Variant gewählt. Eine Variable oder Spalte dieses Typs kann einen Wert eines beliebigen Datentyps speichern. Ausgenommen hiervon sind , ,
, , , , , und
74
4.4 SQL-Datentypen Datentyp
Erklärung
. Wenn möglich, sollten Sie die Verwendung dieses Datentyps vermeiden. table
Speichert eine temporäre Tabelle. Dieser Datentyp wird Insbesondere in sogenannten Tabellenwertfunktionen verwendet. Hierauf kommen wir in Kapitel 7 nochmals zurück. Als Spaltentyp in einer Tabellendeklaration kann dieser Datentyp nicht verwendet werden.
timestamp
Der Datentyp steht für einen 8 Byte langen Binärwert. Wenn Sie in einer Tabelle eine Spalte vom Typ verwenden, so wird diese Spalte automatisch aktualisiert, wann immer die Daten einer Zeile verändert werden. In jeder Datenbank existiert hierfür ein Zähler, der für eine Aktualisierung einer Tabelle inkrementiert wird, sofern diese Tabelle eine -Spalte enthält. Spalten können als Zeilenversionskennung verwendet werden. Wenn Sie beim Lesen einer Zeile aus einer Tabelle die -Spalte speichern und dann später beim Aktualisieren der Zeile feststellen, dass der Wert der -Spalte sich mittlerweile verändert hat, so ist die Zeile in der Zwischenzeit von einer anderen Anwendung verändert worden.
uniqueidentifier
Speichert einen 16 Byte langen GUID. Dieser Datentyp wird zum Beispiel für die Replikation verwendet.
xml
Variablen oder Spalten dieses Typs können XML-Daten aufnehmen. Dieser Thematik ist ein eigenes Kapitel (8) gewidmet.
Konvertierungen SQL Server erlaubt die Konvertierung von Werten eines Datentyps in einen anderen. Hierbei wird unterschieden zwischen einer impliziten und expliziten Konvertierung: Implizite Typkonvertierung. Bei der impliziten Typkonvertierung wird die Konvertierung aus dem Kontext abgeleitet. So ist es zum Beispiel möglich, einen Wert vom Typ einem Datentypen zuzuweisen oder eine Zeichenkette automatisch in einen -Wert zu überführen. Auch die Konvertierung einer Zeichenkette mit dem Wert oder in einen Datentyp kann implizit vorgenommen werden. Hierbei wird aus die 1 und aus der Wert 0. Insbesondere können Werte aller Datentypen den Wert NULL enthalten. Explizite Typkonvertierung. Für eine explizite Typkonvertierung stehen die beiden Konvertierungsfunktionen CAST und CONVERT zur Verfügung. Diese Funktionen werden wir in Abschnitt 4.15.6 näher untersuchen. Sowohl für die explizite als auch für die implizite Typkonvertierung gilt natürlich, dass keine Konvertierung aus prinzipiell unverträglichen Werten erfolgen kann. So wird es zum Beispiel nicht möglich sein, die Zeichenkette in einen Wert vom Typ oder in ein zu konvertieren. Bei beiden Versuchen wird eine Fehlermeldung erzeugt.
75
4 Transact SQL-Grundlagen
4.5
T-SQL-Ausdrücke und -Operatoren Wie jede andere Programmiersprache auch, erlaubt SQL die Berechnung von Ausdrücken. Hierzu steht Ihnen eine Reihe von Operatoren zur Verfügung, mit denen Berechnungen und Vergleiche durchgeführt werden können. Um Ihnen einen Überblick zu verschaffen, werden die verfügbaren Operatoren in diesem Abschnitt zunächst nur aufgezählt. Im weiteren Verlauf des Kapitels werden die Operatoren dann auch zum Einsatz kommen. Insbesondere beschäftigt sich Abschnitt 4.10 mit der Verwendung von Operatoren und Ausdrücken in SELECT-Anweisungen. Arithmetische Operatoren In Tabelle 4.7 finden Sie die zur Verfügung stehenden arithmetischen Operatoren. Tabelle 4.7 Arithmetische Operatoren von SQL Server Operator
Erklärung
+
Additionsoperator. Berechnet die Summe der beiden Operanden.
-
Subtraktionsoperator. Berechnet die Differenz der beiden Operanden.
*
Multiplikationsoperator. Berechnet das Produkt der Operanden.
/
Divisionsoperator. Berechnet den Quotienten der Operanden.
%
Modulooperator. Berechnet den Rest der ganzzahligen Division. Der Rest von 13 geteilt durch 5 ist 3, also liefert der Ausdruck 13 % 5 den Wert 3.
Bitweise Operatoren Die von SQL Server unterstützten bitweisen Operatoren finden Sie in der folgenden Tabelle 4.8. Tabelle 4.8 Bitweise Operatoren von SQL Server Operator
Erklärung
&
Bitweises Und.
|
Bitweises Oder.
^
Bitweises exklusives Oder.
~
Bitweise Negation.
Zeichenkettenoperatoren Tabelle 4.9 zeigt den einzigen für Zeichenketten verfügbaren Operator: Tabelle 4.9 SQL Server: Zeichenkettenoperatoren
76
Operator
Erklärung
+
Verkettungsoperator. Hängt zwei Zeichenketten aneinander.
4.5 T-SQL-Ausdrücke und -Operatoren Vergleichsoperatoren SQL Server erlaubt den Vergleich von Ausdrücken durch die in Tabelle 4.10 angeführten Vergleichsoperatoren: Tabelle 4.10 SQL Server: Vergleichsoperatoren Operator
Erklärung
=
Gibt WAHR zurück, wenn beide Seiten des Vergleichs gleich sind.
>
Gibt WAHR zurück, wenn die linke Seite größer als die rechte ist.
<
Gibt WAHR zurück, wenn die linke Seite kleiner als die rechte ist.
>=
Gibt WAHR zurück, wenn die linke Seite größer oder gleich als die rechte ist
<=
Gibt WAHR zurück, wenn die linke Seite kleiner oder gleich als die rechte ist.
<>
Gibt WAHR zurück, wenn beide Seiten des Vergleichs ungleich sind.
!=
Logische Operatoren Logische Ausdrücke kennen nur die beiden Zustände WAHR und FALSCH2. Beim Anwenden eines logischen Operators auf derartige Ausdrücke kann dieser Wertebereich nicht verlassen werden, das Ergebnis einer solchen Operation muss also wieder WAHR oder FALSCH liefern. Prinzipiell lassen sich alle logischen Operationen auf AND, OR und NOT zurückführen. Die folgende Tabelle 4.11 zeigt die Ergebnisse von AND- und OROperationen für alle möglichen Fälle. Tabelle 4.11 Logische Operationen mit AND, OR und NOT UND
ODER
NICHT
AND
WAHR
FALSCH
OR
WAHR
FALSCH
NOT
WAHR
WAHR
WAHR
FALSCH
WAHR
WAHR
WAHR
WAHR
WAHR
FALSCH
FALSCH
FALSCH
FALSCH
WAHR
FALSCH
Tabelle 4.12 enthält eine Übersicht über die logischen Operatoren von SQL. Solche Operatoren werden vor allem in Abfragen verwendet, um das Abfrageergebnis einzuschränken. In den weiteren Abschnitten dieses Kapitels und auch in Kapitel 7 werden wir diese Operatoren in eigenen Abfragen verwenden und dann jeweils näher darauf eingehen. Die Tabelle soll hier zunächst nur einen Überblick über die verfügbaren Operatoren liefern.
2
SQL bildet hier eine Ausnahme, da SQL-Ausdrücke prinzipiell auch den Wert NULL zurückliefern können. Die dadurch erforderliche dreiwertige Logik wird in Abschnitt 4.16 näher erklärt.
77
4 Transact SQL-Grundlagen Tabelle 4.12 Logische Operatoren von SQL Server Operator
Erklärung
ALL
Mengenvergleich. Gibt WAHR zurück, wenn der Vergleich mit allen in der Menge enthaltenen Werten WAHR ist.
AND
WAHR, wenn beide Seiten WAHR sind (siehe Tabelle 4.11).
ANY SOME
Mengenvergleich. Gibt WAHR zurück, wenn mindestens ein Vergleich mit den in der Menge enthaltenen Werten WAHR ist.
BETWEEN
WAHR, wenn der Operand in einem bestimmten Bereich liegt.
EXISTS
WAHR, wenn die bei EXISTS angegebene Unterabfrage mindestens eine Zeile zurückliefert.
IN
WAHR, wenn der Operand mit mindestens einem Wert aus einer angegebenen Liste übereinstimmt.
LIKE
WAHR, wenn der Operand gleich einem angegebenen Muster ist. (Mustervergleich für Zeichenketten.)
NOT
Logische Umkehrung (siehe Tabelle 4.11).
OR
WAHR, wenn eine der beiden Seiten WAHR ist (siehe Tabelle 4.11).
Zuweisungsoperator SQL kennt auch den Zuweisungsoperator =, mit dem der Wert eines Ausdrucks einem anderen Ausdruck zugewiesen werden kann. Rangfolge von Operatoren in Ausdrücken Da in einem Ausdruck mehrere Operatoren auftreten können, ergibt sich natürlich die Frage nach der Reihenfolge der Auswertung in so einem Fall. Diese Reihenfolge hat Auswirkungen auf das Ergebnis des Ausdrucks und wird durch eine festgelegte OperatorRangfolge definiert. Tabelle 4.13 zeigt die in SQL Server existierende Rangfolge für die weiter oben besprochenen Operatoren. Tabelle 4.13 Rangfolge von Operatoren
78
Rangfolge
Operatoren
1
Bitweise Negation: ~
2
Multiplikation: *, Division: /, % Modulo: %
3
Addition: +, Verkettung: +, Subtraktion: -, bitweises UND: &
4
Vergleichsoperatoren: =, >, <, >=, <=, <>, !=, !>, !<
5
Bitweise Operatoren ^, |
6
NOT
7
AND
8
ALL, ANY, BETWEEN, IN, LIKE, OR, SOME
9
=
4.6 Datenbanken verwalten Falls Sie sich die Reihenfolge der Operatoren nicht einprägen können, so ist es natürlich auch möglich, die Auswertungsreihenfolge durch das Setzen von runden Klammern () explizit zu bestimmen. In Zweifelsfällen sollten Sie stets hierauf zurückgreifen. Wenn die standardmäßige Auswertungsreihenfolge für Ihren Ausdruck nicht verwendet werden kann, so wird Ihnen keine andere Wahl bleiben, als die Reihenfolge durch Klammerung entsprechend zu verändern.
4.6
Datenbanken verwalten Bevor wir damit beginnen können, Daten im SQL Server abzulegen, müssen erst einmal die erforderlichen Objekte erzeugt werden, in denen letztlich diese Daten gespeichert werden sollen. Diese Objekte sind Tabellen, die ihrerseits innerhalb von Datenbanken verwaltet werden. In diesem Abschnitt werden wir uns damit befassen, wie Datenbanken angelegt, geändert und gelöscht werden.
4.6.1
Datenbank erzeugen
Das Anlegen einer Datenbank erfolgt durch die Anweisung CREATE DATABASE. Dieses Kommando hat eine Vielzahl von Optionen, mit denen vor allen Dingen das physikalische Datenbanklayout, aber auch die Standard-Sortierungsreihenfolge und diverse Sicherheitseinstellungen konfiguriert werden können. Dadurch kann eine CREATE DATABASE-Anweisung sehr komplex werden. Die gute Nachricht ist, dass alle Optionen auch weggelassen werden können, wodurch dann im Server hinterlegte Standardwerte für diese Optionen verwendet werden. Tatsächlich ist der Name der erzeugten Datenbank der einzige erforderliche Parameter. Eine Datenbank mit Standardeinstellungen kann also einfach so erstellt werden:
Durch das Kommando wird die Datenbank OmasKochbuch unter Verwendung der Systemdatenbank model als Vorlage erstellt. SQL Server nutzt für die Datenbankverwaltung das Dateisystem des Betriebssystems. Die für die Datenbank OmasKochbuch benötigten Dateien werden durch die obige CREATE DATABASE-Anweisung im Standarddatenverzeichnis des SQL Servers angelegt. Wenn Sie an der Serverkonfiguration nichts verändert haben, ist dies das Verzeichnis:
steht hierbei für das Programmverzeichnis des Computers. In diesem Verzeichnis finden Sie die beiden Dateien OmasKochbuch.mdf und OmasKochbuch_log.ldf. Für jede Datenbank müssen mindestens zwei Dateien existieren: eine Datendatei und eine Protokolldatei. In der Datendatei werden wie der Name bereits vermuten lässt die eigentlichen Daten der Datenbank gespeichert. Die Endung dieser Datei ist mdf. Die Proto-
79
4 Transact SQL-Grundlagen kolldatei mit der Endung ldf enthält Aufzeichnungen über alle in der Datenbank durchgeführten Änderungen. Das Protokoll wird für die Gewährleistung der Datensicherheit und auch für Backup-Strategien benötigt. In den Kapiteln 5 und 6 wird auf die Rolle des Protokolls näher eingegangen. Die Optionen zum physikalischen Datenbanklayout, also die Festlegung, wie eine Datenbank im Dateisystem verteilt werden soll, sind sicherlich diejenigen, die bei CREATE DATABASE am häufigsten verwendet werden. Es kann angegeben werden, wie viele Daten- und Protokolldateien angelegt werden sollen, wie diese Dateien heißen und in welchem Verzeichnis sie abgelegt werden sollen. Es ist möglich zu konfigurieren, auf welche Art und Weise die Dateien automatisch vergrößert werden sollen und ob eine maximale Größe nicht überschritten werden darf. Letztlich ist eine derartige Konfiguration jedoch mehr die Aufgabe des Datenbankadministrators. Für den Datenbankentwickler ist vorrangig die logische Sicht auf die Daten, also die Tabellenstruktur interessant. In welcher Datendatei Tabellendaten oder Protokolleinträge gespeichert werden, interessiert den Datenbankentwickler nur am Rande. Ein Tipp darf jedoch hier nicht fehlen: Aufgrund der unterschiedlichen Verwaltung für Daten- und Protokolldateien und auch, um im Falle eines Medienfehlers eine höhere Wahrscheinlichkeit für eine mögliche Wiederherstellung einer defekten Datenbank zu schaffen, ist es sehr sinnvoll, Daten- und Protokolldateien auf unterschiedlichen Laufwerken abzulegen. Hierfür können Sie die entsprechenden Optionen in der CREATE DATABASE-Anweisung wie folgt angeben:
Ersetzen Sie hierbei bitte und mit den Verzeichnissen für Daten- bzw. Protokolldatei. Die Klausel ON PRIMARY spezifiziert die primäre Dateigruppe für die Datenbank. Jede Datenbank muss genau eine solche primäre Dateigruppe besitzen, in der zumindest die Systemkataloginformationen für die Datenbank gespeichert werden. Die Option NAME gibt jeweils den logischen Dateinamen an, unter dem der SQL Server die physikalische Datei, die über FILENAME spezifiziert wird, verwaltet. Falls Ihnen CREATE DATABASE zu kompliziert ist, können Sie eine Datenbank auch mit dem Objekt-Explorer des Management Studios erzeugen. Öffnen Sie hierzu das Kontextmenü für den Ordner Datenbanken, und wählen Sie Neue Datenbank
(Abbildung 4.5).
80
4.6 Datenbanken verwalten
Abbildung 4.5 Eine Datenbank erzeugen
Es öffnet sich ein Fenster, in dem alle Optionen der anzulegenden Datenbank in einer grafischen Benutzeroberfläche konfiguriert werden können (Abbildung 4.6). Nach Abschluss der Konfiguration kann die Datenbank dann über den Button Hinzufügen erstellt werden. Es ist aber auch möglich, über den Button Skript in der Menüleiste eine CREATE DATABASE-Anweisung erstellen zu lassen und diese dann im Abfrageeditor auszuführen. Wenn Sie dies ein paar Mal gemacht haben, so werden Sie die CREATE DATABASE-Anweisung sehr bald beherrschen.
Abbildung 4.6 Optionen für neue Datenbank festlegen
81
4 Transact SQL-Grundlagen Der Objekt-Explorer zeigt die neue Datenbank im Ordner Datenbanken an (Abbildung 4.7). Möglicherweise müssen Sie aber hierfür noch die Anzeige über den Button Aktualisieren in der Menüleiste des Objekt-Explorers aktualisieren. In der Abbildung ist ersichtlich, dass eine neue Datenbank auch bereits Systemobjekte enthält, die für die Verwaltung der Datenbankobjekte erforderlich sind.
Abbildung 4.7 Die hinzugefügte Datenbank im Objekt-Explorer
Eine existierende Datenbank kann über das USE-Kommando als Kontextdatenbank für die nachfolgenden T-SQL-Anweisungen festgelegt werden:
Der ausgewählte Datenbankkontext hat Auswirkungen auf die Namensauflösung in T-SQLAnweisungen. Hierauf wird in Abschnitt 4.7.1 näher eingegangen.
4.6.2
Datenbank ändern
Eine bestehende Datenbank kann durch die Anweisung ALTER DATABASE geändert werden. Mit dieser Anweisung werden zum Beispiel Datenbankdateien hinzugefügt oder die Eigenschaften von Dateien verändert. ALTER DATABASE wird auch für die Änderung der Optionen oder des Namens einer Datenbank benötigt. ALTER DATABASE enthält viele Optionen und ist ähnlich komplex wie CREATE DATABASE. Oftmals wird für das Ändern von Optionen einer Datenbank ein exklusiver Zugriff auf diese benötigt. ALTER DATABASE wartet dann so lange, bis dieser exklusive Zugriff erfolgreich ist. Es ist aber auch möglich, in der Anweisung Optionen anzugeben, die alle anderen Verbindun-
82
4.6 Datenbanken verwalten gen zur Datenbank beenden, und so dafür zu sorgen, dass der exklusive Zugriff funktioniert. Es folgen einige Beispiele zur Verwendung von ALTER DATABASE: Im ersten Beispiel wird der Datenbankname verändert. Hierfür muss die Datenbank im Exklusivmodus verwendet werden. Es wird so lange gewartet, bis dieser Zugriff möglich ist:
Die folgende ALTER DATABASE-Anweisung setzt die Datenbank in den Offline-Status. Es sind dann keine Verbindungen mehr zur Datenbank möglich. Die Anweisung wartet zehn Sekunden auf den benötigten Exklusivzugriff. Kann dieser nicht erlangt werden, so werden laufende Transaktionen zurückgenommen und alle Verbindungen beendet (dann ist der Exklusivzugriff möglich):
Die nächste ALTER DATABASE-Anweisung setzt die Datenbank in den Nur-LesenStatus. Datenänderungen sind dann nicht mehr möglich.
Abbildung 4.8 Datenbankoptionen verändern
83
4 Transact SQL-Grundlagen Auch Datenbankoptionen können selbstverständlich im Eigenschaftsformular für die Datenbank, das über das Kontextmenü für die Datenbank geöffnet werden kann, verändert werden (Abbildung 4.8). Die Erstellung eines Skriptes für die konfigurierten Änderungen ist natürlich ebenfalls möglich.
4.6.3
Datenbank löschen
Um eine Datenbank zu löschen, verwenden Sie die Anweisung DROP DATABASE:
Eine Datenbank kann nur gelöscht werden, wenn ein exklusiver Zugriff auf diese Datenbank möglich ist. Sobald noch andere Verbindungen zu dieser Datenbank existieren, verwenden diese Verbindungen die Datenbank im Rahmen einer gemeinsamen Sperre. Eine gemeinsame Sperre verhindert den für das Löschen erforderlichen exklusiven Zugriff auf die Datenbank. Daher die USE-Anweisung am Anfang des obigen T-SQL-Skriptes, die dafür sorgt, dass zumindest die Verbindung, die das DROP DATABASE absetzt, die zu löschende Datenbank nicht verwendet. Die Anweisung DROP DATABASE versucht 20 Sekunden lang, exklusiven Zugriff auf die Datenbank zu erhalten. Gelingt dies nicht, so wird eine Fehlermeldung zurückgegeben. Um alle bestehenden Verbindungen zur Datenbank zu beenden, kann für die Datenbank der Einzelbenutzermodus erzwungen werden, bevor DROP DATABASE ausgeführt wird:
Die Option WITH ROLLBACK IMMEDIATE der ALTER DATABASE-Anweisung sorgt dafür, dass alle aktiven Verbindungen zur Datenbank sofort, also ohne auf den Abschluss laufender Transaktionen zu warten, beendet werden. Danach kann die Datenbank dann gelöscht werden.
4.7
Schemas verwenden Schemas sind Container, die Datenbankobjekte enthalten. Jede neu angelegte Datenbank enthält bereits eine Reihe von Standardschemas, darunter auch die Schemas dbo und sys.
84
4.7 Schemas verwenden Die existierenden Schemas finden Sie im Objekt-Explorer unter Sicherheit/Schemas (Abbildung 4.9).
Abbildung 4.9 Standardschemas in einer neuen Datenbank
Standardschemas können nicht gelöscht werden. Bevor etwas weiter unten das Erzeugen von Schemas mit CREATE SCHEMA erklärt wird, folgen noch einige grundlegende Aussagen zu Schemas: 1. Jedes Schema hat einen Besitzer. Wird der Besitzer bei der Erzeugung des Schemas nicht explizit angegeben, so ist das Schema im Besitz des Benutzers, der die CREATE SCHEMA-Anweisung ausführt. 2. Jeder Datenbankbenutzer besitzt ein Standardschema. Das Standardschema für einen Benutzer wird beim Anlegen oder Ändern eines Benutzers zugewiesen. Wenn dies nicht explizit erfolgt, einem Benutzer also kein Standardschema zugewiesen wird, so ist das Standardschema des Benutzers dbo. 3. Die meisten Datenbankobjekte werden in Schemas angelegt. Es gibt hier einige Ausnahmen, wie zum Beispiel Benutzer und Rollen. Wird bei der Erzeugung eines Datenbankobjektes das Schema nicht explizit angegeben, so wird das Objekt im Standardschema des Benutzers angelegt, der das Datenbankobjekt erzeugt. 4. Über ein Schema kann der Zugriff auf die enthaltenen Objekte kontrolliert werden. Für Schemas können Berechtigungen erteilt bzw. entzogen werden. Im Schema enthaltene Objekte erben diese Berechtigungen. Das Schema dbo ist das Standardschema des Datenbankbesitzers (database owner). Für diesen Datenbankbesitzer gibt es auch einen entsprechenden Benutzer, der ebenfalls dbo heißt. Der Benutzer dbo ist auch der Besitzer des Schemas dbo. Im Schema sys werden alle Systemobjekte der Datenbank abgelegt.
85
4 Transact SQL-Grundlagen Abbildung 4.10 verdeutlicht den Zusammenhang zwischen Schemas, Besitzer, Berechtigung und Objekt.
Abbildung 4.10 Schemas: Besitzer und Berechtigungen
Wie bereits erwähnt, wird ein Schema durch die Anweisung CREATE SCHEMA erzeugt, also zum Beispiel so:
In der ersten CREATE SCHEMA-Anweisung wird ein Schema mit dem Namen Marketing erzeugt. Über die Option AUTHORIZATION wird angegeben, dass der Datenbankbenutzer Meier der Besitzer dieses Schemas ist. Wird die AUTHORIZATIONOption nicht angegeben, wie dies in der zweiten Anweisung der Fall ist, so ist der aktuelle Datenbankbenutzer der Besitzer des Schemas. Zum Ändern eines Schemas können Sie die ALTER SCHEMA-Anweisung verwenden. Allerdings können mit dieser Anweisung lediglich Datenbankobjekte in ein anderes Schema verschoben werden. Hierzu kennt die Anweisung ALTER SCHEMA die TRANSFEROption:
Die obige ALTER SCHEMA-Anweisung verschiebt das Datenbankobjekt Umsatz aus dem Schema Verkauf in das Schema Marketing.
86
4.7 Schemas verwenden Zum Ändern des Schema-Besitzers verwenden Sie die Anweisung ALTER AUTHORIZATION. Um den Besitzer Schulze für das Schema Marketing festzulegen, kann diese Anweisung so verwendet werden:
Mit der Anweisung DROP SCHEMA wird ein Schema entfernt:
Hierbei ist zu beachten, dass das Schema nur gelöscht wird, wenn es keine Objekte mehr enthält. Ansonsten schlägt DROP SCHEMA fehl.
4.7.1
Namensauflösung
Auf ein Datenbankobjekt wird durch die Angabe des Objektnamens zugegriffen. Hierbei hat jedes Datenbankobjekt einen aus vier Teilen bestehenden Namen, der so aufgebaut ist:
In einem solchen voll qualifizierten Namen sind die einzelnen Bestandteile jeweils durch einen Punkt voneinander getrennt. Die vier Bestandteile des Namens haben die folgende Bedeutung: obkjektname. Der Objektname ist der Name des Datenbankobjektes, auf das zugegriffen werden soll, also zum Beispiel eine Tabelle oder eine gespeicherte Prozedur. Dieser Teil des Namens muss angegeben werden, ist also obligatorisch. schemaname. Der Name des Schemas, in dem das Datenbankobjekt existiert. Diese Angabe ist optional. Wird das Schema nicht angegeben, so wird das Objekt zunächst im Schema sys gesucht. Wenn das Objekt in diesem Schema nicht gefunden wird, so erfolgt im nächsten Schritt die Suche im Standardschema des Benutzers, der die Anweisung ausführt. Wird das Objekt auch in diesem Schema nicht gefunden, dann wird die Suche im Schema dbo fortgesetzt. Erst wenn auch dort das Objekt nicht gefunden werden kann, erfolgt eine Fehlermeldung. Diese Verfahrensweise ist aus Gründen der Abwärtskompatibilität zu früheren SQL Server-Versionen erforderlich. datenbankname. Der Name der Datenbank, in der das Objekt existiert. Auch diese Angabe ist optional. Wird der Name nicht angegeben, so wird der aktuelle Datenbankkontext verwendet. Dieser Kontext bezieht sich auf die über USE eingestellte Datenbank. servername. Hier wird der Name des SQL Servers angegeben, in dem das Objekt existiert. Die Angabe des Servernamens ist ebenfalls optional und darf nur erfolgen, wenn ein entsprechender Verbindungsserver eingerichtet wurde. Über Verbindungsserver kann auf Objekte entfernter Server zugegriffen werden, so als wenn diese Objekte auf dem lokalen Server existieren würden. In diesem Buch werden wir Verbindungsserver nicht verwenden, den Namensteil für den Servernamen also stets weglassen.
87
4 Transact SQL-Grundlagen Was Schemas anbelangt, so hat die geschilderte Namensauflösung vor allem zwei Konsequenzen: 1. Innerhalb einer Datenbank bildet ein Schema auch einen Namensraum für die enthaltenen Objekte. Dies bedeutet, dass zum Beispiel Tabellen gleichen Namens in unterschiedlichen Schemas existieren können. 2. Wenn Sie für den Zugriff auf Datenbankobjekte keinen Schemanamen angeben, so wird die automatische Namensauflösung anhand von existierenden Standardschemas durchgeführt. Da hierbei ein Datenbankobjekt eventuell über mehrere Ebenen gesucht werden muss, kann die Ausführungszeit Ihrer Abfragen darunter leiden. Geben Sie also nach Möglichkeit den Schemanamen mit an, und vertrauen Sie nicht auf die Automatik. In Abbildung 4.11 sehen Sie ein Beispiel für die Namensauflösung. Die Zeichnung zeigt die beiden Standardschemas sys und dbo sowie zwei benutzerdefinierte Schemas Marketing und Verkauf. In den beiden benutzerdefinierten Schemas ist jeweils ein Objekt (vielleicht eine Tabelle) T1 enthalten, auch im Schema dbo gibt es eine Tabelle mit diesem Namen. Im Szenario existieren weiterhin die beiden Benutzer Meier und Lehmann mit zugehörigen Standardschemas. Dargestellt sind vier verschiedene Fälle für den Zugriff auf die in der Datenbank existierenden Objekte Marketing.T1, Verkauf.T1, dbo.T1 und dbo.T2.
Abbildung 4.11 Schemas, Objekte und Namensauflösung
Die Pfeile zeigen hierbei jeweils den verwendeten Objektnamen wahlweise mit oder ohne Angabe des Schemas. In Tabelle 4.14 finden Sie die Beschreibung der vier dargestellten Fälle. Noch ein Tipp zum Schluss dieses Abschnitts: Versuchen Sie, in Ihren Datenbanken Objektnamen zu vermeiden, die auch im Schema sys existieren. Durch den Start der automatischen Namensauflösung im Schema sys wird immer ein dort enthaltenes Objekt gleichen Namens verwendet, wenn Sie das Schema nicht explizit angeben. Dies kann zu einiger Verwirrung führen.
88
4.7 Schemas verwenden Tabelle 4.14 Beispiele für die automatische Namensauflösung Fall
Szenario
Beschreibung
1
Der Benutzer Meier mit dem zugeordneten Standardschema Marketing greift auf das Datenbankobjekt T1 ohne Angabe des Schemanamens zu.
Da kein Schema angegeben wurde, wird T1 im ersten Schritt im Standardschema sys gesucht3. Diese Suche schlägt fehl und wird im Schema Marketing (dem Standardschema des Benutzers) fortgesetzt. Hier wird das Objekt gefunden.
2
Der Benutzer Lehmann mit dem zugeordneten Standardschema Verkauf greift auf das Datenbankobjekt T1 ohne Angabe des Schemanamens zu.
Auch hier wurde kein Schema angegeben, der Zugriff erfolgt also auf das Objekt T1 im Standardschema des Benutzers Lehmann.
3
Der Benutzer Lehmann mit dem zugeordneten Standardschema Verkauf greift auf das Datenbankobjekt Marketing.T1 zu, gibt also einen Schemanamen an.
Durch die Angabe des Schemanamens wird auf das Objekt T1 im Schema Marketing zugegriffen. Eine Objektsuche bzw. automatische Namensauflösung ist nicht erforderlich.
4
Der Benutzer Lehmann mit dem Standardschema Verkauf greift auf das Datenbankobjekt T2 ohne Angabe des Schemanamens zu.
T2 wird zunächst im Standardschema des Benutzers Lehmann gesucht. Da das Objekt hier nicht gefunden wird, wird die Suche im Schema dbo fortgesetzt. Auf das dort gefundene Objekt T2 wird dann zugegriffen.
4.7.2
Synonyme
Durch die Erstellung eines Synonyms kann auf ein Objekt in einer Datenbank unter einem alternativen Namen zugegriffen werden. Ein Synonym wird durch die Anweisung CREATE SYNONYM wie folgt erzeugt:
In der Datenbank OmasKochbuch existiert nun ein Synonym AWContact. Durch die obige CREATE SYNONYM-Anweisung wird dieses Synonym im Standardschema des Benutzers angelegt, der die Anweisung ausführt. Der exakte Name des Synonyms ist also <Standardschema>.AWContact, wobei <Standardschema> für das Standardschema des anlegenden Benutzers steht. Wann immer in dieser Datenbank der Name <Standardschema>.AWContact in SQL-Anweisungen benutzt wird, erfolgt der Zugriff auf die Tabelle Contact im Schema Person der Datenbank AdventureWorks. Die Anweisung DROP SYNONYM entfernt ein Synonym wieder:
3
Korrekt müsste die Suche im sys-Schema für alle Szenarien dargestellt werden. Für die Szenarien 2, 3 und 4 wurde dieser erste Schritt nur aus Gründen der Übersichtlichkeit weggelassen.
89
4 Transact SQL-Grundlagen Seien Sie bitte vorsichtig mit dem Löschen von Synonymen. Existierender T-SQL-Code (zum Beispiel in gespeicherten Prozeduren), in dem auf das Synonym zugegriffen wird, wird bei DROP SYNONYM nicht überprüft. Dieser Code wird also nach dem Entfernen des Synonyms nicht mehr funktionieren.
4.8
Tabellen entwerfen Letztlich werden Benutzerdaten in Tabellen abgelegt und aus diesen auch wieder abgefragt. Tabellen sind damit unzweifelhaft die wesentlichsten Objekte einer jeden Datenbank. Eine Tabelle besteht aus einer Anordnung von Zeilen und Spalten, wobei für jede Spalte einer Tabelle ein möglicher Wertebereich angegeben werden kann. Dieser Wertebereich wird zunächst einmal durch den Datentyp bestimmt, der für jede Spalte angegeben werden muss. Zusätzlich können dann noch Einschränkungen festgelegt werden, die den Wertebereich weiter eingrenzen.
4.8.1
Tabellen anlegen
Für das Anlegen von Tabellen verwenden Sie die Anweisung CREATE TABLE. Die Anweisung ermöglicht neben der Definition der Tabellenstruktur auch die Festlegung von Optionen, die spezifizieren, in welcher Dateigruppe die Tabelle erzeugt werden soll. Diese Optionen können allerdings auch weggelassen werden, wodurch die Tabellendaten in der Standarddateigruppe abgelegt werden. In diesem Fall sieht das Kommando CREATE TABLE einfach so aus:
Auf die CREATE TABLE-Anweisung folgen zunächst der Name der zu erzeugenden Tabelle und dann in Klammern eine durch Komma getrennte Liste von Spaltendefinitionen: . Dies ist der Name der zu erzeugenden Tabelle. Dieser Name kann auch den Namen einer Datenbank und eines Schemas enthalten. Werden diese Anteile nicht angegeben, so erfolgt die automatische Namensauflösung wie im vorigen Abschnitt geschildert. Allerdings wird die Tabelle niemals im Schema sys abgelegt. Wird kein Schema angegeben, so wird die Tabelle im Standardschema desjenigen Benutzers angelegt, der die CREATE TABLE-Anweisung ausführt. <spaltenname>. Dies ist der Name einer Tabellenspalte. Ein Spaltenname muss den Regeln für Bezeichner genügen, darf also zum Beispiel nicht mit einer Ziffer beginnen. <spaltendefinition>. In der Spaltendefinition muss zumindest der Datentyp der Spalte angegeben werden. Über weitere Optionen ist es dann zum Beispiel auch möglich, den Wertebereich also die für die Spalte gültigen Werte einzuschränken. Hiermit werden wir uns noch im Verlauf dieses Abschnitts naher befassen.
90
4.8 Tabellen entwerfen Nun aber genug der Vorrede. Wir wollen nun zu unserer Datenbank OmasKochbuch eine Tabelle Rezept hinzufügen. Die Tabelle soll zunächst drei Spalten enthalten, in denen Daten für den Namen des Rezeptes, den Energiegehalt in Joule und eine Einstufung des Geschmackes des im Rezept genannten Gerichtes gespeichert werden. Diese Einstufung soll einfach durch eine Ziffer von 1 bis 6 repräsentiert werden, so wie eine Schulnote. Hierzu wird die folgende einfache CREATE TABLE-Anweisung verwendet, die auch eine Besonderheit von CREATE SCHEMA aufzeigt. Innerhalb einer CREATE SCHEMAAnweisung kann nämlich auch eine Tabelle angelegt werden:
Die Tabelle Rezept wird im angelegten Schema Kochen erzeugt. Diese Tabelle enthält drei Spalten Bezeichnung, Joule und Bewertung. Für alle drei Spalten wurde ein Datentyp angegeben, der spezifiziert, dass eine Bezeichnung eine Zeichenkette mit der maximalen Länge von 100 Zeichen ist und dass die Spalten Joule und Bewertung ganze Zahlen enthalten müssen. Im Objekt-Explorer wird die erzeugte Tabelle mit den drei Spalten angezeigt (Abbildung 4.12). Eventuell muss zuvor noch die Anzeige aktualisiert werden. Hierzu können Sie in der Menüleiste des Objekt-Explorers einfach den Button Aktualisieren drücken.
Abbildung 4.12 Die Tabelle Kochen.Rezept im Objekt-Explorer
Grafischer Tabellendesigner Das SQL Server Management Studio stellt Ihnen einen grafischen Tabellendesigner zur Verfügung, der es Ihnen gestattet, eine Tabelle über eine komfortable Benutzeroberfläche zu erstellen oder auch zu ändern. Dieser Designer wird zum Beispiel über das Kontextmenü der Datenbank geöffnet (Abbildung 4.13).
91
4 Transact SQL-Grundlagen
Abbildung 4.13 Eine Tabelle anlegen
In Abbildung 4.14 sehen Sie den Tabellendesigner, in dem Spalten- und Tabelleneigenschaften konfiguriert werden können.
Abbildung 4.14 Der Tabellendesigner des SQL Server Management Studios
Die weiter oben über die Anweisung CREATE TABLE erzeugte Tabelle Kochen.Rezept kann also auch über den Tabellendesigner angelegt werden. Hierfür werden zunächst einfach die Spalten eingegeben, welche die Tabelle enthalten soll. Die obere Liste des Tabellendesigners erlaubt die Eingabe von Spaltennamen und Datentyp. In der dritten Spalte der Liste wird angegeben, ob eine Spalte auch den Wert NULL enthalten, also leer sein darf.
92
4.8 Tabellen entwerfen Im unteren Teil Spalteneigenschaften werden dann zusätzliche Eigenschaften für die einzelnen Tabellenspalten konfiguriert. Hier kann zum Beispiel eine Beschreibung für die Spalte eingegeben werden. Möglich ist dort auch die Festlegung von Optionen für die Replikation der Tabelle. Ist das Tabellenlayout fertig, kann die neue oder geänderte Tabelle durch einen Klick auf das Diskettensymbol in der Menüleiste in die Datenbank übernommen werden. Bei einer neuen Tabelle werden Sie zunächst aufgefordert, einen Tabellennamen einzugeben. Die Tabelle wird dann in der Datenbank gespeichert. Es ist auch hier wieder möglich, ein Skript zu generieren, das die Änderungen als T-SQL-Code enthält. Wählen Sie hierzu aus dem Menü Tabellen-Designer/Änderungsskript generieren
(Abbildung 4.15).
Abbildung 4.15 Ein Skript für die Änderung einer Tabelle generieren
Im anschließenden Dialog haben Sie die Möglichkeit, das erzeugte Skript sofort auszuführen oder für eine spätere Ausführung zu speichern.
4.8.2
Tabellen ändern
Die im weiteren Verlauf dieses Abschnittes besprochenen Aktionen zum Ändern der Tabellenstruktur können selbstverständlich ebenfalls mit dem Tabellendesigner durchgeführt werden. Da dies jedoch ein Buch über Datenbankentwicklung mit T-SQL ist, wollen wir den Tabellendesigner nicht weiter benutzen, sondern die entsprechenden T-SQL-Kommandos verwenden. Bestehende Tabellen können über die ALTER TABLE-Anweisung geändert werden. Mit ALTER TABLE können einzelne Spalten der Tabelle geändert, gelöscht oder hinzugefügt werden. Es ist auch möglich, Optionen zur physikalischen Speicherung der gesamten Tabelle zu konfigurieren, also letztlich festzulegen, in welcher Datei eine Tabelle gespeichert wird. Insbesondere können über ALTER TABLE Einschränkungen auf Spalten- und Tabellenebene vergeben werden. Durch solche Einschränkungen können Sie zum Beispiel den Wertebereich für einzelne Spalten reduzieren. Etwas weiter unten werden Sie sehen, wie dies funktioniert.
93
4 Transact SQL-Grundlagen Spalten hinzufügen mit ADD Zum Hinzufügen von Spalten zu einer Tabelle verwenden Sie die ADD-Klausel der ALTER TABLE-Anweisung. Angenommen, unsere Tabelle Kochen.Rezept soll eine weitere Spalte enthalten, in der gespeichert werden soll, wann das Gericht das letzte Mal gekocht wurde. Hierzu wollen wir eine Spalte ZuletztGekochtAm vom Datentyp hinzufügen. Die entsprechende ALTER TABLE-Anweisung sieht so aus:
Interessanterweise heißt die Klausel zum Hinzufügen von Spalten nur ADD und nicht etwa ADD COLUMN. Spalten ändern mit ALTER COLUMN Zum Ändern von Spalten verwenden Sie die Klausel ALTER COLUMN der Anweisung ALTER TABLE. Durch ALTER COLUMN kann der Datentyp der Spalte geändert werden. Es ist weiterhin möglich festzulegen, ob eine Spalte auch leer sein darf, also den Wert NULL enthalten kann. Das Ändern des Datentyps funktioniert allerdings nur dann, wenn die beiden beteiligten Datentypen kompatibel sind. Dies bedeutet, dass eine implizite Typkonvertierung des alten in den neuen Datentyp möglich sein muss. Ist dies nicht der Fall, so erhalten Sie beim Ausführen einer entsprechenden ALTER TABLE
ALTER COLUMN-Anweisung eine Fehlermeldung. Ebenso gilt für das Ändern der NULLZulässigkeit, dass die Änderung auf nicht zulässig nur erlaubt ist, wenn in der Spalte bislang noch keine NULL-Werte enthalten sind. Für die im vorigen Absatz hinzugefügte Spalte ZuletztGekochtAm wurde der Datentyp angegeben. Wenn Sie sich noch einmal Tabelle 4.4 ansehen, so werden Sie vielleicht feststellen, dass der Wertebereich des Datentyps für diese Spalte ausreichend ist. Um den Datentyp der Spalte ZuletztGekochtAm in zu ändern, verwenden Sie ALTER TABLE wie folgt:
Der Spaltenname selber kann übrigens durch die ALTER COLUMN Klausel nicht geändert werden. Spalten löschen mit DROP COLUMN Die DROP COLUMN-Klausel der ALTER TABLE-Anweisung ermöglicht das Löschen von Spalten aus einer Tabelle. Um zum Beispiel die Spalte ZuletztGekochtAm aus der Tabelle Kochen.Rezept zu entfernen, geben Sie die folgende Anweisung an:
94
4.8 Tabellen entwerfen
4.8.3
Identität/Primärschlüssel
Für die Arbeit mit Tabellen ist es essenziell, dass jede Tabellenzeile von allen anderen Zeilen unterschieden werden kann, oder mit anderen Worten: Eine einzelne Tabellenzeile muss eindeutig identifizierbar sein, eine eigene Identität besitzen. Anderenfalls werden Aktualisierungs- oder Löschoperationen für einzelne, definierte Tabellenzeilen nicht möglich sein, da eine Selektion der für diese Operationen erforderlichen Zeilen unmöglich ist. Eine Zeilenidentität für eine Tabelle wird durch die Vergabe eines Primärschlüssels für diese Tabelle eingeführt. Ein solcher Primärschlüssel besteht letztlich aus einer Aneinanderreihung von Spalten, die zusammengenommen eine Tabellenzeile als eindeutig kennzeichnen. Zum Beispiel könnte in einer Tabelle mit Personendaten die Kombination von Nachname und Vorname als Primärschlüssel definiert werden vorausgesetzt, es gibt keine Personen mit gleichen Nach- und Vornamen. In den meisten Fällen besteht der Primärschlüssel allerdings nur aus einer Spalte, die oftmals auch eigens für diesen Zweck, nämlich für die Verwendung als Primärschlüssel, definiert wurde. In der erwähnten Personentabelle könnte hierfür zum Beispiel eine Personalnummer eingeführt werden, die für jede Person eindeutig vergeben wird. Weitere bekannte Beispiele für eine solche Verfahrensweise sind Kunden- oder Rechnungsnummern in Warenwirtschaftssystemen, die ebenso für die Verwendung als Primärschlüssel hier auch außerhalb der Datenbank eingeführt werden. Bei diesen Nummern versteht es sich von selbst, dass doppelte Werte nicht zulässig sind und somit einer Tabellenzeile für die Kunden- oder Rechnungstabelle eine Zeilenidentität verleihen. Die Wahl eines geeigneten Primärschlüssels für eine Tabelle ist eine wichtige Aufgabe beim Design einer Datenbank. Wesentlich ist vor allem, dass ein Primärschlüssel gefunden wird, dessen Spalten später nicht aktualisiert werden müssen. Eine solche Aktualisierung bringt stets große Probleme mit sich, wie Sie sich sicherlich am Beispiel der erwähnten Kunden- bzw. Rechnungsnummern als Primärschlüssel ausmalen können. Prinzipiell wird zwischen natürlichen und künstlichen Primärschlüsseln unterschieden. Ein natürlicher Primärschlüssel identifiziert ein Objekt auch außerhalb der Datenbank. Beispiele hierfür sind Personalausweisnummer oder Postleitzahl. Natürliche Primärschlüssel können Probleme bereiten, da sie durch Menschen interpretierbar sind. Allein durch diese Möglichkeit sind sie anfällig für Änderungen. Auf der anderen Seite kann durch die Verwendung von natürlichen Primärschlüsseln oftmals eine Tabelle in einer Abfrage eingespart werden. Ein künstlicher Primärschlüssel ist eine eigens für die Verwendung als Primärschlüssel geschaffene Größe, die in einer Spalte einer Tabelle abgebildet wird. Ein solcher Primärschlüssel definiert ein Objekt lediglich innerhalb der Datenbank. SQL Server unterstützt die Vergabe von künstlichen Primärschlüsseln durch die Möglichkeit, automatische Zählwerte für Spalten festlegen zu können. Ein künstlicher Primärschlüssel hat absolut keine Bedeutung für Anwender und wird daher mit großer Wahrscheinlichkeit auch nicht von diesen geändert werden. Ein Nachteil bei der Verwendung eines künstlichen Primärschlüssels ist, dass in Abfragen oftmals eine zusätzliche Tabelle aufgenommen werden muss.
95
4 Transact SQL-Grundlagen Ein Primärschlüssel ist eine Tabelleneinschränkung, die zum Beispiel durch die Anweisung ALTER TABLE oder bereits beim Anlegen der Tabelle über CREATE TABLE festgelegt werden kann. Für in einem Primärschlüssel verwendete Spalten gelten hierbei die folgenden Einschränkungen: Die Spalten dürfen per Deklaration keine NULL-Werte erlauben. Die Spalten dürfen keine doppelten Werte enthalten. Außerdem darf für jede Tabelle nur ein Primärschlüssel angelegt werden. Wir wollen nun für die Tabelle Kochen.Rezept einen Primärschlüssel hinzufügen und suchen in den Spalten der Tabelle zunächst nach einem geeigneten Kandidaten für diesen Primärschlüssel. Möglicherweise könnte die Bezeichnung des Rezeptes als Primärschlüssel verwendet werden, da wir davon ausgehen können, dass die Rezeptliste keine doppelten Gerichte enthalten soll. Allerdings kann diese Bezeichnung 100 Zeichen lang sein. Wann immer eine Zeile der Tabelle Kochen.Rezept in einer SQL-Abfrage identifiziert werden soll, muss dann also eine bis zu 100 Zeichen lange Zeichenkette für diese Identifikation eingegeben werden. Allein das macht die Verwendung dieses Primärschlüssels extrem unhandlich. Außerdem sollte ein Primärschlüssel möglichst kurz sein und vor allem niemals geändert werden und diese beiden Eigenschaften würde der Primärschlüssel ganz sicher nicht aufweisen. Auch die anderen Spalten sowie eine eventuelle Kombination aus diesen sind nicht als Primärschlüssel geeignet. Aus diesem Grund soll ein künstlicher Primärschlüssel eingeführt werden, der für jedes Rezept eine Nummer enthält. Hierzu fügen wir zunächst eine Spalte RezeptId vom Typ zur Tabelle hinzu:
Anschließend kann diese Spalte als Primärschlüssel definiert werden. Hierzu wird eine PRIMARY KEY-Einschränkung zur Tabelle hinzugefügt. Die generelle Syntax der entsprechenden ALTER TABLE-Anweisung ist wie folgt:
und stehen hier stellvertretend für den Namen der
Tabelle, für die der Primärschlüssel erzeugt werden soll, und die Bezeichnung des Primärschlüssels. ist eine durch Kommata getrennte Auflistung der für den Primärschlüssel verwendeten Spalten. In unserem Fall wird nur die gerade hinzugefügte Spalte RezeptId verwendet, die ALTER TABLE-Anweisung sieht also so aus:
Der Primärschlüssel wird hier PK_Rezept genannt. Selbstverständlich können Sie diesen Namen frei wählen. Üblich ist es aber, den Namen für einen Primärschlüssel mit den Buchstaben PK (für Primary Key) zu beginnen.
96
4.8 Tabellen entwerfen Es ist auch möglich, eine Spalte sofort beim Hinzufügen zu einer Tabelle als Primärschlüssel zu definieren, also die beiden obigen Schritte zu einem zusammenzufassen. In unserem Beispiel würde eine solche Definition wie folgt aussehen:
Hierbei vergibt SQL Server automatisch einen Namen für den Primärschlüssel (der aber ebenfalls mit den Buchstaben PK_ beginnt). Ein Primärschlüssel kann auch bereits beim Anlegen einer Tabelle mit CREATE TABLE definiert werden. Hierfür gibt es zwei Möglichkeiten: Erstreckt sich der Primärschlüssel nur über eine Spalte und stören Sie sich nicht an dem vom SQL Server automatisch vergebenen Namen für den Primärschlüssel, so können Sie einfach den Zusatz PRIMARY KEY zur Deklaration der Spalte hinzufügen. Dies ist die gerade etwas weiter oben verwendete Verfahrensweise:
Die andere Möglichkeit bietet die Verwendung der CONSTRAINT-Klausel innerhalb der CREATE TABLE-Anweisung. Hier haben Sie dann auch die Möglichkeit, den Primärschlüssel zu benennen sowie mehrere Spalten in den Schlüssel aufzunehmen:
Die Tabelle Kochen.Rezept verfügt nun über einen Primärschlüssel auf der Spalte RezeptId. Jede Zeile in dieser Tabelle muss daher anhand des Wertes in dieser Spalte eindeutig identifizierbar sein. Insbesondere dürfen keine doppelten Werte in dieser Spalte existieren. Die Sicherstellung dieser Einschränkung von Seiten der Anwendung, die Daten in die Tabelle einfügt, ist natürlich alles andere als einfach. Woher soll denn eine Anwendung wissen, welcher Wert in der Spalte RezeptId noch nicht existiert und daher für das Hinzufügen einer Zeile verwendet werden darf? Dieses Problem verschärft sich in einer Mehrbenutzerumgebung wenn also von mehreren Anwendungen gleichzeitig Zeilen eingefügt werden natürlich noch. Außerdem ist es sicherlich wünschenswert, wenn die RezeptId fortlaufend vergeben, also einfach hochgezählt wird. IDENTITY-Spalten SQL Server bietet die Möglichkeit, dieses Inkrementieren zu übernehmen und somit die Werte für eine Primärschlüsselspalte selbstständig zu vergeben. Eine Tabellenspalte kann
97
4 Transact SQL-Grundlagen hierfür bei der Deklaration mit dem Zusatz IDENTITY versehen werden. Hierbei werden über zwei Parameter der Startwert und die Schrittweite für die automatische Vergabe der Nummern angegeben. Das folgende Beispiel deklariert eine solche Spalte mit einem Startwert von 1000 und einer Schrittweite von 1:
Eine mit der Option IDENTITY deklarierte Spalte wird von SQL Server für jede eingefügte Zeile automatisch inkrementiert und kann somit als Zeilenidentität verwendet werden. Allerdings ist eine IDENTITY-Spalte nicht automatisch auch der Primärschlüssel für die Tabelle, dieser wird stets über eine entsprechende PRIMARY KEY-Klausel festgelegt. Eine Definition der Spalte Id aus dem obigen Beispiel als Primärschlüssel muss explizit erfolgen, also zum Beispiel so:
Da SQL Server die Werte für IDENTITY-Spalten automatisch vergibt, können Werte in diese Spalten normalerweise nicht eingefügt werden. Für derartige Einfügeoperationen muss zuvor die Option IDENTITY_INSERT für die Tabelle aktiviert werden.
4.8.4
Weitere Einschränkungen
Neben dem Primärschlüssel können sowohl für Spalten als auch für die gesamte Tabelle weitere Einschränkungen festgelegt werden. SQL Server sorgt dafür, dass bei Datenänderungsoperationen keine dieser Einschränkungen verletzt wird, und stellt so die Integrität Ihrer Daten sicher. UNIQUE UNIQUE ist eine Einschränkung auf Tabellenebene. Genauso wie für Primärschlüssel werden auch durch UNIQUE-Einschränkungen Spalten festgelegt, die innerhalb der Tabelle eindeutig sein müssen, also keine gleichen Werte in verschiedenen Zeilen zulassen. Eine UNIQUE-Einschränkung wird daher oftmals auch als Sekundärschlüssel bezeichnet. Eine Tabelle kann hierbei auch mehrere UNIQUE-Einschränkungen enthalten. Dabei sind auch NULL-Werte in einer Spalte zulässig (siehe Abschnitt 4.16). UNIQUE-Einschränkungen können über ALTER TABLE oder auch CREATE TABLE festgelegt werden. Die Syntax ist dabei ganz ähnlich der schon bei der Erstellung von Primärschlüsseln besprochenen:
98
4.8 Tabellen entwerfen
Der im zweiten Beispiel bei der Erzeugung der Tabelle T2 verwendete Name für die UNIQUE-Einschränkung beginnt mit den Buchstaben UQ_ und hält sich damit an die Namenskonvention für diesen Einschränkungstyp. Wie bereits weiter oben erwähnt kann eine UNIQUE-Einschränkung auch nachträglich einer Tabelle hinzugefügt werden. Selbstverständlich dürfen die betreffenden Spalten dann keine Duplikate aufweisen. Um in unserer Tabelle Kochen.Rezept keine doppelten Bezeichnungen für ein Rezept zuzulassen, fügen wir eine entsprechende UNIQUE-Einschränkung für die Spalte Bezeichnung hinzu. Die erforderliche ALTER TABLE-Anweisung erinnert wieder an die bereits im Abschnitt über Primärschlüssel verwendete:
Eine existierende Einschränkung kann über DROP CONSTRAINT wie folgt entfernt werden:
NULL und NOT NULL Über die Spalteneinschränkungen NULL bzw. NOT NULL wird festgelegt, ob eine Spalte auch NULL-Werte enthalten darf. Nach Möglichkeit sollten Sie keine leeren Spalten verwenden, manchmal lässt sich dies allerdings nicht vermeiden. In Abschnitt 4.16 erfahren Sie hierzu mehr. Um zum Beispiel für unsere Spalte Bezeichnung keine NULL-Werte zuzulassen Kochrezepte ohne Bezeichnung sind in der Tabelle nicht sinnvoll , kann über eine ALTER TABLE-Anweisung die entsprechende Einschränkung hinzugefügt werden:
Für jede innerhalb von CREATE TABLE oder ALTER TABLE angelegte Spalte wird immer festgelegt, ob diese Spalte auch NULL-Werte aufnehmen kann. Wenn Sie dies nicht explizit angeben, so erfolgt es implizit, so wie in unseren bisherigen Beispielen. Abhängig von der Einstellung der Option wird automatisch NULL oder NOT NULL zur Spaltendeklaration hinzugefügt. Ist die Option ein (ON), so wird NULL addiert, wenn die Option aus (OFF) ist, NOT NULL.
99
4 Transact SQL-Grundlagen
In den die Datenbankeigenschaften können Sie ebenfalls festlegen, wie der Standard sein soll. Öffnen Sie das Eigenschaftsfenster für die Datenbank aus dem Kontextmenü. Auf der Seite Optionen finden Sie die Option ANSI Null Default, die ein- oder ausgeschaltet werden kann (Abbildung 4.16).
Abbildung 4.16 Festlegen der Datenbankoption ANSI NULL Default
Ich kann Ihnen nur raten, sich nicht auf die Standardeinstellungen zu verlassen und stattdessen immer explizit anzugeben, ob eine Spalte NULL-Werte enthalten darf oder nicht. Der nur minimal erhöhte Schreibaufwand ist die Mühe wert, denn Sie erreichen dadurch eine größere Unabhängigkeit von globalen Einstellungen. CHECK Über eine CHECK-Einschränkung können Bedingungen angegeben werden, die Tabellenzeilen oder auch einzelne Spalten erfüllen müssen. Die CHECK-Einschränkung kann als Spalten- oder Tabelleneinschränkung angelegt werden. Sie ist wie gewohnt über ALTER TABLE oder CREATE TABLE konfigurierbar. Eine Einschränkung auf Tabellenebene kann bei CREATE TABLE als separate CONSTRAINT-Klausel angegeben werden:
100
4.8 Tabellen entwerfen
Über die CHECK-Option wird die Bedingung angegeben, die erfüllt sein muss, damit eine Tabellenzeile gültig ist. Mit anderen Worten: Die obige Tabelle T1 darf nur Zeilen enthalten, in denen der Wert der Spalte C1 größer als der Wert der Spalte C2 ist. Der Name der Einschränkung beginnt mit CK_ und folgt damit wieder der Konvention. Es soll nun eine Einschränkung für unsere Tabelle Kochen.Rezept hinzugefügt werden, die dafür sorgt, dass in die Spalte Geschmack nur die Werte 1 bis 6 eingetragen werden dürfen. Zuvor soll der Datentyp für diese Spalte noch auf geändert werden. Außerdem wollen wir dafür sorgen, dass NULL-Werte nicht mehr erlaubt sind. Die entsprechenden ALTER TABLE-Anweisungen sehen so aus:
Beim Erstellen einer Tabelle über CREATE TABLE können in der Spaltendeklaration ebenfalls CHECK-Einschränkungen angegeben werden, die sich dann wiederum nur auf die Spalte beziehen und automatisch benannt werden. Für die Spalte Bewertung hätten wir beim Anlegen der Tabelle auch so vorgehen können:
Der Ausdruck für die CHECK-Einschränkung verwendet hier den BETWEEN-Operator. Der Ausdruck gibt WAHR zurück, wenn die Bewertung zwischen 1 und 6 liegt. DEFAULT Über die DEFAULT-Option definieren Sie einen Standardwert für eine Spalte. Die Spalte erhält diesen Wert bei Einfügeoperationen immer dann, wenn kein expliziter Wert angegeben wird. Alle Spalten, für die kein Standardwert über DEFAULT angegeben wird, erhalten implizit NULL als Standard. Dies gilt auch, wenn die Spalte an sich keine NULLWerte erlaubt. Anders gesagt: Werte für Spalten, die als NOT NULL und ohne DEFAULT deklariert werden, müssen bei Einfügeoperationen immer angegeben werden.
101
4 Transact SQL-Grundlagen Am einfachsten geben Sie die DEFAULT-Option bei der Spaltendeklaration in der CREATE TABLE-Anweisung an:
Für die Spalte C1 wird der Standardwert 0 angegeben. Der Wert dieser Spalte wird dadurch bei Einfügeoperationen immer auf 0 gesetzt, wenn für die Spalte kein expliziter Wert angegeben wird. Der angegebene DEFAULT-Wert muss übrigens keine Konstante sein. Er kann zum Beispiel auch durch eine der in Abschnitt 4.15 angegebenen Funktionen berechnet werden. Es ist auch natürlich ebenfalls möglich, eine DEFAULT-Einschränkung zu einer existierenden Spalte hinzuzufügen. Hierzu verwenden Sie ALTER TABLE in der folgenden Form:
Pro Spalte kann selbstverständlich nur ein Standardwert vergeben werden. Wenn für eine Spalte bereits ein Standardwert existiert, so schlägt die ADD DEFAULT-Option fehl. Beim Festlegen von DEFAULT-Einschränkungen sollten Sie beachten, dass eine Typenkompatibilität der Spaltendeklaration mit dem DEFAULT-Wert gewährleistet ist. So kann zum Beispiel die folgende Tabelle ohne Probleme erstellt werden:
Obwohl der DEFAULT-Wert nicht in ein passt, wird ein Fehler nicht beim Anlegen der Tabelle, sondern erst beim Einfügen einer Zeile ohne explizite Angabe eines Wertes für die Spalte C1 erzeugt.
4.8.5
Berechnete Spalten
SQL Server erlaubt berechnete Spalten in Tabellen. Solche Spalten enthalten lediglich einen Ausdruck, durch den der Spaltenwert aus anderen Tabellenspalten berechnet wird. Dieser Ausdruck wird nach dem Spaltennamen und dem Schlüsselwort AS angegeben:
In der Tabelle Person wird eine berechnete Spalte Name deklariert, die den kompletten Namen einfach durch eine Verkettung der Spaltenwerte für Nachname und Vorname durch ein Komma getrennt berechnet. Eine berechnete Spalte benötigt keinen Speicherplatz zur Speicherung der Spaltenwerte.
102
4.8 Tabellen entwerfen
4.8.6
Ein Wiedersehen mit CREATE TABLE
Nachdem Sie nun wissen, wie Tabellen- und Spalteneinschränkungen beim Anlegen einer Tabelle festgelegt werden, können wir die CREATE TABLE-Anweisung für das Erzeugen der Tabelle Kochen.Rezept mit allen erforderlichen Einschränkungen konstruieren. Fassen wir noch einmal zusammen, wie diese Tabelle aufgebaut sein soll. Tabelle 4.15 zeigt zunächst noch einmal die Spalten für Kochen.Rezept: Tabelle 4.15 Spalten für die Tabelle Kochen.Rezept Spalte
Datentyp
NULL-Werte?
Standardwert
RezeptId
int
Nein
Keiner
Bezeichnung
varchar(100)
Nein
Keiner
Joule
smallint
Nein
0
Bewertung
smallint
Nein
Keiner
ZuletztGekochtAm
smalldatetime
Ja
Keiner
Zusätzlich sollen die folgenden Einschränkungen gelten: 1. Die Spalte RezeptId soll der Primärschlüssel sein. Diese Spalte wird als Identitätsspalte mit dem Startwert 1 und der Schrittweite 1 angelegt. 2. Die Werte für die Spalte Bezeichnung sollen eindeutig innerhalb der Tabelle sein. 3. In die Spalte Joule dürfen nur Werte größer als 0 und kleiner als 10000 eingetragen werden. 4. Der Wert für die Spalte Bewertung muss zwischen 1 und 6 liegen. Aus den obigen Bedingungen ergibt sich dann die folgende CREATE TABLE-Anweisung:
Im oberen Teil der Anweisung werden zunächst die Spalten deklariert. Der zweite Teil legt die erforderlichen Einschränkungen fest. Für die Spalte ZuletztGekochtAm sind hier auch NULL-Werte zugelassen. Der Wert NULL in dieser Spalte soll anzeigen, dass das entsprechende Gericht noch nie gekocht wurde.
103
4 Transact SQL-Grundlagen Da sich alle Einschränkungen nur auf jeweils eine Spalte beziehen, könnte die Tabelle Kochen.Rezept mit gleichen Eigenschaften auch so erzeugt werden:
Es ist also in unserem Fall auch möglich, die Einschränkungen gleich bei der Deklaration der einzelnen Spalten anzugeben. Die für eine Tabelle existierenden Einschränkungen können Sie sich im Objekt-Explorer auch ansehen. Hierzu öffnen Sie den Ordner Einschränkungen für die entsprechende Tabelle (Abbildung 4.17).
Abbildung 4.17 Einschränkungen für die Tabelle Kochen.Rezept
Dort sehen Sie allerdings nur die Namen der Einschränkungen, wobei im Übrigen zu erkennen ist, dass für die DEFAULT-Einschränkung der Spalte Joule automatisch eine Bezeichnung generiert wurde. Über das Kontextmenü der einzelnen Einschränkungen ist es möglich, eine T-SQL-Anweisung für das Erzeugen, Ändern oder Löschen der Einschränkung zu erstellen.
4.8.7
Beziehungen festlegen
Zur Datenbank OmasKochbuch soll nun eine weitere Tabelle hinzugefügt werden, in der die Zutaten für ein Rezept enthalten sind. Diese Tabelle wollen wir auch im Schema Kochen erzeugen und Zutat nennen. In jeder Tabellenzeile soll eine Zutat gespeichert werden, wobei die Daten für eine Zutat sich aus einer Bezeichnung, einer Menge und der Mengeneinheit zusammensetzen. Für die Einheit der Menge sind hierbei nur bestimmte Zeichenketten zugelassen. Eine entsprechende CREATE TABLE-Anweisung könnte also so aussehen:
104
4.8 Tabellen entwerfen
Die CHECK-Einschränkung für die Einheit bewirkt, dass nur die in der Auflistung enthaltenen Zeichenketten als Werte für diese Spalte zulässig sind. Außerdem hat die Tabelle eine Identitätsspalte bekommen, die als Primärschlüssel festgelegt wird. In die Tabelle Kochen.Zutat können nun ebenfalls Zeilen eingefügt werden. Allerdings besteht im Moment noch keinerlei Bezug zur Tabelle Kochen.Rezept. Eingefügte Zutaten hängen also sozusagen in der Luft und können nicht einem bestimmten Rezept, also einer Zeile in der Tabelle Kochen.Rezept, zugeordnet werden. Um diese Zuordnung zu ermöglichen, müssen die Tabellen irgendwie miteinander verbunden in Beziehung gesetzt werden. Da für ein Rezept in der Regel mehrere Zutaten benötigt werden, spricht man in diesem Fall von einer 1:n-Beziehung zwischen Rezept und Zutat. Eine solche Beziehung wird in einer relationalen Datenbank durch einen sogenannten Fremdschlüssel repräsentiert. Hierzu muss die Tabelle auf der 1-Seite die eindeutige Identifikation einer Zeile erlauben, also einen Primärschlüssel besitzen. Die Tabelle auf der n-Seite der Beziehung muss Spalten enthalten, die den Primärschlüssel der 1-Seite duplizieren Über diese, in beiden Tabellen enthaltenen, Spalten kann dann eine Fremdschlüsselbeziehung erstellt werden, welche die Tabellen miteinander verbindet. In unserem Fall besitzt die Tabelle Kochen.Rezept einen Primärschlüssel auf der Spalte RezeptId. Für eine 1:n-Fremdschlüsselbeziehung zwischen Kochen.Rezept und Kochen.Zutat muss diese Primärschlüsselspalte im ersten Schritt auch in der Tabelle Kochen.Zutat erzeugt werden. Hierzu verwenden wir, wie gewohnt, ALTER TABLE:
Der Name für diese Spalte ist frei wählbar, sie wird hier aus Gründen der Übersichtlichkeit ebenfalls RezeptId benannt. Im zweiten Schritt wird nun die eigentliche Fremdschlüsselbeziehung erzeugt, die auf der in beiden beteiligten Tabellen enthaltenen Spalte RezeptId basiert. Auch dies wird über die Anweisung ALTER TABLE erledigt. Auch eine Fremdschlüsselbeziehung ist eine Einschränkung, die wie üblich durch die Klausel ADD CONSTRAINT erzeugt wird. Der Typ der Einschränkung ist FOREIGN KEY (für Fremdschlüssel), wobei die am Fremdschlüssel beteiligten lokalen und über REFERENCES entfernten Spalten angegeben werden. Damit sieht die entsprechende ALTER TABLE-Anweisung so aus:
105
4 Transact SQL-Grundlagen Zur Tabelle Kochen.Zutat wird ein Fremdschlüssel FK_Zutat_Rezept hinzugefügt. Der Fremdschlüssel verwendet die lokale Spalte RezeptId und bezieht sich auf die Spalte RezeptId in der Tabelle Kochen.Rezept. Die Fremdschlüsselbeziehung bewirkt, dass in die Spalte RezeptId der Tabelle Kochen.Zutat nur Werte eingetragen werden können, für die ein gleicher Wert in einer Zeile der Tabelle Kochen.Rezept existiert. Dadurch, dass die Spalte Kochen.Zutat.RezeptId als NOT NULL deklariert wurde, muss jede Zutat zu einem existierenden Rezept gehören es gibt also keine Zutat ohne Rezept. Wären für die Spalte NULL-Werte erlaubt, so dürften auch Zeilen mit dem Wert NULL in dieser Spalte hinzugefügt werden. Solche Zeilen sind ebenfalls erlaubt, da die Fremdschlüsselbeziehung nur für Werte sichergestellt wird, in denen die beteiligten Spalten auf der n-Seite ungleich NULL sind. Wie alle bisherigen Einschränkungen, so kann auch eine Fremdschlüsselbeziehung bereits beim Anlegen einer Tabelle erzeugt werden. Für die Tabelle Kochen.Zutat wäre die Tabellenerstellung also auch so möglich gewesen:
Hier wird die Fremdschlüsselbeziehung in der Spaltendeklaration angegeben, möglich wäre auch wieder die Spezifizierung nach der Deklaration der Spalten in einer separaten CONSTRAINT-Klausel. Fremdschlüsselbeziehungen definieren Anhängigkeiten zur Sicherstellung der referenziellen Integrität. So dürfen zum Beispiel in der Tabelle Kochen.Rezept keine Zeilen gelöscht werden, auf die noch durch eine Fremdschlüsselbeziehung verwiesen wird. Gleiches gilt für die Aktualisierung solcher Zeilen. Auch hierbei wird SQL Server die referenzielle Integrität gewährleisten, es also nicht gestatten, deklarierte Fremdschlüsselbeziehungen zu verletzen. Bei der Deklaration eines Fremdschlüssels kann angegeben werden, wie mit Zeilen in der abhängigen Tabelle bei Schlüsseländerungen verfahren werden soll. Die FOREIGN KEYKlausel kennt hierzu die Optionen ON DELETE und ON UPDATE, über welche die auszuführende Aktion durch die folgenden Optionen spezifiziert werden kann: NO ACTION. Das Ändern oder Löschen von Zeilen in der Primärtabelle führt zu einem Fehler, wenn Verweise auf diese Zeilen existieren. Dies ist die Standardeinstellung, wenn keine Option angegeben wird. CASCADE. Alle Zeilen mit Fremdschlüsseln, die auf die gelöschte oder geänderte Zeile verweisen, werden ebenfalls gelöscht oder geändert.
106
4.8 Tabellen entwerfen SET NULL. In allen Zeilen mit Fremdschlüsseln, die auf die gelöschte oder geänderte Zeile verweisen, werden die an der Fremdschlüsselbeziehung beteiligten Spalten auf den Wert NULL gesetzt. SET DEFAULT. In allen Zeilen mit Fremdschlüsseln, die auf die gelöschte oder geänderte Zeile verweisen, werden die an der Fremdschlüsselbeziehung beteiligten Spalten auf den Standardwert gesetzt. Dies kann wenn nicht anders festgelegt natürlich ebenfalls der Wert NULL sein. Um zum Beispiel dafür zu sorgen, dass beim Löschen eines Rezeptes alle zugehörigen Zutaten ebenfalls gelöscht werden, kann die Fremdschlüsselbeziehung zwischen den Tabellen Kochen.Rezept und Kochen.Zutat durch die Klausel ON DELETE CASCADE ergänzt werden.
4.8.8
Zusammenfassung zu Einschränkungen
Dieser Abschnitt enthält noch einmal eine Zusammenfassung der verfügbaren Einschränkungen, die Ihnen zur Sicherstellung der Datenintegrität zur Verfügung stehen. NOT NULL. Eine durch den Zusatz NOT NULL deklarierte Spalte darf nicht leer sein, lässt also keine NULL-Werte zu. CHECK. CHECK-Einschränkungen beschränken den Wertebereich für einzelne Spalten. Mit CHECK-Einschränkungen können auch Bedingungen deklariert werden, die eine Tabellenzeile erfüllen muss. UNIQUE. Durch eine UNIQUE-Einschränkung wird die Eindeutigkeit von Spaltenwerten innerhalb einer Tabelle erzwungen. PRIMARY KEY. Eine PRIMARY KEY-Einschränkung dient zur eindeutigen Identifikation einer Tabellenzeile. Mehrere Zeilen in einer Tabelle dürfen also nicht denselben Wert für den Primärschlüssel verwenden. FOREIGN KEY. Eine FOREIGN KEY-Einschränkung erzwingt eine Beziehung zwischen Tabellen.
4.8.9
Tabellen löschen
Eine Tabelle wird durch die Anweisung DROP TABLE gelöscht. Diese Anweisung erwartet als einzigen Parameter den Tabellennamen. Unsere Tabelle Kochen.Zutat könnten wir also so entfernen:
Sie dürfen keine Tabelle löschen, auf die noch durch einen Fremdschlüssel verwiesen wird. Bei einem entsprechenden Versuch erhalten Sie eine Fehlermeldung.
107
4 Transact SQL-Grundlagen
4.8.10 Datenbankdiagramme Der Objekt-Explorer bietet die fantastische Möglichkeit, grafische Präsentationen einer Datenbank zu erstellen. Hierzu existiert für jede Datenbank der Ordner Datenbankdiagrame. In diesem Ordner können Sie Diagramme Ihrer Datenbank erstellen und speichern. Wenn Sie das erste Mal auf den Ordner klicken, werden Sie zunächst aufgefordert, die erforderlichen Unterstützungsobjekte für die Verwaltung von Datenbankdiagrammen zu installieren (Abbildung 4.18).
Abbildung 4.18 Unterstützungsobjekte für Diagramme anlegen
Bestätigen Sie die Frage mit Ja, so haben Sie die Möglichkeit, anschließend Diagramme zu erstellen. Aus dem Kontextmenü des Ordners Datenbankdiagramme wählen Sie hierzu den Punkt Neues Datenbankdiagramm. Es öffnet sich ein Dialog, in dem Sie die im Diagramm darzustellenden Tabellen bzw. Sichten auswählen können. Die beiden Tabellen unser Datenbank OmasKochbuch sehen Sie in (Abbildung 4.19).
Abbildung 4.19 Tabellen zum Datenbankdiagramm hinzufügen
Wählen Sie für beide Tabellen Hinzufügen, und schließen Sie den Dialog, so ergibt sich das in Abbildung 4.20 gezeigte Datenbankdiagramm. Das Diagramm zeigt die Tabellen mit ihren jeweiligen Spalten, aber auch die Beziehungen zwischen den ausgewählten Tabellen. Für eine 1:n-Beziehung, wie in unserem Beispiel, wird die 1-Seite durch den Schlüssel dargestellt. Die n-Seite der Beziehung wird durch das Zeichen (Unendlich) gekennzeichnet. Im Datenbankdiagramm können Sie zwischen verschiedenen Darstellungsformen wählen. So ist es zum Beispiel möglich, neben den Spaltennamen auch die Datentypen der Spalten anzuzeigen. Datenbankdiagramme können (in den Systemtabellen
108
4.8 Tabellen entwerfen der Datenbank) gespeichert und natürlich auch wieder abgerufen werden. Außerdem dürfen pro Datenbank beliebig viele Diagramme angelegt werden.
Abbildung 4.20 Datenbankdiagramm für die Datenbank OmasKochbuch
Gerade für unbekannte Datenbanken sind Datenbankdiagramme eine enorme Hilfe bei der Einarbeitung in die Datenbankstruktur; hauptsächlich deshalb, weil auch die Beziehungen zwischen den Tabellen angezeigt werden. Erstellen Sie ruhig einmal ein Diagramm der Beispieldatenbank AdventureWorks mit allen Tabellen, und schauen Sie sich die Struktur dieser Datenbank an.
4.8.11 Temporäre Tabellen SQL Server erlaubt die Erstellung von temporären Tabellen. Solche Tabellen werden grundsätzlich in der Systemdatenbank tempdb angelegt. Bei temporären Tabellen wird zwischen lokalen und globalen Tabellen unterschieden. Eine lokale temporäre Tabelle wird einfach dadurch erzeugt, dass der Tabellenname mit einem #-Zeichen beginnt, also zum Beispiel so:
Die Tabelle #T1 ist lokal, weil sie nur für die Verbindung, in der sie angelegt wurde, existiert. Andere Verbindungen können auf diese Tabelle nicht zugreifen. Der Name einer globalen temporären Tabelle beginnt mit zwei ##-Zeichen, also beispielsweise so:
Die Tabelle ##T2 ist ebenfalls eine temporäre Tabelle. Diese Tabelle ist jedoch auch für andere Verbindungen sichtbar. Sowohl globale als auch lokale temporäre Tabellen werden beim Beenden der Verbindung, in der sie erzeugt wurden, automatisch gelöscht. Temporäre Tabellen werden oftmals in der T-SQL-Programmierung eingesetzt, um Daten aus unterschiedlichen Tabellen zusammenzusuchen, in einer temporären Tabelle zwischenzuspeichern und dann die Zeilen dieser Tabelle zurückzugeben.
109
4 Transact SQL-Grundlagen
4.9
INSERT Daten einfügen Zum Hinzufügen von Zeilen zu einer Tabelle wird die INSERT-Anweisung verwendet. Diese Anweisung hat eine einfache Syntax und ist daher entsprechend gradlinig zu benutzen:
Die einzelnen Bestandteile der Anweisung werden im Folgenden kurz erklärt: [into]. Die Angabe dieser Klausel ist in T-SQL optional. . Dies ist die Bezeichnung der Tabelle, in welche die Zeile eingefügt werden soll. <spaltenliste>. Die Spaltenliste enthält die durch Kommata getrennten Namen der Spalten, in die Werte eingefügt werden sollen. Hier müssen nicht alle Spalten der Tabelle angegeben werden. Nicht genannte Spalten werden mit dem Standardwert belegt. Die Angabe der Spaltenliste ist optional. Wird die Liste weggelassen, wird so verfahren, als wären alle Spalten in der bei CREATE TABLE angegebenen Reihenfolge angegeben worden. <spaltenwerte>. Die einzelnen Spaltenwerte für die neue Tabellenzeile, ebenfalls durch Kommata getrennt. Wenn eine <spaltenliste> angegeben wird, muss die Anzahl der Spaltenwerte mit der Anzahl der Spalten in der Spaltenliste übereinstimmen. Die folgenden vier INSERT-Anweisungen fügen Zeilen in die Tabelle Kochen.Rezept ein:
Interessant sind vor allem die Werte für die Spalte ZuletztGekochtAm vom Typ . Diese Werte werden in drei unterschiedlichen Formaten angegeben: 1. Als Zeichenkette in der Form 'JJJJMMTT'. Es erfolgt eine implizite Konvertierung in den Datentyp . Der Zeitanteil für den Wert wird in der Zeichenkette nicht angegeben und ist daher 00:00:00. 2. NULL. In die Spalte wird der Wert NULL eingetragen. 3. DEFAULT. In die Spalte wird der bei der Deklaration angegebene Standardwert eingetragen. In unserem Fall ist dieser Wert ebenfalls NULL. Da die Spalte RezeptId eine Identitätsspalte ist, sorgt SQL Server intern dafür, dass für jede Zeile ein Spaltenwert vergeben wird. Wenn Sie versuchen, explizit einen Wert für diese Spalte anzugeben, erhalten Sie eine Fehlermeldung. SQL Server gewährleistet auch,
110
4.10 SELECT Daten abfragen dass die Integritätsbedingungen eingehalten werden. Die folgenden INSERT-Anweisungen werden daher nicht ausgeführt, sondern liefern eine Fehlermeldung:
4.10
SELECT Daten abfragen Mit der SELECT-Anweisung werden Daten aus existierenden Tabellen abgefragt. SELECT ist sicher das mit Abstand am häufigsten verwendete und auch komplexeste SQL-Kommando. In diesem Abschnitt werden wir zunächst sehen, wie die Komplexität aus der SELECT-Anweisung herausgenommen werden kann, und uns das Kommando mit seinen Möglichkeiten und Tücken Schritt für Schritt erarbeiten. In Abschnitt 4.17 werden wir dann noch einmal auf SELECT zurückkommen und komplexere Abfragen entwerfen. Die grundlegende Struktur einer SELECT-Anweisung besteht aus den folgenden Teilen:
Die nachstehende Aufzählung erklärt die einzelnen Bestandteile: . In den meisten Fällen wird diese Liste einfach eine durch Komma getrennte Auflistung von Spaltennamen sein, die sich auf die in der FROM-Klausel angegebene Tabelle bezieht. Es ist aber auch möglich, Ausdrücke anzugeben, um zum Beispiel Berechnungen mit Tabellenspalten durchzuführen. [from ]. Über diese Klausel wird die Tabelle angegeben, die abgefragt werden soll. Die Klausel ist tatsächlich optional, SELECT kann also auch einfach dazu verwendet werden, Ausdrücke zu berechnen und auszugeben. []. Hier können weitere Optionen angegeben werden, die allesamt nicht obligatorisch sind. Auf einige der zur Verfügung stehenden Optionen werden wir im Verlaufe dieses Kapitels noch mehrfach zurückkommen. Eine SELECT-Anweisung muss also nicht zwingend eine Tabelle abfragen. Auch die folgende Anweisung, mit der einfach nur Ausdrck berechnet wrd, funktioniert:
Die Anweisung gibt tatsächlich diese Zeile zurück:
111
4 Transact SQL-Grundlagen Das zweite Beispiel zeigt eine SELECT-Anweisung, mit der die Spalten Bezeichnung und Bewertung aus der Tabelle Kochen.Rezept abgefragt werden:
Hier ist das Ergebnis:
Eine bequeme Möglichkeit ist die Angabe eines Sternchens * für die Spaltenliste als Abkürzung für alle Spalten:
Das Abfrageergebnis enthält alle Spalten der Tabelle:
Aliasnamen für Spalten Die für die Ausgabe verwendeten Spaltennamen können durch die Klausel AS angegeben werden. Schauen Sie sich noch einmal das obige Beispiel an, in dem nur Ausdrücke berechnet werden. In der Ausgabe wird für beide Spalten der Name (Kein Spaltenname) angegeben. Diese wenig aussagekräftigen Spaltenbezeichnungen können über die ASKlausel geändert werden:
Die Ausgabe sieht nun so aus:
In Aliasnamen können auch Zeichen verwendet werden, die ansonsten in Bezeichnern nicht erlaubt sind, also zum Beispiel Leerzeichen oder Zeichen, die Operatoren kennzeichnen. In so einem Fall muss der Name für den Alias aber in eckige Klammern oder doppelte Anführungszeichen eingeschlossen werden:
112
4.10 SELECT Daten abfragen Spaltennamen und Aliasnamen für Tabellen Ein voll qualifizierter Spaltenname in einer SELECT-Anweisung ist so aufgebaut:
Der Name besteht also aus dem Tabellennamen gefolgt vom eigentlichen Namen der Spalte, beide Namensteile werden durch ein Komma getrennt. Die obige Abfrage zum Ausgeben der Spalten Bezeichnung und Bewertung aus der Tabelle Kochen.Rezept hätte korrekt so formuliert werden müssen:
Der Zugriff auf Spaltennamen ohne Angabe des Tabellennamens ist immer dann problemlos möglich, wenn der Name der Spalte über alle an der Abfrage beteiligten Tabellen eindeutig ist. In so einem Fall erfolgt die Zuordnung des Spaltennamens zu einer Tabelle automatisch. In unserem Fall wird nur eine Tabelle verwendet, also sollte diese Automatik funktionieren. Sind an einer Abfrage mehrere Tabellen beteiligt, so müssen für Spaltennamen, die nicht eindeutig sind, also in mindestens zwei Tabellen vorkommen, die voll qualifizierten Bezeichnungen verwendet werden. In so einem Fall kann ein Aliasname für die Tabelle die Schreibarbeit erleichtern. Genau wie für Spalten werden Aliasnamen für Tabellen durch den Zusatz AS vergeben, der hier an den Tabellennamen angehängt wird. Ist für eine Tabelle ein Aliasname vergeben worden, kann dieser Aliasname anstelle des Tabellennamens in der Abfrage verwendet werden. Die obige SELECT-Anweisung könnte also auch so aussehen:
In T-SQL kann die Option AS auch weggelassen und stattdessen der Aliasname direkt an den Tabellennamen angehängt werden:
Für Abfragen mit nur einer Tabelle sind Aliasnamen für Tabellen zwar möglich, aber nicht erforderlich. Sortieren Das Ergebnis einer Abfrage kann über eine ORDER BY-Klausel sortiert werden. Diese Klausel muss am Ende der SELECT-Anweisung angegeben werden und eine Liste von Spaltennamen oder -nummern enthalten. Nach diesen Spalten wird die Ausgabe dann sortiert. Für jede in der Liste aufgezählte Spalte kann über die Option ASC oder DESC angegeben werden, ob die Sortierung aufsteigend (ASC) oder absteigend (DESC) erfolgen soll. Die Angabe dieser Option ist optional, der Standard ist ASC. Für eine Ausgabe aller Spal-
113
4 Transact SQL-Grundlagen ten der Tabelle Kochen.Rezept und eine aufsteigende Sortierung nach der Spalte Joule können Sie die ORDER BY-Klausel wie folgt angeben:
Soll die Sortierung absteigend erfolgen, geben Sie die Option DESC an:
Testdaten einfügen Bevor wir nun mit SELECT fortfahren, sollen zunächst noch einige Zeilen in die Tabelle Kochen.Zutat eingefügt werden:
Die Datenbanktabellen Kochen.Rezept und Kochen.Zutat haben nun die in Tabelle 4.16 und Tabelle 4.17
dargestellten Inhalte.
Tabelle 4.16 Inhalt der Tabelle Kochen.Rezept
114
RezeptId
Bezeichnung
Joule
Bewertung
ZuletztGekochtAm
1
Schweinebraten
1600
1
2005-04-05 00:00:00
2
Erbsensuppe
660
3
NULL
3
Kassler
1430
5
NULL
4
Matjes
1230
2
2005-02-16 00:00:00
4.10 SELECT Daten abfragen Tabelle 4.17 Inhalt der Tabelle Kochen.Zutat
ZutatId
RezeptId
Bezeichnung
Menge
Einheit
1
1
Schweinenacken
1.00
kg
2
1
Kartoffeln
650.00
g
3
1
Salz
1.00
Prise
4
2
Erbsen
1.30
kg
5
2
Speck
350.00
g
6
2
Wasser
1.40
l
7
4
Hering
1.30
kg
8
4
Mayonnaise
450.00
ml
9
4
Zwiebeln
4.00
Stück
10
4
Gurken
600.00
g
4.10.1 Filtern: Projektion und Selektion Die bisherigen Abfragen haben stets alle Zeilen der Tabelle Kochen.Rezept zurückgegeben. Dies wird in den meisten Fällen nicht erwünscht sein, in der Regel soll eine Abfrage nur ein eingeschränktes Ergebnis also eben nicht alle Zeilen liefern. Genauso wie aus der Menge der in einer Tabelle verfügbaren Spalten durch die Angabe von Spaltennamen nur einige ausgewählt werden können, ist es natürlich auch möglich, bestimmte Zeilen herauszufiltern und nur diese in das Ergebnis aufzunehmen. Die Spaltenauswahl also die vertikale Filterung wird auch als Projektion bezeichnet, die Zeilenauswahl ist die horizontale Filterung und heißt Selektion (Abbildung 4.21).
Abbildung 4.21 Projektion und Selektion
115
4 Transact SQL-Grundlagen Eine Projektion ist in der Regel relativ simpel. In vielen Fällen reicht die Auflistung der Spaltennamen. Allerdings können die einzelnen Ausgabespalten auch durch Ausdrücke berechnet werden. Dadurch ist es möglich, Spalten zusammenzufassen oder auch Spalten zum Ergebnis hinzuzufügen, die nicht in der Tabelle enthalten sind. Der Schwerpunkt in diesem und auch in allen folgenden Abschnitten ist das Entwerfen horizontaler Filter. Filterbedingungen können durch die Vielzahl der zur Verfügung stehenden Operatoren recht komplex werden. Wir werden zunächst mit einfachen Filterbedingungen beginnen und dann im weiteren Verlauf des Kapitels die Komplexität nach und nach erhöhen. WHERE Die Zeilenfilterbedingung wird in der WHERE-Klausel der SELECT-Anweisung als logischer Ausdruck angegeben. Für einen solchen Ausdruck werden vor allem die in Abschnitt 4.5, dort in Tabelle 4.10 bzw. Tabelle 4.12, genannten logischen und Vergleichsoperatoren benötigt. Der angegebene Filter-Ausdruck wird für jede Zeile der Tabelle ausgewertet. Nur diejenigen Zeilen, für welche die Auswertung den Wert WAHR ergibt, werden dann in das Abfrageergebnis aufgenommen. Um zum Beispiel aus der Tabelle Kochen.Rezept nur alle Zeilen zu erhalten, in denen die Spalte Joule kleiner als 1200 ist, verwenden Sie die WHERE-Klausel folgendermaßen:
Möchten Sie alle Zutaten für das Rezept mit der RezeptId 1 sehen, benutzen Sie die folgende SELECT-Anweisung:
Eine WHERE-Klausel kann mehrere Bedingungen, die durch logische Operatoren miteinander verknüpft werden, enthalten. Sollen alle Rezepte ausgegeben werden, die eine Bewertung von besser als 3 haben und vor dem 01.01.2006 das letzte Mal gekocht wurden, sieht die WHERE-Klausel so aus:
Hier ist das Ergebnis:
Die beiden Zeilen mit einem NULL-Wert in der Spalte ZuletztGekochtAm werden nicht in das Ergebnis aufgenommen. Warum dies so ist, erfahren Sie in Abschnitt 4.16.
116
4.10 SELECT Daten abfragen Übrigens: Ob beim Vergleich von Zeichenketten zwischen Groß- und Kleinschreibung unterschieden wird, hängt von der eingestellten Sortierreihenfolge der Datenbank ab. Normalerweise werden Sie eine Sortierreihenfolge benutzen, in der dies nicht der Fall ist. Wenn Sie bei der SQL Server-Installation nichts anderes angegeben haben, dann ist die standardmäßige Sortierreihenfolge so, dass nicht zwischen Groß- und Kleinschreibung unterschieden wird. Die gewählte Einstellung gilt auch für alle Objektnamen in der Datenbank. Bei einer Datenbank, die zwischen Groß- und Kleinschreibung unterscheidet, muss also zum Beispiel ein Tabellenname unter Beachtung der Groß- und Kleinschreibung angegeben werden. In allen folgenden Beispielen wird davon ausgegangen, dass nicht zwischen Groß- und Kleinschreibung unterschieden wird. IS NULL Auf NULL-Werte in Spalten muss explizit getestet werden. Die sonst üblichen Vergleichsoperatoren können Sie für so einen Test nicht verwenden. Hier hilft nur der IS NULL-Operator, der WAHR zurückgibt, wenn ein Ausdruck NULL ist:
Um alle Rezepte zu erhalten, die noch nie gekocht wurden, verwenden Sie IS NULL in der WHERE-Klausel:
Gerichte, die bereits einmal gekocht wurden, enthalten ein Datum in der Spalte ZuletztGekochtAm. Diese Zeilen werden von der folgenden Abfrage zurückgegeben:
BETWEEN Der Operator BETWEEN ermöglicht Bereichsabfragen. Mit BETWEEN wird geprüft, ob der Wert eines Ausdrucks zwischen einer unteren und oberen Grenze inklusive der Grenzwerte liegt. Der BETWEEN-Operator hat diese Syntax:
Um alle Rezepte mit einer Bewertung zwischen 2 und 4 zu erhalten, können Sie diese Abfrage angeben:
BETWEEN kann immer durch die Vergleichsoperatoren >= und <= ersetzt werden. Die folgende Abfrage liefert dasselbe Ergebnis wie die obige SELECT-Anweisung mit BETWEEN:
117
4 Transact SQL-Grundlagen IN Der IN-Operator testet einen Ausdruck auf eine Zugehörigkeit zu einer Liste von Werten. Die generelle Syntax des IN-Operators ist wie folgt:
Eine Abfrage, die alle Gerichte zurückgibt, die eine Bewertung von 1, 3, oder 5 haben, sieht unter Verwendung des IN-Operators so aus:
Ein Ausdruck unter Verwendung des IN-Operators kann immer durch OR verknüpfte Tests auf Gleichheit ersetzt werden. Die obige Abfrage hätte auch so formuliert werden können:
Der IN-Operator ist insbesondere dann nützlich, wenn die Werte der Liste nicht explizit angegeben, sondern aus einer Abfrage erstellt werden. Hierzu kann in den runden Klammern auch eine SELECT-Anweisung stehen, die dann genau eine Spalte zurückgeben muss (aber natürlich beliebig viele Zeilen enthalten kann). Die folgende Abfrage gibt die (sortierten) Bezeichnungen aller Zutaten für Gerichte, die noch nie gekocht wurden, aus:
Abfragen dieser Art sind ein häufiger Einsatzfall für den IN-Operator. EXISTS Der EXISTS-Operator testet auf die Existenz von Zeilen in einer Tabelle. Dies ist die Syntax für den Operator EXISTS:
Die Unterabfrage ist eine SELECT-Anweisung, bei der es egal ist, welche Spalten sie enthält. Sobald die Unterabfrage mindestens eine Zeile zurückgibt, liefert der Operator den Wert WAHR. Die folgende Abfrage gibt alle Rezepte zurück, für die keine Zutaten existieren:
Die Abfrage enthält eine äußere und eine innere SELECT-Anweisung, die miteinander verbunden sind. Die Verbindung zwischen beiden Abfragen wird durch die WHEREKlausel in der inneren Anweisung hergestellt:
118
4.10 SELECT Daten abfragen Die innere SELECT-Anweisung wird für jede Zeile des von der äußeren Anweisung zurückgegebenen Ergebnisses ausgeführt. Dabei bekommt die innere Anweisung den Wert der Spalte Kochen.Rezept.RezeptId sozusagen als Parameter übergeben und filtert dann nur Zeilen für diese eine übergebene RezeptId heraus. Sobald auch nur eine Zeile gefunden wurde, liefert der EXISTS-Operator den Wert WAHR, der durch den NOT-Operator allerdings noch invertiert wird. Die in der äußeren Abfrage angegebene WHERE-Klausel wird also nur dann WAHR, wenn für eine RezeptId keine Zeilen in der Tabelle Kochen.Zutat existieren. Somit liefert die Abfrage nur Gerichte, für die keine Zutaten vorhanden sind. LIKE Der LIKE-Operator ermöglicht einen Mustervergleich von Zeichenketten. Bei einer Übereinstimmung gibt der Vergleich WAHR zurück. Die generelle Syntax für den LIKE Operator ist wie folgt:
In einem Vergleichsmuster können verschiedene Platzhalter verwendet werden, die auch miteinander kombiniert werden dürfen. Folgende Platzhalter stehen zur Verfügung: _. Ein Unterstrich steht für genau ein beliebiges Zeichen innerhalb des Musters. Beispiele Muster
Erklärung
WAHR für
'Me_er'
Zeichenketten, die mit Me beginnen und auf er enden. An dritter Stelle kann ein beliebiges Zeichen stehen.
Meier, Meyer
M__er
Zeichenketten, die mit M beginnen und auf er enden. An dritter und vierter Stelle kann jeweils ein beliebiges Zeichen stehen.
Meier, Meyer, Mayer, Maier, Mixer
%. Das Prozentzeichen ist ein Platzhalter für beliebig viele Zeichen. Beispiele Muster
Erklärung
WAHR für
'M%'
Alle Zeichenketten, die mit M beginnen.
Max, Mann, Monika
'M%r'
Alle Zeichenketten, die mit M beginnen und mit r enden.
Meier, Müller, Münchner, Marsianer
[]. Ein Platzhalter für ein Zeichen. Die eckigen Klammern enthalten eine Aufzählung erlaubter Zeichen. Durch einen Bindestrich kann auch ein Bereich angegeben werden. Beispiele Muster
Erklärung
WAHR für
[aeiou]%
Alle Zeichenketten, die mit einem Vokal beginnen.
Angelika, Otto, Uwe
[0-9][0-9][0-9]
Eine Zeichenkette aus genau drei Ziffern.
000, 123, 999
119
4 Transact SQL-Grundlagen Muster
Erklärung
WAHR für
M[ae][iy]er
Alle Zeichenketten, die mit M beginnen und mit er aufhören. An zweiter Stelle steht entweder ein a oder e, an dritter Stelle ein i oder y.
Maier, Mayer, Meier, Meyer
[a-e]%r
Alle Zeichenketten, die mit A bis E beginnen und mit r enden.
Anger, Bootsverleiher Eckensteher,
M[ae][iy]%
Alle Zeichenketten, die mit M beginnen und an zweiter Stelle entweder ein a oder e sowie an dritter Stelle entweder ein i oder y enthalten.
Maier, Meier, Meise, Meyenburg
[^]. Steht für ein beliebiges Zeichen, das sich nicht im angegebenen Bereich bzw. in der Aufzählung befindet. Beispiele Muster
Erklärung
WAHR für
[^xy]%
Alle Zeichenketten, die nicht mit einem x oder y beginnen.
Frank, Max
[^0-9]%
Alle Zeichenketten, die nicht mit einer Ziffer beginnen
Eins, Zwei, Drei
Die folgende Abfrage liefert nur Zeilen aus der Tabelle Kochen.Rezept, in deren Spalte Bezeichnung die Zeichenkette schwein enthalten ist:
Damit würden zum Beispiel solche Übereinstimmungen gefunden: Schweinebraten, Gegrillter Schweinebauch, Tischwein. TOP Die Angabe von TOP(n) vor der Spaltenliste bewirkt, dass nur die ersten n Zeilen aus dem Ergebnis ausgegeben werden. TOP ist insbesondere nützlich bei der gleichzeitigen Verwendung der ORDER BY-Klausel, weil so die Möglichkeit besteht, Ranglisten auszugeben. Diese Abfrage gibt die Spalten Bezeichnung und ZuletztGekochtAm zurück, wobei die Ausgabe absteigend sortiert wird:
Als Ergebnis bekommen wir dies:
120
4.10 SELECT Daten abfragen Wenn nun die Klausel TOP(1) vor der eigentlichen Spaltenliste hinzugefügt wird, so enthält das Ergebnis nur noch die erste Zeile der obigen Tabelle und gibt somit den Termin aus, an dem zuletzt etwas gekocht wurde:
Das Ergebnis ist wie erwartet:
In der TOP-Klausel kann auch ein Prozentwert angegeben werden. In diesem Fall wird aus dem Ergebnis nur die angegebene Prozentzahl von Zeilen ausgegeben. Schauen Sie sich hierzu bitte die folgende Anweisung an:
Die Abfrage sortiert die Zeilen der Tabelle Kochen.Rezept und gibt anschließend die Hälfte der Zeilen zurück. TABLESAMPLE Durch die Angabe der Klausel TABLESAMPLE werden aus dem Gesamtergebnis einer Abfrage zufällig Daten ausgewählt. Diese Klausel ist besonders nützlich für statistische Berechnungen. Wenn derartige Berechnungen mit sehr großen Tabellen durchgeführt werden sollen, ist es oft ausreichend, nur eine Stichprobe für die Berechnung zu verwenden. Eine solche Stichprobe kann durch die TABLESAMPLE-Klausel erzeugt werden, die nach der FROM-Klausel angegeben wird:
Die obige Anweisung soll 20 Prozent der in der Tabelle enthaltenen Zeilen zurückgeben. Allerdings funktioniert dies nicht wie erwartet, stattdessen werden keine Zeilen zurückgegeben. Ursache ist, dass das Ergebnis durch TABLESAMPLE nicht zeilenweise ausgewertet wird. TABLESAMPLE wählt nach einem Zufallsprinzip ganze Datenseiten aus und nimmt dann alle Zeilen der Seite in das Ergebnis auf. Diese Verfahrensweise ist sehr performant, aber eben vor allem bei kleinen Tabellen auch etwas ungenau. In unserem Fall ist die Tabelle so klein, dass sie komplett in nur eine Datenseite passt. Je nach Angabe der Prozentzahl wird diese Datenseite in das Ergebnis aufgenommen oder nicht. Da alle Zeilen der Tabelle in dieser einen Seite liegen, enthält das Ergebnis also entweder alle oder keine Zeilen der Tabelle. Erwarten Sie also bitte nicht, dass TABLESAMPLE immer exakt die angeforderte Prozentzahl von Zeilen liefert. Wenn Sie dies benötigen, werden Sie auf TOP zurückgreifen müssen.
121
4 Transact SQL-Grundlagen DISTINCT Durch die Angabe von DISTINCT vor der Spaltenliste werden doppelte Zeilen aus dem Ergebnis entfernt. Schauen Sie sich hierzu die folgenden beiden SELECT-Anweisungen an, die nur den Wert der Spalte Einheit aus der Tabelle Kochen.Zutat ausgeben:
Hier sind die Ergebnisse der beiden SELECT-Anweisungen:
Wie Sie sehen, werden durch die DISTINCT-Option die doppelten Zeilen entfernt.
4.11
Informationen ausgeben mit PRINT Die Anweisung PRINT erwartet eine Zeichenkette als einzigen Parameter. Diese Zeichenkette wird durch PRINT einfach in den Meldungsbereich der Ausgabe geschrieben. Dies ist der Bereich, in dem auch Fehlermeldungen ausgegeben werden. Dadurch ermöglicht PRINT die Ausgabe von zusätzlichen Informationen, die nicht im eigentlichen Ausgabebereich der Ausgabe erscheinen sollen. Der Aufruf kann zum Beispiel so aussehen:
4.12
Ausführen von gespeicherten Prozeduren SQL Server bietet die Möglichkeit, T-SQL-Code in Form von gespeicherten Prozeduren in einer Datenbank zu speichern. Jede erzeugte Datenbank enthält bereits eine Reihe von gespeicherten Systemprozeduren, von denen einige in Kapitel 6 näher betrachtet werden. An dieser Stelle soll zunächst nur demonstriert werden, wie gespeicherte Prozeduren aufgerufen also ausgeführt werden. Dafür gibt es prinzipiell zwei Möglichkeiten:
122
4.13 UPDATE Daten ändern Die Anweisung EXECUTE (oder abgekürzt: EXEC) erlaubt den Aufruf einer gespeicherten Prozedur an einer beliebigen Stelle im T-SQL-Skript. Hierzu wird nach EXECUTE der Name der Prozedur angegeben. Falls die Prozedur Parameter erwartet, so werden diese durch Komma getrennt aufgezählt. Ein Prozeduraufruf kann also zum Beispiel so aussehen:
ist also der Name der Prozedur, die Zeichenkette ein Pa-
rameter, der an diese Prozedur übergeben wird. Eine Prozedur kann auch einfach durch ihren Namen und die Parameterliste aufgerufen werden, also ohne die Verwendung von EXEC(UTE), wenn der Prozeduraufruf die erste Anweisung in einem T-SQL-Stapel ist. In so einem Fall können Sie also auch einfach schreiben:
In Kapitel 7 werden wir eigene gespeicherte Prozeduren entwickeln. Dort werden wir auch noch einmal etwas genauer auf die Übergabe von Parametern an gespeicherte Prozeduren eingehen.
4.13
UPDATE Daten ändern Um bestehende Zeilen in einer Tabelle zu ändern, verwenden Sie die UPDATE-Anweisung. Diese Anweisung hat die folgende Syntax:
Die folgende Aufzählung erläutert die einzelnen Bestandteile der UPDATE-Anweisung: . Dies ist der Name der zu aktualisierenden Tabelle. set <spalte> = . Über die SET-Klausel werden die neuen Werte für die angegebenen Spalten durch einzelne Zuweisungen, die jeweils durch ein Komma getrennt sind, festgelegt. Hierbei kann der Spaltenname sowohl auf der linken als auch auf der rechten Seite der Zuweisung auftreten. Dadurch ist es möglich, einen existierenden Spaltenwert zur Berechnung eines neuen Spaltenwertes zu verwenden. Die folgende SET-Klausel zum Beispiel wird den Wert der Spalte Listenpreis um 3% erhöhen:
where . Über die WHERE-Klausel wird eine Zeilenfilterbedingung angegeben, welche die Zeilen für die Aktualisierung selektiert. Die Angabe der WHEREKlausel ist optional. Wird sie weggelassen, so wird das UPDATE für alle Zeilen der
123
4 Transact SQL-Grundlagen Tabelle durchgeführt. Für die Konstruktion der Filterbedingung existieren die im vorhergehenden Abschnitt aufgezeigten Möglichkeiten. SQL Server stellt sicher, dass bei UPDATE-Operationen keine Integritätsbedingungen verletzt werden. So werden zum Beispiel Wertbereichseinschränkungen, die beispielsweise durch CHECK-Optionen angegeben wurden, in jedem Fall eingehalten. Auch die referenzielle Integrität wird selbstverständlich nicht verletzt. Wenn Sie versuchen, eine Spalte zu aktualisieren, die an einer Fremdschlüsselbeziehung beteiligt ist, so wird die Aktualisierung nur funktionieren, wenn die Fremdschlüsselbeziehung auch nach der Aktualisierung gültig ist. Wie verfahren wird, ist auch abhängig von der Option ON UPDATE, die bei der Erzeugung oder Änderung der FOREIGN KEY-Einschränkung angegeben wurde. Soll die Menge für die Zutat mit der Id 1 von derzeit 1 kg auf 1200 g geändert werden, so ist diese UPDATE-Anweisung erforderlich:
Die folgende UPDATE-Anweisung erzeugt eine Fehlermeldung:
Durch eine CHECK-Einschränkung wurde angegeben, dass für die Spalte Bewertung nur Werte zwischen 1 und 6 zugelassen sind. Der Wert 0 ist also für die Spalte Bewertung unzulässig. Die erzeugte Fehlermeldung macht dies auch unmissverständlich klar:
Auch die Verletzung der referenziellen Integrität ist nicht möglich, die folgende UPDATEAnweisung liefert daher ebenfalls eine Fehlermeldung:
Da es in der Tabelle Kochen.Rezept keine Zeile gibt, in welcher der Wert der Spalte RezeptId 22 ist, wird die obige UPDATE-Anweisung nicht ausgeführt und erzeugt lediglich die folgende Fehlermeldung:
Es ist jedoch möglich, den Wert der Spalte Kochen.Zutat.RezeptId so zu ändern, dass die Zutat einem anderen Rezept zugeordnet wird. Um alle Zutaten für das Rezept mit der Id 3 dem Rezept mit der Id 2 zuzuordnen, können wir schreiben:
124
4.14 DELETE Daten löschen
Durch die UPDATE-Anweisung wird die referenzielle Integrität nicht verletzt, da in der Tabelle Kochen.Rezept eine Zeile mit der RezeptId 2 existiert.
4.14
DELETE Daten löschen Die DELETE-Anweisung ermöglicht das Löschen von Zeilen aus einer Tabelle. Das Kommando hat die folgende Syntax:
In T-SQL ist die Angabe des Schlüsselwortes FROM optional. Um die zu löschenden Zeilen auszuwählen, kann über die WHERE-Klausel wiederum ein Zeilenfilter angegeben werden. Wie schon bei UPDATE, so ist auch hier die Angabe der WHERE-Klausel optional. Wenn Sie diese Klausel weglassen (oder vergessen!), werden einfach alle Zeilen der Tabelle gelöscht. SQL Server wird selbstverständlich auch für DELETE-Anweisungen die Datenintegrität gewährleisten. Es dürfen also keine Zeilen gelöscht werden, auf die noch durch eine Fremdschlüsselbeziehung verwiesen wird, es sei denn, dass für diese Fremdschlüsselbeziehung über die Option ON DELETE ein entsprechendes Verhalten konfiguriert wurde. Schauen Sie sich hierzu bitte das folgende Listing 4.1 an: Listing 4.1 ON DELETE CASCADE
125
4 Transact SQL-Grundlagen
Die zweite DELETE-Anweisung funktioniert, weil durch die Option ON DELETE CASCADE angegeben wurde, dass alle abhängigen Zeilen in der Tabelle Kochen.Zutat automatisch gelöscht werden sollen, wenn eine Zeile in der Primärtabelle Kochen.Rezept gelöscht wird. Eine Tabelle leeren Um alle Zeilen einer Tabelle zu entfernen, kann auch die Anweisung TRUNCATE TABLE verwendet werden. Die folgenden beiden T-SQL-Kommandos haben letztlich dieselbe Auswirkung:
TRUNCATE hat den Vorteil, dass es weniger Ressourcen benötigt und um einiges schneller arbeitet als das zeilenweise Löschen mit DELETE. Außerdem werden bei TRUNCATE alle von der Tabelle belegten Datenseiten freigegeben, die Tabelle wird also auch physikalisch verkleinert. Dies ist bei DELETE nicht der Fall. TRUNCTATE TABLE kann allerdings nur für Tabellen durchgeführt werden, auf die keine FOREIGN KEY-Einschränkung verweist. Dies ist unabhängig davon, ob tatsächlich Zeilen existieren, durch deren Löschen die referenzielle Integrität verletzt werden würde. Allein die Existenz der FOREIGN KEY-Einschränkung ist dafür ausreichend, dass TRUNCATE TABLE in so einem Fall nicht funktioniert. Folglich kann die Tabelle Kochen.Rezept nicht mit TRUNCATE abgeschnitten werden, auch nicht wenn für den Fremdschlüssel eine ON DELETE CASCADE-Option angegeben wurde.
4.15
SQL-Funktionen SQL Server stellt eine Vielzahl von integrierten Funktionen bereit, die Sie in Ausdrücken bzw. Abfragen verwenden können. In diesem Abschnitt soll lediglich eine Übersicht über die wichtigsten dieser Funktionen gegeben werden. Eine vollständige Beschreibung aller verfügbaren Funktionen würde ein eigenes Buch füllen, daher finden Sie an dieser Stelle eine Auswahl der am häufigsten benötigten Funktionen. Außerdem werden einige der integrierten Funktionen in späteren Kapiteln benötigt und dann auch dort näher erklärt.
126
4.15 SQL-Funktionen In diesem Abschnitt finden Sie eine Auswahl von Funktionen aus den Bereichen Systemfunktionen, Konfigurationsfunktionen, numerische Funktionen, Zeichenkettenfunktionen, Konvertierungsfunktionen, Datums- und Zeitfunktionen sowie Rangfolgefunktionen.
4.15.1 Systemfunktionen SQL Server-Systemfunktionen liefern Informationen zu einer SQL Server-Instanz. Die am häufigsten benutzten Systemfunktionen finden Sie in den folgenden Abschnitten. HOST_NAME Syntax.
Erklärung. Die Funktion HOST_NAME() gibt den Namen des Computers zurück. Beispiele.
IDENT_CURRENT Syntax.
Erklärung. Die Funktion liefert den letzten generierten Identitätswert für die IDENTITY-Spalte einer Tabelle. Beispiele.
SERVERPROPERTY Syntax.
Erklärung. Die Funktion fragt Eigenschaften der SQL Server-Instanz ab. Hierbei muss die Eigenschaft, die abgefragt werden soll, als Zeichenkette an die Funktion übergeben werden. Für eine vollständige Aufstellung aller verfügbaren Eigenschaften möchte ich Sie an die Online-Dokumentation von SQL Server verweisen. Hier nur einige Beispiele: Eigenschaft
Rückgabewert
Edition
Der Name der SQL Server Edition, also zum Beispiel: Enterprise Edition Express Edition Developer Edition
InstanceName
Der Name der SQL Server-Instanz.
127
4 Transact SQL-Grundlagen Eigenschaft
Rückgabewert
MachineName
Der Name des Computers, auf dem SQL Server läuft.
ProductVersion
Die Versionsnummer von SQL Server in der Form: major.minor.build
ProductLevel
Gibt Informationen zum installierten Servicepack zurück. Mögliche Rückgabewerte sind: RTM Für die Originalversion von SQL Server SPn Für ein installiertes Servicepack mit der Nummer n
Beispiele.
SESSION_USER Syntax.
Erklärung. Die Funktion gibt den Namen des angemeldeten Datenbankbenutzers zurück. Beispiele.
SYSTEM_USER Syntax.
Erklärung. Gibt den Namen der SQL Server-Anmeldung zurück. Beispiele.
128
4.15 SQL-Funktionen
4.15.2 Konfigurationsfunktionen Konfigurationsfunktionen liefern Informationen zu SQL Server-Konfigurationsoptionen. Dieser Funktionstyp ist daran zu erkennen, dass der Name mit einem doppelten @@ beginnt. Die folgenden Konfigurationsfunktionen sind besonders wichtig: @@DATEFIRST Syntax.
Erklärung. @@DATEFIRST zeigt den ersten Tag in der Woche an. Für eine deutsche SQL Server-Version ist dieser Wert 1 für Montag. In einer US-englischen Version ist der Wert 7 für Sonntag. Für eine Verbindung zu SQL Server kann der Wert mit der SET-Anweisung SET DATEFIRST angepasst werden (siehe unten). Beispiele.
@@SERVERNAME Syntax.
Erklärung. Gibt den Namen der SQL Server-Instanz in der Form servername\instanzname zurück. Wenn eine Standardinstanz von SQL Server installiert ist, wird der Name des Computers zurückgegeben. Beispiele.
@@SPID Syntax.
Erklärung. Gibt die ID der aktuellen Verbindung zurück. Jede Verbindung zu SQL Server bekommt eine eindeutige Verbindungs-ID, mit der zum Beispiel bestimmt werden kann, welche Blockierungen diese Verbindung auslöst. Beispiele.
129
4 Transact SQL-Grundlagen @@VERSION Syntax. @@version Erklärung. Gibt Versionsinformationen zu SQL Server zurück. Hierzu gehören Prozessortyp, Erstellungsdatum und Informationen zum Betriebssystem. Beispiele.
4.15.3 Metadatenfunktionen Metadatenfunktionen geben Informationen zu Datenbanken und Datenbankobjekten zurück. Zu den interessanteren Metadatenfunktionen zählen die folgenden: DATABASEPROPERTY Syntax.
Erklärung. Die Funktion gibt den Wert einer Datenbankeigenschaft zurück. Die Namen der Datenbank und der abzufragenden Eigenschaft müssen als Zeichenkette an die Funktion übergeben werden. Die Liste der zur Verfügung stehenden Eigenschaften ist lang, in der folgenden Tabelle finden Sie nur einige Beispiele: Eigenschaft
Beschreibung
IsOffline
Gibt 1 zurück, wenn die Datenbank offline ist, ansonsten 0.
IsReadOnly
Gibt 1 zurück, wenn die Datenbank schreibgeschützt ist, ansonsten 0.
IsSingleUser
Gibt 1 zurück, wenn die Datenbank im Einzelbenutzermodus ist, ansonsten 0.
Beispiele.
DATABASEPROPERTYEX Syntax.
Erklärung. Über diese Funktion können erweiterte Eigenschaften einer Datenbank abgefragt werden. DATABASEPROPERTYEX erwartet ebenfalls den Datenbanknamen und den Namen der abzufragenden Eigenschaft als Parameter. Die folgende Tabelle enthält eine Auswahl der zur Verfügung stehenden Eigenschaften:
130
4.15 SQL-Funktionen Eigenschaft
Erklärung
Collation
Die Bezeichnung der Standardsortierung der Datenbank
IsAutoClose
1, wenn die Datenbank im Auto-Close-Modus ist, ansonsten 0. Eine Datenbank im Auto-Close-Modus wird automatisch heruntergefahren, wenn keine Verbindungen mehr zu ihr existieren.
Recovery
Liefert das Wiederherstellungsmodell für die Datenbank. Der Wert dieser Eigenschaft kann FULL, BULK_LOGGED oder SIMPLE sein. Näheres hierzu erfahren Sie in Kapitel 6.
Status
Liefert den momentanen Status der Datenbank. Dieser Status kann zum Beispiel ONLINE, OFFLINE oder RESTORING sein.
Beispiele.
DB_ID Syntax.
Erklärung. Für jede Datenbank wird intern eine ID vergeben, die oftmals benötigt wird, um Tabellen oder Sichten des Systemkatalogs abzufragen. Mit der Funktion DB_ID kann diese ID ermittelt werden. Hierzu muss der Funktion der Datenbankname übergeben werden. Der Datenbankname kann auch weggelassen werden, in diesem Fall wird der Name der aktuellen Datenbank eingesetzt. Beispiele.
DB_NAME Syntax.
Erklärung. In den SQL Server-Systemkatalogen wird stets nur die ID der Datenbank gespeichert. Um den einer solchen ID zugeordneten Datenbanknamen zu ermitteln, kann die Funktion DB_NAME verwendet werden. DB_NAME ist die Umkehrung zu DB_ID. Die Funktion gibt zu einer bekannten Datenbank-ID den Namen der Datenbank zurück. Die Übergabe der ID ist optional. Wenn keine ID übergeben wird, so wird die aktuelle Datenbank-ID eingesetzt.
131
4 Transact SQL-Grundlagen Beispiele.
OBJECT_NAME Syntax.
Erklärung. Auch Datenbankobjekte werden mit einer ID also einer eindeutigen Kennung versehen. Wenn Sie Systemkataloge abfragen oder sich Informationen zu Blockierungen bzw. aktuellen Aktivitäten im Aktivitätsmonitor ansehen, wird in der Regel nur die ID der beteiligten Objekte, wie zum Beispiel Tabellen, abgezeigt. Über die Funktion OBJECT_NAME kann aus einer solchen ID der Name des Objektes ermittelt werden. Die übergebene ID wird dabei immer im Kontext der aktuellen Datenbank ausgewertet. Beispiele.
4.15.4 Numerische Funktionen In diesem Abschnitt werden die in SQL Server integrierten mathematischen Funktionen vorgestellt. Winkelfunktionen SQL Server unterstützt die in der folgenden Tabelle aufgeführten Winkelfunktionen:
132
Funktion
Syntax
Erklärung
ACOS
x
Berechnet den Arcuskosinus im Bogenmaß. Der Wert für x muss zwischen 1 und 1 liegen, ansonsten wird ein Fehler erzeugt.
ASIN
x
Berechnet den Arcussinus im Bogenmaß. Der Wert für x muss zwischen 1 und 1 liegen, ansonsten wird ein Fehler erzeugt.
ATAN
x
Berechnet den Arcustangens im Bogenmaß.
COS
x
Berechnet den Kosinus für den Winkel x. Der Winkel muss im Bogenmaß übergeben werden.
COT
x
Berechnet den Kotangens des im Bogenmaß übergebenen Winkels.
DEGREES
x
Konvertiert den im Bogenmaß übergebenen Winkel in einen Grad-Wert. Zu beachten ist, dass der Typ des Rückgabewertes dem Typ des im Parameter übergebenen Wertes entspricht. So werden zum Beispiel die beiden folgenden Aufrufe unterschiedliche Ergebnisse liefern:
4.15 SQL-Funktionen Funktion
Syntax
Erklärung
Ursache ist, dass der erste Aufruf einen Wert vom Typ int zurückliefert, der zweite einen Wert vom Typ float. Diese Rückgabetypen werden jeweils aus dem Typ des Parameters abgeleitet, der int bzw. float ist. PI
Berechnet den Wert von Pi (3,1415926
)
SIN
x
Berechnet den Sinus des im Bogenmaß übergebenen Winkels.
TAN
x
Berechnet den Tangens des im Bogenmaß übergebenen Winkels.
RADIANS
x
Konvertiert den als Grad übergebenen Winkel in das Bogenmaß. Auch hier wird wie bei DEGREE der Wert des Rückgabetyps aus dem Typ des übergebenen Parameters abgeleitet.
Beispiele für den Aufruf der Winkelfunktionen.
ABS Syntax.
Erklärung. Berechnet den Absolutwert des übergebenen Ausdrucks. Zu beachten ist, dass der Rückgabetyp des berechneten Wertes aus dem Typ des übergebenen Parameters abgeleitet wird. Beispiele.
CEILING Syntax.
Erklärung. Gibt die kleinste ganze Zahl zurück, die größer oder gleich dem übergebenen Ausdruck ist. Beispiele.
133
4 Transact SQL-Grundlagen EXP Syntax.
Erklärung. Berechnet den Wert ex für den übergebenen Ausdruck x. Beispiele.
FLOOR Syntax.
Erklärung. Gibt die größte ganze Zahl zurück, die kleiner oder gleich dem übergebenem Ausdruck ist. Beispiele.
LOG Syntax.
Erklärung. Berechnet den natürlichen Logarithmus, also den Logarithmus zur Basis e. Der übergebene Parameter muss größer als 0 sein. Beispiele.
LOG10 Syntax.
Erklärung. Berechnet für den übergebenen Ausdruck den Logarithmus zur Basis 10. Der übergebene Ausdruck muss einen Wert größer 0 liefern. Beispiele.
POWER Syntax.
134
4.15 SQL-Funktionen Erklärung. Berechnet für die übergebenen Parameter die Potenz, also den Wert xy. Beispiele.
RAND Syntax.
Erklärung. Liefert einen Zufallswert im Bereich 0 bis 1. Über den Parameter x kann ein Ausgangswert festgelegt werden, der für die Verbindung zu SQL Server gilt. Wenn ein solcher Ausgangswert angegeben wird, so erzeugen alle nachfolgenden Aufrufe von RAND innerhalb der Verbindung immer dieselben Werte (siehe Beispiele). Der Parameter x ist vom Typ int. Beispiele.
ROUND Syntax.
Erklärung. Rundet den Wert x auf die angegebene Länge oder schneidet den Wert ab. Ob gerundet oder abgeschnitten wird, hängt vom Parameter typ ab. Ist dieser Parameter 0 (dies ist auch der Standardwert), so erfolgt eine Rundung, wenn typ ungleich 0 ist, so wird nicht gerundet, sondern abgeschnitten. Beispiele. Die folgende Tabelle zeigt einige Beispiele für den Aufruf von ROUND und das zurückgelieferte Ergebnis. Aufruf
Ergebnis
100,500
100,500
100,400
1360,000
1400,000
1000,000
0,000
135
4 Transact SQL-Grundlagen SQRT Syntax.
Erklärung. Berechnet die Quadratwurzel von x. Der Wert x darf nicht kleiner als 0 sein. Beispiele.
SQUARE Syntax.
Erklärung. Berechtet das Quadrat von x, also den Wert x * x. Beispiele.
4.15.5 Funktionen zur Zeichenkettenbearbeitung SQL Server bietet die im folgenden Abschnitt aufgeführten Funktionen zur Zeichenkettenbearbeitung. Mit diesen Funktionen können zum Beispiel Zeichenketten durchsucht, umgewandelt oder Teilbereiche in Zeichenketten durch angegebenen Text ersetzt werden. CHAR Syntax.
Erklärung. Die CHAR-Funktion wandelt einen ASCII-Code in ein Zeichen um. Dies ist insbesondere nützlich, wenn Steuerzeichen in die Ausgabe eingefügt werden sollen. Hierzu kann die CHAR-Funktion mit diesen Parametern verwendet werden: Tabluator
char(9)
Zeilenvorschub
char(10)
Wagenrücklauf
char(13)
Beispiele.
136
4.15 SQL-Funktionen CHARINDEX Syntax.
Erklärung. Gibt die Position von zeichenkette1 innerhalb von zeichenkette2 zurück. Wird zeichenkette1 nicht gefunden, so gibt die Funktion 0 zurück. Über den Parameter start kann angegeben werden, an welcher Position die Suche beginnen soll. Der Standard ist 1 für den Anfang von zeichenkette2. Beispiele.
DIFFERENCE Syntax.
Erklärung. Gibt eine ganze Zahl zurück, die den phonetischen Unterschied zwischen zwei Zeichenketten angibt. Diese Zahl liegt im Bereich von 0 bis 4, wobei 0 angibt, dass keine Übereinstimmung besteht und 4 für die größtmögliche Ähnlichkeit steht. Mit der Funktion kann zum Beispiel in Namenslisten nach ähnlich lautenden Namen gesucht werden, was durch einen Mustervergleich mit LIKE nicht immer möglich ist. Beispiele.
LEFT Syntax.
Erklärung. Schneidet eine zeichenkette nach der angegebenen Anzahl von zeichen ab, gibt also nur den linken Teil der zeichenkette zurück. Beispiele.
137
4 Transact SQL-Grundlagen LEN Syntax.
Erklärung. Gibt die Anzahl von Zeichen in einer zeichenkette zurück. Beispiele.
LOWER Syntax.
Erklärung. Gibt eine Zeichenkette zurück, in der alle Zeichen aus der übergebenen zeichenkette in Kleinbuchstaben konvertiert wurden. Beispiele.
LTRIM Syntax.
Erklärung. Gibt eine Zeichenkette zurück, in der führende Leerzeichen in der übergebenen zeichenkette entfernt wurden. Beispiele.
PATINDEX Syntax.
Erklärung. Sucht ein muster innerhalb einer zeichenkette und gibt die Position des ersten Auftretens dieses Musters innerhalb der Zeichenkette aus. Wird keine Übereinstimmung festgestellt, gibt die Funktion 0 zurück. Für das Muster können die beim LIKE-Operator angegebenen Platzhalter verwendet werden. Beispiele. Angenommen, die zu durchsuchende Zeichenkette ist:
138
4.15 SQL-Funktionen Die folgende Tabelle enthält einige ausgewählte Beispiele für Muster und das Ergebnis des Funktionsaufrufs: Muster
Erklärung
Ergebnis
'%woch%'
Sucht nach der Zeichenkette woch.
8
'%[^aeiou]%'
Sucht nach dem ersten Auftreten eines Zeichens, das kein Vokal ist.
2
'%[def]%'
Sucht nach dem Buchstaben d, e oder f.
15
'%[0-9][0-9]:[0-9][0-9]%'
Sucht nach einer Zeichenkette im Format 00:00, die also eine Uhrzeit repräsentiert.
32
'%x%'
Sucht nach dem Auftreten des Buchstabens x.
0
In Abfragen kann die Funktion PATINDEX zum Beispiel so verwendet werden:
REPLACE Syntax.
Erklärung. Gibt eine Zeichenkette zurück, in der alle Vorkommen von zeichenkette2 in zeichenkette1 durch zeichenkette3 ersetzt wurden. Beispiele.
REPLICATE Syntax.
Erklärung. Gibt eine Zeichenkette zurück, die aus anzahl Wiederholungen der übergebenen zeichenkette besteht.
139
4 Transact SQL-Grundlagen Beispiele.
REVERSE Syntax.
Erklärung. Gibt die übergebene Zeichenkette umgekehrt also von hinten nach vorne aus. Beispiele.
RIGHT Syntax.
Erklärung. Gibt den anzahl Zeichen langen rechten Teil der übergebenen zeichenkette zurück. Beispiele.
RTRIM Syntax.
Erklärung. Gibt eine Zeichenfolge zurück, aus der am Ende stehende Leerzeichen entfernt wurden. Beispiele.
SOUNDEX Syntax.
140
4.15 SQL-Funktionen Erklärung. SOUNDEX gibt eine Zeichenkette zurück, die aus vier Zeichen besteht. Diese Zeichenkette ist eine Kodierung der übergebenen zeichenkette und kann zum Vergleich der phonetischen Ähnlichkeit verwendet werden. Beispiele.
SPACE Syntax. space(anzahl) Erklärung. Gibt eine Zeichenfolge zurück, die aus der übergebenen Anzahl von Leerzeichen besteht. Beispiele.
STUFF Syntax.
Erklärung. Die Funktion gibt eine Zeichenkette zurück. In dieser Zeichenkette wurden anzahl Zeichen aus zeichenkette1 beginnend ab der Position start entfernt und durch zeichenkette2 ersetzt. Beispiele.
SUBSTRING Syntax.
Erklärung. Liest einen Teil der im Parameter ausdruck übergebenen Daten. Es werden anzahl Zeichen, beginnend an der Stelle start, gelesen. Im ausdruck können hierbei nicht nur Zeichenketten, sondern Werte der Datentypen text, image, binary oder varbinary stehen.
141
4 Transact SQL-Grundlagen Beispiele.
UPPER Syntax.
Erklärung. Gibt eine Zeichenkette zurück, in der alle Zeichen der übergebenen zeichenkette in Großbuchstaben konvertiert wurden. Beispiele.
4.15.6 Konvertierungsfunktionen Konvertierungsfunktionen erlauben eine explizite Typkonvertierung, also die Überführung eines Wertes aus einem Datentyp in einen anderen. Hierdurch können zum Beispiel numerische Werte in Zeichenketten oder Zeichenketten in Datums- und Zeitwerte umgewandelt werden. SQL Server stellt hierfür die folgenden Funktionen zur Verfügung: CONVERT Syntax.
Erklärung. Die Funktion CONVERT nimmt eine explizite Typkonvertierung des übergebenen Ausdrucks in den im ersten Parameter angegebenen Datentyp vor und gibt den konvertierten Wert zurück. Falls die Typkonvertierung nicht durchgeführt werden kann, liefert CONVERT eine Fehlermeldung zurück. Der Parameter format wird vor allem für die Umwandlung von datetime-Werten in Zeichenketten und von Zeichenketten in datetime-Werte benötigt. Er bestimmt jeweils das Format der Zeichenkette. Soll eine Zeichenkette in einen datetime-Wert umgewandelt werden, so wird über den Parameter format das Format des Parameters ausdruck festgelegt. Anderenfalls also wenn ein datetime-Wert im Parameter ausdruck übergeben wird, der in eine Zeichenkette umgewandelt werden soll bestimmt format das Aussehen der zurückgelieferten Zeichenkette. Der Parameter legt durch einen integer-Wert fest, wie die Zeichenkette aussehen soll. Hierbei wird bei Werten über 100 das Datum vierstellig also mit Jahrhundertangabe interpretiert. Für Werte kleiner 100 wird das Jahr nur zweistellig angenommen. Die folgende Tabelle zeigt die wichtigsten Formate am Beispiel des Datums:
142
4.15 SQL-Funktionen Ohne
Mit
Format
Bemerkung
Jahrhundert 1
101
12/19/2004
US-Format
2
102
2004.12.19
ANSI-Format
3
103
19/12/2004
Britisch und Französisch
4
104
19.12.2004
Deutsches Format
5
105
19-12-2004
Italienisches Format
6
106
19 Dez 2004
7
107
Dez 10, 2004
8
108
14:24:12
Zeitanteil ohne Millisekunden
9 oder 109
Dez 19 2004 2:24:12
Datum und Uhrzeit im 12-Stunden-Format
10
110
12-19-2004
US-Format
11
111
2004/12/19
Japanisches Format
12
112
20041219
ANSI-Fomat
14:24:12.323
Zeitanteil im 24-Stunden-Format mit Millisekunden
14 oder114
Beispiele.
CAST Syntax.
Erklärung. CAST dient ebenfalls zur expliziten Konvertierung eines Wertes in einen anderen Datentyp. Der an die Funktion übergebene ausdruck wird als Wert vom Datentyp datentyp dargestellt, der ebenfalls an die Funktion CAST übergeben wird. Im Unterschied zu CONVERT erlaubt die Funktion CAST jedoch nicht die Angabe eines Parameters für die Formatierung. Beispiele.
143
4 Transact SQL-Grundlagen STR Syntax.
Erklärung. Die Funktion STR konvertiert einen numerischen Ausdruck in eine Zeichenkette. In dieser Zeichenkette wird der konvertierte Ausdruck mit der im Parameter dezimalstellen angegebenen Anzahl von Dezimalstellen dargestellt und ist so lang, wie im Parameter länge angefordert. Diese beiden Parameter sind optional. Der Standardwert für länge ist 10, für dezimalstellen ist der Standardwert 0. Wenn Dezimalstellen abgeschnitten werden, so erfolgt eine Rundung. Beispiele.
4.15.7 Datums- und Zeitfunktionen Die SQL Server-Datums- und -Zeitfunktionen ermöglichen das Rechnen mit Werten vom Typ datetime und das Zerlegen dieser Werte in die einzelnen Bestandteile, wie zum Beispiel Jahr, Monat oder Tag. In Funktionen, die Berechnungen mit datetime-Werten durchführen, muss hierbei oftmals ein Intervall angegeben werden. Dieses Intervall wird in der Parameterliste der im folgenden Teil erklärten Funktionen stets mit intervall bezeichnet. Erwartet wird dort stets eine Konstante, welche die in der folgenden Tabelle 4.18 aufgeführten Werte annehmen kann. Tabelle 4.18 Intervalle für das Rechnen mit datetime-Werten
144
Intervall
Synonyme
Erklärung
year
yy, yyyy
Steht für ein gesamtes Kalenderjahr
quarter
qq, q
Steht für ein Quartal
month
mm, m
Steht für einen Monat
dayofyear
dy, y
Steht für den Tag im Jahr, also den Tag ab Jahresbeginn
day
dd, d
Steht für einen Tag
week
wk, ww
Steht für eine Woche
weekday
dw, w
Steht für den Tag in der Woche, also die Nummer des Wochentags
hour
hh
Steht für eine Stunde
minute
mi, n
Steht für eine Minute
second
ss, s
Steht für eine Sekunde
millisecond
ms
Steht für eine Millisekunde
4.15 SQL-Funktionen DATEADD Syntax.
Erklärung. Gibt einen datetime-Wert zurück, in dem zum übergebenen datetimewert die angegebene anzahl von intervallen addiert wurde. Für den Parameter intervall muss eine der in Tabelle 4.18 genannten Konstanten angegeben werden. Beispiele.
DATEDIFF Syntax.
Erklärung. Gibt die Anzahl von intervallen zurück, die zwischen den beiden datetime-Werten start und ende liegt. Für intervall kann dabei einer der in Tabelle 4.18 genannten Konstanten eingesetzt werden. Der Rückgabewert ist immer ganzzahlig, ein Wert wie zum Beispiel 0,68 Jahre wird abgerundet auf 0 (Jahre). Beispiele.
DATENAME Syntax.
Erklärung. Die Funktion gibt eine Zeichenkette zurück, welche die Bezeichnung für den im Parameter intervall angegebenen Datumsteil des übergebenen datetime-Wertes enthält. Echte Bezeichnungen werden hierbei nur für die Intervall-Konstanten month und weekday ausgegeben, alle andere Werte für intervall, wie zum Beispiel year, month oder hour, geben die entsprechende Zahl zurück. Beispiele.
145
4 Transact SQL-Grundlagen DATEPART Syntax.
Erklärung. Gibt einen integer-Wert zurück, der den über intervall angegebenen Teil des übergebenen datetimewertes enthält. Beispiele.
DAY Syntax.
Erklärung. Gibt einen integer-Wert zurück, der den Tag des übergebenen datetime-Wertes enthält. ist ein Synonym für . Beispiele.
GETDATE Syntax.
Erklärung. Die Funktion gibt das aktuelle SQL Server-Datum inklusive Zeit zurück. Beispiele.
146
4.15 SQL-Funktionen
MONTH Syntax.
Erklärung. Gibt einen integer-Wert zurück, der den Monat des übergebenen datetime-Wertes enthält. ist ein Synonym für . Beispiele.
YEAR Syntax.
Erklärung. Gibt einen integer-Wert zurück, der das Jahr des übergebenen datetime-Wertes enthält. ist ein Synonym für . Beispiele.
Oftmals werden Sie in Ihren Datenbanken eine Kalendertabelle benötigen, in der einfach nur Datumswerte gespeichert sind. Eine solche Tabelle ist in vielen Abfragen nützlich. Mit den vorgestellten Datums- und Zeitfunktionen kann eine solche Tabelle so erstellt werden:
147
4 Transact SQL-Grundlagen
Die Tabelle Kalender wird hier im Schema Extras angelegt. Diese Tabelle enthält eigentlich nur eine einzige echte Spalte mit Namen Datum. In dieser Spalte wird pro Zeile ein Datumswert gespeichert. Alle anderen Spalten der Tabelle sind berechnete Spalten, welche Datumsanteile mit den gerade vorgestellten Datums- und Zeitfunktionen ermitteln. Die folgenden INSERT-Anweisungen fügen zur Tabelle Extras.Kalender einige Zeilen hinzu:
Um alle Zeilen und Spalten der Tabelle zu erhalten, kann diese Abfrage verwendet werden:
Abbildung 4.22 zeigt das Ergebnis der obigen Abfrage.
Abbildung 4.22 Inhalt der Tabelle Extras.Kalender
Wir werden die Tabelle Extras.Kalender im weiteren Verlauf dieses bzw. nachfolgender Kapitel noch modifizieren und dabei auch sehen, wie diese Tabelle mit Werten gefüllt werden kann.
148
4.15 SQL-Funktionen
4.15.8 Sonstige Funktionen und Operatoren In diesem Abschnitt werden weitere Funktionen aufgeführt, die in keine der bisherigen Kategorien eingeordnet werden konnten. CASE Syntax.
Erklärung. Die CASE-Funktion wertet eine Reihe von Bedingungen aus. Trifft eine Bedingung zu, so wird der für diese Bedingung angegebene Ausdruck berechnet und dessen Wert zurückgegeben. Eine CASE-Funktion kann in zwei unterschiedlichen Formaten angegeben werden: Einfache CASE-Funktion. Hier wird der Wert eines Ausdrucks nacheinander in unterschiedlichen WHEN-Anweisungen mit verschiedenen diskreten Werten verglichen. Ist der ausdruck mit einem in der WHEN-Anweisung aufgeführten wert identisch, so wird das in der THEN-Klausel zu dieser WHEN-Anweisung angegebene ergebnis zurückgegeben. Komplexe CASE-Funktion. In der komplexen CASE-Funktion enthält jede WHENAnweisung eine eigene Bedingung, die durch einen booleschen Ausdruck beschrieben wird. Liefert dieser Ausdruck den Wert WAHR, so wird das im zugehörigen THEN-Zweig angegebene Ergebnis zurückgegeben. In beiden Formaten kann jeweils eine optionale ELSE-Klausel enthalten sein. Der in dieser Klausel angegebene Wert wird zurückgegeben, wenn keine der vorherigen Bedingungen bzw. Vergleiche zutreffend ist. Beispiele.
149
4 Transact SQL-Grundlagen
@@ERROR Syntax.
Erklärung. Die Funktion @@ERROR gibt die Fehlernummer für die zuletzt ausgeführte T-SQL-Anweisung zurück. Wenn diese Anweisung fehlerfrei war, wird der Wert 0 zurückgegeben. Zu beachten ist, dass wirklich jede T-SQL-Anweisung den von dieser Funktion zurückgelieferten Wert beeinflusst. Dadurch muss diese Funktion unmittelbar nach einer T-SQL-Anweisung aufgerufen werden, um die Fehlernummer zu erhalten. Die Fehlernummern von SQL Server sind mit dem zugehörigen Text in der Systemsicht enthalten. Diese Systemsicht können Sie abfragen, um den Fehlertext zu erhalten (siehe Beispiel). SQL Server bietet die Möglichkeit der strukturierten Ausnahmebehandlung mit TRY/CATCH-Konstrukten. Sie sollten dieser Möglichkeit den Vorzug gegenüber @@ERROR geben, da sie wesentlich leistungsfähiger ist. Mehr hierzu erfahren Sie in Kapitel 7. Beispiele.
ISDATE Syntax.
Erklärung. Die Funktion ISDATE gibt 1 zurück, wenn der angegebene ausdruck ein gültiges Datum zurückliefert, ansonsten 0. Diese Funktion ist besonders für Konvertierungen nützlich, da vor der Konvertierung in einen datetime-Wert geprüft werden
150
4.15 SQL-Funktionen kann, ob die Konvertierung überhaupt möglich ist und nicht eventuell einen Fehler zurückliefert. Beispiele.
Hier ist das Ergebnis der obigen Abfrage:
ISNUMERIC Syntax.
Erklärung. Die Funktion ISNUMERIC gibt 1 zurück, wenn der übergebene ausdruck als numerischer Wert interpretiert werden kann, ansonsten 0. Auch diese Funktion ist für Typumwandlungen nützlich, da vor der Umwandlung in einen numerischen Datentyp geprüft werden kann, ob dies überhaupt möglich ist. Beispiele.
NEWID Syntax.
Erklärung. Jeder Aufruf erzeugt einen eindeutigen Wert vom Typ uniqueidentifier. Beispiele.
151
4 Transact SQL-Grundlagen
@@ROWCOUNT Syntax.
Erklärung. Die Funktion @@ROWCOUNT gibt die Anzahl der Zeilen zurück, die durch die vorhergehende T-SQL-Anweisung geändert oder abgefragt wurde. Wie bereits bei der @@ERROR-Funktion wird auch @@ROWCOUNT durch jede T-SQLAnweisung beeinflusst, muss also immer unmittelbar nach der entsprechenden T-SQLAnweisung abgefragt werden. Beispiele.
WAITFOR Syntax.
Erklärung. WAITFOR blockiert die Ausführung eines T-SQL-Stapels so lange, bis entweder der über die Option TIME angegebene zeitpunkt erreicht oder die über die Option DELAY angegebene zeitspanne verstrichen ist. Sowohl der Zeitpunkt als auch die Zeitspanne werden hierbei in der Form 'HH:MM:SS' angegeben. Dadurch kann eine WAITFOR-Blockierung maximal 24 Stunden dauern. Beispiele.
152
4.16 Die Beispieldatenbank AdventureWorks
4.16
Die Beispieldatenbank AdventureWorks Für die Beispiele in den folgenden Kapiteln wird die einfache Datenbank OmasKochbuch oftmals nicht ausreichend sein. Wir werden in vielen Fällen auf die Beispieldatenbank AdventureWorks zurückgreifen, allerdings benötigen wir für unsere Beispiele nicht die komplette Datenbank, sondern lediglich eine Handvoll von Tabellen und Sichten, die in Tabelle 4.19 und Abbildung 4.23 erklärt bzw. dargestellt sind. Tabelle 4.19 Verwendete Tabellen aus der AdventureWorks-Datenbank Tabelle/Sicht
Erklärung
Sales.vSalesPerson
Eine Sicht mit der Liste der Vertriebsmitarbeiter.
HumanResources.vEmployee
Eine Sicht mit der Liste aller Angestellten.
Sales.SalesOrderHeader
Eine Tabelle mit allen Bestellungen. Über die Spalte SalesPersonID existiert ein Fremdschlüssel zur Sicht vSalesPerson. Die Spalte kann allerdings auch NULL sein, es ist also möglich, Bestellungen ohne Vertriebsmitarbeiter einzufügen.
Sales.SalesOrderDetail
Eine Tabelle mit allen Bestellpositionen. Jede Position ist über die Spalte SalesOrderID einer Bestellung zugeordnet.
Production.ProductCategory
Eine Tabelle mit den Produktkategorien.
Production.ProductSubCategory
Eine Tabelle mit Produkt-Unterkategorien. Jede Unterkategorie kann einer Kategorie über die Spalte ProductCategoryID zugeordnet werden. Diese Zuordnung ist allerdings nicht erforderlich, d.h., es kann auch Unterkategorien ohne Kategorie geben.
Production.Product
Eine Tabelle mit der Liste aller Produkte. Ein Produkt kann über die Spalte ProductSubCategoryID in eine Unterkategorie eingeordnet werden, dies ist aber nicht zwingend notwendig.
Abbildung 4.23 auf der nächsten Seite zeigt ein Datenbankdiagramm, in dem Sie sehen können, wie die oben aufgeführten Tabellen zueinander in Beziehung stehen. Diese Tabellen enthalten übrigens mehr Spalten als im Diagramm dargestellt sind, für eine bessere Übersicht präsentiert das Diagramm nur die in unseren Beispielen benötigten Spalten. Wir werden die Spalte Sales.SalesOrderHeader.TotalDue im Folgenden als Bestellwert interpretieren, auch wenn dies sicher nicht ganz korrekt ist. Für die verwendeten Beispiele ist dies jedoch unerheblich.
153
4 Transact SQL-Grundlagen
Abbildung 4.23 Auszug aus der Datenbank AdventureWorks
4.17
Sichten SQL Server erlaubt die Erstellung von Sichten, die gespeicherte Abfragen enthalten. Eine Sicht ist ein Datenbankobjekt, das wie alle anderen Datenbankobjekte auch unter einem Namen in einem Schema der Datenbank abgelegt wird. Hinter diesem Namen verbirgt sich im Falle einer Sicht einfach eine Abfrage. Eine Sicht wird durch die Anweisung CREATE VIEW erzeugt, welche die folgende generelle Syntax aufweist:
Sichten sind vor allem nützlich, wenn komplexe Abfragen immer wieder verwendet werden müssen. In einem solchen Fall können Sie durch die Definition entsprechender Sichten für diese komplexen Abfragen sozusagen virtuelle Tabellen einrichten. Eine Sicht ist dann letztlich nichts anderes als eine vordefinierte Anordnung von Projektion und Selektion, so wie bereits in Abbildung 4.21 dargestellt. Um die Daten von Kochrezepten zu erhalten, die weniger als 1200 Joule schwer sind, können Sie die folgende Abfrage verwenden:
154
4.17 Sichten
Falls Sie in Ihrem T-SQL Code wieder und wieder solche leichten Gerichte benötigen, so wird die obige Abfrage an den entsprechenden Stellen im Code stets erneut angegeben werden müssen. In diesem Fall kann sich die Definition einer Sicht lohnen, die so angelegt wird:
Die Sicht Kochen.vLeichteGerichte definiert eine virtuelle Tabelle, die in SELECT- und eventuell auch in UPDATE-, INSERT- und DELETE-Anweisungen verwendet werden kann. Wichtig ist hierbei, dass Sie verstehen, dass in der Sicht keine Daten gespeichert werden. Durch CREATE VIEW wird also nicht etwa eine Kopie der entsprechenden Tabellenzeilen angelegt, sondern tatsächlich nur die enthaltene Abfrage gespeichert. Beim Zugriff auf die Sicht durch eine der genannten Anweisungen wird dann diese Abfrage ausgeführt. Im Objekt-Explorer werden die in einer Datenbank existierenden Sichten im Zweig Sichten angezeigt. Hier erscheint auch die gerade angelegte Sicht Kochen.vLeichteGerichte:
Eine Sicht kann in einer Abfrage wie eine Tabelle verwendet werden. Die folgende Abfrage gibt alle Zeilen der Sicht zurück:
Die obige SELECT-Anweisung bewirkt letztlich die Ausführung der in der Sicht hinterlegten Abfrage, also:
Das Ergebnis ist dann wie erwartet:
155
4 Transact SQL-Grundlagen Sichten ermöglichen es Ihnen, eine Datenzugriffsschicht für bestimmte Anwender zu erstellen. So ist es zum Beispiel denkbar, dass Sie für Benutzer, die Berichte entwerfen sollen, ein spezielles Schema in Ihrer Datenbank anlegen und in diesem Schema Sichten ablegen, welche die Daten für Ihre Berichtsentwickler bereitstellen. Die Entwickler von Berichten müssen dadurch nicht die komplette Datenbank kennen und begreifen, sondern können auf eine Datenbasis zurückgreifen, die sie verstehen und für die sie auch entsprechende Zugriffsberechtigungen besitzen.
4.17.1 Ändern von Sichten Stellen Sie sich vor, dass die Bedingung für die Einstufung eines Gerichtes in die Kategorie Leicht sich geändert hat. Von nun an sollen alle Gerichte, deren Energiegehalt kleiner als 1400 Joule ist, als Leichte Gerichte angesehen werden. Ohne eine Sicht für die Abfrage von leichten Gerichten müssten Sie nun alle Stellen in Ihrem T-SQL-Code entsprechend anpassen und die Zeilenfilterbedingung von
ändern in:
Bei der Verwendung einer Sicht können Sie einfach nur Definition der Sicht anpassen ein weiterer Vorteil bei der Verwendung von Sichten. Zum Verändern einer bestehenden Sicht verwenden Sie die Anweisung ALTER VIEW wie folgt:
Für das Ändern unserer Zeilenfilterbedingung können wir also schreiben:
Ab sofort wird dann an allen Stellen, an denen auf die Sicht Kochen.vLeichteGerichte zugegriffen wird, die geänderte Definition der Sicht verwendet.
4.17.2 Aktualisierung von Daten Die Interpretation einer Sicht als virtuelle Tabelle wirft natürlich die Frage auf, inwiefern es möglich ist, die Daten dieser Tabelle auch zu verändern. Wie verhalten sich UPDATE-, INSERT- und DELETE-Anweisungen in Verbindung mit Sichten? Unter Beachtung einiger Einschränkungen ist es tatsächlich möglich, Datenänderungsoperationen auf Sichten durchzuführen. Zunächst einmal müssen alle von einer Sicht zurückgegebenen Zeilen eindeutig den dahinter liegenden Tabellen zugeordnet werden können. In einer Sicht, die nur eine Tabelle enthält, ist dies in der Regel kein Problem. Eine Sicht ge-
156
4.17 Sichten stattet jedoch auch die Verwendung komplexerer Abfragen über mehrere Tabellen. In einem solchen Fall ist eine Aktualisierung einer Sicht in aller Regel nicht oder nur unter sehr eingeschränkten Bedingungen möglich. Sollen Daten zu einer Sicht hinzugefügt also INSERT-Anweisungen ausgeführt werden, dann muss die Sicht alle Spalten der zugrunde liegenden Tabelle(n) zurückliefern, für die kein Standardwert oder keine NULL-Zulässigkeit angegeben wurde. Unsere Sicht Kochen.vLeichteGerichte erfüllt diese Bedingung derzeit nicht, da die Spalte Bewertung in der Ausgabe fehlt. Für diese Spalte, die als NOT NULL deklariert wurde, ist kein Standardwert angegeben worden. Um INSERT-Anweisungen auf diese Sicht anwenden zu können, wollen wir zunächst diese Spalte zur Sicht hinzufügen:
Danach kann über INSERT eine Zeile hinzugefügt werden:
Wenn wir nun die von der Sicht zurückgelieferten Daten anschauen, so ist die gerade über INSERT hinzugefügte Zeile darin nicht enthalten. Natürlich, wir haben ja auch kein leichtes Gericht hinzugefügt, die in der Sicht enthaltene WHERE-Klausel verhindert, dass die hinzugefügte Zeile angezeigt wird. Dieses Verhalten kann wenn man eine Sicht als virtuelle Tabelle ansieht einigermaßen verwirrend sein. Schließlich erwartet man, dass zu einer Tabelle über INSERT hinzugefügte Zeilen über SELECT auch wieder abgefragt werden können. In unserem Fall ist dies nicht so, die neue Zeile kann über eine SELECTAnweisung auf der Sicht nicht abgefragt werden. Es ist jedoch möglich, die Definition einer Sicht so zu gestalten, dass diese Art Anomalie verhindert wird. WITH CHECK OPTION Eine Sicht kann unter Verwendung der Klausel WITH CHECK OPTION deklariert werden. Eine so erstellte Sicht wird auch für Aktualisierungen also INSERT- und UPDATEAnweisungen die in der WHERE-Klausel angegebene Zeilenfilterbedingung überprüfen. Aktualisierungen werden nur durchgeführt, wenn die Zeilenfilterbedingung für die aktualisierten Daten den Wert WAHR ergibt. Dadurch ist sichergestellt, dass nach einer Aktualisierung die geänderten Daten auch über die Sicht abgefragt werden können. Die WITH CHECK OPTION geben Sie bei der Deklaration der Sicht so an:
Wenn nun versucht wird, eine Zeile hinzuzufügen oder zu ändern, welche die WHEREBedingung verletzt, so wird die Aktualisierung nicht durchgeführt. Stattdessen gibt es nur eine Fehlermeldung, die so aussieht:
157
4 Transact SQL-Grundlagen
4.18
Daten einfügen mit SELECT Die SELECT-Anweisung kann auch zum Einfügen von Daten in Tabellen verwendet werden. Hierbei ist es möglich, Daten an eine vorhandene Tabelle anzuhängen oder aber sogar automatisch eine neue Tabelle zu erzeugen.
4.18.1 INSERT und SELECT Die Spaltenwerte einer INSERT-Anweisung können auch durch eine SELECT-Anweisung ermittelt werden. Dadurch besteht die Möglichkeit, mit einer einzigen INSERT-Anweisung, mehrere Zeilen in eine Tabelle einzufügen. Hierzu verwenden Sie INSERT in Kombination mit SELECT in der folgenden allgemeinen Form:
Die Zieltabelle muss bereits existieren. Die Angabe der ist hier optional. Wenn Sie diese Liste weglassen, so müssen Sie für alle in der Zieltabelle existierenden Spalten in der korrekten Reihenfolge Werte in der angeben. Mit korrekter Reihenfolge ist die physikalische Anordnung der Spalten in der Tabelle gemeint. Dies ist die Reihenfolge, in der die Spalten auch im Objekt-Explorer angezeigt werden. Das folgende Skript erzeugt eine Tabelle Kochen.LeichteGerichte und fügt alle Zeilen der Sicht Kochen.vLeichteGerichte in diese Tabelle ein:
Selbstverständlich ist es möglich, für die in der obigen Syntax als bezeichnete Tabelle auch komplexe JOINs oder aber auch Sichten zu verwenden so wie gerade im Beispiel geschehen.
4.18.2 SELECT INTO Über INSERT in Verbindung mit SELECT haben Sie also die Möglichkeit, Zeilen zu bereits existierenden Tabellen hinzuzufügen. Was aber nun, wenn die Zieltabelle noch gar
158
4.19 Null nicht existiert? In diesem Fall müssten Sie die Tabelle zuvor erzeugen, zum Beispiel durch die Anweisung CREATE TABLE. Sie können die SELECT-Anweisung aber auch mit der INTO-Klausel zusammen verwenden, um die Zieltabelle automatisch zu erzeugen. Hierfür sieht die Syntax so aus:
Die Zieltabelle darf in diesem Fall noch nicht existieren, ansonsten wird eine Fehlermeldung erzeugt. Zu beachten ist auch, dass die Struktur der Zieltabelle nicht unbedingt identisch mit der Struktur der Quelltabelle sein wird. Dies gilt insbesondere für die Einschränkungen dieser Tabelle. Das ist sicher leicht einzusehen, da auch hier wieder die Quelltabelle eine komplexe Abfrage und nicht lediglich eine einzelne Tabelle darstellen kann. Außerdem ist es natürlich möglich, in der Spaltenliste Ausdrücke zu verwenden, wodurch Einschränkungen auf Spaltenebene in der Zieltabelle nicht bestimmt werden können oder willkürlich festgelegt werden müssten. Die IDENTITY-Eigenschaft einer Quellspalte wird in die Zieltabelle übernommen, wenn die Spalte in der Spaltenliste direkt (also ohne Verwendung eines Ausdrucks) angegeben wird. Falls Sie Ausdrücke verwenden, um Spaltenwerte zu berechnen, so muss für jede Spalte ein Aliasname angegeben werden, der den Spaltennamen in der Zieltabelle bestimmt. Die folgende Anweisung verwendet die weiter oben eingeführte Tabelle Extras.Kalender. Es wird eine Tabelle J2004 erzeugt, in der alle Zeilen mit dem Wert Jahr=2004 aus der Tabelle Extras.Kalender enthalten sind:
Wenn Sie sich die erstellte Tabelle J2004 einmal ansehen, so werden Sie feststellen, dass dort keine berechneten Spalten vorhanden sind, so wie dies in der Quelltabelle der Fall war. SELECT INTO erzeugt alle Spalten als echte Datenwerte. Die NOT NULLEinschränkung wurde allerdings für alle Spalten übernommen.
4.19
Null Der Wert NULL nimmt im Typsystem von SQL eine Sonderstellung ein und führt regelmäßig zu Verwirrungen. Aus diesem Grund sollen die zu beachtenden Besonderheiten und bei der Verwendung von NULL auftretenden Probleme hier noch einmal explizit herausgestellt werden. Variablen jedes beliebigen SQL-Datentyps können prinzipiell auch leer blieben, also einfach keinen Wert enthalten. In diesem Fall wird der Wert NULL benutzt, der anzeigt, dass für eine entsprechende Variable oder auch Spalte in einer Tabellenzeile kein passender oder bekannter Wert existiert. Probleme bei der Verarbeitung von solchen unbekannten Werten resultieren daraus, dass in SQL dadurch keine binäre, sondern eine dreiwertige Logik implementiert werden muss. Hinzu kommt, dass ein NULL-Wert letztlich aus zwei Gründen existieren kann: Der Wert ist unbekannt oder unpassend. Diese beiden Problemquellen sollen im folgenden Abschnitt etwas genauer untersucht werden.
159
4 Transact SQL-Grundlagen
4.19.1 Dreiwertige Logik Die bekannte boolesche Algebra, die auf einer zweiwertigen Logik basiert, ist leider in SQL nicht gültig. Dadurch, dass der Wert NULL als unbekannt angesehen wird und auch Variablen mit diesem Wert in die Auswertung von logischen Ausdrücken einbezogen werden müssen, muss in SQL mit einer dreiwertigen Logik gearbeitet werden. Das Ergebnis eines logischen Ausdrucks, also z. B. einer Vergleichsoperation, kann in SQL die Werte wahr, falsch oder unbekannt4 annehmen. Tabelle 4.20 und Tabelle 4.21 verdeutlichen dies anhand der Operatoren NOT, OR und AND. Tabelle 4.20 Dreiwertige Logik des NOT-Operators Ausdruck
NOT-Ausdruck
wahr
falsch
falsch
wahr
unbekannt
unbekannt
Tabelle 4.21 Dreiwertige Logik von OR und AND wahr
falsch
unbekannt
OR
wahr
wahr
wahr
AND
wahr
falsch
unbekannt
OR
wahr
falsch
unbekannt
AND
falsch
falsch
falsch
OR
wahr
unbekannt
unbekannt
AND
unbekannt
falsch
unbekannt
wahr
falsch
unbekannt
So ist z. B. das Ergebnis eines Ausdrucks NULL AND wahr unbekannt, da ja für den Fall, dass der NULL-Wert wahr wäre, der Ausdruck ebenfalls wahr werden würde und für den Fall, dass der Wert NULL mit einem Ausdruck ersetzt werden würde, der falsch zurückgibt, der Gesamtausdruck falsch ergeben würde. Aus diesem Grund kann über das Ergebnis des Ausdrucks nichts ausgesagt werden es ist also unbestimmt. Ein großes Problem ergibt sich aus der nicht konsistenten Behandlung von NULL innerhalb der SQL-Sprachelemente. Die Verwendung von unbekannten logischen Resultaten und NULLs kann dadurch wirklich sehr verwirrend werden. In Abfragefiltern, also WHERE-, ON- und HAVING-Klauseln, wird der Wert unbekannt als falsch betrachtet. 4
160
Wenn in diesem Zusammenhang von unbekannt die Rede ist, so bezieht sich dies stets auf den SQL Server. Es ist natürlich möglich, dass ein Wert dem Benutzer sehr wohl bekannt ist, dass dieser Wert aber wegen bestehender Einschränkungen des Wertebereichs nicht in eine Spalte eingetragen werden kann. In diesem Fall würde die Spalte einen NULL-Wert enthalten, obwohl der ursprüngliche Wert durchaus bekannt war. Für die Ausdrucksverarbeitung im SQL Server, also aus Server-Sicht, ist der Wert dann aber im weiteren Verlauf einfach unbekannt.
4.19 Null Dies bedeutet, dass Zeilen, für die eine Filterbedingung den Wert NULL zurückliefert, aus dem Ergebnis ausgeschlossen werden. Schauen Sie sich einmal die folgende Abfrage an:
Diese Abfrage kann leicht so umformuliert werden:
Das WHERE-Prädikat besteht aus drei Vergleichsoperationen, die durch OR miteinander verknüpft sind. Die ersten beiden Vergleiche (1 = 2) und (1 = 3) liefern jeweils den Wert falsch, der letzte Ausdruck (1 = null) den Wert unbekannt. Letztlich bleibt also der Ausdruck falsch OR unbekannt übrig, was nach der dreiwertigen Logik (siehe Tabelle 4.21) wiederum unbekannt ergibt. Da nun in der WHERE-Bedingung unbekannt mit falsch gleichgesetzt wird, gibt die gesamte Abfrage keine Zeilen zurück, obwohl die 1 in der Aufzählung nicht explizit enthalten ist. In diese Falle kann man wirklich leicht hineinlaufen, da das IN-Prädikat auch Unterabfragen zulässt. Betrachten Sie hierzu bitte einmal die folgende Abfrage, die alle Mitarbeiter zurückliefern soll, die noch keine Bestellungen aufgenommen haben:
Die Abfrage gibt keine Zeilen zurück, obgleich eine ganze Reihe von Angestellten existiert, die noch nie eine Bestellungen initiiert haben. Der Grund hierfür ist, dass die Unterabfrage auch eine Reihe von Zeilen liefert, die den Wert NULL für die Spalte enthalten, was dann so wie weiter oben beschrieben dazu führt, dass die gesamte WHERE-Klausel einfach falsch zurückliefert. Auch die Anwendung von Vergleichsoperatoren auf NULL-Werte ergibt stets den Wert unbestimmt. Die folgenden vier Abfragen geben keine Zeilen zurück:
Für alle vier Abfragen liefert der Ausdruck in der WHERE-Klausel den Wert unbestimmt, der wie oben erläutert in dieser Klausel als falsch interpretiert wird. Keine der Abfragen wird also Werte zurückliefern. Führen Sie in Gedanken bitte einmal diese drei Abfragen aus, und überlegen Sie, was Sie als Ergebnis erwarten:
161
4 Transact SQL-Grundlagen
Auf den ersten Blick könnte man denken, dass die Summe der von den ersten beiden Abfragen zurückgelieferten Zeilenanzahl gleich der Gesamtsumme der in der Tabelle enthaltenen Anzahl von Zeilen, die von der dritten Abfrage zurückgegeben wird, entsprechen sollte. Dies ist jedoch nicht der Fall! Die Tabelle enthält eine Reihe von Zeilen, in denen die Spalte Color nicht belegt ist, also NULL enthält. Diese Zeilen werden weder in der ersten noch in der zweiten SELECTAnweisung berücksichtigt, da der Ausdruck genauso unbestimmt ist wie der Ausdruck . Um die Anzahl aller Produkte zu erhalten, für die keine Farbe angegeben wurde, schreibt man:
Ob der Wert einer Variablen oder Spalte NULL ist, muss also stets explizit mit IS NULL abgefragt werden. Genauso können Sie auch IS NOT NULL verwenden, um zu prüfen, ob ein Wert nicht NULL ist:
Im Gegensatz zur WHERE-Klausel wird in einer CHECK-Bedingung der Wert unbekannt, also NULL, als wahr behandelt. Schauen Sie sich die folgende Tabellendefinition an:
Die Tabelle Produkt enthält eine Spalte Masse, in die auch NULL-Werte eingetragen werden dürfen. Allerdings ist auch festgelegt, dass die Spalte nur die Werte Schwer, Mittel und Leicht enthalten darf. Trotz der CHECK-Bedingung darf allerdings der Wert NULL in die Spalte eingetragen werden:
UNIQUE-Einschränkungen behandeln NULL-Werte als gleich. Die folgende Anweisung wird daher eine Fehlermeldung zurückliefern:
Da der Wert NULL bereits in einer anderen Zeile existiert, führt die UNIQUEEinschränkung dazu, dass die INSERT-Anweisung nicht ausgeführt wird. Auch in den Klauseln GROUP BY und ORDER BY werden NULL-Werte als identisch betrachtet. Hier werden jeweils alle Spalten mit NULL-Werten zusammengefasst.
162
4.19 Null
4.19.2 Arithmetische Operationen So wie logische Operatoren unbekannte Ergebnisse liefern können, gilt dies gleichermaßen auch für arithmetische Operationen. Auch hier gibt es wiederum einige Besonderheiten zu beachten. Schauen Sie sich zunächst bitte die folgende Anweisung an, die einige Berechnungen mit NULL-Werten durchführt:
Das Ergebnis der obigen Abfrage sehen Sie hier:
NULL wird also immer im Sinne von unbekannt interpretiert. Aus dieser Sicht ist das Ergebnis also durchaus verständlich wann immer Sie eine Berechnung mit einem unbekannten Ausdruck durchführen, ist das Ergebnis selbst auch wieder unbestimmt. Für die letzten drei Spalten sieht die Sache allerdings ein wenig anders aus. Die Berechnung (null/0) wird immer einen Fehler liefern, sobald Sie die null mit einem beliebigen Ausdruck ersetzen, der nicht NULL zurückliefert. SQL Server gibt allerdings auch hier NULL zurück. Gleiches gilt für den Ausdruck (0*null), der für einen von NULL verschiedenen Ausdruck immer 0 liefern wird. Eine etwas kuriose Besonderheit ist das Ergebnis von (1*null). Hier wird völlig korrekt NULL als Resultat zurückgegeben. Diese NULL ist jedoch nicht das Ergebnis einer wirklichen Berechnung, vielmehr wird sie aus der Unbestimmtheit des Ausdrucks abgeleitet. Bei der Berechnung von Aggregaten werden NULL-Werte nicht berücksichtigt. Schauen Sie sich hierzu bitte die folgende Tabellendefinition an:
Wir wollen nun einige Zeilen zu dieser Tabelle hinzufügen:
Schließlich berechnen wir die Gesamtsumme für die Werte in der Spalte Summe:
Die letzte hinzugefügte Zeile enthält den Wert NULL in der Spalte Summe. Diese Zeile wird einfach aus der Summenberechnung ausgeklammert. Der Wert der Summe ist also nicht NULL, sondern 33.000. Haben Sie die Option SET ANSI_WARNINGS auf ON gesetzt
163
4 Transact SQL-Grundlagen (dies ist der Standard), erhalten Sie eine Warnung, dass NULL-Werte bei der Aggregation ignoriert werden. Ist ANSI_WARNINGS OFF, so wird diese Warnung nicht ausgegeben.
4.19.3 Operationen mit Zeichenketten Überlegen Sie sich nun bitte einmal, was Sie als Ergebnis erwarten, wenn Sie einen Text mit einem NULL-Wert verketten. Interpretieren Sie NULL als nichts, also als leere Zeichenkette, so werden Sie sich als Ergebnis sicherlich den ursprünglichen Text wünschen. Hängt man an einen Text nichts an, so findet eben keine Veränderung statt. Wenn Sie NULL allerdings als unbekannt betrachten, dann würden Sie auch hier ein Unbekannt also eine NULL als Ergebnis erwarten. Es ist nicht so einfach zu entscheiden und dazu oftmals vom speziellen Fall abhängig, welche Variante zu bevorzugen ist. SQL Server hat in der Version 6.5 NULL stets als leere Zeichenkette interpretiert, also den ursprünglichen Text zurückgegeben. Ab der Version 7.0 konnte das gewünschte Verhalten dann über die SET-Option CONCAT_NULL_YIELDS_NULL festgelegt werden. Ist diese Option auf ON gesetzt (das ist die Standardeinstellung), so gibt die Verkettung einer Zeichenkette mit NULL stets NULL zurück, wie das folgende Beispiel demonstriert:
Das Ergebnis der obigen SELECT-Anweisung zeigt die folgende Abbildung:
Stellen Sie CONCAT_NULL_YIELDS_NULL auf OFF, so wird NULL als leerer Text behandelt, das Ergebnis der Verkettung ist dann also die ursprüngliche Zeichenkette:
Hier sehen Sie das Ergebnis der Abfrage:
Die Standardeinstellung für die Option CONCAT_NULL_YIELDS_NULL wird in der Datenbank, also bei CREATE DATABASE oder ALTER DATABASE, festgelegt, z. B. so:
Diese Standardeinstellung wird allerdings auf Verbindungsebene überschrieben, die bei der Verbindung zur Datenbank angegebene Option dominiert also. Wenn Sie im SQL Server Management Studio eine neue Abfrage öffnen, wird die Option CONCAT_NULL_YIELDS_NULL für die Verbindung stets explizit gesetzt, sodass die Einstellung auf Datenbankebene für im Management Studio ausgeführte Abfragen keine Auswirkung hat. Denken Sie bitte unbedingt daran, wenn Sie die Einstellung verändern und unerwartete Ergebnisse erhalten.
164
4.19 Null Die in der Datenbank eingestellte Option kann über die Spalte der Systemsicht abgefragt werden:
Für die Optionen der Verbindung verwenden Sie das Kommando:
Sie erhalten ein Ergebnis, das in etwa so aussieht:
Das Abrufen und Einstellen der Verbindungsoptionen können Sie auch über das Menü Abfrage/Abfrageoptionen
vornehmen:
165
4 Transact SQL-Grundlagen Wenn Sie indizierte Sichten oder Indizes auf berechneten Spalten erstellen oder ändern, muss CONCAT_NULL_YIELDS_NULL auf ON festgelegt sein. Bitte beachten Sie, dass diese Option für die Verbindung eingeschaltet sein muss, die ein CREATE INDEX oder ALTER INDEX ausführt.
4.19.4 Hinweise für die Verwendung von NULL Aus all dem in den vorangegangenen Abschnitten Gesagten, aus den möglichen Problemen, die bei der Verwendung von NULL auftreten können, aus der Sonderstellung, die der Wert NULL innerhalb des SQL Server-Typsystems einnimmt, lässt sich eine sehr einfache Regel ableiten: Vermeiden Sie die Verwendung von NULL, wann immer dies möglich ist. In vielen Fällen können Sie durch eine Belegung mit Standardwerten, die außerhalb des gültigen Wertebereichs liegen, aber doch noch dem Datentyp einer Spalte entsprechen, NULL-Werte vermeiden. Nehmen Sie z. B. eine Spalte Gehalt in einer Mitarbeitertabelle, hier kann wenn das Gehalt noch nicht bekannt ist statt NULL auch der Wert 0 eingetragen werden. Da es sicherlich keine Mitarbeiter mit einem Gehalt von 0 geben wird, ist die 0 hier ebenso geeignet, einen fehlenden Wert anzuzeigen. In einigen Fällen wird es allerdings schwierig oder unmöglich sein, auf NULL zu verzichten. Haben Sie in der Mitarbeitertabelle auch ein Datum, das die Beendigung des Arbeitsverhältnisses anzeigen soll, so kann der Wert NULL hier dafür stehen, dass der Mitarbeiter noch nicht aus der Firma ausgeschieden ist. Hier ein Standarddatum (etwa den 01.01.1900) zu erfinden, das für Mitarbeiter im aktiven Angestelltenverhältnis steht, wird sicherlich für Verwirrung sorgen. Der Einsatz von NULL wird sich also nicht vermeiden lassen, versuchen Sie aber, ihn so weit wie möglich einzuschränken. Eine der Hauptursachen für die Schwierigkeiten mit NULL ist sicherlich, dass der Wert NULL allein keinerlei Information darüber liefert, warum er für einen Spaltenwert eingetragen wurde. Der Erfinder des Relationenmodells, Dr. E. F. Codd, hatte seinerzeit dafür plädiert, für fehlende Werte zwei unterschiedliche Markierungen einzuführen: eine für unbekannt und eine andere für unpassend. Nehmen Sie z. B. eine Spalte, in welche die Farbe eines Automobils eingetragen werden soll. Diese Spalte möge über eine UNIQUEEinschränkung nur die Werte Rot, Grün und Blau zulassen. Sollen Sie nun die Farbe für ein Fahrzeug eintragen, das unter einer Abdeckplane steht, so ist Ihnen diese Farbe unbekannt. Ist ein Auto gelb, so ist die Farbe zwar bekannt, kann aber ebenso nicht in die Spalte eingetragen werden. In beiden Fällen wird die Spalte für die Farbe den Wert NULL enthalten und somit lediglich anzeigen, dass der Wert nicht eingetragen werden konnte, aber nicht, warum dies so ist. Diese Information geht einfach verloren, wenn Sie sie nicht separat in der Tabelle speichern. Und dies ist auch schon der nächste Tipp: Sie können eine NULL-wertige Spalte paarweise mit einer Informationsspalte verwenden, in die Sie den Grund für die NULL eintragen. In unserer gerade besprochenen Tabelle mit der Spalte über die Fahrzeugfarbe könnte dies so aussehen:
166
4.19 Null
Die Tabelle Fahrzeug hat neben der Spalte Farbe auch eine Spalte FarbInfo, in der nähere Informationen zum in der Spalte Farbe enthaltenen Wert stehen. Ist der Wert der Spalte Farbe NULL, so kann aus der FarbInfo-Spalte abgelesen werden, warum dies so ist.
4.19.5 Spezielle Funktionen für NULL-Werte SQL Server stellt zwei Funktionen bereit, die NULL-Werte in deterministische Werte überführen können. COALESCE Syntax.
Erklärung. Die Funktion COALESCE kann eine beliebige Anzahl von Parametern verarbeiten. Die übergebenen Parameter sind typischerweise Ausdrücke, die der Reihe nach ausgewertet werden. Der erste Ausdruck, der einen Wert ungleich NULL zurückliefert, bestimmt das Ergebnis der Funktion. Geben alle Ausdrücke NULL zurück, so ist das Ergebnis der Funktion NULL. Es müssen mindestens zwei Parameter übergeben werden. Außerdem sollten Sie darauf achten, dass alle Ausdrücke einen Wert vom selben Datentyp zurückliefern oder zumindest eine implizite Typumwandlung unterstützen. Die Funktion COALESCE ist äquivalent mit der folgenden CASE-Funktion:
Beispiele.
ISNULL Syntax.
167
4 Transact SQL-Grundlagen Erklärung. Die Funktion ISNULL wertet den übergebenen ausdruck_1 aus. Ist der Wert NULL, so wird der durch ausdruck_2 bestimmte Wert zurückgegeben. Wenn der Wert von ausdruck_1 ungleich NULL ist, so wird dieser Wert zurückgegeben. ISNULL ist somit gleichbedeutend mit dem folgenden Aufruf von COALESCE:
Beispiele.
Unsere in Abschnitt 4.15.7 vorgestellte Tabelle Extras.Kalender besitzt bislang noch keinen Primärschlüssel. Ein solcher Primärschlüssel sollte sicherstellen, dass ein bestimmter Datumswert nur einmal in der Tabelle existieren kann. Für den Primärschlüssel ist die Spalte Datum somit kein geeigneter Kandidat, da in dieser Spalte auch eine Zeitkomponente enthalten ist. Die folgenden beiden Werte für diese Spalte sind daher unterschiedlich, obwohl sie dasselbe Datum enthalten:
Ein Primärschlüssel auf der Spalte Datum würde somit mehrfache Einträge für ein Kalenderdatum zulassen und ist daher ungeeignet für unsere Zwecke. Ein geeigneter Primärschlüssel wäre die Kombination aus den berechneten Spalten Jahr, Monat und Tag. Versuchen wir also, einen solchen Primärschlüssel zur Tabelle hinzuzufügen:
Leider erzeugt die obige ALTER TABLE-Anweisung aber keinen Primärschlüssel, sondern nur die folgende Fehlermeldung:
Ursache für den Fehler ist, dass bei einer berechneten Spalte davon ausgegangen wird, dass der Ausdruck zur Berechnung des Spaltenwertes prinzipiell auch den Wert NULL zurückgeben kann. Und Spalten, die NULL-Werte erlauben, sind nun einmal in Primärschlüsseln nicht erlaubt. Hier hilft ein kleiner Trick weiter. Die Berechnungsausdrücke für die Spalten Jahr, Monat und Tag können unter Verwendung von ISNULL folgendermaßen geändert werden, um explizit anzuzeigen, dass diese Spalten keine NULL-Werte enthalten:
168
4.20 Fehlerbehandlung in T-SQL
Da ein Berechnungsausdruck nicht über ALTER COLUMN geändert werden kann, müssen die drei Spalten über DROP COLUMN entfernt und dann über die Klausel ADD wieder hinzugefügt werden. Durch die Verwendung von ISNULL im Berechnungsausdruck erkennt SQL Server, dass die entsprechenden Spalten niemals den Wert NULL enthalten werden. Das Anlegen eines Primärschlüssels für diese drei Spalten ist nun möglich:
4.20
Fehlerbehandlung in T-SQL Bereits im Abschnitt 0 wurde die @@ERROR-Funktion besprochen, die zum Behandeln aufgetretener Fehler verwendet werden kann. T-SQL bietet auch die Möglichkeit einer strukturierten Ausnahmebehandlung, ähnlich wie dies von anderen Programmiersprachen (zum Beispiel C++ oder C#) bekannt ist. Hierzu kann eine Gruppe von T-SQLAnweisungen in einen BEGIN TRY-/END TRY-Block eingeschlossen werden. Auf einen solchen Block muss unmittelbar ein BEGIN CATCH-/END CATCH-Block folgen. In diesen zweiten Block wird immer dann verzweigt, wenn in irgendeiner der Anweisungen zwischen BEGIN TRY und END TRY ein Fehler auftritt. Dadurch muss nicht nach jeder Anweisung der Wert der Funktion @@ERROR ausgewertet werden. Ein TRY/CATCHBlock kann zum Beispiel so aussehen:
Das obige Beispiel zeigt auch die Benutzung der zur Verfügung stehenden ERROR_...()Funktionen, über die Sie nähere Informationen zum aufgetretenen Fehler erhalten können.
169
4 Transact SQL-Grundlagen
4.21
Komplexe Abfragen mit SELECT In diesem Abschnitt werden wir uns noch einmal mit der SELECT-Anweisung beschäftigen und komplexere Abfragen entwerfen sowie weitere SQL Server-Funktionen für die Verwendung mit SELECT kennenlernen. Im Mittelpunkt werden hierbei Abfragen über mehrere Tabellen stehen. Für die verwendeten Beispiele ist unsere einfache Datenbank OmasKochbuch dabei oftmals nicht mehr ausreichend. In vielen Fällen wird daher auf die Beispieldatenbank AdventureWorks zurückgegriffen. Wir verwenden hierbei die in Tabelle 4.19 bzw. Abbildung 4.20 aufgelisteten Tabellen und Sichten.
4.21.1 Mengenoperationen mit SQL Am Anfang dieses Kapitels wurde bereits erwähnt, dass SQL eine mengenorientierte Sprache ist. Genau genommen geben SELECT-Anweisungen also keine Tabellen, sondern Ergebnismengen zurück. Mit diesen Ergebnismengen können auch Operationen durchgeführt werden. So ist es zum Beispiel möglich, die Ergebnismengen unterschiedlicher SELECTAnweisungen zu vereinen oder voneinander abzuziehen. T-SQL kennt hierfür Operatoren, die auf komplette SELECT-Anweisungen angewendet werden können. Die generelle Syntax einer entsprechenden Mengenoperation sieht so aus:
Die an einer solchen Operation beteiligten SELECT-Anweisungen müssen alle dieselbe Anzahl von Spalten zurückliefern, wobei weiterhin gilt, dass die Spalten an derselben Position in den einzelnen SELECT-Anweisungen kompatible Datentypen aufweisen müssen. Um Ergebnismengen unterschiedlicher SELECT-Anweisungen miteinander zu verknüpfen, stehen Ihnen die folgenden Mengenoperatoren zur Verfügung: UNION Der Operator UNION bildet die Vereinigungsmenge der beiden Ergebnisse. Das Endergebnis enthält also alle Zeilen, die entweder von der ersten oder von der zweiten SELECTAnweisung zurückgegeben wurden. Allerdings werden doppelte Zeilen, also Duplikate, aus dem Ergebnis entfernt. UNION ALL Dieser Operator arbeitet genauso wie UNION, bildet also die Vereinigungsmenge. Duplikate bleiben allerdings erhalten, das Endergebnis kann also mehrere Zeilen mit denselben Daten enthalten.
170
4.21 Komplexe Abfragen mit SELECT INTERSECT Der INTERSECT-Operator bildet die Schnittmenge der beiden Ergebnisse. Im Endergebnis sind also alle Zeilen enthalten, die sowohl von der ersten als auch von der zweiten SELECT-Anweisung zurückgegeben wurden. Duplikate werden hierbei aus dem Ergebnis entfernt. EXCEPT Bei Verwendung des Operators EXCEPT erhalten Sie die Differenzmenge. Im Ergebnis sind also genau diejenigen Zeilen enthalten, die von der ersten, aber nicht von der zweiten SELECT-Anweisung zurückgegeben wurden. Es soll nun an einem einfachen Beispiel erläutert werden, wie die Mengenoperatoren arbeiten. Angenommen, wir haben zwei Tabellen T1 und T2, die so erzeugt werden:
In die einzige Spalte dieser Tabelle werden dann über eine Reihe von INSERT-Anweisungen jeweils fünf Zeilen mit den folgenden Werte für die Spalte x eingefügt: T1: (1, 2, 3, 4, 5) T2: (1, 2, 3, 8, 9) Tabelle 4.22 veranschaulicht die Ergebnisse der unterschiedlichen Mengenoperationen. Tabelle 4.22 Veranschaulichung der T-SQL-Mengenoperatoren Anweisung
Ergebnis
1, 2, 3, 4, 5, 8, 9
Veranschaulichung
1, 2, 3, 4, 5, 1, 2, 3, 8, 9
1, 2, 3
4, 5
171
4 Transact SQL-Grundlagen
4.21.2 Abfragen über mehrere Tabellen In den SELECT-Anweisungen, die Sie bislang kennengelernt haben, wurde stets nur eine einzige Tabelle verwendet. In einer relationalen Datenbank haben wir es jedoch mit Tabellen zu tun, die zueinander in Beziehung stehen. Schauen Sie sich nur einmal unsere einfache Beispieldatenbank OmasKochbuch in Abbildung 4.20 an. Die beiden einzigen enthaltenen Tabellen stehen hier ebenfalls zueinander in Beziehung. Angenommen, Sie möchten die Bezeichnung Ihrer Rezepte mit allen dazugehörigen Zutaten ausgeben. Um diese Informationen zu erhalten, müssen beide Tabellen abgefragt werden. Natürlich kann dies in zwei separaten SELECT-Anweisungen geschehen:
Um nun alle Zutaten für die einzelnen Rezepte zu erhalten, können Sie jeweils eine zweite SELECT-Anweisung angeben, welche die Zutaten für ein bestimmtes Rezept zurückgibt:
Eine solche Verfahrensweise ist natürlich umständlich und auch unpraktikabel. Viel besser wäre es doch, beide Tabellen zu verknüpfen und in einem Ergebnis zurückzugeben. Abfragen dieser Art, in denen also mehrere Tabellen verwendet werden, sollen in diesem Abschnitt entworfen werden. SQL kennt hierzu eine Reihe von JOIN-Operatoren, mit denen Sie Tabellen in Abfragen auf unterschiedliche Art verknüpfen können. Die allgemeine Syntax bei der Verwendung von JOIN in Abfragen ist hierbei wie folgt:
Der erste Teil der obigen SELECT-Anweisung sieht zunächst sehr bekannt aus. Er besteht aus der Spaltenliste und der ersten Tabelle für die Abfrage. Über unterschiedliche JOINKlauseln können dann zu dieser ersten Tabelle weitere Tabellen hinzugefügt werden, wobei in den einzelnen Verknüpfungsbedingungen jeweils angegeben wird, wie die an der Abfrage beteiligten Tabellen zueinander in Beziehung stehen. Diese Verknüpfungsbedingungen werden in der Regel Spalten der beteiligten Tabellen zueinander in Beziehung setzen, die durch Fremdschlüsselbeziehungen bestimmt werden. Die Angabe dieser Bedingungen ist optional, wir werden gleich sehen, was passiert, wenn Bedingungen weggelassen werden. In der Spaltenliste der SELECT-Anweisung haben Sie Zugriff auf die Spalten aller an der Abfrage beteiligten Tabellen. T-SQL kennt unterschiedliche JOINs, die im folgenden Teil erklärt und anhand einiger Beispiele näher untersucht werden.
172
4.21 Komplexe Abfragen mit SELECT CROSS JOIN In der Klausel CROSS JOIN kann keine Verknüpfungsbedingung angegeben werden. Ein CROSS JOIN liefert das kartesische Produkt der beiden beteiligten Tabellen. Die folgende Anweisung gibt alle Spalten der Tabellen Kochen.Rezept und Kochen.Zutat zurück, wobei für jede Zeile der Tabelle Kochen.Rezept jeweils alle Zeilen der Tabelle Kochen.Zutat ausgegeben werden:
Abbildung 4.24 zeigt einen Ausschnitt aus dem Ergebnis.
Abbildung 4.24 Ergebnis des CROSS JOIN
Angenommen, die Tabelle Kochen.Rezept enthält n Zeilen, die Tabelle Kochen.Zutat möge m Zeilen enthalten. Das Ergebnis enthält dann n * m Zeilen! Seien Sie also vorsichtig mit CROSS JOINs, bereits zwei kleine Tabellen mit jeweils nur 1000 Zeilen führen zu einem Ergebnis von 1000.000 Zeilen! Ein CROSS JOIN wird relativ selten benötigt, wichtiger sind auf jeden Fall die im folgenden Abschnitt beschriebenen INNER JOINs. INNER JOIN Ein INNER JOIN verknüpft zwei Tabellen anhand einer Bedingung. Im Ergebnis sind nur diejenigen Zeilen enthalten, für welche die Auswertung dieser Bedingung den Wert WAHR ergibt. Typischerweise wird die Bedingung einfach Primär- bzw. Fremdschlüsselspalten der beteiligten Tabellen auf Gleichheit prüfen. Um zum Beispiel die Bezeichnung aller Rezepte und die dazugehörigen Zutaten auszugeben, kann der folgende INNER JOIN verwendet werden:
173
4 Transact SQL-Grundlagen Die Tabellen Kochen.Rezept und Kochen.Zutat werden hier durch die Fremdschlüsselbeziehung über die gemeinsame Spalte RezeptId miteinander verbunden. Aus der Menge der möglichen Spalten werden dann nur die in beiden Tabellen vorhandenen Bezeichnungen für Rezept und Zutat sowie die Menge mit der zugehörigen Einheit aus der Tabelle Kochen.Zutat ausgegeben. Die Abfrage verwendet Aliasnamen für beide Tabellen, um etwas Schreibarbeit zu sparen. Das Ergebnis der Abfrage sehen Sie in Abbildung 4.25.
Abbildung 4.25 Ergebnis der INNER JOINs
Ein INNER JOIN kann auch über eine Zeilenfilterbedingung in der WHERE-Klausel erstellt werden. Hierzu werden einfach die beteiligten Tabellen in der FROM-Klausel aufgelistet, die WHERE-Klausel enthält dann die Verknüpfungsbedingung. Die obige Abfrage hätte auch so formuliert werden können:
An einem JOIN können grundsätzlich nur zwei Tabellen beteiligt sein. Es ist jedoch möglich, mehr als zwei Tabellen in einer Abfrage zu verwenden. In so einem Fall müssen die beteiligten Tabellen durch mehrere JOINs paarweise miteinander verkettet werden. Das klingt zunächst einmal komplizierter, als es tatsächlich ist. Schauen Sie sich hierzu bitte noch einmal Tabelle 4.19 und Abbildung 4.23 an. Dort finden Sie die Tabellen Production.Product, Production.ProductSubCategory und Production.ProductCategory, die miteinander verbunden sind. Um die Namen aller Produktkategorien, Produktunterkategorien und Produkte in einer Abfrage zurückzugeben, müssen alle drei Tabellen miteinander verbunden werden. Die entsprechende Abfrage sieht so aus:
174
4.21 Komplexe Abfragen mit SELECT
Die Abfrage zeigt so ganz nebenbei übrigens auch, dass in der ORDER BY-Klausel Spaltennummern anstelle der Spaltennamen angegeben werden können. Abbildung 4.26 zeigt das Abfrageergebnis.
Abbildung 4.26 INNER JOIN über drei Tabellen
OUTER JOINs Vielleicht ist Ihnen bei der Abfrage unserer Kochbuch-Tabellen aufgefallen, dass Rezepte ohne zugehörige Zutaten beim INNER JOIN nicht angezeigt wurden. Im Ergebnis waren nur die Zeilen enthalten, welche die Verknüpfungsbedingung erfüllen. Wenn für eine Zeile in der Tabelle Kochen.Rezept keine Zeilen in Kochen.Zutat enthalten sind, so kann die Verknüpfungsbedingung niemals WAHR sein, daher wird auch die in Kochen.Zutat existierende Zeile nicht angezeigt. Möchten Sie aus der Tabelle Kochen.Rezept immer alle Zeilen anzeigen, und zwar unabhängig davon, ob bereits Zutaten für ein Rezept hinzugefügt wurden, so muss eine andere Verknüpfungsform, ein sogenannter OUTER JOIN, verwendet werden. Bei OUTER JOINs unterscheidet man zwischen drei verschiedenen Formen. Die Angabe der Option OUTER ist hierbei optional. LEFT [OUTER] JOIN. Aus der linken Tabelle der Verknüpfung werden immer alle Zeilen angezeigt. Existieren in der rechten Tabelle der Verknüpfung keine Zeilen für die Verknüpfungsbedingung, wird für die Spalten dieser Tabelle ein NULL-Wert ausgegeben. Die folgende SELECT-Anweisung verwendet einen LEFT OUTER JOIN für die Verknüpfung der Tabellen Kochen.Rezept und Kochen.Zutat:
175
4 Transact SQL-Grundlagen
Abbildung 4.27 zeigt das Ergebnis dieser Abfrage. Dort sind nun alle Zeilen aus der Tabelle Kochen.Rezept enthalten. Wenn keine Zutaten für ein Rezept existieren, dann sind die aus der Tabelle Kochen.Zutat angezeigten Spalten NULL.
Abbildung 4.27 Ergebnis des LEFT OUTER JOIN
RIGHT [OUTER] JOIN. Bei einem RIGHT OUTER JOIN enthält das Ergebnis alle Zeilen der rechten Tabelle. Für Spalten der linken Tabelle wird NULL ausgegeben, wenn keine Zeilen existieren. Ein RIGHT OUTER JOIN ist also zuzusagen die Umkehrung des LEFT OUTER JOINs. FULL [OUTER] JOIN. Ein FULL OUTER JOIN ist eine Kombination von LEFT und RIGHT JOIN. Hier werden stets alle Zeilen der beteiligten Tabellen ausgegeben. Im Ergebnis werden die Spaltenwerte wahlweise links oder rechts mit NULL aufgefüllt, je nachdem, ob verknüpfte Zeilen vorhanden sind oder nicht. Ausführungsreihenfolge bei JOINs Prägen Sie sich bitte ein, dass ein JOIN stets vor der Auswertung der WHERE-Klausel ausgeführt wird. Es ist sehr wichtig, dass Sie dies verinnerlichen, anderenfalls werden Sie bei der Ausführung Ihrer Abfragen insbesondere mit OUTER JOINs einige Überraschungen erleben. Schauen Sie sich die beiden folgenden Abfragen an, die dasselbe Ergebnis zurückgeben:
176
4.21 Komplexe Abfragen mit SELECT
In der ersten Anweisung gibt der INNER JOIN nur alle Zeilen zurück, welche die Verknüpfungsbedingung erfüllen. Das Ergebnis enthält also die Zeilen, in denen der Wert der Spalte RezeptId in beiden Tabellen identisch und der Wert der Spalte Kochen.Rezept.Joule kleiner als 1400 ist. In der zweiten Anweisung werden durch den JOIN zunächst alle Zeilen zurückgegeben, in denen die RezeptId übereinstimmt. Dieses Ergebnis wird anschließend durch den in der WHERE-Klausel angegebenen Zeilenfilter nochmals eingeschränkt. Das Ergebnis ist in beiden Fällen dasselbe. Bei einem INNER JOIN ist es egal, ob in der JOIN-Bedingung oder in der WHERE-Klausel gefiltert wird. Für OUTER JOINs sieht die Sache ein wenig anders aus. Wir wollen hierzu ebenfalls zwei Abfragen betrachten, welche die zusätzliche Filterbedingung im JOIN bzw. in der WHERE-Klausel enthalten. Schauen Sie sich bitte die erste dieser beiden Abfragen an:
In einem LEFT JOIN sind immer alle Zeilen der linken Tabelle enthalten. Die zusätzliche Bedingung sorgt hier nur dafür, dass zusätzliche Zeilen mit NULLWerten für die Spalten der Tabelle Kochen.Zutat existieren. Dies sind genau diejenigen Zeilen, für welche die JOIN-Bedingung nicht erfüllt ist. Das Ergebnis der Abfrage zeigt Abbildung 4.28.
Abbildung 4.28 LEFT JOIN mit Filterbedingung
177
4 Transact SQL-Grundlagen Wie Sie in der Abbildung sehen, sind auch Zeilen mit einem Joule-Wert >= 1400 im Ergebnis enthalten, die Spalten der rechten Tabelle der Abfrage sind für solche Zeilen allesamt NULL. Verschiebt man die Filterbedingung aus der ON-Klausel des JOINs in die WHEREKlausel, sieht die Abfrage so aus:
Durch den LEFT JOIN werden zunächst wiederum alle Zeilen der Tabelle Kochen.Rezept zurückgegeben. Erst danach werden diese Zeilen durch die WHERE-Klausel nochmals gefiltert. Heraus kommt das in Abbildung 4.29 gezeigte Ergebnis.
Abbildung 4.29 LEFT JOIN mit WHERE-Klausel
In der WHERE-Klausel kann also auf alle Zeilen zugegriffen werden, die der OUTER JOIN liefert. Die Spaltenwerte für Zeilen ohne Übereinstimmung mit der Verknüpfungsbedingung sind allesamt NULL für die OUTER-Tabelle. Um eine Liste aller Angestellten zu erhalten, die noch keine Bestellung aufgenommen haben, kann also diese Abfrage verwendet werden:
Die WHERE-Klausel bewirkt hier, dass nur diejenigen Zeilen ausgegeben werden, für welche die Verknüpfungsbedingung gerade nicht erfüllt ist. Bei OUTER JOINS ist es also nicht egal, ob Sie eine Einschränkung des Ergebnisses in der JOIN-Klausel oder der WHERE-Klausel vornehmen. Bitte erinnern Sie sich hieran, wenn Ihre OUTER JOINs ungewollte Ergebnisse liefern.
178
4.21 Komplexe Abfragen mit SELECT
4.21.3 Aggregatfunktionen und GROUP BY SQL Server-Aggregatfunktionen ermöglichen eine Berechnung von zusammengefassten Werten aus einzelnen Spaltenwerten. Schauen Sie sich bitte noch einmal die Tabelle Sales.SalesOrderHeader aus Abbildung 4.23 an. Diese Tabelle enthält eine Spalte TotalDue, in welcher der Betrag einer Bestellung gespeichert wird. Angenommen, Sie möchten nun die Summe aller Bestellwerte abfragen, dann können Sie hierfür die Aggregatfunktion SUM in der folgenden Art und Weise verwenden:
Das Ergebnis ist eine einzige Zeile (und auch nur eine Spalte):
Neben der Aggregatfunktion SUM, die Sie soeben kennengelernt haben, unterstützt SQL Server unter anderem die in der folgenden Tabelle 4.23 genannten Aggregatfunktionen: Tabelle 4.23 SQL Server: Aggregatfunktionen Aggregatfunktion
Erklärung
Berechnet den arithmetischen Mittelwert aller ausdrucks-Auswertungen.
Zählt die Anzahl von Zeilen. COUNT(*) zählt alle Zeilen. COUNT([ALL] ausdruck) zählt alle Werte, für welche die Auswertung von ausdruck nicht NULL ergibt. COUNT(DISTINCT ausdruck) zählt alle eindeutigen Werte, für welche die Auswertung von ausdruck nicht NULL ergibt.
Gibt den größten Wert von ausdruck zurück.
Gibt den kleinsten Wert von ausdruck zurück.
Gibt die Summe aller Auswertungen von ausdruck zurück.
Gibt die statistische Standardabweichung aller Auswertungen von ausdruck zurück.
Gibt die statistische Varianz aller Auswertungen von ausdruck zurück.
Alle in der obigen Tabelle genannten Aggregatfunktionen ignorieren bei der Berechnung NULL-Werte, wenn dies nicht anders angegeben ist.
179
4 Transact SQL-Grundlagen Um aus der Tabelle Sales.SalesOrderHeader den maximalen Bestellwert abzufragen, können wir also schreiben:
Sollen für diese eine Bestellung die interessierenden Daten zurückgegeben werden, so kann die Aggregatfunktion auch in der Zeilenfilterbedingung verwendet werden:
Zum Abschluss soll nun auch noch der für diese Bestellung verantwortliche Vertriebsmitarbeiter ausgegeben werden. Hierzu muss die Sicht Sales.vSalesPerson zur obigen Abfrage hinzugefügt werden:
Wir verwenden hier einen LEFT JOIN, da es auch Bestellungen ohne Vertriebsmitarbeiter geben kann. Das Ergebnis der Abfrage sehen Sie hier:
Die folgende SELECTAnweisung zählt alle Zeilen in der Sicht Sales.vSalesPerson:
Um zu ermitteln, aus wie viel verschiedenen Jahren die Bestellungen datieren, können wir DISTINCT in Verbindung mit COUNT verwenden:
Die COUNT-Funktion zählt hier alle unterschiedlichen Werte von . Das Ergebnis ist 4. Gruppieren mit GROUP BY Mit Aggregatfunktionen können nicht nur Gesamtwerte für eine Tabelle berechnet werden. Es ist auch möglich, Berechnungen für Teilbereiche durchzuführen. Interessant wäre es zum Beispiel, Bestellsummen je Kalenderjahr zu bestimmen. Eine erste Idee für eine entsprechende Abfrage könnte so aussehen:
180
4.21 Komplexe Abfragen mit SELECT Allerdings liefert die obige Abfrage lediglich diese Fehlermeldung:
SQL Server erwartet die explizite Angebe der Gruppen, für welche die Berechnung durchgeführt werden soll, über eine GROUP BY-Klausel in der Abfrage. Diese Klausel muss exakt die für die Ausgabe der Gruppen verwendeten Spaltenausdrücke enthalten. Um also eine Gruppierung der Berechnung nach year(OrderDate) zu erhalten, muss die Abfrage so gestaltet werden:
Jetzt werden die Gruppensummen je Kalenderjahr berechnet und das Endergebnis noch ansteigend nach der Spalte Jahr sortiert. Die Abfrage liefert das folgende Ergebnis:
Selbstverständlich kann auch eine Gruppierung über mehrere Spalten erfolgen. Die folgende Abfrage fügt zur obigen Ausgabe eine weitere Spalte für den Kalendermonat hinzu:
Hier ist ein Auszug aus dem Ergebnis:
Filtern mit HAVING Angenommen, es sollen alle Vertriebsmitarbeiter mit der Anzahl ihrer Bestellungen ausgegeben werden. Sie möchten jedoch nur die Vertriebsmitarbeiter im Abfrageergebnis sehen, die mehr als 400 Bestellungen initiiert haben. Eine entsprechende Abfrage könnte vielleicht so aussehen:
181
4 Transact SQL-Grundlagen
Die Abfrage verwendet einen RIGHT OUTER JOIN, um auch Bestellungen ohne verantwortlichen Vertriebsmitarbeiter zu erhalten. Die obige Abfrage funktioniert nicht, sondern liefert lediglich diese Fehlermeldung:
Aggregatfunktionen dürfen nur in besonderen Fällen in der WHERE-Klausel verwendet werden. Ursache ist, dass die WHERE-Klausel in der logischen Ausführungsreihenfolge vor dem GROUP BY liegt, daher kann in der WHERE-Klausel noch nicht auf berechnete Aggregate zugegriffen werden. Für Zeilenfilterbedingungen mit Aggregatfunktionen müssen Sie die HAVING-Klausel nach der Angabe von GROUP BY verwenden. Die obige Abfrage sieht damit so aus:
Jetzt erhalten wir das gewünschte Resultat:
In der ersten Zeile des Ergebnisses sehen Sie, dass der überwiegende Anteil der Bestellungen ohne das Zutun eines Vertriebsmitarbeiters aufgenommen wurde. Im Übrigen geht die Abfrage davon aus, dass für Vertriebsmitarbeiter keine doppelten Nachnamen existieren. Falls dies der Fall ist, werden diese Mitarbeiter in einer Zeile der Ausgabe zusammengefasst, was sicherlich nicht korrekt wäre. Berechnung von Endsummen mit COMPUTE Über die COMPUTE-Klausel können Summenberechnungen für Abfragen durchgeführt werden. Das Ergebnis dieser Summenberechnung wird in einem separaten Abfrageergeb-
182
4.21 Komplexe Abfragen mit SELECT nis ausgegeben. Dies ist etwas ungewöhnlich, da die Verwendung der COMPUTE-Klausel dazu führt, dass eine Abfrage mehrere Ergebnisse hervorbringt. Um in eine Abfrage aller Bestellungen aus dem Jahr 2002 die Gesamtsumme des Bestellwertes einzubeziehen, kann die folgende Abfrage verwendet werden:
Das Ergebnis der COMPUTE-Klausel wird in einem eigenen Abfrageergebnis zurückgegeben (Abbildung 4.30).
Abbildung 4.30 Ergebnis einer Abfrage mit COMPUTE
Zur Berechnung von Zwischenergebnissen kann COMPUTE auch mit einer BY-Klausel verwendet werden. In dieser Klausel wird eine Gruppierung angegeben, für welche die Berechnung von Zwischensummen durchgeführt werden soll. Voraussetzung für die Verwendung der Zwischensummenberechnung über BY ist, dass das Abfrageergebnis nach den in der BY-Klausel angegebenen Spalten (es können mehrere sein, die durch Kommata getrennt sind) sortiert ist. Die folgende Abfrage gibt wiederum die Summe aller Bestellungen je Monat und Jahr aus:
Durch die Angabe der COMPUTE BY-Klausel werden mehrere Ergebnisse zurückgegeben. Für jedes Kalenderjahr wird hierbei ein eigenes Abfrageergebnis erzeugt. Auch die beiden über COMPUTE berechneten Werte für Gesamtsumme und Mittelwert der Bestel-
183
4 Transact SQL-Grundlagen lungen werden in einem separaten Abfrageergebnis ausgegeben. Abbildung 4.31 zeigt einen Auszug aus dem Ergebnis.
Abbildung 4.31 Abfrageergebnis mit COMPUTE BY
COMPUTE BY kann auch zusammen mit COMPUTE verwendet werden. So könnte zu obigen Abfrage noch eine COMPUTE-Klausel hinzugefügt werden, um auch die Gesamtsumme zu erhalten:
ROLLUP Bei Verwendung des ROLLUP-Operators in einer Gruppierung werden automatisch Zwischensummen für die an der Gruppierung beteiligten Spalten berechnet und ausgegeben, wobei die Hierarchie der Gruppierung beachtet wird. Dies lässt sich am besten an einem Beispiel verdeutlichen. Hierzu nehmen wir nochmals die Abfrage zur Berechnung der Bestellwerte je Jahr und Monat, fügen dieser jedoch eine Klausel WITH ROLLUP hinzu:
184
4.21 Komplexe Abfragen mit SELECT
Das Ergebnis enthält nun Zwischensummen für jedes Jahr und auch eine Gesamtsumme. Abbildung 4.32 zeigt einen Ausschnitt aus diesem Ergebnis.
Abbildung 4.32 Ergebnis der Abfrage mit ROLLUP
Die Spalten mit dem Wert NULL für den Monat enthalten jeweils die Zwischensumme für die Gruppe . In der ersten Zeile hier ist sowohl der Wert für die Spalte Jahr als auch der Wert für die Spalte Monal NULL wird die Gesamtsumme ausgegeben. CUBE Der CUBE-Operator arbeitet ähnlich wie ROLLUP. CUBE bewirkt ebenfalls die Ausgabe von Zwischensummen, nur werden diese Summen für alle Kombinationen der Werte der an der Gruppierung beteiligten Spalten erzeugt. Auch dies soll an einem Beispiel verdeutlicht werden. Hierzu verwenden wir noch einmal die bei ROLLUP entworfene Abfrage, ersetzen jedoch die Klausel WITH ROLLUP einfach durch WITH CUBE:
Es werden nun auch für jeden Monat über alle Jahre Zwischensummen ausgegeben, wie in Abbildung 4.33 zu sehen ist.
185
4 Transact SQL-Grundlagen
Abbildung 4.33 Ergebnis der Abfrage mit CUBE
Der Wert NULL in einer Spalte zeigt wiederum an, dass es sich bei dem in der Spalte Bestellwert ausgegebenen Wert um eine Zwischensumme handelt. Wie Sie in Abbildung 4.33 erkennen können, werden nun auch Summen über alle Jahre jedes Monats ausgegeben.
4.21.4 Rangfolgefunktionen Rangfolgefunktionen ermöglichen die Rückgabe eines Rangfolgewertes für jede Zeile im Abfrageergebnis. Dadurch können Sie dem Ergebnis zum Beispiel Zeilennummern hinzuzufügen oder ein Ergebnis mit einer Rangfolge versehen, die angibt, auf welcher Position eine Zeile steht, also welchen Rang diese Zeile einnimmt. Es gibt insgesamt die vier Rangfolgefunktionen: ROW_NUMBER, RANK, DENSE_RANK und NTILE. Für alle Funktionen können mittels einer OVER()-Klausel sowohl Sortierung als auch Gruppierung bzw. Partitionierung für die Rangfolge angegeben werden. Die allgemeine Struktur einer Abfrage unter Verwendung einer der vier Rangfolgefunktionen sieht hierbei so aus:
ROW_NUMBER Die Funktion ROW_NUMBER fügt dem Abfrageergebnis eine Spalte mit einer Zeilennummer hinzu. Schauen Sie sich bitte das folgende Beispiel an:
186
4.21 Komplexe Abfragen mit SELECT Über die OVER-Funktion wird die Rangfolge ausgegeben. Im Beispiel fügen wir eine Spalte Zeilennummer hinzu, die für die Sortierung nach der Spalte LastName vergeben wird. Einen Auszug aus dem Ergebnis sehen Sie in Abbildung 4.34.
Abbildung 4.34 Zeilennummerierung mit ROW_NUMBER
Eine Rangfolgefunktion kann für jede Spalte im Ergebnis hinzugefügt werden. Die folgende Abfrage enthält zwei Spalten mit dem Nachnamen eines Vertriebsmitarbeiters und seinem Gesamtbestellwert. Über ROW_NUMBER wird eine Zeilennummer hinzugefügt, wobei absteigend nach der Gesamtbestellsumme sortiert wird der Vertriebsmitarbeiter mit dem höchsten Bestellwert bekommt also die Zeilennummer 1:
Die ersten Zeilen des Ergebnisses der obigen Abfrage sehen Sie in Abbildung 4.35.
Abbildung 4.35 ROW_NUMBER mit ORDER BY DESC
187
4 Transact SQL-Grundlagen In der OVER-Funktion kann über PARTITION BY auch eine Gruppierung bzw. Partitionierung angegeben werden. In diesem Fall beginnt die Nummerierung für jede Partition von vorne. Schauen Sie sich das folgende Beispiel an, in dem eine Partitionierung nach Kalenderjahr erfolgt:
Um das Ergebnis einzuschränken, werden hier über die LIKE-Klausel nur Vertriebsmitarbeiter ausgewählt, bei denen an der zweiten Position des Nachnamens kein Vokal steht. Abbildung 4.36 zeigt einen Auszug aus dem Ergebnis.
Abbildung 4.36 ROW_NUMBER mit Partitionierung
Die Zeilennummern beginnen in jedem Jahr neu und geben so eine Rangfolge für den Bestellwert pro Vertriebsmitarbeiter und Jahr an. Für die Erzeugung von Ranglisten bietet SQL Server noch zwei weitere Funktionen an, die in den folgenden beiden Abschnitten untersucht werden. RANK und DENSE_RANK Durch die RANK-Funktion kann dem Abfrageergebnis ein Rang hinzugefügt werden, der auf dem Wert einer Spalte des Abfrageergebnisses basiert. So ist es beispielsweise möglich, die Listenpreise der Produkte mit Rangfolge zu bewerten. Zu beachten ist, dass hierbei gleiche Werte auch den gleichen Rang erhalten. Der nächste vergebene Rang ergibt sich dann durch die Anzahl der vorherigen Ränge plus eins. Das ist wie im Sport: Wenn
188
4.21 Komplexe Abfragen mit SELECT zwei Goldmedaillen vergeben werden, bekommt der nächstplatzierte nicht Silber, sondern Bronze. Dadurch können in der Rangfolge auch Lücken entstehen. Bei DENSE_RANK() wird ebenfalls eine Rangfolge vergeben, allerdings treten hierbei keine Lücken in der Nummerierung auf. Betrachten Sie bitte das folgende Beispiel, in dem eine Liste der Produkte mit ihrem Listenpreis ausgegeben wird. Hierbei wird das Produkt mit dem kleinsten Listenpreis an erster Stelle genannt, steht also in der Rangfolge an der ersten Position:
Die beiden letzten Spalten, berechnen die Rangfolge über RANK bzw. DENSE_RANK. Wenn Sie sich den Ausschnitt aus dem Ergebnis der Abfrage in Abbildung 4.37 ansehen, erkennen Sie sofort den Unterscheid zwischen diesen beiden Funktionen.
Abbildung 4.37 RANK und DENSE_RANK
NTILE Mit der NTILE-Funktion können Sie Ihren Abfrageergebnissen Gruppennummern hinzufügen. Hierbei wird nicht jede einzelne Zeile nummeriert, stattdessen wird eine bestimmte Anzahl von Zeilen zu einer Gruppe zusammengefasst. Die Zeilen einer Gruppe bekommen dann dieselbe Gruppennummer. Auch dies lässt sich sicherlich am einfachsten an einem Beispiel demonstrieren. Angenommen, die Vertriebsmitarbeiter sollen nach dem Bestellwert, für den sie verantwortlich sind, in drei unterschiedliche Kategorien Gut, Normal, Unterdurchschnittlich eingeteilt werden. Unter Verwendung von NTILE kann eine entsprechende Abfrage sehr einfach entworfen werden:
189
4 Transact SQL-Grundlagen Listing 4.2 Gruppierung mit NTILE
Die Spalte Kategorie wird eine Gruppennummer von 1 bis 3 enthalten. Dies wird durch die Angabe von erreicht. Die Einordnung in eine der drei Gruppen wird durch die OVER-Funktion anhand des Gesamtbestellwertes für den jeweiligen Vertriebsmitarbeiter vorgenommen. Das Ergebnis sicht dann so aus, wie in Abbildung 4.38 gezeigt.
Abbildung 4.38 Gruppierung mit NTILE
NTILE verwendet für die Einstufung eine Gleichverteilung, versucht also, gleich viele Zeilen in jede Gruppe einzuordnen. Gelingt dies nicht, weil die Anzahl der Zeilen kein Vielfaches der Anzahl der Gruppen ist, so werden zunächst die Gruppen mit der niedrigeren Ordnungszahl um zusätzliche Zeilen ergänzt. Dies ist im obigen Beispiel der Fall. Da die Anzahl der Zeilen (17) geteilt durch die Anzahl der Gruppen (3) einen Rest von 2 liefert, enthalten die ersten beiden Gruppen jeweils sechs Zeilen und die letzte Gruppe fünf. OVER Neben dem Einsatz in Rangfolgefunktionen kann die OVER-Klausel auch zur Berechnung von Aggregaten also zusammen mit Aggregatfunktionen verwendet werden. Dadurch haben Sie die Möglichkeit, Aggregatberechnungen auch sozusagen außerhalb von GROUP BY durchzuführen, indem Sie OVER zusammen mit PARTITION BY verwenden. Die
190
4.21 Komplexe Abfragen mit SELECT folgende Abfrage gibt die Gesamtsumme über alle Bestellwerte zusammen mit der Summe für jeden einzelnen Vertriebsmitarbeiter in einer Zeile aus:
Auffällig ist, dass kein GROUP BY mehr erforderlich ist, wenn die Gruppierung über die OVER-Klausel angegeben wird. Die gesamte OVER-Klausel kann hierbei auch leer sein, in diesem Fall wird das Aggregat über alle Zeilen berechnet. Im Beispiel werden eine Gesamtsumme und eine Summe für jeden Vertriebsmitarbeiter gebildet. Dies erfolgt für alle Zeilen der Verknüpfung der beiden an der Abfrage beteiligten Tabellen. Aus diesem Grund werden mehrfache Zeilen durch die Klausel DISTINCT noch aus dem Ergebnis entfernt. Ein Teilergebnis der Abfrage zeigt Abbildung 4.39.
Abbildung 4.39 Summierung mit OVER
4.21.5 Korrelierende Unterabfragen Die bisher besprochenen Abfragen bestanden meist nur aus einer einzigen SELECTAnweisung. SELECT-Abfragen können jedoch auch ineinander geschachtelt werden. In einem solchen Fall besteht die Abfrage aus inneren und äußeren SELECT-Anweisungen, die in der Regel durch eine Verknüpfungsbedingung miteinander verbunden sind. Solche Abfragen werden als korrelierende Unterabfragen bezeichnet. Hierbei können Unterabfragen an verschiedenen Stellen auftreten. So kann zum Beispiel eine Unterabfrage entworfen werden, die nur einen einzigen Wert (also eine Zeile und Spalte) liefert. Eine solche Abfrage darf als Spaltenausdruck eingesetzt werden:
191
4 Transact SQL-Grundlagen Die obige Abfrage liefert die Namen aller Vertriebsmitarbeiter und deren GesamtBestellwert. Der Bestellwert wird hierbei über eine Unterabfrage ermittelt. Diese Unterabfrage ist über die gemeinsame Spalte SalesPersonId mit der äußeren Abfrage verbunden. Die obige Abfrage liefert dasselbe Ergebnis wie diese:
Und doch unterscheiden sich beide Abfragen. In der ersten Abfrage würden doppelte Nachnamen für Vertriebsmitarbeiter auch in separaten Zeilen ausgegeben. Die zweite Abfrage mit GROUP BY würde diese Werte in einer Zeile zusammenfassen (was nicht korrekt ist). Auch in der Zeilenfilterbedingung können korrelierende Unterabfragen verwendet werden, zum Beispiel in Verbindung mit der IN-Klausel. Eine solche Unterabfrage darf nur eine Spalte, aber beliebig viele Zeilen liefern. Die folgende Abfrage gibt alle Jahre aus, in denen Vertriebsmitarbeiter, deren Namen mit Ab beginnt, Bestellungen initiiert haben:
4.21.6 Abgeleitete Tabellen Bei der Einführung zur SELECT-Anweisung wurde für die FROM-Klausel gesagt, dass hinter dieser Klausel eine Tabelle angegeben werden muss. Dass diese Aussage nicht ganz korrekt war und bei der Einführung nur zur Vereinfachung diente, wissen Sie inzwischen. Immerhin haben wir ja auch bereits Abfragen mit mehreren Tabellen bzw. Sichten erstellt. Wenn man es ganz genau nimmt, dann kann überall dort, wo wir bislang eine Tabelle oder Sicht verwendet haben, ein Tabellenausdruck stehen, also eine Anweisung, die eine Tabelle zurückgibt. In Kapitel 7 werden Sie sehen, wie man benutzerdefinierte Funktionen entwirft, die eine Tabelle zurückliefern. Solche Funktionen können als Tabellenausdruck in SELECT-Anweisungen verwendet werden. An dieser Stelle wollen wir zunächst betrachten, wie SELECT-Anweisungen als Tabellenausdrücke verwendet werden. Die allgemeine Regel hierzu ist recht einfach: Sowohl in der FROM-Klausel als auch im JOIN sind nicht nur Tabellen, sondern auch Tabellenausdrücke erlaubt. Betrachten Sie bitte die folgenden beiden Anweisungen, die dasselbe Ergebnis liefern:
192
4.21 Komplexe Abfragen mit SELECT Die zweite Abfrage verwendet eine sogenannte abgeleitete Tabelle. In der FROM-Klausel wird sozusagen erst die Tabelle für die eigentliche Abfrage erzeugt. Auch die folgenden beiden Abfragen sind logisch äquivalent und liefern identische Ergebnisse:
Die beiden einfachen Beispiele verdeutlichen vielleicht nicht die Nützlichkeit des Konzeptes. Es gibt aber durchaus Fälle, in denen abgeleitete Tabellen erforderlich und auch sinnvoll sind. Für abgeleitete Tabellen muss übrigens immer ein Aliasname angegeben werden. In unserem Beispiel ist dies der Name .
4.21.7 Allgemeine Tabellenausdrücke Allgemeine Tabellenausdrücke (englisch Common Table Expressions, CTE) können Sie sich wie eine Sicht vorstellen, die nur für die Dauer einer Abfrage gültig ist. Eine solche Sicht wird nicht über CREATE VIEW, sondern über die folgende Anweisung erzeugt:
Über die WITH-Klausel wird eine temporäre Sicht erzeugt, auf die dann in der anschließenden SELECT-Anweisung zugegriffen werden kann. In dieser SELECT-Anweisung kann die temporäre Sicht ganz normal in der FROM-Klausel oder auch in JOINs verwendet werden. Nach dem Beenden der gesamten Anweisung existiert die temporäre Sicht nicht mehr. Allgemeine Tabellenausdrücke sind besonders in zwei Fällen nützlich. Zum einen können sie verwendet werden, um Abfragen übersichtlicher zu gestalten. Anstelle eines MonsterSELECTs können Sie beim Einsatz von allgemeinen Tabellenausdrücken mehrere SELECT-Anweisungen verwenden und so die Übersichtlichkeit erhöhen. In einigen Fällen ist es sogar erforderlich, auf CTEs zurückzugreifen, da ein einfaches SELECT nicht die
193
4 Transact SQL-Grundlagen erforderlichen Berechnungen abbilden kann. Ein Beispiel hierfür werden Sie in Kapitel 8 kennenlernen. Ein zweites Einsatzgebiet für allgemeine Tabellenausdrücke sind rekursive Abfragen, die wir weiter hinten in diesem Abschnitt untersuchen werden. Schauen Sie sich bitte nochmals Listing 4.2 und das Ergebnis der Abfrage in Abbildung 4.38 an. Angenommen, Sie möchten statt der Gruppennummer in der letzten Spalte lieber einen Text ausgeben, der die Benotung des entsprechenden Vertriebsmitarbeiters enthält. Hierfür könnten Sie die folgende Abfrage verwenden:
Über die WITH-Klausel wird hier zunächst eine temporäre Sicht mit Namen VBOrders erstellt. Diese Sicht enthält genau die Abfrage aus Listing 4.2. In der äußeren SELECTAnweisung wird VBOrders in der FROM-Klausel ganz genau so wie eine Tabelle verwendet. Über eine CASE-Anweisung werden die Nummern der einzelnen Gruppen in Texte umgewandelt. Außerdem erfolgt eine Sortierung nach dem Nachnamen der Vertriebsmitarbeiter. Ein Teilergebnis der Abfrage zeigt Abbildung 4.40.
Abbildung 4.40 Abfrageergebnis mit allgemeinem Tabellenausdruck
Allgemeine Tabellenausdrücke sind besonders dann nützlich, wenn Sie in Ihren SELECTAnweisungen Unterabfragen verwenden müssen, die mehrfach in derselben Form auftreten. In so einem Fall lohnt sich natürlich die Überführung dieser Unterabfragen in allgemeine Tabellenausdrücke.
194
4.21 Komplexe Abfragen mit SELECT Schauen Sie sich die folgende Anweisung an, in der eine durch einen allgemeinen Tabellenausdruck deklarierte Abfrage mit sich selbst verknüpft wird:
Die Abfrage ermittelt die Anzahl der Bestellungen pro Jahr und Monat und gibt auch eine laufende Summe aus. In Abbildung 4.41 sehen Sie einen Auszug aus dem Ergebnis. Wenn Sie versuchen, dasselbe Ergebnis ohne Verwendung einer CTE zur erhalten, wird die Abfrage erheblich komplizierter (versuchen Sie es ruhig einmal!). Die obige CTE-Deklaration zeigt auch, wie Sie Spaltennamen für die Abfrage in Klammern angeben können.
Abbildung 4.41 Berechnung der laufenden Summe mit einem allgemeinen Tabellenausdruck
Es ist übrigens auch möglich, mehrere Tabellenausdrücke zu deklarieren und diese dann in einer anschließenden SELECT-Anweisung zu verwenden. Es besteht also keine Beschränkung auf nur eine CTE. Wenn Sie mehrere CTEs verwenden möchten, so müssen die einzelnen Deklarationen jeweils durch ein Komma voneinander getrennt werden. Das sieht dann so aus:
195
4 Transact SQL-Grundlagen
Die Abfrage verwendet zwei CTEs,. um den Bestellwert in allen Produktkategorien über die Jahre zu summieren. Ein Teilergebnis der Abfrage zeigt Abbildung 4.42.
Abbildung 4.42 Teilergebnis für mehrere CTEs
Rekursive Abfragen Eine interessante Möglichkeit mit allgemeinen Tabellenausdrücken ist der Umstand, dass in der inneren SELECT-Anweisung auf den Tabellenausdruck selber verwiesen werden kann. Dadurch sind bei der Verwendung von CTEs rekursive Abfragen möglich. Dies soll am Beispiel der Tabelle HumanResources.Employee gezeigt werden. Zunächst einmal schauen wir uns einige Zeilen und Spalten der Tabelle an und verwenden hierfür die folgende Anweisung:
In Abbildung 4.43 sehen Sie einen Auszug aus dem Ergebnis der Abfrage.
196
4.21 Komplexe Abfragen mit SELECT
Abbildung 4.43 Inhalt der Tabelle HumanResources.Employee
Interessant ist hierbei vor allem die Spalte ManagerID. Durch diese Spalte verweist die Employee-Tabelle auf sich selber. Jeder Manager ist auch wieder ein Angestellter, wird also ebenfalls durch eine Zeile in der Tabelle Employee repräsentiert. Der in der Spalte ManagerID enthaltene Wert verweist also auf eine EmployeeID in derselben Tabelle. Hierbei kann ein Manager natürlich auch wieder einen übergeordneten Manager haben. Lediglich ein einziger Angestellter besitzt keinen Manager, dies ist gerade der Chef der gesamten Firma (siehe Abbildung 4.43). Im Normalfall sind derartige rekursive Strukturen mit SQL nur sehr schwer abzubilden. Die Abfrage aller Angestellten mit ihren Managern ist mit normalen Mitteln relativ schwierig und unübersichtlich nicht so jedoch beim Verwenden einer CTE. Allgemeine Tabellenausdrücke erlauben rekursive Abfragen. Hierbei kann eine CTE auf sich selbst verweisen. Eine CTE muss hierfür zunächst ein sogenanntes Anker-Element definieren, welches das Ende einer Rekursion kennzeichnet. Über den UNION ALL-Operator kann dann der rekursive Teil der Abfrage hinzugefügt werden. Angenommen, es sollen alle Mitarbeiter mit ihren Managern angezeigt werden. Eine entsprechende CTE-Abfrage sieht so aus:
197
4 Transact SQL-Grundlagen Der erste Teil der Abfrage legt das Anker-Element fest. Dies ist gerade der einzige Mitarbeiter ohne Manager, also der oberste Chef. Im Anschluss wird dann die Rekursion definiert. Die CTE verweist hier im JOIN auf sich selber über eine Verknüpfung der Spalten ManagerID und EmployeeID. Die äußere SELECT-Anweisung muss schließlich nur noch die erzeugte Liste zurückgeben. Der SQL Server schützt sich vor Endlos-Rekursionen in allgemeinen Tabellenausdrücken. Zu diesem Zweck ist die maximale Rekursionstiefe auf 100 festgelegt. Beim Überschreiten dieser Rekursionstiefe erzeugt die Abfrage eine Fehlermeldung. Über die Option MAXRECURSION kann auch ein anderer Wert definiert werden, im Beispiel verwenden wir eine maximale Rekursionstiefe von 10. Einen Ausschnitt aus dem Ergebnis der Abfrage sehen Sie in Abbildung 4.44.
Abbildung 4.44 Eine rekursive CTE-Abfrage
4.21.8 PIVOT und UNPIVOT Sehen Sie sich bitte die folgende Abfrage an, die eine Summe der Bestellwerte je Vertriebsmitarbeiter und Kalenderjahr ermittelt:
198
4.21 Komplexe Abfragen mit SELECT Abbildung 4.45 zeigt ein Teilergebnis der obigen Abfrage.
Abbildung 4.45 Bestellwert pro Mitarbeiter und Kalenderjahr
Angenommen, Sie möchten nun anstelle der Ausgabe einer separaten Zeile für jedes Kalenderjahr und jeden Vertriebsmitarbeiter die Ausgabe in einer zweidimensionalen Matrix organisieren. Hierbei soll für jeden Vertriebsmitarbeiter eine Zeile und für jedes Jahr eine Spalte in der Matrix enthalten sein. Die Zelle am Kreuzungspunkt zwischen Vertriebsmitarbeiter und Jahr wird dann den entsprechenden Bestellwert enthalten. Das Ergebnis soll also so aussehen, wie in Abbildung 4.46 gezeigt:
Abbildung 4.46 Vertauschung von Zeilen und Spalten
Haben Sie eine Idee, wie so etwas bewerkstelligt werden kann? Die CASE-Anweisung hilft hier weiter. Sie können für jedes Jahr eine eigene berechnete Spalte erzeugen und über eine CASE-Anweisung jeweils nur diejenigen Werte summieren, die zur Spaltenüberschrift also zum Jahr passen. Eine entsprechende SELECT-Anweisung sieht dann so aus:
199
4 Transact SQL-Grundlagen
Diese Anweisung erzeugt exakt die Ausgabe aus Abbildung 4.46. Über den PIVOT-Operator ist es möglich, auch ohne CASE-Anweisung auszukommen. Für die obige SELECT-Anweisung sieht eine adäquate Anweisung unter Verwendung von PIVOT so aus:
Stören Sie sich bitte nicht an der Verwendung einer abgeleiteten Tabelle in der FROMKlausel. Entscheidend ist der PIVOT-Operator, der folgendermaßen arbeitet: Der erste Ausdruck in der Klammer ist eine Aggregatfunktion, die für die einzelnen Zellen der Ausgabematrix berechnet wird. Die FOR-Klausel bestimmt, aus welcher Spalte die Daten für die Ergebnisspalten berechnet werden sollen. Über die IN-Klausel werden die in der Ausgabe enthaltenen Spalten explizit angegeben. Und wie werden die Zeilen für die Ausgabematrix bestimmt? Diese Zeilen ergeben sich durch Gruppierung nach den Spalten der äußeren SELECT-Anweisung, die nicht explizit in der PIVOT-Klausel angegeben werden. In unserem Fall ist dies nur die Spalte VB. Wenn Sie zu Beginn Verständnisprobleme mit der Arbeitsweise des PIVOT-Operators haben, so liegt dies sicherlich vor allem daran, dass die Bestimmung der Zeilen für die Ausgabe nicht so ohne Weiteres zu erkennen ist. Verzweifeln Sie in solch einem Fall bitte nicht, Sie sind mit diesen Problemen in bester Gesellschaft! Ich habe bislang noch niemanden getroffen mich selbst eingeschlossen der die Arbeitsweise von PIVOT auf Anhieb verstanden hat. Mein Rat: Probieren Sie einfach unterschiedliche Varianten aus, und versuchen Sie das erzeugte Ergebnis bzw. die produzierten Fehler zu interpretieren. Die beiden SELECT-Anweisungen unter Verwendung von CASE und PIVOT liefern ein fast identisches Ergebnis. Wenn Sie die beiden Anweisungen ausprobieren, werden Sie feststellen, dass der Unterschied lediglich in der Darstellung von Zellen besteht, für die keine Werte existieren. Die Version mit CASE gibt in diesen Spalten den Wert 0 aus, bei der Verwendung von PIVOT erhalten Sie an dieser Stelle eine NULL. Wir können die Variante mit PIVOT jedoch einfach anpassen und die NULL-Werte in 0 umwandeln:
200
4.21 Komplexe Abfragen mit SELECT
Der Operator UNPIVOT ist die Umkehrung von PIVOT. Mit UNPIVOT können Spalten in Zeilen konvertiert werden. Angenommen, wir hätten eine Tabelle PivotTable folgendermaßen erzeugt:
Der Inhalt dieser Tabelle ist in Abbildung 4.46 zu sehen. Wenn nun die Spalten für die einzelnen Jahre in Zeilen ausgegeben werden sollen, könnte so verfahren werden:
Die einzelnen SELECT-Anweisungen geben jeweils die Werte für ein Jahr zurück. Die SELECT-Anweisungen werden einfach durch UNION-Operatoren aneinandergehängt, das Ergebnis der gesamten Abfrage ist somit das bereits in Abbildung 4.45 gezeigte. Durch die Verwendung des UNPIVOT-Operators kann dasselbe Ergebnis erzielt werden. Die entsprechende Abfrage sieht so aus:
Im UNPIVOT-Operator wird zunächst die Spalte angegeben, für welche die UNPIVOTOperation ausgeführt werden soll. In unserem Fall ist dies die Spalte Betrag. In der
201
4 Transact SQL-Grundlagen FOR-Klausel werden dann die Spalten, die in Zeilen konvertiert werden sollen, explizit benannt. Ein Vergleich von PIVOT und CASE zeigt keinerlei Unterschiede in der Laufzeit. Hier ist es tatsächlich egal, ob Sie die CASE- oder PIVOT-Version bevorzugen. Bei UNPIVOT und UNION sieht die Sache ein wenig anders aus. Die Version mit UNPIVOT ist schneller als eine vergleichbare Anweisung unter Verwendung mehrerer SELECT-Anweisungen, die mit UNION verknüpft werden. Hier sollten Sie also in jedem Fall UNPIVOT den Vorzug geben.
4.21.9 OUTPUT In DELETE-, INSERT- und UPDATE-Anweisungen kann über die OUTPUT-Klausel eine genaue Information darüber ausgegeben werden, welche Zeilen von der jeweiligen Operation betroffen waren. Zu diesem Zweck stehen Ihnen innerhalb der OUTPUT-Klausel die beiden virtuellen Tabellen INSERTED und DELETED zur Verfügung, die Informationen über den Zustand vor und nach der Datenänderung enthalten. Nicht für alle Datenänderungsanweisungen stehen beide Tabellen zur Verfügung. Tabelle 4.1 enthält eine Übersicht über die mögliche Verwendung dieser Tabellen. Tabelle 4.24 INSERTED und DELETED Virtuelle Tabelle
Beschreibung
Verfügbar für
INSERTED
Enthält die Spaltenwerte nach der Änderungsoperation
INSERT UPDATE
DELETED
Enthält die Spaltenwerte vor der Änderungsoperation
DELETE UPDATE
Die folgende Anweisung löscht Zeilen aus der gerade im vorangegangenen Abschnitt erzeugten Tabelle PivotTable und gibt die gelöschten Zeilen im Ergebnisbereich aus:
Die UPDATE-Anweisung ist die einzige Anweisung, innerhalb der ein Zugriff auf beide Tabellen INSERTED und DELETED möglich ist. Die folgende UPDATE-Anweisung ändert einige Zeilen in der Tabelle PivotTable und gibt den Zustand vor und nach der Änderung aus:
Abbildung 4.1 zeigt das via OUTPUT erzeugte Ergebnis der Abfrage:
202
4.21 Komplexe Abfragen mit SELECT
Abbildung 4.47 Ergebnis der UPDATE-Operation mit OUTPUT
In Kapitel 7 werden Sie sehen, wie Sie die OUTPUT-Klausel in der Programmierung mit T-SQL verwenden können.
4.21.10 APPLY APPLY ermöglicht das Aufrufen eines Tabellenwertausdrucks für jede Zeile, die von einer Abfrage zurückgegeben wird. Ein Tabellenwertausdruck ist ein Ausdruck, der eine Tabelle zurückgibt. Dies kann zum Beispiel eine Abfrage oder auch eine Tabellenwertfunktion (siehe Kapitel 7) sein. Es gibt zwei Sorten von APPLY: CROSS APPLY und OUTER APPLY, die sich ähnlich wie bei JOINs in der Behandlung von nicht existierenden Zeilen unterscheiden. CROSS APPLY liefert nur Werte für Zeilen, in denen der Tabellenwertausdruck auch ein Ergebnis liefert, über OUTER APPLY werden alle Zeilen zurückgegeben, wobei für nicht existierende Zeilen NULL als Spaltenwert eingetragen wird. Die folgende Abfrage gibt die drei letzten Bestellungen des Vertriebsmitarbeiters mit der 284 zurück:
Wenn Sie nun für alle Angestellten eine Liste der letzten drei initiierten Bestellungen erhalten möchten, so können Sie die obige Abfrage für jeden Mitarbeiter ausführen, wobei Sie jeweils die des Mitarbeiters als Parameter für die Abfrage verwenden. Durch den APPLY-Operator ist eine solche Abfrage sehr einfach zu schreiben:
Von der äußeren Abfrage werden alle Mitarbeiter zurückgeliefert. Für jede Zeile dieser Abfrage wird dann über CROSS APPLY eine weitere Abfrage gestartet, an die sozusagen
203
4 Transact SQL-Grundlagen als Parameter die des Mitarbeiters übergeben wird. Die über CROSS APPLY ausgeführte Abfrage liefert dann die letzten drei Bestellungen für den übergebenen Mitarbeiter. Ein Teilergebnis der Abfrage zeigt Abbildung 4.48:
Abbildung 4.48 Ergebnis der Abfrage mit CROSS APPLY
Wenn Sie in der obigen Abfrage CROSS durch OUTER ersetzen, dann werden in das Ergebnis immer alle Zeilen der äußeren Abfrage aufgenommen, und zwar unabhängig davon, ob der APPLY-Ausdruck Zeilen zurückliefert oder nicht. OUTER APPLY funktioniert also im Prinzip so wie ein OUTER JOIN: Die Spalten, für die der Tabellenwertausdruck keine Zeilen liefert, werden in der Ausgabe mit dem Wert NULL belegt. In Abbildung 4.49 sehen Sie einen Ausschnitt aus dem OUTER APPLY-Ergebnis.
Abbildung 4.49 Ergebnis der Abfrage mit OUTER APPLY
In Kapitel 7 wird der APPLY-Operator nochmals in Verbindung mit Tabellenwertfunktionen verwendet.
204
4.22 Zusammenfassung
4.22
Zusammenfassung In diesem Kapitel haben Sie eine umfassende Einführung in die Verwendung von T-SQL und die Möglichkeiten für die Abfrage sowie das Einfügen, Ändern und Löschen von Daten mittels T-SQL erhalten. Sie wissen nun, welche Datentypen T-SQL zur Verfügung stellt, wie Sie Ausdrücke zur Berechnung von einzelnen Werten oder auch kompletten Tabellen verwenden können und wie Sie in solchen Ausdrücken integrierte Funktionen einsetzen. Das Kapitel hat Ihnen gezeigt, wie Datenbanken angelegt und Tabellen innerhalb von Schemas in Datenbanken organisiert werden. Das Anlegen von Tabellen und die Festlegung von Integritätsbedingungen für diese Tabellen war ein wesentlicher Bestandteil dieses Kapitels. Ihnen ist nun auch klar, wofür Sichten eingesetzt werden können und welche Möglichkeiten Sie durch die Verwendung von Sichten als Datenzugriffsschicht haben. Eine sehr wichtige Besonderheit von SQL ist die Behandlung von NULL-Werten. Dieser Thematik wurde daher ein eigener Abschnitt innerhalb dieses Kapitels gewidmet. Zum Abschluss des Kapitels haben Sie schließlich einen Einblick in die Entwicklung komplexer Abfragen erhalten.
205
5 Transaktionen und Sperren Transaktionen sind ein sehr wesentliches Konzept für DBMS. Durch Transaktionsmechanismen stellt ein DBMS die Funktionalität zur Gewährleistung der Datenintegrität zur Verfügung. Innerhalb von Transaktionen sperrt der SQL Server Ressourcen, die von der Transaktion verwendet werden, und verweigert dadurch anderen Transaktionen den gleichzeitigen Zugriff auf diese Ressourcen. Diese Sperren ermöglichen letztlich einen konkurrierenden Zugriff auf Ressourcen, da Anforderungen an eine gesperrte Ressource in eine Warteschlange eingereiht und nacheinander abgearbeitet werden. In diesem Kapitel werden Sie erfahren, wie das SQL Server-Transaktionsmodell aufgebaut ist. Hierbei wird Ihnen deutlich werden, in welcher Weise der SQL Server Ressourcen für andere Prozesse sperrt und wie der Datenbankentwickler die Art und Weise dieser Sperren beeinflussen kann.
5.1
Eigenschaften von Transaktionen Transaktionen fassen einzelne SQL-Anweisungen zu Gruppen zusammen. Die wesentliche Eigenschaft einer Transaktion ist, dass alle in ihr enthaltenen Anweisungen entweder als Gesamtheit oder gar nicht ausgeführt werden. Das Paradebeispiel für eine bildhafte Erklärung ist hier immer wieder eine finanzielle Transaktion (in diesem Zusammenhang wird der Begriff Transaktion aus der Bankenwelt verwendet), bei der von einem Konto ein Betrag abgebucht und dann einem anderen Konto gutgeschrieben wird. Eine solche Überweisung kann durch zwei einfache INSERT-Anweisungen ausgeführt werden:
Ein Problem tritt auf, wenn zum Beispiel nach der Ausführung der ersten INSERTAnweisung ein Systemausfall zu verzeichnen ist, sodass die Ausführung der zweiten INSERT-Anweisung verhindert wird. In diesem Fall wäre zwar eine Abbuchung, nicht aber
207
5 Transaktionen und Sperren die Zubuchung auf das zweite Konto erfolgt. Um so etwas zu vermeiden, können Sie beide Anweisungen in eine Transaktion einbetten. Der SQL Server wird dann durch seine Transaktionsmechanismen sicherstellen, dass entweder beide Anweisungen ausgeführt werden oder eben keine. Stellen Sie sich nun bitte einmal vor, dass eine Benutzerin den durchschnittlichen Listenpreis aller Produkte wissen möchte. Sie führt dafür folgendes Kommando aus:
Während dieses Kommando aktiv ist, wird in der Vertriebsabteilung entschieden, dass alle Listenpreise um 3% erhöht werden sollen. Hierzu verwendet eine Mitarbeiterin dieses UPDATE-Kommando:
Die beiden Anweisungen konkurrieren im Zugriff auf die Zeilen der Tabelle Production.Product. Wenn das UPDATE-Kommando ausgeführt wird, während das SELECT-Kommando läuft, wird das SELECT auf einen inkonsistenten Datenbestand zugreifen und ein falsches Ergebnis liefern. Es muss daher dafür gesorgt werden, dass beide Anweisungen nicht gleichzeitig auf die Tabelle zugreifen, sondern gegeneinander abgeschirmt (isoliert) werden. Auch hierfür bieten Transaktionen die entsprechenden Mechanismen an. Allgemein zeichnen sich Transaktionen durch vier grundlegende Eigenschaften aus: Atomicity (Atomarität), Consistency (Konsistenz), Isolation (Isolation) und Durability (Beständigkeit). Diese vier Eigenschaften werden Ihnen oftmals einfach in der Abkürzung ACID begegnen. Hinter diesen vier Transaktionseigenschaften verbergen sich die folgenden Konzepte: Atomarität (Atomicity) Das Atomaritätsprinzip kennzeichnet das Ganz oder gar nicht-Verhalten einer Transaktion. Wir haben dieses Prinzip bereits in unserem Beispiel mit den Kontobewegungen veranschaulicht, die entweder als Gesamtheit oder gar nicht ausgeführt werden sollen. Es werden also entweder alle in der Transaktion enthaltenen Änderungen permanent gespeichert oder keine. Eine Transaktion ist nicht teilbar ganz im Gegensatz zu Atomen übrigens, die für die Namensgebung dieser Eigenschaft herhalten mussten. Konsistenz (Consistency) Mit Konsistenz ist gemeint, dass sich der Datenbestand nach Abschluss einer Transaktion in einem Zustand befindet, in dem alle existierenden Integritäts-, aber auch Geschäftsregeln gültig sind. Änderungen, die gegen bestehende Regeln verstoßen, werden nicht zugelassen. Bitte bedenken Sie, dass diese Aussage nur für abgeschlossene, also bestätigte oder abgebrochene, Transaktionen gilt. Während der Ausführung einer Transaktion kann sehr wohl gegen geltende Regeln verstoßen werden. Nach Abschluss der Transaktion allerdings darf dies nicht mehr der Fall sein. Eine Transaktion überführt also einen Datenbestand aus einem konsistenten Zustand in einen anderen.
208
5.1 Eigenschaften von Transaktionen Isolation (Isolation) Transaktionen isolieren sich gegen andere Transaktionen, indem sie benötigte Ressourcen sperren und somit anderen Transaktionen zeitweilig den Zugriff auf diese Ressourcen verweigern. Weiter oben haben wir ein Beispiel mit der gleichzeitigen Abfrage des durchschnittlichen Listenpreises und der Aktualisierung aller Listenpreise betrachtet. Diese beiden Abfragen müssen gegeneinander isoliert werden, damit eine gleichzeitige Ausführung verhindert wird. Im einfachsten Fall können Sie sich diese Isolierung wie eine Serialisierung vorstellen: Die zweite Abfrage muss einfach so lange warten, bis die erste Abfrage beendet wurde. Auf diese Art und Weise wird dem Benutzer eine quasi Parallelverarbeitung vorgegaukelt, während die Sperrmechanismen innerhalb der Transaktion dafür sorgen, dass beide Anweisungen in Wirklichkeit nacheinander abgearbeitet werden. Dies ist natürlich mit Wartezuständen verbunden, da Abfragen, die Ressourcen verwenden möchten, die gerade von einer anderen Transaktion gesperrt sind, auf die Freigabe dieser Ressourcen warten müssen. Um solche Wartezustände zu minimieren, ist die Transaktionsisolation in verschiedenen Stufen einstellbar. Durch die Einstellung der Isolierungsstufe können Sie somit das Verhalten Ihrer Transaktionen entscheidend beeinflussen. Wir werden später in diesem Kapitel noch genauer auf die Einstellung der Isolierungsstufe und die Sperrmechanismen des SQL Servers eingehen. Beständigkeit (Durability) Mit Transaktionsbeständigkeit ist gemeint, dass alle abgeschlossenen Transaktionen dauerhaft in der Datenbank gespeichert sind. Selbst ein Systemausfall kann nicht dazu führen, dass bestätigte Transaktionen in Ihrem Datenbestand fehlen. Wie der SQL Server dies unter Verwendung des Transaktionsprotokolls sicherstellt, wird später in diesem Kapitel noch erläutert. Abschließend möchte ich zu dieser Einführung noch einen Gedanken äußern: Transaktionen bieten Ihnen als Datenbankentwickler die Möglichkeit, Datenänderungen so zusammenzufassen, dass diese ganz oder gar nicht ausgeführt werden. Die Transaktionsmechanismen können aber natürlich nicht überwachen, wie semantisch sinnvoll Sie Ihre Transaktionen entworfen haben und ausführen. So könnten Sie im obigen Beispiel mit den Kontobewegungen ja auch jede der beiden INSERT-Anweisungen in eine separate Transaktion einfügen und nicht beide zusammen in eine. Aus der Sicht des DBMS wäre dies völlig korrekt, aus der Sicht Ihrer Anwendung sicherlich nicht. Es liegt also in der Verantwortung des Entwicklers, dass eine Transaktion nur bestätigt wird, wenn die Daten in einem logisch korrekten Zustand sind bei dieser Entscheidung kann Ihnen auch ein hoch entwickeltes DBMS nicht helfen.
209
5 Transaktionen und Sperren
5.2
Start und Ende einer Transaktion Generell werden Ihre SQL-Anweisungen immer innerhalb einer Transaktion ausgeführt Punkt. An dieser Tatsache können Sie nichts ändern und wollen das hoffentlich auch nicht. Durch das Ausführen aller Kommandos innerhalb von Transaktionen schützt das DBMS Ihre Daten vor Inkonsistenz und ermöglicht konkurrierenden Zugriff auf gemeinsam verwendete Daten. Sie können eine Transaktion explizit mit dem Kommando BEGIN TRANSACTION (es reicht auch einfach BEGIN TRAN) starten. Optional können Sie der Transaktion auch einen Namen geben, den Sie hinter BEGIN TRANSACTION angeben. Möchten Sie die innerhalb der Transaktion vorgenommenen Änderungen permanent speichern, so beenden Sie die Transaktion mit COMMIT TRANSACTION (auch dieses Kommando können Sie durch COMMIT TRAN oder auch einfach nur durch COMMIT abkürzen.) Falls die Änderungen zurückgenommen werden sollen, verwenden Sie ROLLBACK, ROLLBACK TRAN oder ROLLBACK TRANSACTION. Eine Transaktion kann also zum Beispiel so aussehen:
Die obige Transaktion erhöht den Listenpreis für alle vorhandenen Produkte und gibt anschließend die neue Produktliste aus. Dann wird die Transaktion allerdings nicht bestätigt, sondern zurückgenommen, der alte Listenpreis ist also weiterhin gültig. Transaktionen können geschachtelt werden. In diesem Fall gilt ein ROLLBACK oder COMMIT immer für die unmittelbar davor liegende BEGIN TRANSACTIONAnweisung. Über die globale Variable @@TRANCOUNT können Sie die aktuelle Anzahl aktiver Transaktionen abfragen.
Das innere ROLLBACK (oder COMMIT) hat für den Datenbestand keinerlei funktionelle Bedeutung. Nur ein COMMIT für die äußere Transaktion sorgt dafür, dass Ihre Daten permanent gespeichert werden. Innere COMMITs oder ROLLBACKs erniedrigen ledig-
210
5.2 Start und Ende einer Transaktion lich den Transaktionszähler, vermindern also den Wert von @@TRANCOUNT um 1, genau so, wie ein inneres BEGIN TRANSACTION diesen Zähler lediglich erhöht. Im obigen Beispiel wird also der Wert der Variablen @@TRANCOUNT in der inneren Transaktion 2 sein, das COMMIT der inneren Transaktion bewirkt hier keine permanente Datenspeicherung, da die gesamte Transaktion durch das ROLLBACK in der äußeren Transaktion zurückgesetzt wird. Sie müssen unbedingt darauf achten, dass Ihre T-SQL-Stapel nach der Beendigung keine offenen Transaktionen mehr haben; all Ihre mit BEGIN TRANSACTION gestarteten Transaktionen also auch mit ROLLBACK oder COMMIT abgeschlossen wurden. Laufende, also offene, Transaktionen halten die benötigten Sperren von Datenbank- und Serverobjekten aufrecht und schränken dadurch die Möglichkeiten des konkurrierenden Zugriffs auf diese Objekte ein, was wiederum ein teilweise beträchtlich verschlechtertes Laufzeitverhalten Ihrer Applikationen zur Folge haben kann. Über @@TRANCOUNT können Sie abfragen, ob beim Beenden Ihres T-SQL Stapels noch offene Transaktionen vorhanden sind. Ist dies der Fall, so müssen Sie entsprechend reagieren und diese Transaktionen bestätigen oder abbrechen. Weiter oben habe ich Ihnen gesagt, dass all Ihre T-SQL-Anweisungen immer innerhalb einer Transaktion ausgeführt werden. Was ist denn aber nun, wenn Sie kein BEGIN TRANSACTION verwenden, also einfach nur ein T-SQL-Kommando eingeben, so wie wir es bisher in zahlreichen Beispielen getan haben? Wie werden Ihre Anweisungen in so einem Fall in Transaktionen verpackt? Der SQL Server erlaubt Ihnen die Konfiguration dieser automatischen Transaktionen. Lesen Sie hierzu einfach den folgenden Abschnitt.
5.2.1
Transaktionsmodi des SQL Servers
SQL Server kennt die drei unterschiedlichen Transaktionsmodi, Autocommit, Implizit und Explizit, mit denen Sie steuern können, wie Ihre Anweisungen in Transaktionen eingeschlossen werden: Autocommit Dies ist das Standardverhalten des SQL Servers. Jede einzelne T-SQL-Anweisung wird als eine eigene Transaktion behandelt. Schlägt die Anweisung fehl, so wird für diese eine Anweisung ein ROLLBACK durchgeführt, anderenfalls ein COMMIT. Wichtig ist, dass Sie verstehen, dass tatsächlich jede einzelne Anweisung als separate Transaktion angesehen wird, nicht der gesamte T-SQL-Stapel. Implizite Transaktionen Eine Transaktion wird implizit durch SQL-Kommandos, die Daten verändern, gestartet Die Kommandos, die eine implizite Transaktion öffnen, sind:
211
5 Transaktionen und Sperren ALTER TABLE CREATE DELETE DROP FETCH GRANT INSERT OPEN REVOKE SELECT TRUNCATE TABLE UPDATE Wenn eine Verbindung sich bereits in einer impliziten Transaktion befindet, so wird durch die angegebenen Kommandos allerdings keine weitere Transaktion geöffnet. (Stören Sie sich bitte nicht daran, wenn Sie im Moment noch nicht alle der oben aufgelisteten Befehle verstehen, wir werden diese in späteren Kapiteln noch behandeln.) Jedes der angeführten Kommandos startet also implizit ein BEGIN TRANSACTION. Das COMMIT oder ROLLBACK müssen Sie im T-SQL-Code selber durchführen. Dies dürfen Sie auf keinen Fall vergessen, da offene Transaktionen wie bereits oben erwähnt Ressourcen sperren und somit andere Prozesse blockieren. Sie versetzen eine Verbindung in den impliziten Transaktionsmodus durch das Kommando SET IMPLICIT_TRANSACTIONS ON:
Das UPDATE-Kommando startet eine implizite Transaktion. Das abschließende ROLLBACK oder COMMIT ist hier obligatorisch! Alternativ können Sie die Einstellung des impliziten Transaktionsmodus auch über das Menü Abfrage/Abfrageoptionen
und im Fenster Abfrageoptionen dann im Bereich Ausführung/ANSI vornehmen, wie Sie in Abbildung 5.1 sehen.
212
5.3 Sperren
Abbildung 5.1 Transaktionsmodus auf Implizit setzen
Explizite Transaktionen Der explizite Transaktionsmodus wird stets automatisch aktiviert, sobald Sie das Kommando BEGIN TRANSACTION absetzen. Ein korrespondierendes ROLLBACK oder COMMIT beendet den expliziten Modus wieder.
5.3
Sperren In den vorangegangenen Abschnitten wurde bereits mehrfach erwähnt, dass der SQL Server innerhalb von Transaktionen Ressourcen sperrt und somit anderen Transaktionen den gleichzeitigen Zugriff auf diese Ressourcen verwehren kann. Transaktionen, die bereits gesperrte Ressourcen verwenden möchten, müssen eventuell auf die Freigabe dieser Ressourcen durch die sperrende Transaktion warten. Eventuell deshalb, weil es durchaus auch Sperren gibt, die einen gleichzeitigen Zugriff auf gesperrte Ressourcen gestatten. In diesem Abschnitt soll nun näher auf die Sperrmechanismen des SQL Servers eingegangen werden. Hierbei werden folgende Fragen geklärt: Welche Ressourcen sind überhaupt sperrbar? Weiter oben wurde bereits am Rande erwähnt, dass innerhalb von Transaktionen Server- und Datenbankobjekte gesperrt werden können. SQL Server kennt allerdings noch eine Reihe weiterer sperrbarer Ressourcen. Wie sperrt der SQL Server diese Ressourcen? Abhängig von der jeweiligen SQLAnweisung gibt es unterschiedliche Sperrtypen, die sogar kompatibel zueinander sein können. Wie beeinflussen Sperren Transaktionen, und wie kann der Datenbankentwickler hierauf Einfluss nehmen?
213
5 Transaktionen und Sperren
5.3.1
Sperrbare Ressourcen
SQL Server kann eine Reihe von Ressourcen sperren, die für den Datenbankentwickler wichtigsten sind in Tabelle 5.1 aufgeführt: Tabelle 5.1 Sperrbare Ressourcen des SQL Servers Ressource
Erklärung
RID
Eine Zeile in einer Tabelle über die RowID dieser Zeile
KEY
Eine Schlüsselsperre, d. h. eine Zeile in einem Index
HOBT
Heap Or BTree. Eine Sperre auf einem Heap oder Index
PAGE
Eine 8 KB große Seite in einer Tabelle oder einem Index.
EXTENT
Eine zusammenhängende Gruppe von acht Seiten
TABLE
Eine gesamte Tabelle
DATABASE
Eine gesamte Datenbank
METADATA
Sperre für den Zugriff auf Kataloginformationen
SQL Server versucht stets, mit Sperren sparsam umzugehen, also so wenige Ressourcen wie möglich zu sperren. Durch dieses Verhalten wird die Parallelverarbeitung verbessert: Je weniger gesperrt ist, umso weniger Blockierungen, also Wartezustände, können natürlich auftreten. Der SQL Server kennt allerdings auch einen Mechanismus namens Lock Escalation. Durch diesen Mechanismus können viele kleine Sperren aufgehoben und durch einige wenige Sperren der nächsthöheren Ressource ersetzt werden. Lock Escalation wird immer dann angewendet, wenn die Anzahl der aufrechtzuerhaltenden Sperren so groß wird, dass die Sperren selber zu viele Ressourcen belegen. Das Sperren und Freigeben von Elementen benötigt ja zum Beispiel auch Zeit und Speicherplatz. In so einem Fall kann SQL Server entscheiden, lieber wenige Sperren auf großen Einheiten als viele Sperren auf kleinen Einheiten zu verwenden. Schauen Sie sich bitte noch einmal unsere Anweisung zum Erhöhen des Listenpreises an:
SQL Server wird zunächst beginnen, die Zeilen in der Tabelle Production.Product zu sperren. Sind so viele Zeilen betroffen, dass hierfür zu viele SQL Server-Ressourcen benötigt werden, so können die Zeilensperren aufgehoben und stattdessen die nächsthöheren Einheit, also Seiten gesperrt werden. Sind auch hier zu viele Seiten betroffen, so mag der Server entscheiden, dass nun Extents gesperrt werden. Wenn die Anzahl der gesperrten Extents ebenfalls zu groß wird, so kann schließlich die gesamte Tabelle gesperrt werden. Bei Lock Escalation geht der SQL Server davon aus, dass die Ausführungszeit der Abfrage durch weniger Sperren auf größeren Objekten insgesamt besser ist. Sie können sich die momentan aktiven Sperren im Aktivitätsmonitor ansehen. Die dynamische Verwaltungssicht , die für jede Datenbank existiert, gibt Ihnen ebenfalls Auskunft über die aktuellen Sperranforderungen für eine Datenbank. Sie können
214
5.3 Sperren dies ausprobieren, indem Sie zunächst eine explizite Transaktion starten und dann nochmals unser UPDATE-Kommando innerhalb dieser Transaktion ausführen, ohne allerdings die Transaktion auch explizit zurückzusetzen oder zu bestätigen:
Das erste SELECT-Kommando gibt die ProzessID der Verbindung zurück, die wir später im Aktivitätsmonitor noch benötigen werden. Alternativ können Sie diese ID auch in der Statuszeile des Abfragefensters in Klammern neben Ihrem Anmeldenamen sehen:
Da in der Transaktion das abschließende COMMIT oder ROLLBACK fehlt, werden die von dieser Transaktion verwendeten Sperren aufrechterhalten und können im Aktivitätsmonitor beobachtet werden. Den Aktivitätsmonitor starten Sie über den Objekt-Explorer im Zweig Verwaltung/Aktivitätsmonitor (Abbildung 5.2).
. Abbildung 5.2 Starten des Aktivitätsmonitors
Wählen Sie im linken Teil des Aktivitätsmonitors Sperren nach Prozess und dann in der Combobox Ausgewählter Prozess Ihre Prozess-ID. Anschließend können Sie sehen, welche Sperren Ihre nicht abgeschlossene Transaktion aufrechterhält (Abbildung 5.3).
215
5 Transaktionen und Sperren
Abbildung 5.3 Aktive Sperren im Aktivitätsmonitor
In Beispiel sehen Sie eine Reihe von Seitensperren, die aus dem Lock-EscalationMechanismus hervorgegangen sind. SQL Server hat hier also entschieden, die einzelnen Zeilensperren in Seitensperren zu überführen. Alternativ können Sie die aktiven Sperren auch über die dynamische Verwaltungssicht abfragen:
Diese Sicht gibt im Prinzip genau die im Aktivitätsmonitor dargestellte Tabelle zurück. enthält die aktiven Sperren aller Verbindungen, sie können aber, wie im obigen Beispiel gezeigt, auch nur die Sperren für eine bestimmte Verbindung herausfiltern. Im Aktivitätsmonitor sehen Sie auch eine Spalte Anforderungsmodus, die Auskunft darüber gibt, welcher Sperrtyp angefordert wurde. Die verschiedenen Sperrtypen sollen nun im folgenden Abschnitt näher erläutert werden.
216
5.3 Sperren
5.3.2
Sperrtypen
Der SQL Server kann Ressourcen in verschiedenen Modi sperren und dadurch konkurrierenden Transaktionen eventuell ebenfalls Zugriff auf eine gesperrte Ressource ermöglichen. Das Ziel ist auch hier wieder eine Erhöhung der Parallelverarbeitung dadurch, dass nur so wenig wie möglich gesperrt wird. So wird zum Beispiel lesender Zugriff auf Tabellendaten, wie er von SELECT-Anweisungen benötigt wird, von mehreren Transaktionen gleichzeitig gestattet. SQL Server kann Ressourcen in den folgenden Modi sperren: Gemeinsam (Shared). Gemeinsame Sperren werden für Lesezugriffe, wie SELECT, benutzt. Diese Sperren erlauben einen konkurrierenden Zugriff auf Ressourcen. Andere Transaktionen können gemeinsam gesperrte Bereiche aber nicht verändern. Wann eine gemeinsame Sperre wieder freigegeben wird, hängt von der eingestellten Isolationsstufe ab. Die Sperre kann sowohl unmittelbar nach der Beendigung der Operation als auch erst am Ende der Transaktion aufgehoben werden. Kapitel 5.4 geht hierauf nochmals ein. Exklusiv (EXclusive). Diese Sperre wird für Datenänderungsoperationen wie INSERT, UPDATE und DELETE benötigt. Die Sperre verhindert, dass mehrere Transaktionen gleichzeitig Daten modifizieren können. Leseoperationen auf den gesperrten Bereichen sind möglicherweise erlaubt, auch dies ist wiederum abhängig von der eingestellten Transaktions-Isolationsstufe. Aktualisierung (Update). Aktualisierungssperren werden verwendet, um die Wahrscheinlichkeit von Deadlocks (siehe Abschnitt 5.3.3) zu minimieren. Wenn zwei Transaktionen gleichzeitig zunächst Daten lesen, um diese dann anschließend zu verändern, so wird für beide Transaktionen am Beginn nur eine gemeinsame Sperre benötigt. Für die Aktualisierung muss diese Sperre dann allerdings in eine exklusive Sperre überführt werden. Die exklusive Sperre kann aber von keiner Transaktion gesetzt werden, da bereits eine gemeinsame Sperre auf dem Bereich existiert (siehe auch Tabelle 5.1 weiter unten). Beide Transaktionen müssen nun gegenseitig darauf warten, dass die jeweils andere die gemeinsame Sperre wieder auflöst, was allerdings nie passiert. Die Transaktionen befinden sich in einer endlosen Warteschleife. Aktualisierungssperren verhindern dies, da eine Aktualisierungssperre auf einer Ressource nur von einer Transaktion zurzeit benutzt werden kann. Beabsichtigt (Intent). SQL Server benutzt beabsichtigte Sperren, um anzuzeigen, dass bestimmte Ressourcen von Transaktionen demnächst in bestimmten Modi gesperrt werden. Durch beabsichtigte Sperren entwickelt der SQL Server eine Art vorausschauendes Sperrverhalten. Auf diese Weise können laufende Transaktionen verhindern, dass andere Transaktionen auf Ressourcen zugreifen, die in der laufenden Transaktion benötigt werden. Beabsichtigte Sperren werden im Rahmen der Lock Escalation benutzt, um eine Sperrhierarchie zu erzeugen. SQL Server kann so zum Beispiel anzeigen, dass demnächst Seiten gesperrt werden, wo jetzt noch Zeilensperren verwendet werden. Beabsichtigte Sperren können in drei unterschiedlichen Kategorien auftreten:
217
5 Transaktionen und Sperren Beabsichtigt gemeinsam (Intent Shared). Zeigt an, dass eine gemeinsame Sperre auf einem Bereich geplant ist, und verhindert so den Zugriff auf die diesem Bereich untergeordneten Elemente. Beabsichtigt exklusiv (Intent EXclusive). Zeigt an, dass eine exklusive Sperre auf einem Bereich geplant ist. Gemeinsam, aber exklusiv beabsichtigt (Shared with Intent EXclusive). Zeigt an, dass eine gemeinsame Sperre auf einem untergeordneten Bereich demnächst in eine exklusive Sperre in einem übergeordneten Bereich konvertiert wird. Katalog. Eine Katalogsperre wird während der Ausführung von Operationen, die von der Kataloginformation abhängen, benötigt. Es gibt zwei Typen von Katalogsperren: Katalogänderung (Schema-Modification). DDL-Operationen wie zum Beispiel ALTER TABLE erfordern diese Sperre, um zum Beispiel zu verhindern, dass andere Transaktionen auf die gerade in der Änderungsoperation befindliche Tabelle zugreifen. Katalogstabilität (Schema-Stability). Diese Sperre wird während der Übersetzung von Abfragen benötigt. Sie blockiert keine anderen Sperren, auch keine exklusiven Sperren. Lediglich DDL-Operationen, die auf den gesperrten Bereich zugreifen möchten, werden nicht zugelassen. Die fett hervorgehobenen Buchstaben in der englischsprachigen Bezeichnung kennzeichnen hier jeweils die offizielle Abkürzung, wie sie zum Beispiel in der SQL ServerDokumentation, in der Spalte request_mode der dynamischen Verwaltungssicht und auch in der Spalte Anforderungsmodus des Aktivitätsmonitors (siehe Abbildung 5.3) verwendet wird. Die unterschiedlichen Sperrtypen haben alle zum Ziel, gemeinsame Sperren auf Ressourcen zuzulassen und somit mehreren Transaktionen gleichzeitig Zugriff auf dieselbe Ressource zu gestatten. Dadurch werden Wartezustände minimiert und die Parallelverarbeitung von Transaktionen verbessert. Hierzu wird der Begriff der Sperrkompatibilität eingeführt. Die Sperrkompatibilität gibt Auskunft darüber, welche Sperren gemeinsam verwendet werden können. Tabelle 5.2 enthält eine Übersicht hierzu. Dort können Sie zum Beispiel entnehmen, dass gemeinsame (S) Sperren, die von SELECT-Anweisungen aufrechterhalten werden, zueinander kompatibel sind. Dadurch sind lesende Zugriffe durch SELECTs von unterschiedlichen Transaktionen gleichzeitig möglich. Die Tabelle zeigt auch, dass exklusive Sperren, die zum Beispiel während UPDATE, INSERT und DELETE verwendet werden, zu keinem anderen Modus kompatibel sind (darum ja auch der Name Exklusiv). Somit können zum Beispiel während einer UPDATE-Operation keine anderen Datenänderungen auf demselben Bereich stattfinden, Datenänderungsoperationen auf denselben Daten werden immer nacheinander ausgeführt.
218
5.3 Sperren Tabelle 5.2 Kompatibilität von Sperren Existierende Sperre Anforderungsmodus
IS
S
U
IX
SIX
X
Beabsichtigt gemeinsam Intent Shared (IS)
Ja
Ja
Ja
Ja
Ja
Nein
Gemeinsam Shared (S)
Ja
Ja
Ja
Nein
Nein
Nein
Aktualisierung Update (U)
Ja
Ja
Nein
Nein
Nein
Nein
Beabsichtigt exklusiv Intent Exclusive (IX)
Ja
Nein
Nein
Ja
Nein
Nein
Gemeinsam, aber exklusiv beabsichtigt Shared with Intent Exclusive (SIX)
Ja
Nein
Nein
Nein
Nein
Nein
Exklusiv Exclusive (X)
Nein
Nein
Nein
Nein
Nein
Nein
Jede Operation, also jedes SQL-Kommando, wird letztlich Ressourcen in der Datenbank in einem bestimmten Modus sperren. Hierbei sind den unterschiedlichen Kommandos, wie zum Beispiel SELECT, UPDATE und INSERT, standardmäßig sowohl Sperrtypen als auch die Dauer der Sperre zugeordnet. Wann immer Sie also ein SELECT-Kommando verwenden, wird zum Beispiel eine gemeinsame Sperre benutzt, die den abgefragten Bereich vor Änderungen während der SELECT-Ausführung schützt. Diese Sperre wird sofort nach Beendigung des SELECT wieder aufgehoben. Als Datenbankentwickler können Sie aber das Standardverhalten bezüglich Dauer und Art und Art der Sperre in einem gewissen Rahmen modifizieren. Hierzu können Sie sowohl die Isolationsstufe für Transaktionen verändern als auch für einzelne Abfragen Sperrhinweise verwenden. Mit den Isolationsstufen beschäftigt sich Abschnitt 5.4. Wie Sie Sperrhinweise verwenden, erfahren Sie dann im Abschnitt 5.5. Zunächst aber müssen wir noch eine Besonderheit besprechen, die im Zusammenhang mit Sperrmechanismen auftreten können: die sogenannten Deadlocks. Der folgende Abschnitt beschäftigt sich hiermit.
5.3.3
Deadlocks
Ein Deadlock bezeichnet einen Zustand, an dem mehrere Transaktionen beteiligt sind, die gegenseitig auf die Freigabe von gesperrten Ressourcen von anderen Transaktionen warten. Abbildung 5.4 veranschaulicht dies am Beispiel von drei Transaktionen.
219
5 Transaktionen und Sperren
Abbildung 5.4 Ein Deadlock mit drei beteiligten Transaktionen
Die Transaktion T1 sperrt eine Ressource A und wartet ihrerseits auf eine Ressource B, die derzeit von Transaktion T2 gesperrt wird. T2 wiederum wartet auf die Ressource C, die aber derzeit von T3 gesperrt ist. Schließlich kann auch T3 nicht beendet werden, da die Ressource A benötigt wird, die aber gerade von T1 gesperrt ist. Die drei Transaktionen warten somit in einer Schleife gegenseitig auf die Freigabe von Ressourcen. Deadlocks können generell nicht vermieden werden. SQL Server hat allerdings einen Deadlock-Monitor, der Deadlocks erkennt. Dieser Deadlock-Monitor wird aus den an einem Deadlock beteiligten Transaktionen nach einer bestimmten Zeit ein sogenanntes Deadlock-Opfer auswählen und diese Transaktion abbrechen. Die initiale Wartezeit hierfür ist fünf Sekunden. Erkennt der Deadlock-Monitor allerdings viele Deadlocks, so kann diese Zeit auf bis zu 100 ms abgesenkt werden. Treten dann wieder weniger Deadlocks auf, so wird diese Wartezeit schrittweise wieder erhöht, bis sie erneut fünf Sekunden beträgt. Sie können den Deadlock-Monitor einmal in Aktion erleben, indem Sie die folgenden beiden Abfragen in jeweils einer eigenen Verbindung zur Datenbank AdventureWorks kurz nacheinander starten: Listing 5.1 Einen Deadlock erzeugen
220
5.3 Sperren
Die erste Transaktion sperrt die Zeile für , die zweite sperrt die Zeile für . Die erste Transaktion wartet dann auf die Freigabe der Sperre für , während die zweite Transaktion auf die Zeile wartet. Beide Transaktionen warten somit auf die Freigabe von gegenseitig gesperrten Ressourcen ein Deadlock tritt auf. Der Deadlock-Monitor erkennt dies und bricht eine der beiden Transaktionen ab. Nach einer Weile werden Sie im Ergebnisbereich des Abfragefensters einer der beiden Transaktionen folgende Fehlermeldung erhalten:
Sie können Deadlocks auch im SQL Server Profiler grafisch darstellen. Hierzu starten Sie den Profiler und öffnen eine neue Ablaufverfolgung. Auf der Registerkarte Allgemein wählen Sie in der Combobox Vorlage verwenden als Vorlage Leer aus. Wechseln Sie dann in die Registerkarte Ereignisauswahl, und markieren Sie sowohl die Optionen Alle Ereignisse anzeigen als auch Alle Spalten anzeigen. Unter Events wählen Sie dann bitte die Ereignisklasse Locks und dort das Ereignis Deadlock Graph aus (Abbildung 5.5):
Abbildung 5.5 Ablaufverfolgung mit Deadlock Graph
Starten Sie anschließend die Ablaufverfolgung, und führen Sie die beiden Transaktionen aus Listing 5.1 erneut aus. Nach einer Weile sollten Sie in Ihrer Ablaufverfolgung das in Abbildung 5.6 gezeigte Bild sehen.
221
5 Transaktionen und Sperren
Abbildung 5.6 Deadlock Graph für Listing 5.1
Die grafische Darstellung zeigt sehr schön den Zyklus in den Wartezuständen. Es ist auch zu sehen, dass die linke Transaktion mit der Prozess-ID 54 als Deadlock-Opfer auserkoren und abgebrochen wurde. Der Deadlock-Monitor ist ein Schutzmechanismus von SQL Server. Durch ihn wird verhindert, dass Sperren, die sowieso niemals mehr nützlich sein können, wieder freigegeben werden. Auf diese Weise wird dafür gesorgt, dass Ressourcen nicht unnötig blockiert werden. Für den Datenbankentwickler ist dieser Schutzmechanismus allein nicht ausreichend. Der Mechanismus sorgt ja nur dafür, dass Sie stets damit rechnen müssen, dass Transaktionen, die sich aus welchen Gründen auch immer in einer Deadlock-Situation befinden, einfach abgebrochen werden. Als Datenbankentwickler müssen Sie eventuell auf derartige Situationen vorbereitet sein und entsprechend reagieren. Im T-SQL-Programmcode können Sie das unter Verwendung der Möglichkeit zur strukturierenden Ausnahmebehandlung folgendermaßen erledigen: Listing 5.2 Deadlocks im Programmcode abfangen
Der obige Code verpackt den durch den Deadlock gefährdeten Bereich einfach in einen BEGIN TRY
END TRY-Block. Im CATCH-Bereich wird zunächst einmal die Transaktion zurückgesetzt. Über die Funktion ERROR_NUMBER(), die jeweils den letzten aufgetretenen Fehler zurückliefert, dann überprüft, ob der aufgetretene Fehler 1205 ist. Dies ist die Fehlernummer für Transaktionen, die als Deadlock-Opfer ausgewählt wurden (siehe die Fehlermeldung weiter oben bei unserem ersten Versuch). Ist dies der Fall, so wird die
222
5.3 Sperren Transaktion über das GOTO-Kommando einfach nochmals begonnen, nachdem der Text der Fehlermeldung zur Information ausgegeben wurde. Diesen Text könnten Sie natürlich ebenso zur Protokollierung des aufgetretenen Deadlocks in einem eigenen Logbuch verwenden. Wenn Sie beide Transaktionen aus Listing 5.1 derart in TRY/CATCH-Blöcke einbetten, wie oben gezeigt, wird der entstehende Deadlock nun aufgefangen. Beide Transaktionen werden dadurch erfolgreich beendet. Falls Sie die oben gestartete Ablaufverfolgung noch geöffnet hatten, können Sie übrigens auch für den letzten Versuch den Deadlock in grafischer Form sehen. Dieser ist ja auch tatsächlich aufgetreten, er wurde eben nur im Programmcode aufgefangen und entsprechend behandelt. Die seit dem letzten Start von SQL Server aufgetretenen Deadlocks werden übrigens auch vom Server selbst protokolliert. Sie können sich diese über die dynamische Verwaltungssicht anzeigen lassen:
Über die WHERE-Bedingung werden hier nur die Ereignisse herausgefiltert, die in ihrem Namen die Zeichenkette dead enthalten. Als Ergebnis erhalten Sie ungefähr so etwas:
Abbildung 5.7 Deadlock-Protokoll des Servers
In der markierten Zeile 13 sehen Sie die Anzahl der seit dem Start von SQL Server aufgetretenen Deadlocks. Generell können Deadlocks nicht verhindert werden. Sie können Ihren T-SQL-Code aber so entwerfen, dass Sie die Möglichkeit für das Auftreten von Deadlocks minimieren. Kapitel 5.7 wird Ihnen hierzu einige Hinweise geben. Zunächst jedoch wollen wir uns mit den Isolationsstufen beschäftigen, mit denen eingestellt werden kann, wie Transaktionen gegeneinander abgegrenzt werden.
223
5 Transaktionen und Sperren
5.4
Isolationsstufen Die unterschiedlichen Isolationsstufen ermöglichen es Ihnen, das Isolationsverhalten Ihrer Transaktionen zu kontrollieren. Die Isolationsstufen bestimmen die Abgrenzung von Leseund Schreiboperationen gegeneinander, steuern also den konkurrierenden Zugriff von Lese- und Schreiboperationen auf gemeinsame Datenbestände. Nur Schreiboperationen sind immer voneinander isoliert, genauso wie nur Leseoperationen immer gleichzeitig durchgeführt werden es sei denn, Sie verwenden für Ihre Abfragen spezielle Optionen, um ein anderes Verhalten zu erzwingen. Kapitel 5.5 geht auf Sperrhinweise für Abfragen näher ein. SQL Server kennt fünf unterschiedliche Isolationsstufen, mit denen Sie das Verhalten von Transaktionen beeinflussen können. Diese Isolationsstufen und ihre Auswirkungen auf Transaktionen werden in den folgenden Abschnitten erörtert. Die einzelnen Isolationsstufen werden jeweils durch das Kommando
eingestellt, wie Sie in den aufgeführten Beispielen erkennen werden.
5.4.1
READ UNCOMMITTED
Dies ist die niedrigste Isolationsstufe. Eine Transaktion kann noch nicht bestätigte Schreibvorgänge einer anderen Transaktion lesen. Dadurch ist es möglich, dass die erste Transaktion Daten liest, die nach Abschluss der zweiten Transaktion nicht mehr existieren. Dies sind sogenannte Dirty Reads. Tabelle 5.3 veranschaulicht dieses Verhalten, wobei Sie sich bitte vorstellen, dass die Zeitachse von oben nach unten läuft. Die SELECT-Anweisung in Transaktion 1 wird nach dem UPDATE-Kommando durchgeführt. Durch die Isolationsstufe READ UNCOMMITTED wird dieses SELECT keine gemeinsame Sperre anfordern. Die exklusive Sperre, die durch das UPDATE für aufrechterhalten wird, kollidiert daher nicht mit dem SELECT. Das SELECT gibt dadurch den Wert Betty zurück. Transaktion 2 wird nun zurückgesetzt, dadurch existiert in der Tabelle Person.Contact wieder der vorherige Wert für . Transaktion 1 verwendet nun einen Wert, der nicht in der Tabelle Person.Contact existiert. Auch die durch das INSERT-Kommando aus Transaktion 2 hinzugefügte Zeile kann in Transaktion 1 verwendet werden, was ebenfalls zu Dateninkonsistenzen führen kann. Für ein eventuell ausgeführtes DELETE gilt diese Aussage analog: Für Transaktion 1 würden die gelöschten Zeilen nicht existent sein, obwohl diese nach Abschluss von Transaktionen durch ROLLBACK wieder vorhanden sind. Dieses Phänomen mit hinzugefügten oder nicht vorhandenen Zeilen wird auch als Phantom Read bezeichnet.
224
5.4 Isolationsstufen Tabelle 5.3 Transaktion 1
Transaktion 2
5.4.2
READ COMMITTED
Dies ist die Standardeinstellung für SQL Server-Verbindungen. Solange Sie die Isolierungsstufe nicht explizit ändern, arbeitet Ihre Verbindung im READ COMMITTEDModus. In diesem Modus können lesende Transaktionen nur Daten von abgeschlossenen Transaktionen erhalten. Anders gesagt: Eine Leseoperation muss warten, bis die Datenänderungen von der anderen Transaktionen bestätigt wurden. Schauen Sie sich bitte hierzu das Beispiel in Tabelle 5.4 an. Das UPDATE-Kommando in Transaktion 2 errichtet eine exklusive Sperre für die Zeile . Das SELECT-Kommando von Transaktion 1 kann nun keine gemeinsame Sperre für diese Zeile erhalten. Transaktion 1 muss warten, bis Transaktion 2 abgeschlossen wurde. Erst nach dem ROLLBACK von Transaktion 2 wird das SELECT-Kommando von Transaktion 1 ausgeführt, denn erst jetzt kann dieses SELECT eine gemeinsame Sperre für die Zeile erhalten. Das SELECT liest nun den nach dem ROLLBACK aktuellen Wert aus der Spalte FirstName aus. Dirty Reads sind im Modus READ COMMITTED somit nicht möglich. Allerdings erkauft man diesen Vorteil in Bezug auf die Datenkonsistenz durch den Nachteil, dass hier Wartezustände auftreten Transaktion 1 muss ja auf die Beendigung von Transaktion 2 warten.
225
5 Transaktionen und Sperren Tabelle 5.4 Beispiel zu READ COMMITTED Transaktion 1
Transaktion 2
5.4.3
REPEATABLE READ
Die beiden oben erläuterten Isolierungsstufen erlauben beide nicht wiederholbare Lesevorgänge. Schauen Sie sich hierzu bitte zunächst den Code in der folgenden Tabelle an. Tabelle 5.5 Nicht wiederholbare Lesevorgänge Transaktion 1
Transaktion 2
226
5.4 Isolationsstufen Transaktion 1 liest den Wert der Spalte FirstName für die Zeile . Das SELECT-Kommando fordert hierzu eine gemeinsame Sperre an, die aber nur für die Dauer dieses Kommandos gehalten wird. Das anschließende UPDATE in Transaktion 2 kann daher ausgeführt werden, da die hierfür erforderliche exklusive Sperranforderung erfolgreich ist. Wenn Transaktion 1 nun, nach dem COMMIT von Transaktion 2, die SELECT-Anweisung wiederholt, so wird ein anderer Wert zurückgegeben. Um in einer Transaktion wiederholbare Lesevorgänge zu ermöglichen, kann die Isolationsstufe auf REPEATABLE READ gesetzt werden. Hierzu ändern Sie die erste Codezeile für Transaktion 1 einfach in:
Wenn Sie den obigen Code nun erneut ausführen, so wird durch das erste SELECTKommando in Transaktion 1 eine gemeinsame Sperre für die Zeile erzeugt. Diese Sperre wird nun aber nicht unmittelbar nach dem Beenden der SELECT-Anwesung wieder aufgehoben, sondern bis zum Ende der Transaktion aufrechterhalten. Das UPDATE von Transaktion 2 kann daher erst ausgeführt werden, nachdem Transaktion 1 abgeschlossen wurde, Transaktion 2 muss also warten.
5.4.4
SNAPSHOT
Diese Isolationsstufe ist neu im SQL Server 2005. Die SNAPSHOT-Isolation wird durch eine Zeilenversionsverwaltung ermöglicht. Aktualisierungskommandos erzeugen in dieser Isolationsstufe zunächst Kopien der von der Aktualisierung betroffenen Zeilen. Alle Kommandos, die Daten lesen, also im Allgemeinen SELECTs, holen die Daten dann nicht aus den Zeilen der betroffenen Tabellen, sondern aus den angelegten Kopien der Zeilen. Dadurch sind Blockierungen zwischen Lese- und Schreibvorgängen prinzipiell nicht möglich. Betrachten Sie hierzu bitte noch einmal Tabelle 5.4. In dem dort gezeigten Beispiel muss das SELECT in Transaktion 1 auf das Ende von Transaktion 2 warten, bevor es ausgeführt werden kann. In der Isolierungsstufe SNAPSHOT wird dieser Wartezustand vermieden. Das UPDATE in Transaktion 2 legt eine Kopie der betroffenen Zeilen an, das SELECT erhält dann die Daten dieser Kopie. Diese Kopien enthalten die Daten für die Zeilen, wie sie vor dem UPDATE waren. Das SELECT liest also konsistente Daten, ohne warten zu müssen. Sie müssen die SNAPSHOT-Isolierungsstufe allerdings auch bezahlen. Die hierfür erforderliche Zeilenversionsverwaltung benötigt natürlich Ressourcen. Die Zeilenkopien werden in der tempdb gespeichert, die ja auf jedem Server nur einmal vorhanden ist. Alle auf einem SQL Server existierenden Datenbanken müssen sich also die tempdb teilen. Wenn Sie viele Datenbanken haben, welche die SNAPSHOT-Isolierung verwenden, so kann die gemeinsam genutzte tempdb ein echter Performance-Engpass werden. Auch aus diesem Grund können Sie die SNAPSHOT-Isolierungsstufe erst benutzen, wenn Sie Ihre Datenbank hierfür konfiguriert haben. Ein Skript wie zum Beispiel:
227
5 Transaktionen und Sperren wird daher eventuell diesen Fehler zurückliefern:
Sie müssen also, um die SNAPSHOT-Isolationsstufe verwenden zu können, zunächst die Datenbank hierfür konfigurieren. Dafür benutzen Sie ALTER DATABASE:
Erst danach können Sie die Isolierungsstufe SNAPSHOT verwenden. Erinnern Sie sich bitte noch einmal daran, dass die Isolierungsstufe READ_COMMITTED die Standardeinstellung für SQL Server-Verbindungen ist. Sie können READ COMMITTED auch in Zusammenhang mit SNAPSHOT als Standard festlegen. Auch hierfür verwenden Sie ALTER DATABASE:
Wenn Sie Ihre Datenbank so konfigurieren, dann verwendet diese nach wie vor die READ COMMITTED-Isolationsstufe, erweitert diese allerdings um die SNAPSHOTZeilenversionsverwaltung. Hierdurch haben Sie die Möglichkeit, Blockierungen von Leseund Schreibvorgängen zu reduzieren, ohne dass Sie dafür irgendetwas programmieren müssen. Dies dürfte vor allem für den Umstieg von einer Vorgängerversion des SQL Servers interessant sein, da in diesem Fall wie gesagt keinerlei Änderungen am Anwendungscode erforderlich sind. Schauen Sie sich bitte noch einmal Tabelle 5.4 an. Nach der Ausführung der beiden oben gezeigten ALTER DATABASE-Kommandos muss Transaktion 1 nicht mehr auf die Beendigung von Transaktion 2 warten. Sie können den Code genau so wie in der Tabelle dargestellt ausführen, es treten nun keine Blockierungen mehr auf.
5.4.5
SERIALIZABLE
Wie der Name bereits sagt, wird durch diese Isolationsstufe eine serielle Verarbeitung erzwungen. Alle Sperren werden grundsätzlich bis zum Ende der jeweiligen Transaktion aufrechterhalten. Dadurch können Lese- und Schreibzugriffe auf gemeinsamen Datenbereichen niemals auftreten lediglich Leseoperationen, die ja nur den Sperrtyp Gemeinsam benutzen, können gleichzeitig ausgeführt werden. Diese Isolationsstufe bietet die größte Datensicherheit, ist aber natürlich auch mit den meisten Wartezuständen verbunden.
5.4.6
Abschließende Betrachtungen zu Isolationsstufen
Isolationsstufen sind ein wesentliches Konzept, das Sie als Datenbankentwickler verstehen und beherrschen sollten. Generell können Sie für Ihre Verbindungen eine niedrige oder eine hohe Isolationsstufen wählen beide haben jeweils ihre Vor und Nachteile. Eine niedrige Isolationsstufe erzeugt weniger Blockierungen, erhöht aber auch die Gefahr möglicher Inkonsistenzen. Eine hohe Isolationsstufe gewährleistet einen hohen Grad an Konsistenz,
228
5.5 Sperren explizit anfordern Sperrhinweise führt aber auch zu vielen Blockierungen und den damit verbundenen Wartezuständen. Zusätzlich erhöht sich in diesem Fall auch die Wahrscheinlichkeit von Deadlocks. Tabelle 5.6 fasst noch einmal die in den unterschiedlichen Isolationsstufen möglichen nomalien zusammen. Tabelle 5.6 Mögliche Anomalien der unterschiedlichen Isolationsstufen Mögliche Anomalien Dirty Read
Nicht wiederholbarer Lesevorgang
Phantomwerte
Update-Konflikt
READ TED
Ja
Ja
Ja
Ja
READ COMMITTED
Nein
Ja
Ja
Ja
REPEATABLE READ
Nein
Nein
Ja
Nein
SNAPSHOT
Nein
Nein
Nein
Ja
SERIALIZABLE
Nein
Nein
Nein
Nein
Isolationsstufe
Die Spalte Update-Konflikt in der obigen Tabelle bezieht sich darauf, dass eine Transaktion Datenänderungen durchführt, die auf zuvor gelesenen Daten basieren. Dies kann zunächst einmal immer dann passieren, wenn ein nicht wiederholbarer Lesevorgang möglich ist. Zusätzlich ist eine solche Anomalie aber auch in der Isolationsstufe SNAPSHOT möglich. Da in der SNAPSHOT-Isolationsstufe die Lesevorgänge aus einer Kopie geholt werden, kann es vorkommen, dass die Daten dieser Kopie nicht mehr aktuell sind, wenn sie irgendwann im Verlauf der Transaktion für eine Datenänderung benutzt werden Es ist ja denkbar, dass eine andere Transaktion genau die der Kopie zugrunde liegenden Daten inzwischen verändert hat. Die Version der Zeilenkopie, die zur Änderung herangezogen wird, würde dann nicht mehr mit dem aktuellen Datenbestand übereinstimmen. In so einem Fall würden die anhand der Zeilenkopien durchgeführten Änderungen eventuell nicht korrekt sein.
5.5
Sperren explizit anfordern Sperrhinweise In den vorangegangenen Abschnitten haben Sie gesehen, wie der SQL ServerSperrmechanismus im Rahmen von Transaktionen funktioniert. Sie haben erfahren, dass SQL-Anweisungen angemessene Sperren nach sich ziehen, die automatisch angefordert werden. Art und Dauer der Sperre sind hierbei abhängig von der eingestellten Isolationsstufe der Transaktion. Falls Sie innerhalb einer Transaktion von dem durch die Isolationsstufe festgelegten Standard für Sperren abweichen möchten, so können Sie für einzelne SQL-Anweisungen
229
5 Transaktionen und Sperren Sperrhinweise angeben. Hierzu kennt T-SQL die WITH Klausel für die Anweisungen SELECT, UPDATE, INSERT und DELETE, die Sie wie folgt verwenden können:
Ein Sperrhinweis kann also für jede beteiligte Tabelle angegeben werden. Der Hinweis wird direkt nach dem Tabellennamen oder, wenn die Abfrage einen Aliasnamen für die Tabelle verwendet, nach dem Aliasnamen angegeben. Das Schlüsselwort WITH ist für die hier im Weiteren besprochenen Sperrhinweise übrigens optional, muss also nicht angegeben werden. Es reicht auch aus, die Sperrhinweise (es können mehrere angegeben werden) einfach im Klammern, durch Komma getrennt, anzugeben. Ein Sperrhinweis kann nützlich sein, um die Isolationsstufe temporär heraufzusetzen. Stellen Sie sich z .B. vor, dass für Ihre Anwendung die Isolationsstufe READ COMMITTED generell ausreichend ist, Sie aber innerhalb einer Transaktion eine SELECT-Anweisung verwenden, die ein wiederholbares Lesen, also die REPEATABLE READ-Isolationsstufe, benötigt. Anstatt nun für die gesamte Transaktion die Isolationsstufe hochzusetzen, was ja nur unnötige Sperren und somit Blockierungen erzeugen würde, können Sie für dieses einzelne SELECT-Kommando den Sperrhinweis REPEATABLE READ verwenden und somit die angeforderten Sperren minimieren. Sperrhinweise können in zwei Kategorien unterteilt werden: Granularitätshinweise. Durch diese Hinweise bestimmen Sie die Elemente, die gesperrt werden. Hierfür stehen Ihnen die folgenden Optionen zur Verfügung: ROWLOCK. Für die Tabelle werden Sperren auf Zeilenebene angefordert. PAGLOCK. Für die Tabelle werden Seitensperren angefordert. TABLOCK. Für die gesamte Tabelle wird eine gemeinsame Sperre angefordert. TABLOCKX. Für die gesamte Tabelle wird eine exklusive Sperre angefordert. Bitte beachten Sie, dass Granularitätshinweise nicht den Lock-EscalationMechanismus des SQL Servers außer Kraft setzen. Wenn Sie also zum Beispiel für eine Tabelle ROWLOCK angeben, so wird SQL Server zunächst mit Zeilensperren beginnen, kann aber dann in weiteren Verlauf durchaus auch Seiten oder die gesamte Tabelle sperren. Isolationsstufenhinweise. Isolationsstufenhinweise ermöglichen Ihnen die Einstellung der TransaktionsIsolationsstufe für einzelne Tabellen in SQL-Anweisungen. Hierfür haben Sie die folgenden Möglichkeiten:
230
5.5 Sperren explizit anfordern Sperrhinweise READUNCOMMITTED. Durch diese Option wird die Isolationsstufe für die Tabelle auf READ UNCOMMITTED gesetzt. NOLOCK. NOLOCK ist lediglich ein Synonym für READUNCOMMITTED. READCOMMITTED. Hierdurch wird die Isolationsstufe für die Tabelle auf READ COMMITTED gesetzt. REPEATABLEREAD. Die Option stellt die Isolationsstufe auf REPEATABLE READ. SERIALIZABLE. Dieser Sperrhinweis setzt die Isolationsstufe auf SERIALIZABLE. HOLDLOCK. HOLDLOCK ist ein Synonym für SERIALIZABLE. Generell können Sie in einem Sperrhinweis jeweils eine Option aus jeder der beiden Kategorien angeben, die Sie durch ein Komma trennen.1 Hierbei sind jedoch nicht alle Kombinationen sinnvoll und daher auch nicht erlaubt. Wenn Sie widersprüchliche Sperrhinweise angeben, wie zum Beispiel die Kombination von READUNCOMMITTED und TABLOCKX, so erhalten Sie eine entsprechende Fehlermeldung:
Außerdem sind nicht alle Sperrhinweise für alle SQL-Kommandos zulässig. Aktualisierungskommandos können zum Beispiel nicht mit READUNCOMMITTED oder NOLOCK verwendet werden. Versuchen Sie dies, so erhalten Sie ebenfalls eine Fehlermeldung:
Betrachten wir noch einmal das UPDATE-Kommando, das alle Listenpreise um 3% heraufsetzt. Dieses Kommando verändert Daten in jeder Tabellenzeile, daher könnten wir SQL Server hier auch gleich mitteilen, dass die gesamte Tabelle gesperrt werden soll:
Möglicherweise würde dies den automatischen Sperrmechanismus von SQL Server entlasten. Aber ist dies auch wirklich so? Was ist zum Beispiel mit Zeilen, in denen der Wert für ListPrice NULL oder 0 ist? Diese Zeilen würden durch das UPDATE ja nicht verändert. Sie könnten also aus dem UPDATE ausgeschlossen und bräuchten daher auch niemals gesperrt zu werden. Möglicherweise bietet die aktuelle Version von SQL Server dieses Sperrverhalten noch nicht, aber zukünftige Versionen könnten ja eine solche Sperrverwaltung enthalten. Durch den TABLOCKX-Hinweis würden Sie in so einem Fall die Granularität unnötig heraufsetzen.
1
Alternativ können Sie die Optionen auch durch Leerzeichen voneinander trennen. Dies wird jedoch nur noch aus Gründen der Abwärtskompatibilität zu früheren SQL Server-Versionen unterstützt. Für neuen T-SQL-Code sollten Sie daher die Kommatrennung verwenden.
231
5 Transaktionen und Sperren Abschließend möchte ich Ihnen daher noch einen Rat mit auf den Weg geben: Sperrhinweise sind etwas für wirklich erfahrene Datenbankentwickler. Versuchen Sie nach Möglichkeit, Sperrhinweise zu vermeiden, und verwenden Sie diese nur in einigen Ausnahmefällen, in denen Sie wirklich genau wissen, was Sie anrichten. Die vorangegangenen Abschnitte haben gezeigt, wie Sie Transaktionen in Ihrem Anwendungscode einsetzen können. SQL Server verwendet die Transaktionsverwaltung aber auch intern, um die Datenkonsistenz zu gewährleisten und sich gegen Systemausfälle zu schützen. Wie dies funktioniert, erfahren Sie im folgenden Kapitel.
5.6
Das Transaktionsprotokoll Wodurch ist eigentlich ein ROLLBACK möglich? Wenn in einer laufenden Transaktion ein Fehler auftritt und die Transaktion abgebrochen wird, können Sie über eine ROLLBACK-Anweisung ja genau den Zustand wieder herstellen, wie er vor dem Beginn der fehlerhaften Transaktion war. Wie verarbeitet der SQL Server Transaktionen, um so etwas sicherzustellen? SQL Server führt hierzu Protokoll über alle durchgeführten Transaktionen. Dies ist das Sie haben es sicher bereits erraten Transaktionsprotokoll des SQL Servers. In diesem Protokoll werden Beginn und Ende einer jeden Transaktion vermerkt. Das Protokoll enthält außerdem Einträge für alle während der Transaktion vorgenommenen Änderungen, und zwar jeweils den Zustand vor und nach der Änderung. SQL Server benutzt ein Write Ahead-Protokoll, um sicherzustellen, dass nur diejenigen Datenänderungen in die Datendateien einer Datenbank geschrieben werden, für die auch ein Protokolleintrag existiert. SQL Server verwaltet hierzu einen speziellen Pufferspeicher. Der erste Schritt bei Datenänderungsoperationen lädt die betroffenen Datenseiten in diesen Pufferspeicher. Die Änderung selber findet dann zunächst auf der im Puffer existierenden Kopie der Daten statt. Jede Änderung im Pufferspeicher erzeugt dann auch einen Eintrag in der Protokolldatei. Hierbei wird jeder Protokolleintrag mit einer eindeutigen Protokollsequenznummer, der LSN (Log Sequence Number), versehen. Einträge werden seriell in das Protokoll geschrieben, wobei jeder neue Eintrag eine höhere LSN erhält als der vorherige. Erst nachdem dieser Protokolleintrag erstellt wurde, können die im Puffer vorgenommenen Änderungen auch in die Datendatei übertragen werden. Anderenfalls könnte es vorkommen, dass die Datendatei Änderungen enthält, für die kein Protokolleintrag existiert. Für diese Änderungen wäre somit kein Rollback möglich. Das Schreiben der im Pufferspeicher geänderten Seiten in die Datendateien erfolgt erst bei Auftreten eines sogenannten Prüfpunktes (Checkpoint). Ein solcher Prüfpunkt wird von SQL Server automatisch in periodischen Abständen initiiert. Er wird aber zum Beispiel auch durch das Beenden einer SQL Server-Instanz, das Erstellen einer vollständigen Da-
232
5.6 Das Transaktionsprotokoll tenbanksicherung oder das Kommando ALTER DATABASE erreicht. Zusätzlich können Sie einen Prüfpunkt auch manuell durch Eingabe des Kommandos
erzwingen. Durch das Schreiben in die Datendatei werden die entsprechenden Seiten im Puffer geleert. Ein COMMIT bewirkt schließlich, dass die im Transaktionsprotokoll enthaltenen Protokolleinträge in die Datendatei geschrieben werden. Im Transaktionsprotokoll existieren somit drei unterschiedliche Typen von Protokolleinträgen: Vollständig verarbeitete Transaktionen. Alle erforderlichen Änderungen sind bereits in die Datendatei geschrieben worden. Der Eintrag in der Protokolldatei besteht aber weiterhin. Abgeschlossene Transaktionen. Dies sind diejenigen Transaktionen, für die im Protokoll bereits das Ende der Transaktion vermerkt wurde, die aber noch nicht in die Datendatei übertragen wurden. Nicht abgeschlossene Transaktionen. Dies sind Transaktionen, die gerade in Bearbeitung sind. Dieser Teil des Transaktionsprotokolls wird auch als aktiver Teil bezeichnet. Da das Transaktionsprotokoll seriell geschrieben wird, wobei jeder Eintrag durch eine LSN gekennzeichnet wird, ist der aktive Teil des Protokolls derjenige mit den größten LS. Anders gesagt: Es lässt sich eine minimale LSN bestimmen, an welcher der aktive Teil des Protokolls anfängt. Durch die oben geschilderte Verfahrensweise wird letztlich sichergestellt, dass nur abgeschlossene Transaktionen in die Datendatei geschrieben werden können. Dies ist sicherlich die hauptsächliche Aufgabe der Transaktionsprotokollverwaltung. Das Transaktionsprotokoll kann jedoch auch für andere Zwecke verwendet werden. Da es alle durchgeführten Datenbankänderungen enthält, kann es auch benutzt werden, um eben diese Änderungen in einer anderen als der ursprünglichen Datenbank einzuspielen. Die sich hieraus ergebenden Einsatzmöglichkeiten betreffen die Datensicherheit und die Verfügbarkeit: Einrichten von Replikationen. Eine Replikation kann mehrere Datenbanken aus unterschiedlichen Servern umfassen, die wechselseitig ihre Datenbestände synchronisieren. Als Basis für diese Synchronisation können die Transaktionsprotokolle der beteiligten Datenbanken dienen. Einrichten von Stand-by-Servern. SQL Server bietet hierzu den Transaktionsprotokollversand oder auch die Einrichtung von Datenbankspiegelungen auf der Basis des Transaktionsprotokolls.
5.6.1
Abschneiden des Protokolls
Die Verwaltung des Transaktionsprotokolls nach der gerade geschilderten Vorgehensweise lässt das Protokoll ständig anwachsen. SQL Server schneidet das Protokoll daher bei Auf-
233
5 Transaktionen und Sperren treten bestimmter Ereignisse ab. Wann das Abschneiden des Transaktionsprotokolls genau passiert, hängt vom gewählten Wiederherstellungsmodell ab: Beim einfachen Wiederherstellungsmodell wird das Protokoll beim Auftreten eines Prüfpunktes abgeschnitten, allerdings nur dann, wenn gerade keine BACKUPAnweisung aktiv ist. Das Protokoll wird immer abgeschnitten, wenn es über BACKUP LOG gesichert wird und hier nicht die Option NO TRUNCATE verwendet wurde. Das Abschneiden des Protokolls verkleinert übrigens nicht die Protokolldatei, sondern gibt nur den verwendeten Speicherplatz zur Benutzung durch andere Protokolleinträge wieder frei. Möchten Sie auch die Datei verkleinern, so müssen Sie dies über die Task Datenbank verkleinern explizit durchführen.
5.6.2
Verhalten im Fehlerfall
SQL Server verwendet das Transaktionsprotokoll auch, um den Durability-Aspekt des ACID-Prinzips zu gewährleisten. Hierzu wird bei jedem Start einer SQL Server-Instanz eine automatische Wiederherstellung aller Datenbanken ausgeführt. Für jede im Transaktionsprotokoll aufgezeichnete vollständige Datenänderung (also abgeschlossene Transaktion), die noch nicht in die Datendateien übernommen wurde, wird zunächst ein Rollforward ausgeführt, diese Änderungen werden also während der Wiederherstellung aus dem Transaktionsprotokoll in die Datendateien übertragen. Anschließend wird für den aktiven Teil des Transaktionsprotokolls, der ja alle Transaktionen, die noch nicht abgeschlossen werden konnten, enthält, ein Rollback ausgeführt. Durch dieses Verhalten ist sichergestellt, dass der Datenbestand sich stets in einem konsistenten Zustand befindet. Es können keine verlorenen Transaktionen existieren, die Daten in einem undefinierten oder teilweise veränderten Zustand zurücklassen.
5.7
Hinweise zur Verwendung von Transaktionen Nach dem bisher Gesagten können wir zu Transaktionen zwei grundlegende Erklärungen abgeben: 1. Transaktionen sind erforderlich und hilfreich, denn sie stellen die Datenkonsistenz sicher. 2. Transaktionen sind ein Problem, denn sie erzeugen Blockierungen und Wartezustände. Da Sie nun wissen, wie die Transaktionsverarbeitung von SQL Server funktioniert, werden Sie der ersten Aussage sicherlich uneingeschränkt zustimmen. Es ist die zweite Aussage, die Sie als Datenbankentwickler in die Verantwortung nimmt. Sie sollten stets versuchen, Ihren T-SQL-Code so zu entwerfen, dass Blockierungen und Wartezustände möglichst minimal sind. Um dies zu erreichen, versuchen Sie bitte die folgenden Hinweise zu beherzigen:
234
5.7 Hinweise zur Verwendung von Transaktionen Halten Sie Ihre Transaktionen möglichst kurz. Wenn möglich, spalten Sie große Transaktionen, die viele Zeilen verändern, in mehrere kleinere Transaktionen auf. Statt also zum Beispiel zu schreiben:
könnten Sie auch so vorgehen:
Eine solche Verfahrensweise ist allerdings nur dann möglich, wenn es aus Datenkonsistenzsicht auch erlaubt ist. Die obige Änderung des Listenpreises in drei hintereinander ablaufende Transaktionen kann natürlich zu Inkonsistenzen führen. Vermeiden Sie auch Transaktionen, in deren Verlauf Benutzerinteraktionen erforderlich oder möglich sind. Eine Benutzerin könnte entscheiden, nach Transaktionsstart zunächst in eine Besprechung zu gehen und erst danach weiterzuarbeiten. Dies hätte zur Folge, dass alle Sperren so lange aufrechterhalten werden, bis die Benutzerin ihre Arbeit beendet. Halten Sie Verbindungen zur Datenbank nur so lange aufrecht, wie Sie diese tatsächlich zum Lesen oder Ändern von Daten benötigen. Minimieren Sie die Wahrscheinlichkeit von Deadlocks. Deadlocks können grundsätzlich nicht vermieden werden, Sie können Ihren Code aber so gestalten, dass Deadlocks minimiert werden. Hierzu sollten Sie zunächst Rat Nummer 1 befolgen: Halten Sie die Transaktionen kurz. Zusätzlich sollten Sie die Anweisungen innerhalb Ihrer Transaktionen immer in derselben Reihenfolge verwenden. Betrachten Sie bitte noch einmal unser Beispiel aus Listing 5.1. Hätten wir hier die UPDATE-Anweisungen in beiden Transaktionen in derselben Reihenfolge abgegeben, so wäre der Deadlock nicht aufgetreten. Wählen Sie die Isolationsstufe nur so hoch wie nötig und so niedrig wie möglich. Eine hohe Isolationsstufe erzeugt mehr Sperranforderungen und somit auch mehr Blockierungen. Im Allgemeinen ist die Isolationsstufe READ_COMMITTED mit ALLOW_SNAPSHOT_ISOLATION ON eine gute Wahl. Verwenden Sie keine Sperrhinweise auf Tabellenebene. Sperrhinweise auf Tabellenebene setzen die automatische Sperrverwaltung von SQL Server außer Kraft. Setzen Sie diese daher sparsam ein, und verzichten Sie nach Möglichkeit ganz darauf. Arbeiten Sie mit der automatischen Sperrverwaltung des SQL Servers und nicht gegen sie! Hinterlassen Sie keine verlorenen Transaktionen. Explizite Transaktionen, die einen Fehler erzeugen, werden nicht zurückgesetzt. Denken Sie daran, dass Sie in einem sol-
235
5 Transaktionen und Sperren chen Fall ein ROLLBACK ausführen müssen! Ansonsten werden die von der Transaktion erzeugten Sperren nicht wieder freigegeben. Diese Aussage gilt auch für TRY/CATCH-Blöcke. Wenn aufgrund eines aufgetretenen Fehlers in den CATCHBlock verzweigt wird, wird die betroffene Transaktion, innerhalb derer der Fehler aufgetreten ist, nicht automatisch zurückgenommen! Dies müssen Sie explizit durch eine ROLLBACK-Anweisung im CATCH-Block erledigen. Dieses ROLLBACK sollte möglichst am Anfang des CATCH-Blockes stehen, damit die verlorene Transaktion so bald wie möglich beendet wird. Überwachen Sie die tempdb. Falls Sie mit der Isolationsstufe SNAPSHOT arbeiten, kann die Systemdatenbank tempdb eine Ursache für verschlechterte Performance sein.
5.8
Zusammenfassung In diesem Kapitel haben Sie erfahren, wie der SQL Server Transaktionen verwendet, um konkurrierenden Zugriff auf Ressourcen zu ermöglichen, ohne dabei die Datenkonsistenz zu verletzen. Hierbei wurde erläutert, wie die Sperrmechanismen von SQL Server funktionieren. Sie haben auch gesehen, wie Sie aktive Sperren beobachten können, welchen Einfluss Sie als Datenbankentwickler auf das Sperrverhalten von SQL Server nehmen können und welche Möglichkeiten Ihnen zur Verfügung stehen, um Blockierungen durch gesperrte Ressourcen zu minimieren. In einem weiteren Abschnitt wurde Ihnen gezeigt, wie SQL Server die Transaktionsverarbeitung intern organisiert, um gegen Systemausfälle gefeit zu sein. Abschließend haben Sie dann noch einige nützliche Hinweise für den Entwurf von Transaktionen in Ihrem Anwendungscode erhalten.
236
6 Administration für Datenbankentwickler In der Regel wird eine Einteilung in Datenbankentwickler und Datenbankadministrator zu einem großen Teil einfach willkürlich vorgenommen. So wie in jedem Datenbankadministrator auch ein wenig von einem Entwickler stecken muss, wird auch der Datenbankentwickler nicht umhinkommen, zumindest in die Grundlagen der Administration von SQL Server und Datenbanken einzusteigen. Und dies also die Vermittlung von Grundlagenwissen für die Administration ist auch genau das Ziel dieses Kapitels. Bitte erwarten Sie an dieser Stelle also keine umfassende Einführung in die SQL Server-Administration; hierfür gibt es eigene Bücher, die sich ausschließlich mit dieser Thematik auseinandersetzen (siehe zum Beispiel [BAUD06]). Dieses Kapitel wird Ihnen die Grundlagen der Administration vermitteln, auf denen Sie aufbauen können und die es Ihnen ermöglichen, bei Bedarf tiefer in die Administration einzusteigen. Aus diesem Grund werden Sie im vorliegenden Kapitel auch an ungewöhnlich vielen Stellen von mir auf die Online-Dokumentation verwiesen. Glauben Sie mir bitte, dass ich dies wirklich nicht gern tue. Wer ein Buch erwirbt, möchte schließlich dort auch alle relevanten Informationen vorfinden. Nur ist dies eben ein Buch über Datenbankentwicklung, in dem nicht die Administration im Mittelpunkt steht. Wir werden uns daher auf drei wesentliche Punkte konzentrieren, die auch ein Datenbankentwickler beherrschen sollte: Sicherung und Wiederherstellung von Datenbanken Steuerung von Zugriffserlaubnissen auf SQL Server und Datenbanken Überwachung wesentlicher Zustände von SQL Server und Datenbanken
6.1
Datensicherung und -wiederherstellung Als ein DBMS bietet SQL Server natürlich auch komfortable Möglichkeiten für die Sicherung und Wiederherstellung von System- und Benutzerdatenbanken. Eine Sicherung von Datenbanken kann online erfolgen, d. h., Benutzer können mit der Datenbank weiterarbei-
237
6 Administration für Datenbankentwickler ten, während eine Datenbanksicherung läuft. Der SQL Server kennt drei unterschiedliche Arten der Datensicherung: Vollständige Sicherung einer Datenbank. Hierbei werden alle Daten der Datenbank sowie weiterführende Informationen über Datenbankstrukturen (die Kataloginformationen) gesichert. Die vollständige Sicherung spreichert auch diejenigen Teile des Transaktionsprotokolls, die während der Sicherung durch Benutzeraktionen hinzugekommen sind. Auf diese Weise wird eine Online-Sicherung ermöglicht. Differenzielle Sicherung einer Datenbank. Eine differenzielle Datenbanksicherung enthält nur die nach der letzten vollständigen Datensicherung veränderten Daten. Sicherung des Transaktionsprotokolls. Das Transaktionsprotokoll enthält Einträge über alle nach der letzten vollständigen Datensicherung veränderten Daten. In dieser Sicherung sind auch Verweise zu eventuell vorhandenen differenziellen Sicherungen gespeichert, die es ermöglichen, für die Wiederherstellung auf einer solchen differenziellen Sicherung aufzusetzen. Zusätzlich besteht auch die Möglichkeit, nur einzelne Dateien oder Dateigruppen zu sichern eine Variante, die wir in dieser Einführung nicht weiter betrachten wollen. SQL Server kann als Sicherungsmedien sowohl Bandlaufwerke als auch Festplatten verwenden. Es existiert außerdem die Möglichkeit, für SQL Server sogenannte Sicherungsmedien zu erzeugen. Ein Sicherungsmedium ermöglicht die Verwendung eines logischen Namens für ein physikalisches Medium, wie Band oder Festplatte, innerhalb von SQL Server. Wir werden für unsere Beispiele ein solches Sicherungsmedium verwenden, müssen es also zunächst erzeugen. Dies erledigen wir einfach mit dem Management Studio. Im Bereich Serverobjekte kann über das Kontextmenü für Sicherungsmedien ein Medium hinzugefügt werden (Abbildung 6.1).
Abbildung 6.1 Anlegen eines Sicherungsmediums
Im Fenster Sicherungsmedium vergeben Sie für das Medium einen Namen und legen den physikalischen Speicherort fest (Abbildung 6.2). Für den Fall, dass Sie eine Datei verwenden möchten (so wie im Beispiel dargestellt), muss diese Datei noch nicht existieren. Allerdings ist darauf zu achten, dass SQL Server oder genauer: die Anmeldung, unter welcher der SQL Server-Dienst läuft entsprechende Schreibrechte für das angegebene
238
6.1 Datensicherung und -wiederherstellung Verzeichnis besitzt. Die Sicherungsdatei kann auch auf einer Freigabe im Netzwerk erzeugt werden, dann ist auf die Zugriffsberechtigung natürlich besonders zu achten. Das Sicherungsmedium wird auch (nach einer entsprechenden Rückfrage) erzeugt, wenn kein Zugriff auf den angegebenen Pfad besteht. In so einem Fall bekommen Sie erst beim Versuch, eine Datensicherung auf dem Medium durchzuführen, eine Fehlermeldung.
Abbildung 6.2 Konfigurieren eines Sicherungsmediums
Wenn Sie das Fenster mit OK verlassen, wird das Sicherungsmedium im ObjektExplorer auch angezeigt (Abbildung 6.3). Wir werden dieses Sicherungsmedium in der Folge verwenden, um Datensicherungen von Benutzer- und Systemdatenbanken zu erstellen.
Abbildung 6.3 Das erzeugte Sicherungsmedium im Objekt-Explorer
239
6 Administration für Datenbankentwickler
6.1.1
Wiederherstellungsmodelle
Für jede Benutzerdatenbank (genau genommen auch für die Systemdatenbanken) kann ein sogenanntes Wiederherstellungsmodell gewählt werden, das Auswirkungen auf die Verwaltung der Protokolldatei der Datenbank und somit auch auf die Möglichkeiten zur Wiederherstellung einer Datenbank hat. SQL Server kennt drei verschiedene Wiederherstellungsmodelle: Einfaches Wiederherstellungsmodell Im einfachen Wiederherstellungsmodell wird das Transaktionsprotokoll beim Erreichen eines Prüfpunktes abgeschnitten. Dies bedeutet, dass alle bereits bestätigten und in die Datendatei übernommenen Transaktionen aus dem Protokoll entfernt werden. Das Transaktionsprotokoll wird hierbei nicht verkleinert, der durch das Abschneiden freigegebene Platz steht jedoch für weitere Protokolleinträge wieder zur Verfügung. In diesem Wiederherstellungsmodell können keine Transaktionsprotokollsicherungen (und natürlich auch keine entsprechenden Rücksicherungen) durchgeführt werden. Im Falle eines Datenverlustes kann nur auf den Stand der letzten kompletten bzw. differenziellen Datensicherung wiederhergestellt werden. Vollständiges Wiederherstellungsmodell Für eine Datenbank im vollständigen Wiederherstellungsmodell werden alle Datenänderungen im Transaktionsprotokoll aufgezeichnet. Diese Protokolleinträge bleiben so lange erhalten, bis das Protokoll gesichert wird. Dies gilt auch für bereits bestätigte Transaktionen, diese werden ebenso im Protokoll gespeichert. Daraus folgt vor allem zweierlei: 1. Das Transaktionsprotokoll wächst ständig an. Sie müssen das Protokoll sichern, um die bestätigten Transaktionen hieraus zu entfernen. Anderenfalls wird irgendwann Ihre Festplatte an die Grenzen der Kapazität gelangen. 2. Die im Transaktionsprotokoll aufgezeichneten Datenänderungen können basierend auf der letzten vollständigen oder auch differenziellen Datensicherung wieder eingespielt werden; ein Prozess, der als Roll Forward bezeichnet wird. Dadurch besteht die Möglichkeit, dass bei einem Datenverlust bis zu dem Zeitpunkt unmittelbar vor der Störung eine Wiederherstellung erfolgen kann. Insbesondere kann ein Transaktionsprotokoll auch bis zu einem beliebigen Zeitpunkt in der Vergangenheit wieder eingespielt werden etwa für den Fall, dass Datenänderungsoperationen rückgängig gemacht werden sollen. Massenprotokolliertes Wiederherstellungsmodell Im massenprotokollierten Wiederherstellungsmodell werden abgeschlossene Transaktionen ebenfalls im Transaktionsprotokoll belassen. Allerdings werden nicht alle Datenänderungen im Transaktionsprotokoll gespeichert. Es sind gerade die Massen-Einfügeoperationen, wie zum Beispiel SELECT INTO oder BULK INSERT, die nicht protokolliert
240
6.1 Datensicherung und -wiederherstellung werden, von daher ist die Bezeichnung Massenprotokolliert vielleicht etwas unglücklich gewählt. Eine Datenbank in diesem Wiederherstellungsmodus kann unter Verwendung des Transaktionsprotokolls nur dann bis zu einem beliebigen Zeitpunkt wiederhergestellt werden, wenn nach jeder (nicht protokollierten) Massen-Einfügeoperation eine vollständige oder differenzielle Datensicherung durchgeführt wird. Einstellen des Wiederherstellungsmodells für eine Datenbank Um für eine Datenbank ein Wiederherstellungsmodell festzulegen, kann die Anweisung ALTER DATABASE wie folgt verwendet werden:
Für dürfen Sie die folgenden drei Werte angeben: FULL. Die Datenbank wird in das vollständige Wiederherstellungsmodell versetzt. BULK_LOGGED. Die Datenbank verwendet das massenprotokollierte Wiederherstellungsmodell. SIMPLE. Die Datenbank verwendet das einfache Wiederherstellungsmodell. Alternativ können Sie das Wiederherstellungsmodell natürlich im Objekt-Explorer einstellen. Hierzu wählen Sie für eine existierende Datenbank die Eigenschaften aus und gehen in den Bereich Optionen (Abbildung 6.4).
Abbildung 6.4 Auswahl der Wiederherstellungsmodells
241
6 Administration für Datenbankentwickler Im folgenden Teil wird davon ausgegangen, dass für die Datenbank AdventureWorks das vollständige Wiederherstellungsmodell ausgewählt wurde.
6.1.2
Prüfen der Konsistenz einer Datenbank
Bevor Sie eine Sicherung einer Datenbank durchführen, ist es ratsam, die betreffende Datenbank zu überprüfen. Hierzu existiert das T-SQL-Kommando DBCC (DataBase Consistency Checker). Dieses Kommando kennt eine Vielzahl von Optionen, unter anderem kann es auch zur Konsistenzprüfung einer kompletten Datenbank verwendet werden. Hierzu verwenden Sie DBCC in der folgenden Form:
wobei Sie für ' den Namen der zu prüfenden Datenbank angeben. Die von DBCC erzeugte Ausgabe ist im Normalfall derart umfangreich, dass eine Lokalisierung eventueller Fehlermeldungen innerhalb dieser Ausgabe relativ schwierig ist. Aus diesem Grund wird das Kommando mit der Option aufgerufen, wodurch dann nur noch Fehlermeldungen ausgegeben werden. Bitte überprüfen Sie Ihre Datenbanken stets vor einer vollständigen Datenbanksicherung. Eine Datenbank, die in einem fehlerhaften Zustand gesichert wurde, ist für eine Wiederherstellung natürlich wenig nützlich.
6.1.3
Vollständige Datenbanksicherung
Nun aber zunächst einmal genug der Theorie zu Datensicherungen und Wiederherstellungsmodellen. Wir wollen jetzt eine vollständige Sicherung der Datenbank AdventureWorks in unser angelegtes Sicherungsmedium aufnehmen. Natürlich kann auch diese Aufgabe wieder wahlweise mit dem Management Studio oder durch entsprechende T-SQL-Kommandos erledigt werden. Wir werden in diesem Beispiel das Management Studio verwenden, um ein entsprechendes T-SQL-Skript erzeugen zu lassen. Dieses Skript soll dann zur Erstellung eines Auftrags für den SQL Server Agent verwendet werden. Dadurch wollen wir erreichen, dass die Sicherung der Datenbank zyklisch erfolgt, etwa jede Nacht um 03:00 Uhr. Es ist in jedem Fall sinnvoll, Datensicherungen so zu planen, dass diese bei nur geringer Benutzeraktivität durchgeführt werden wir gehen davon aus, dass dies um 03:00 Uhr in der Nacht der Fall sein wird. Zwar sind Online-Datensicherungen möglich, wenn eine Sicherung allerdings während starker Aktivität von Benutzern durchgeführt wird, so wird eine solche Sicherung sehr groß werden und auch entsprechend lange dauern schließlich werden in der vollständigen Datensicherung auch alle während der Sicherung vorgenommenen Datenänderungen protokolliert. Die Datensicherung wird über das Kontextmenü der Datenbank gestartet. Wählen Sie dort den Menüeintrag Tasks/Sichern
(Abbildung 6.5).
242
6.1 Datensicherung und -wiederherstellung
Abbildung 6.5 Starten der Datenbanksicherung
Im Fenster Datenbank sichern AdventureWorks (Abbildung 6.6) können Sie anschließend die Optionen für die zu erstellende Sicherung einstellen.
Abbildung 6.6 Einstellen der Sicherungsparameter
243
6 Administration für Datenbankentwickler Unsere Sicherung soll auf das gerade angelegte Sicherungsmedium erfolgen. Hierzu entfernen Sie bitte zunächst alle Einträge in der unteren Liste durch Auswahl und Klicken auf Entfernen. Anschließend klicken Sie auf Hinzufügen, und wählen Sie im Dialog Sicherungsziel auswählen (Abbildung 6.7) das angelegte Sicherungsmedium aus.
Abbildung 6.7 Auswahl des Sicherungsmediums als Ziel
Abbildung 6.6 zeigt die Auswahl dieses Mediums in der Liste. In der Abbildung ist auch zu erkennen, dass für die Sicherung eine Lebensdauer von sieben Tagen angegeben wurde. Dadurch kann diese Datensicherung erst nach Ablauf dieses Zeitraums überschrieben werden, die erzeugte Datensicherung wird also mindestens eine Woche lang aufbewahrt. Im Bereich Optionen können weitere Einstellungen vorgenommen werden, zum Beispiel für die Initialisierung des Mediums vor der Datensicherung. Diese Einstellungen belassen wir auf den Standardwerten. Nachdem nun alles konfiguriert ist, soll die Datensicherung nicht ausgeführt werden. Stattdessen werden wir ein Skript erzeugen lassen und dieses direkt in einen Auftrag übertragen. Hierzu lassen wir uns über das Menü Skript/Skript für Aktion in Auftrag schreiben des Fensters einen Auftrag erzeugen. Das daraufhin erscheinende Fenster (Abbildung 6.8) enthält bereits Einstellungen für einen Auftrag des SQL Server Agents, die allerdings noch ein wenig angepasst werden müssen. Wie bereits gesagt, soll der Auftrag so gestaltet werden, dass unsere Datenbanksicherung jede Nacht um 03:00 Uhr ausgeführt wird. Zunächst einmal sollte die Bezeichnung für den Auftrag entsprechend geändert werden. Wenn Sie möchten, können Sie auch eine zusätzliche Beschreibung für diesen Auftrag vergeben. Wichtig ist aber vor allem die Konfiguration der Auftragsschritte und des Zeitplanes für die automatische Auftragsausführung. Wählen Sie zunächst die Seite Schritte, um den einzigen Auftragsschritt die Sicherung der Datenbank anzusehen. Markieren Sie den Auftragsschritt, und wählen Sie Bearbeiten aus. Es öffnet sich ein Fenster, in dem der Schritt konfiguriert werden kann (Abbildung 6.9).
244
6.1 Datensicherung und -wiederherstellung
Abbildung 6.8 Erzeugen eines Auftrags für die Sicherung
Abbildung 6.9 Der (einzige) Auftragsschritt für die Sicherung
245
6 Administration für Datenbankentwickler Der T-SQL-Code für diesen Auftragsschritt wurde ebenso wie der Schritt selber automatisch erzeugt. Der Schritt enthält das Kommando BACKUP DATABASE für die Erstellung einer vollständigen Datenbanksicherung auf dem Medium BackupServer mit allen erforderlichen Optionen. Auf der Seite Erweitert können weitere Einstellungen für diesen Auftragsschritt vorgenommen werden. Dies ist vor allem interessant, wenn ein Auftrag aus mehreren Schritten besteht. Für unseren Auftrag müssen hier keine Änderungen an den Standardeinstellungen durchgeführt werden. Bestätigen Sie die Einstellungen mit OK, um zurück zum Fenster Auftragseigenschaften zu gelangen. Was nun noch fehlt, ist die Konfiguration eines entsprechenden Zeitplanes. Gehen Sie hierzu auf die Seite Zeitpläne, und wählen Sie Neu
aus. Es öffnet sich das Fenster zur Konfiguration eines Zeitplanes für die automatische Auftragsausführung (Abbildung 6.10).
Abbildung 6.10 Konfigurieren des Zeitplans für die nächtliche Sicherung der Datenbank
Im Fenster Neuer Auftragszeitplan werden nun alle Einstellungen vorgenommen, die dafür sorgen, dass der Auftrag jede Nacht um 03:00 Uhr automatisch gestartet wird, so wie in Abbildung 6.10 zu sehen. Bestätigen Sie diesen Dialog und anschließend auch den Dialog Auftragseigenschaften mit OK. Der Auftrag ist damit fertig konfiguriert. Wichtig ist, dass Sie das Fenster für die Erstellung der Datensicherung nun nicht mit OK bestätigen, sondern Abbrechen wählen. Die Datensicherung wird ja über den angelegten Auftrag erstellt, soll also nicht sofort durchgeführt werden. Im Ordner Aufträge des SQL Server Agents wird der erzeugte Auftrag angezeigt (Abbildung 6.11).
246
6.1 Datensicherung und -wiederherstellung
Abbildung 6.11 Der erzeugte Auftrag für die Sicherung der Datenbank
In der kommenden Nacht wird nun also eine Sicherung der Datenbank AdventureWorks erstellt werden. Wenn Sie nicht so lange warten möchten, um zu sehen, ob Ihr Auftrag auch funktioniert, dann können Sie den Auftrag auch manuell starten. Wählen Sie hierzu aus dem Kontextmenü für den Auftrag die Option Auftrag starten bei Schritt
. Da unser Auftrag nur aus einem Schritt besteht, wird er unmittelbar ausgeführt. Abbildung 6.12 zeigt die Meldung nach erfolgreicher Auftragsausführung.
Abbildung 6.12 Erfolgreiche Auftragsausführung
Abschließend sollte nun noch kontrolliert werden, ob die Sicherung auch auf unserem Sicherungsmedium existiert. Dies kann zum Beispiel über den Eigenschaftsdialog des Mediums geschehen. Dort finden Sie auf der Seite Medieninhalt alle im Medium enthaltenen Datensicherungen (Abbildung 6.13 auf der nächsten Seite). Im Moment enthält unser Medium nur eine einzige Sicherung, wir werden in Kürze einige weitere hinzufügen. Um den Auftrag für die Datensicherung zu komplettieren, sollten Sie nun noch in einem ersten Auftragsschritt also vor der eigentlichen Datensicherung eine Konsistenzprüfung der AdventureWorks-Datenbank mit DBCC vornehmen, so wie weiter oben beschrieben. Hierzu bietet der Auftragseditor Möglichkeiten zur Ablaufsteuerung einzelner Auftragsschritte. Insbesondere ist es möglich anzugeben, dass ein nachfolgender Auftragsschritt nur ausgeführt werden darf, wenn der vorherige Schritt erfolgreich war genau das, was wir benötigen. Versuchen Sie also ruhig einmal, einen entsprechenden Schritt zum Auftrag hinzuzufügen und den Ablauf entsprechend zu konfigurieren.
247
6 Administration für Datenbankentwickler
Abbildung 6.13 Inhalt des Sicherungsmediums
6.1.4
Sicherung des Transaktionsprotokolls
Nachdem nun dafür gesorgt ist, dass jede Nacht eine vollständige Sicherung der Datenbank erstellt wird, wollen wir nun noch jede Stunde eine Transaktionsprotokollsicherung der AdventureWorks-Datenbank zum Sicherungsmedium hinzufügen. Auch dies kann auf dieselbe Weise konfiguriert werden, wie im vorangegangenen Abschnitt bei der vollständigen Datensicherung geschehen. Um zu zeigen, dass es auch anders geht, soll hier jedoch der Auftrag direkt erzeugt werden. Zunächst benötigen wir die BACKUP-Anweisung zur Erstellung der Protokollsicherung. Diese Anweisung wird später in einem Auftragsschritt benötigt und sieht so aus:
Zum Erstellen einer Protokollsicherung wird die Aneisung BACKUP LOG verwendet. Diese Anweisung erwartet den Namen der Datenbank und des Sicherungsmediums bzw. der Sicherungsdatei (in unserem Fall wird ein Sicherungsmedium benutzt). Über die Option RETAINDAYS wird angegeben, wie lange die Sicherung aufgehoben wird, bevor sie überschrieben werden kann. Optional ist es möglich, einen Namen für die Sicherung zu vergeben, dies wird in der obigen Anweisung durch die Option NAME erledigt. Natürlich kennt die BACKUP-Anweisung noch eine Vielzahl weiterer Klauseln und Optionen, die in dieser Einführung jedoch außen vor gelassen werden (müssen). Das Kommando in der obigen Form ist für unsere Zwecke ausreichend.
248
6.1 Datensicherung und -wiederherstellung
Abbildung 6.14 Einen Auftrag hinzufügen
Um nun einen Auftrag zu erzeugen, der dieses Kommando stündlich ausführt, kann im Objekt-Explorer über das Kontextmenü für den Zweig SQL Server Agent/Aufträge ein Auftrag hinzugefügt werden (Abbildung 6.14). Daraufhin öffnet sich zunächst das Fenster zur Konfiguration der allgemeinen Auftragsoptionen (siehe nochmals Abbildung 6.8). Vergeben Sie hier einen Namen für den Auftrag und, wenn Sie möchten, auch eine Beschreibung. Wählen Sie anschließend die Seite Schritte, um den Auftragsschritt für die Konfiguration der Protokollsicherung anzulegen (Abbildung 6.15). Dort klicken Sie auf Neu
, um einen Schritt hinzuzufügen. Im Auftragsschritt selber wird einfach nur das oben gezeigte BACKUP LOG-Kommando als Befehl eingetragen. Achten Sie bitte darauf, dass als Schritttyp Transact-SQL-Skript ausgewählt ist, und vergeben Sie auch einen Namen für diesen Schritt. Bestätigen Sie den Dialog mit OK, und gehen Sie anschließend auf die Seite Zeitpläne. Klicken Sie auch dort auf Neu..., um einen Zeitplan hinzuzufügen.
Abbildung 6.15 Den Auftragsschritt für die Sicherung des Protokolls konfigurieren
249
6 Administration für Datenbankentwickler Abbildung 6.16 zeigt, wie der Zeitplan für die stündliche Ausführung der Protokollsicherung konfiguriert wird.
Abbildung 6.16 Einen Zeitplan erstellen, der jede Stunde aktiviert wird
Abbildung 6.17 Inhalt des Sicherungsmediums mit Protokollsicherungen
250
6.1 Datensicherung und -wiederherstellung Sie können nun alle noch offenen Fenster durch OK bestätigen, der Auftrag zur stündlichen Erstellung einer Protokollsicherung der AdventureWorks-Datenbank ist damit fertig. Wenn Sie sich nach einigen Stunden den Inhalt des Sicherungsmediums nochmals ansehen, wird dieser in etwa so aussehen, wie in Abbildung 6.17 gezeigt. (Falls Sie nicht bis zum Ablauf einer vollen Stunde warten möchten, ist es natürlich auch hier wieder möglich, den Auftrag manuell zu starten.) Sichern des Transaktionsprotokolls einer beschädigten Datenbank Erinnern Sie sich bitte daran, dass Daten und Protokolle für jede Datenbank in unterschiedlichen Dateien gespeichert werden. In diesem Szenario ist es durchaus vorstellbar, dass nur eine Datendatei einer Datenbank defekt, das Transaktionsprotokoll aber trotzdem nach wie vor intakt ist. Wenn SQL Server erkennt, dass eine Datendatei beschädigt ist, so wird diese Datenbank als fehlerverdächtig eingestuft. Ein Zugriff auf diese Datenbank ist dann nicht mehr möglich mit einer Ausnahme: Das Transaktionsprotokoll dieser Datenbank kann gesichert werden. Allerdings wird das Transaktionsprotokoll bei der Sicherung standardmäßig abgeschnitten, d. h., alle bereits bestätigten Transaktionen werden aus dem Protokoll entfernt. Für eine fehlerverdächtige Datenbank ist das Abschneiden des Protokolls nicht möglich SQL Server geht verständlicherweise davon aus, dass eine fehlerhafte Datenbank nicht dadurch repariert werden kann, dass die Dateien dieser Datenbank verändert werden. Es ist aber möglich, das Protokoll zu sichern, ohne es abzuschneiden. Hierzu verwenden Sie die Option WITH NO_TRUNCATE der BACKUP LOG-Anweisung:
Ein so gesichertes Protokoll kann dann ebenfalls für eine Rücksicherung verwendet werden, wodurch in den meisten Fällen eine Wiederherstellung bis unmittelbar vor dem Systemausfall möglich ist. Aus dieser Möglichkeit ergeben sich vor allem zwei Konsequenzen: 1. Speichern Sie Daten- und Protokolldateien nach Möglichkeit auf unterschiedlichen physikalischen Laufwerken. 2. Das Transaktionsprotokoll ist für eine Wiederherstellung immens wichtig. Erstellen Sie Duplikate Ihrer Transaktionsprotokollsicherungen, wenn möglich sofort nach dem Sichern eines Protokolls
6.1.5
Sichern der Systemdatenbanken
Vergessen Sie bitte nicht, die Systemdatenbanken in Ihre Sicherungsstrategie einzubeziehen. SQL Server verwendet diese Datenbanken, um Informationen für die Systemverwaltung zu speichern. Auf jeden Fall sollten Sie die master-Datenbank und die Datenbank msdb sichern. Eine Sicherung der model-Datenbank können Sie ebenfalls vornehmen, wenn Sie diese Datenbank als Vorlage für neu anzulegende Datenbanken verwenden. Die Sicherung der Datenbank tempdb ist nicht erforderlich, diese Datenbank wird bei jedem Systemstart von SQL Server neu erzeugt.
251
6 Administration für Datenbankentwickler
6.2
Wiederherstellung von Datenbanken Die beste Datensicherung ist natürlich nutzlos, wenn eine Wiederherstellung nach dem Systemausfall nicht funktioniert. In diesem Abschnitt werden wir einen Systemausfall annehmen und die Datenbank AdventureWorks aus der Datensicherung wiederherstellen. Jede Wiederherstellung einer Datenbank muss immer mit einer vollständigen Datensicherung begonnen werden. Aufbauend auf dieser vollständigen Wiederherstellung können dann zugehörige differenzielle und/oder Protokollsicherungen wiederhergestellt werden. Die Wiederherstellung einer Datenbank kann einfach über das Kontextmenü der Datenbank durch Auswahl von Tasks/Wiederherstellen/Datenbank
gestartet werden (Abbildung 6.18).
Abbildung 6.18 Wiederherstellen einer Datenbank
Der Sicherungsverlauf für alle Datenbanken wird in der msdb gespeichert. Wenn Ihre msdb intakt ist, so werden Ihnen alle seit der letzten vollständigen Sicherung existierenden Datensicherungen für die ausgewählte Datenbank in einer Liste angeboten (Abbildung 6.19). In der Auswahlliste ist bereits die letzte Protokollsicherung als Punkt für die Wiederherstellung gekennzeichnet. Für eine Wiederherstellung bis zu diesem Punkt muss in unserem Fall zunächst die letzte vollständige Sicherung eingespielt werden. Daran anschließend müssen dann alle nach der vollständigen Sicherung vorgenommen Protokollsicherungen in der exakten Reihenfolge eingespielt werden. Sie können diese Wiederherstellung Schritt für Schritt vornehmen, also jede Sicherung einzeln einspielen. Der Dialog für die Wiederherstellung der Datenbank ermöglicht es Ihnen aber auch, dass Sie die für den Punkt der gewünschten Wiederherstellung benötigte Protokollsicherung einfach auswählen. Wenn der Sicherungsverlauf in der msdb korrekt gespeichert ist, so werden dann automatisch alle
252
6.2 Wiederherstellung von Datenbanken
Abbildung 6.19 Datenbank wiederherstellen
weiteren Protokolle und auch vollständige bzw. differenzielle Sicherungen in der Liste markiert. Es ist dann möglich, alle für die Wiederherstellung erforderlichen Sicherungen einfach durch einen Klick auf OK einzuspielen. Der Dialog bietet Ihnen auch die Möglichkeit, ein bestimmtes Sicherungsmedium auszuwählen, von dem die Wiederherstellung erfolgen soll. In unserem Fall verzichten wir auf diese Möglichkeit, weil eine Wiederherstellung bis zum spätest möglichen Zeitpunkt erfolgen soll und die dafür erforderlichen Sicherungen bereits in der Auswahlliste markiert sind. Falls Sie eine Wiederherstellung zu einem früheren Zeitpunkt also einem Punkt, der vor der letzten vollständigen Datenbanksicherung liegt wünschen oder falls in Ihrer msdb kein Sicherungsverlauf für die Datenbank gespeichert ist (etwa weil Sie das Sicherungsmedium von einem anderen Server einspielen müssen), so können Sie ein bestimmtes Sicherungsmedium auswählen und dann die erforderlichen Sicherungen aus diesem Medium selektieren. Hierbei müssen Sie dann selbst darauf achten, dass Sie die Rücksicherung der einzelnen Datensicherungen in der erforderlichen Reihenfolge durchführen. Sollen Transaktionsprotokolle eingespielt werden, so muss auf der Seite Optionen für die Wiederherstellung die Option Vorhandene Datenbank überschreiben ausgewählt werden, ansonsten kann die Rücksicherung des letzten Protokolls nicht durchgeführt werden (Abbildung 6.20).
253
6 Administration für Datenbankentwickler
Abbildung 6.20 Auswahl der Optionen für die Wiederherstellung
Wenn die wiederherzustellende Datenbank das vollständige Wiederherstellungsmodell verwendet, so können Sie diese Datenbank bis zu einem beliebigen Zeitpunkt in der Vergangenheit wiederherstellen eine sehr nützliche Möglichkeit für den Fall versehentlich durchgeführter Datenänderungen. Hierzu öffnen Sie über den Button mit den drei Punkten neben dem Text Bis zu Zeitpunkt einen weiteren Dialog zur Einstellung des gewünschten Zeitpunktes der natürlich durch die ausgewählten Protokolle auch abgedeckt sein muss. Die SQL Server-Version mit Service Pack 1 hat hierbei allerdings noch ein Problem mit europäischen Datumsformaten. Sobald Sie einen entsprechenden Zeitpunkt auswählen und den Dialog mit OK bestätigen, erhalten Sie die in Abbildung 6.21 gezeigte Fehlermeldung.
Abbildung 6.21 Fehlermeldung bei Einstellung eines Zeitpunktes
Das Problem wird mit dem Service Pack 2 behoben sein. Wenn Sie das Service Pack 2 noch nicht installiert haben, so müssen Sie die Wiederherstellung in diesem Fall durch entsprechende RESTORE LOG-Anweisungen unter Verwendung der STOPAT-Option vornehmen. Ich gehe davon aus, dass das Service Pack 2 bei Erscheinen dieses Buchs verfügbar sein wird. Daher möchte ich an dieser Stelle auf eine Erläuterung von RESTORE LOG verzichten und Sie an die Online-Dokumentation verweisen. Wenn alle Einstellungen vorgenommen wurden, kann die Wiederherstellung durch einen Klick auf OK gestartet werden. Während der laufenden Wiederherstellung wird in der unteren linken Ecke des Fensters eine Statusanzeige über den Fortschritt der Rücksicherung ausgegeben. Nach erfolgreichem Abschluss der Wiederherstellung erhalten Sie eine entsprechende Information (Abbildung 6.22).
254
6.2 Wiederherstellung von Datenbanken
Abbildung 6.22 Erfolgsmeldung nach Abschluss der Wiederherstellung
Im Gegensatz zur Sicherung die online erfolgen kann muss eine Datenbank für eine Wiederherstellung exklusiv gesperrt werden. Da jede bestehende Verbindung zur Datenbank diese Datenbank im gemeinsamen Modus sperrt, ist eine exklusive Sperre für eine Datenbank, zu der noch offene Verbindungen existieren, nicht möglich. Die Datenbank kann in so einem Fall also wenn noch Verbindungen aufrechterhalten werden nicht wiederhergestellt werden. Dann nämlich erhalten Sie beim Versuch der Wiederherstellung eine Fehlermeldung. Offene Verbindungen müssen zunächst beendet werden. Dies können Sie zum Beispiel über ALTER DATABASE erledigen, so wie in Kapitel 4 beschrieben.
6.2.1
Automatische Wiederherstellung bei Systemstart
SQL Server beginnt nach jedem Start mit einem automatischen Wiederherstellungsprozess für alle Datenbanken. Diese automatische Wiederherstellung kann nicht unterbunden werden. Sie sorgt dafür, dass sich alle Datenbanken in einem Zustand befinden, in dem keine anhängigen Transaktionen mehr existieren. In Kapitel 5 wurde bereits erklärt, wie SQL Server dies unter Verwendung des Transaktionsprotokolls bewerkstelligt. Die automatische Wiederherstellung ist im Übrigen unabhängig vom eingestellten Wiederherstellungsmodus. Sie funktioniert gleichermaßen in allen drei Modi.
6.2.2
Wiederherstellung der Systemdatenbanken
Die Systemdatenbanken von SQL Server können auf die gleiche Weise wiederhergestellt werden wie oben beschrieben mit einer Ausnahme: Eine Wiederherstellung der masterDatenbank ist im Normalbetrieb von SQL Server nicht möglich. Hierfür muss SQL Server zunächst beendet und im Einzelbenutzermodus neu gestartet werden. Falls die Struktur der master-Datenbank noch intakt ist, startet der Server, und die Datenbank kann restauriert werden. Das Starten von SQL Server im Einzelbenutzermodus können Sie für die Standardinstanz von SQL Server über die Anweisung
erreichen. Das Dienstprogramm finden Sie im Programmverzeichnis des SQL Servers unter .\MSSQL.1\MSSQL\Binn. Wie der Name bereits vermuten lässt, gestattet SQL Server im Einzelbenutzermodus nur eine einzige Verbindung. Es ist möglich, diese Verbindung durch das Management Studio zu öffnen, indem der Objekt-Explorer für den Server geöffnet wird. Aus dem Objekt-Explorer kann dann eine Wiederherstellung der
255
6 Administration für Datenbankentwickler master-Datenbank durchgeführt werden. Eine Wiederherstellung durch Sqlcmd mit einer entsprechenden RESTORE DATABASE-Anweisung ist natürlich ebenso möglich. Nach der Wiederherstellung der master-Datenbank wird der SQL Server-Dienst automatisch beendet. Das Management Studio reagiert hierauf mit einer Fehlermeldung, die in Abbildung 6.23 gezeigt wird.
Abbildung 6.23 Meldung nach erfolgreicher Wiederherstellung der master-Datenbank
Dieses Verhalten ist sicherlich etwas eigenartig. Die Fehlermeldung aus Abbildung 6.23 ist ja letztlich die Erfolgsmeldung für eine abgeschlossene Wiederherstellung der masterDatenbank. Der SQL Server Dienst kann anschließend wieder normal gestartet und ausgeführt werden. Für den Fall, dass Ihre master-Datenbank defekt ist, gestaltet sich die Angelegenheit etwas komplizierter. SQL Server wird mit einer defekten master-Datenbank nicht starten. In einem solchen Fall müssen Sie zunächst dafür sorgen, dass die beim Start von SQL Server benötigte master-Datenbank intakt ist. Hierzu können Sie aus der Installation von SQL Server die Systemdatenbanken wiederherstellen. (Bitte schlagen Sie dies in der OnlineDokumentation unter REINSTALLMODE nach.) Alternativ ist es auch möglich, eine zuvor angefertigte Kopie der Dateien master.mdf und mastlog.ldf in das Datenverzeichnis von SQL Server zu übertragen und den Server dann im Einzelbenutzermodus zu starten, um eine Wiederherstellung der master-Datenbank aus einer vorhandenen Sicherung durchzuführen so wie oben beschrieben.
6.3
Zugriffskontrolle SQL Server bietet umfangreiche Möglichkeiten für die Zugriffskontrolle sowohl auf den Server selbst als auch auf die zugeordneten Datenbanken und Datenbankobjekte. Um dies zu verwirklichen, existiert ein Konzept, das die Zugriffserlaubnis auf zwei unterschiedlichen Ebenen kontrolliert. Zunächst einmal müssen Benutzer, die auf Datenbanken zugreifen wollen, sich gegenüber dem Server authentifizieren. Ein Benutzer muss also die Berechtigung besitzen, sich überhaupt am Server anmelden zu dürfen. Wie diese Berechtigung erteilt, entzogen und überprüft wird, zeigt der folgende Abschnitt. Für den Zugriff
256
6.3 Zugriffskontrolle auf Objekte einer bestimmten Datenbank muss ein Benutzer dann noch explizit die erforderlichen Berechtigungen erteilt bekommen. Stellen Sie sich dies zum Beispiel wie eine Tiefgarage vor, in die Ihnen der Zutritt gewährt wurde. Um ein bestimmtes Auto zu öffnen und damit loszufahren, reicht die Zutrittsberechtigung allein nicht aus. Hierfür benötigen Sie in der Regel zumindest noch einen Fahrzeugschlüssel. Abbildung 6.24 verdeutlicht noch einmal das Sicherheitskonzept von SQL Server. Die Darstellung für den Zugriff auf unterschiedliche Datenbanken ist in der Abbildung der Einfachheit halber so dargestellt, dass der Zugriff komplett gestattet oder verboten ist. Solche Einstellung ist natürlich möglich, auch wenn in der Realität sicherlich die Zugriffserlaubnisse deutlich feinkörniger basierend auf einzelnen Objekten einer Datenbank vergeben werden.
Abbildung 6.24 Sicherheitskonzept von SQL Server
6.3.1
SQL Server-Anmeldungen
Benutzer müssen sich gegenüber SQL Server authentifizieren, sich also am Server anmelden, um überhaupt Zutritt zu Datenbankobjekten zu erhalten. In der SQL ServerDokumentation wird in diesem Zusammenhang übrigens nicht von Benutzern gesprochen. Dort werden SQL Server-Anmeldungen unter dem allgemeinen Begriff Prinzipale zusammengefasst. Prinzipale können wie Sie gleich bzw. in späteren Kapiteln sehen werden nicht nur Benutzer, sondern zum Beispiel auch Windows-Gruppen, DomänenAccounts oder Prozesse sein. SQL Server bietet grundsätzlich zwei unterschiedliche Verfahrensweisen zur Authentifizierung: die integrierte Windows-Anmeldung sowie eine SQL Server-eigene Authentifizierung. Existierende SQL Server-Anmeldungen (Logins) können im Objekt-Explorer, dort im Ordner Sicherheit/Anmeldungen aufgelistet werden (Abbildung 6.25). Dort existiert auch die Möglichkeit, Anmeldungen zu bearbeiten. Nach der SQL Server-Installation sind bereits diverse Anmeldeinformationen vorhanden, von denen einige weiter unten erläutert werden.
257
6 Administration für Datenbankentwickler
Abbildung 6.25 SQL Server-Anmeldungen (Logins)
Integrierte Windows-Anmeldung SQL Server bietet die Möglichkeit, die existierenden Windows-Anmeldungen für die Authentifizierung zu verwenden. Dies hat vor allem folgende Vorteile: SQL Server kann sich auf die Windows-Anmeldung verlassen. Einstellungen wie Kennwortrichtlinien oder Ähnliches sind automatisch auch für SQL Server-Anmeldungen gültig. Bei einer Anmeldung an SQL Server werden keine Benutzerinformationen mehr über das Netzwerk übertragen. Der Aufbau einer Verbindung zu SQL Server unter Verwendung der integrierten Windows-Anmeldung ist schneller, da die Identität des Benutzers bei der Anmeldung nicht nochmals von SQL Server überprüft werden muss. Es besteht die Möglichkeit, Windows-Gruppen als SQL Server-Anmeldungen zu verwenden. Alle Mitglieder einer solchen Gruppe haben dann ebenfalls die Anmeldeberechtigung an SQL Server. Eine Verwendung von Domänen-Accounts ist ebenfalls möglich. Dies ermöglicht eine Vereinfachung der Verwaltung von SQL ServerAnmeldungen. Als Windows-Gruppen angelegte Anmeldungen werden in der Liste der Anmeldungen mit einem entsprechenden (Gruppen-)Symbol dargestellt (siehe Abbildung 6.25). Der Nachteil dieser Verfahrensweise ist, dass für Benutzer, denen die Berechtigung zur SQL Server-Anmeldung erteilt werden soll, eine entsprechende Windows-Anmeldung oder -Gruppe existieren muss. Dies kann zum Beispiel für Datenbankserver, die über das Internet zugänglich sein sollen durchaus nicht erwünscht sein. Die integrierte Windows-Anmeldung an SQL Server kann nicht deaktiviert werden.
258
6.3 Zugriffskontrolle SQL Server-eigene Anmeldung SQL Server bietet auch die Möglichkeit einer eigenen Authentifizierung. Die Verwaltung von Benutzern, Kennwörtern und entsprechenden Kennwortrichtlinien wird in so einem Fall von SQL Server selbst durchgeführt. Einstellung des Authentifizierungsmodus Sie haben die Möglichkeit zu wählen, ob Ihr SQL Server nur die reine Windows-Authentifizierung oder aber sowohl die Windows- als auch die SQL Server-Authentifizierung unterstützen soll. Hierzu haben Sie bereits bei der Installation von SQL Server die Gelegenheit. Der Authentifizierungsmodus kann aber auch im Nachhinein jederzeit über die Servereigenschaften geändert werden. Hierzu öffnen Sie aus dem Kontextmenü für den Server die Servereigenschaften und stellen auf der Seite Sicherheit den gewünschten Modus ein (Abbildung 6.26).
Abbildung 6.26 Auswahl der SQL Server-Authentifizierung
Wenn Sie den Modus ändern, so muss der SQL Server-Dienst neu gestartet werden, damit die Änderung wirksam wird.
6.3.2
Anmeldungen hinzufügen
Für das Hinzufügen von Anmeldekonten haben Sie wie so oft die Wahl zwischen der Verwendung von T-SQL und der grafischen Benutzeroberfläche des Management Studios. Um eine Anmeldung mit dem Management Studio hinzuzufügen, wählen Sie im Ordner Sicherheit des Servers den Punkt Neue Anmeldung (Abbildung 6.27).
Abbildung 6.27 Eine Anmeldung hinzufügen
259
6 Administration für Datenbankentwickler
Abbildung 6.28 Eine SQL Server-Anmeldung konfigurieren
Im Dialog Anmeldung Neu können Sie anschließend die Parameter für die Anmeldung konfigurieren (Abbildung 6.28). Die Abbildung zeigt die Konfiguration einer reinen SQL Server-Anmeldung. Hierbei können Sie auch Kennwortrichtlinien verwenden allerdings nur, wenn das verwendete Betriebssystem dies auch unterstützt. Dies ist zum Beispiel für Windows 2003 Server, aber nicht für Windows XP der Fall. Fall Sie eine Windows-Anmeldung für SQL Server erstellen, so muss diese bereits in Windows existieren. SQL Server verwalten nur eine Referenz zu dieser Anmeldung, die Sie in der Form DOMÄNE\BENUTZR oder auch DOMÄNE\GRUPPE angeben müssen. Im unteren Bereich haben Sie noch die Möglichkeit, eine Standarddatenbank und eine Standardsprache für die Anmeldung anzugeben. Beim Bestätigen des Dialogs mit OK wird die Anmeldung erzeugt, sofern das Kennwort korrekt angegeben wurde bzw. die Windows-Anmeldung existiert. Einige der weiterführenden Einstellmöglichkeiten werden wir weiter unten noch besprechen. Anmeldungen können selbstverständlich auch durch T-SQL-Anweisungen erstellt werden. Hierzu verwenden Sie CREATE LOGIN:
260
6.3 Zugriffskontrolle
6.3.3
Anmeldungen löschen
Eine existierende Anmeldung kann über das Kontextmenü für die Anmeldung gelöscht werden. Natürlich gibt es auch hierfür ein entsprechendes T-SQL-Kommando. DROP LOGIN ermöglicht das Entfernen existierender Anmeldungen, zum Beispiel so:
Beim Entfernen einer Anmeldung sollten Sie beachten, dass existierende Verbindungen zu Datenbankbenutzern ebenfalls entfernt werden. In einer Datenbank angelegte Benutzer bleiben allerdings erhalten. Für diese Benutzer ist dann aber keine Anmeldung mehr möglich. Auch das Neuanlegen der Anmeldung hilft hier nicht weiter, da jede Anmeldung im SQL Server unter einer eindeutigen Security ID (SID) geführt wird. Auch die Verknüpfung von Anmeldungen zu Datenbankbenutzern wird über diese SID angelegt. Wenn Sie eine Anmeldung löschen und anschließend neu anlegen, so bekommt sie eine neue SID, die Verbindung zu Datenbankbenutzern muss dann neu erzeugt werden. Dies können Sie erreichen, indem Sie die existierenden Datenbankbenutzer löschen und neu anlegen. Alternativ ist es auch möglich, die Zuordnung durch die Verwendung der gespeicherten Prozedur quasi zu reparieren. Wie dies genau funktioniert, erklärt die Online-Dokumentation.
6.3.4
Berechtigungen vergeben
Erzeugte Anmeldungen haben zunächst nur die Berechtigung, sich zu SQL Server zu verbinden. Normalerweise ist dies für die meisten Anmeldungen auch ausreichend. Wichtig ist die Vergabe von Zugriffsberechtigungen auf Datenbankebene, die weiter unten besprochen wird. Es ist aber natürlich möglich, Anmeldungen weiterführende Berechtigungen für Aktionen auf dem Server zu erteilen. Im Eigenschaftsdialog einer Anmeldung können Sie auf der Seite Sicherungsfähige Elemente (siehe nochmals Abbildung 6.28) eine Vielzahl von Aktionen erlauben. So ist es zum Beispiel möglich, für eine Anmeldung die Berechtigung zum Erzeugen von Datenbanken zu erteilen. Dies können Sie wieder wahlweise über die grafische Benutzeroberfläche oder über T-SQL erledigen. Das entsprechende TSQL-Kommando sieht so aus:
In den meisten Fällen werden Sie Berechtigungen für Anmeldungen allerdings über vordefinierte Serverrollen vergeben. Serverrollen Serverrollen fassen erlaubte Aktionen zusammen. Eine Anmeldung kann zu verschiedenen Serverrollen hinzugefügt werden (Abbildung 6.29).
261
6 Administration für Datenbankentwickler
Abbildung 6.29 Berechtigungen über Serverrollen vergeben
SQL Server kennt insgesamt acht unterschiedliche Rollen, die nicht geändert werden dürfen. Sie dürfen also den in vordefinierten Serverrollen enthaltenen Berechtigungssatz nicht verändern. Es ist auch nicht möglich, Serverrollen hinzuzufügen oder zu löschen. Für eine Beschreibung der in den einzelnen Rollen enthaltenen Berechtigungssätze konsultieren Sie bitte die Online-Dokumentation. Die sicherlich wichtigste Rolle ist sysadmin. Anmeldungen, die zu dieser Rolle hinzugefügt werden, haben die volle Berechtigung für alle möglichen Aktionen auf dem Server. In vielen Fällen werden Ihre Anmeldungen in nur zwei Kategorien eingeteilt werden: Anmeldungen, die der Rolle sysadmin zugeordnet wurden, und Anmeldungen, die in keine Serverrolle aufgenommen wurden.
6.3.5
Vordefinierte Anmeldekonten
Nach der Installation von SQL Server existieren bereits einige vordefinierte Anmeldungen. Hierzu zählen auch die zwei folgenden wichtigen Anmeldungen, die beide der sysadmin-Rolle zugeordnet sind: VORDEFINIERT\Administratoren. Dies ist eine Windows-Anmeldung für die Gruppe der lokalen Administratoren des Computers. Durch diese Anmeldung sind alle Computer-Administratoren auch gleichzeitig SQL Server-Administratoren. Die Anmeldung kann entfernt werden, wenn Sie dies nicht möchten.
262
6.3 Zugriffskontrolle sa. Dies ist eine reine SQL Server-Anmeldung. Diese Anmeldung kann nicht gelöscht, sondern nur deaktiviert werden. Hierzu verwenden Sie einfach das Kommando ALTER LOGIN:
Im Authentifizierungsmodus der ausschließlichen Windows-Sicherheit ist diese (reine SQL Server-)Anmeldung natürlich nicht verwendbar.
6.3.6
Datenbankbenutzer
Datenbankbenutzer ermöglichen die Konfiguration der Zugriffsberechtigungen auf Datenbankobjekte. Jeder Datenbankbenutzer muss zunächst mit einer SQL Server-Anmeldung verknüpft werden. Für das Erstellen dieser Verknüpfung gibt es verschiedene Möglichkeiten. Am einfachsten kann dies gleich beim Erzeugen der SQL Server-Anmeldung geschehen. Auf der Seite Benutzerzuordnung haben Sie die Möglichkeit, für alle auf dem Server existierenden Datenbanken auch gleich einen Benutzer anzulegen (Abbildung 6.30). Hierbei können Sie auch gleich ein entsprechendes Standardschema für den Benutzer angeben. Es ist möglich, dass der Benutzername in der Datenbank vom Anmeldenamen abweicht. Für reine SQL Server-Logins ist diese Verfahrensweise nicht zu empfehlen, da man dadurch schnell die Übersicht verlieren kann. Für Windows-Anmeldungen kann es allerdings durchaus sinnvoll sein, zum Beispiel den Teil mit dem Domänennamen für den Namen des Datenbankbenutzers wegzulassen.
Abbildung 6.30 Datenbankbenutzer anlegen
263
6 Administration für Datenbankentwickler Bitte stören Sie sich zunächst nicht an der unteren Liste mit den Datenbankrollen, hierauf werden wir in Kürze zurückkommen. Nach dem Bestätigen des Dialogs mit OK existieren in den ausgewählten Datenbanken die entsprechenden Benutzer, die Sie sich im Ordner Sicherheit für die Datenbank auch anzeigen lassen können (Abbildung 6.31).
Abbildung 6.31 Benutzer der Datenbank OmasKochbuch
Vordefinierte Benutzer So wie bei der Installation von SQL Server bereits vordefinierte Anmeldungen erstellt werden, werden auch beim Erzeugen von Datenbanken Standardbenutzer angelegt. Insbesondere enthält jede Datenbank die folgenden beiden Benutzer (siehe auch Abbildung 6.31): guest. Dies ist der Gastbenutzer einer Datenbank. Er erfüllt eine ähnliche Funktion wie der Benutzer Gast oder Jeder im Betriebssystem bzw. den IIS. Alle Anmeldungen, die nicht explizit einem Datenbankbenutzer zugeordnet wurden, greifen über den Benutzer guest bzw. dessen Berechtigungen auf die Datenbank zu. Der Benutzer muss in jeder Datenbank vorhanden sein, er kann also nicht gelöscht werden. Nach dem Erzeugen einer Datenbank ist der Benutzer allerdings zunächst deaktiviert. Wenn Sie den Zugriff über den guest-Benutzer ermöglichen wollen, so muss diesem Benutzer die Berechtigung zur Verbindung explizit erteilt werden:
dbo. Die Abkürzung steht für database owner. Dieser Benutzer ist immer mit der Administratoranmeldung verknüpft und besitzt stets alle Berechtigungen auf der Datenbank. Dieser Benutzer kann nicht gelöscht oder deaktiviert werden. Ebenso ist es nicht möglich, diesem Benutzer Berechtigungen zu entziehen.
264
6.3 Zugriffskontrolle
6.3.7
Datenbankrollen
Datenbankrollen ermöglichen die Zusammenfassung von Berechtigungen. Ein Datenbankbenutzer kann Mitglied in mehreren Rollen sein. Es ist sogar möglich, Datenbankrollen als Rollenmitglieder einer anderen Rolle aufzunehmen. Jede neu angelegte Datenbank enthält bereits eine Reihe von Standardrollen, die bestimmte Berechtigungen bündeln. Diese Rollen werden in Abbildung 6.32 gezeigt.
Abbildung 6.32 Standardrollen einer Datenbank
Für eine genaue Beschreibung der in den einzelnen Rollen enthaltenen Berechtigungen sehen Sie bitte in der Online-Dokumentation nach. Die in diesen Rollen enthaltenen Berechtigungen dürfen nicht verändert werden. Ebenso ist es nicht möglich, die Standardrollen zu löschen. Wichtig sind insbesondere die folgenden beiden Rollen: db_owner. Dies ist die Rolle für den Besitzer der Datenbank. Alle in dieser Rolle direkt oder indirekt (über andere Rollen) enthaltenen Benutzer besitzen alle Berechtigungen auf der Datenbank. public. Alle Datenbankbenutzer sind immer Mitglied in dieser Gruppe. Die Gruppenmitgliedschaft kann nicht außer Kraft gesetzt werden. Datenbankbenutzer erben Berechtigungen, aber auch den Entzug von Rechten über die Rollenmitgliedschaft. Um Datenbankbenutzer oder andere Rollen zu einer Rolle hinzuzufügen, öffnen Sie den Eigenschaftsdialog für die Rolle (Abbildung 6.33). Über den Button Hinzufügen können Benutzer und Rollen gesucht und zur ausgewählten Rolle hinzugefügt werden. Im Gegensatz zu Serverrollen können Sie selber Datenbankrollen mit entsprechenden Berechtigungssätzen definieren. Sie werden sofort erfahren, wie das funktioniert. Zuvor sind aber noch einige grundlegende Anmerkungen zur Vergabe von Zugriffsrechten notwendig.
265
6 Administration für Datenbankentwickler
Abbildung 6.33 Rollenmitgliedschaft festlegen
6.3.8
Rechte erteilen und entziehen
Die Vergabe von Rechten ist im SQL Server grundsätzlich durch die zwei folgenden Verfahrensweisen möglich: Anweisungsberechtigungen. Wie der Name vermuten lässt, kann durch diese Art von Berechtigungen das Recht zur Ausführung von T-SQL-Anweisungen erteilt werden. Anweisungsberechtigungen werden auf Datenbankebene vergeben. Wenn Sie dies über die grafische Benutzeroberfläche erledigen möchten, öffnen Sie das Eigenschaftsfenster für die Datenbank und wählen die Seite Berechtigungen aus (Abbildung 6.34). Beispiele für Anweisungsberechtigungen sind etwa CREATE TABLE, CREATE VIEW, CREATE SCHEMA oder auch SELECT, INSERT, DELETE und UPDATE. Objektberechtigungen. Über Objektberechtigungen können Zugriffsrechte auf Datenbankobjekte erteilt oder entzogen werden. Datenbankobjekte sind zum Beispiel Tabellen, Sichten, Schemas und gespeicherte Prozeduren. Für diese unterschiedlichen Objekte stehen auch jeweils unterschiedliche Berechtigungen zur Verfügung. So kann zum Beispiel für Tabellen und Sichten eine SELECT-Berechtigung erteilt bzw. entzogen werden. Für gespeicherte Prozeduren ist dies nicht möglich, hier steht die EXECUTEBerechtigung zur Verfügung, mit der Benutzern die Ausführung von Prozeduren gestattet oder verboten werden kann. Eine Besonderheit an dieser Stelle sind Schemas als
266
6.3 Zugriffskontrolle
Abbildung 6.34 Erteilung von Anweisungsberechtigungen auf Datenbankebene
Container für andere Datenbankobjekte. Eine auf einem Schema erteilte Berechtigung für Benutzer oder Rollen wie zum Beispiel SELECT oder EXECUTE wird automatisch an alle im Schema enthaltenen Objekte weitergegeben. Berechtigungshierarchie Lassen Sie sich bitte einmal die folgenden Aussagen durch den Kopf gehen, und denken Sie über die Konsequenzen nach: Ein Benutzer kann Mitglied in mehreren Datenbankrollen sein. Eine Datenbankrolle kann ebenfalls Mitglied im mehreren Datenbankrollen sein. Berechtigungen können für Rollen und Benutzer auf jeder Ebene erteilt bzw. entzogen werden. Berechtigungen können auch für T-SQL-Anweisungen erteilt und entzogen werden Die interessante Frage ist: Was passiert, wenn eine Berechtigung auf einer Ebene erteilt, auf einer anderen Ebene jedoch entzogen wurde? Sehen Sie sich hierzu bitte Abbildung 6.35 an. Die Darstellung zeigt eine geschachtelte Struktur von Rollen und Benutzern. In einigen Rollen und für den Benutzer A sehen Sie ein Symbol, das dafür steht, dass der Zugriff auf ein Objekt explizit erteilt oder entzogen wurde. Diese Berechtigung soll in allen Fällen dieselbe sein, also zum Beispiel eine UPDATE-Berechtigung auf eine bestimmte Tabelle. Interessant ist nun die Frage, ob Benutzer A, dem diese Berechtigung erteilt wurde, die Berechtigung auch tatsächlich besitzt. Die Antwort ist: Nein. Der Benutzer A ist über die Rol-
267
6 Administration für Datenbankentwickler
Abbildung 6.35 Beispiel für eine Rollenhierarchie
le Marketing auch Mitglied der Rolle Produktion, und für diese Rolle wurde die Berechtigung explizit verwehrt. Die grundlegende Aussage ist diese: Eine auf einer beliebigen Ebene entzogene Berechtigung kann niemals durch Erteilung auf einer anderen Ebene wieder erlangt werden. Somit würden also alle direkten oder indirekten Mitglieder der Rolle Produktion die dort entzogene Berechtigung nicht erhalten können egal wie. Auch die Mitglieder der Rolle Einkauf, denen das Recht ja erteilt wurde, haben keinen Zugriff. Benutzerin B besitzt allerdings die Berechtigung, die sie über die Rollenmitgliedschaft in der Rolle Verkauf bekommt. Letztlich ist diese Verfahrensweise ein Schutzmechanismus von SQL Server, der dafür sorgt, dass bei einer Ausuferung der Hierarchie von Rollen und Benutzern nicht versehentlich eine Berechtigung erteilt wird. Der Rat ist natürlich, dass Sie Ihre Benutzer und Rollen möglichst gradlinig und eindeutig organisieren sollten, also eine Struktur, wie Sie in Abbildung 6.35 dargestellt ist, möglichst vermeiden. Empfehlenswert ist auf jeden Fall auch die Benutzung von Schemas zur Vergabe von Berechtigungen auf Objektebene. Vergabe von Berechtigungen auf Objektebene Nun, Sie werden es wahrscheinlich bereits vermuten: Berechtigungen können durch entsprechende T-SQL-Anweisungen oder auch über die grafische Benutzeroberfläche des Management Studios erteilt und entzogen werden. Wenn Sie das Management Studio benutzen, so gibt es verschiedene Möglichkeiten des Einstiegs in die Konfiguration von Rechten. Hierfür können Sie den Eigenschaftsdialog für Rollen oder Benutzer öffnen und dort auf der Seite Sicherungsfähige Elemente Datenbankobjekte auswählen und Zugriffe erlauben bzw. verbieten. Möglich ist dies auch über den Eigenschaftsdialog eines Daten-
268
6.3 Zugriffskontrolle bankobjektes also zum Beispiel einer Tabelle oder eines Schemas. In diesem Dialog existiert eine Seite Berechtigungen, welche die Konfiguration der Zugriffe für Benutzer und Rollen auf dieses Objekt ermöglicht. Bevor wir dies an einem Beispiel betrachten, soll aber zunächst gezeigt werden, wie Zugriffe unter Verwendung von T-SQL eingestellt werden. Hierbei gibt es eine Besonderheit, die zu beachten ist. SQL Server stellt grundsätzlich drei Kommandos für die Einstellung von Berechtigungen zur Verfügung: GRANT. Durch die Anweisung GRANT wird einem Benutzer oder einer Rolle eine Berechtigung explizit gewährt. Hier zwei Beispiele für die Verwendung von GRANT:
DENY. Dies ist die Anweisung zum Entziehen einer Berechtigung. Wie bereits weiter oben gesagt, kann eine einmal entzogene Berechtigung nicht auf einer anderen Ebene durch GRANT wieder erteilt werden. Es ist aber natürlich möglich, das Recht auf der gleichen Ebene wieder zu erteilen. Eine Berechtigung wird zum Beispiel so entzogen:
REVOKE. Über REVOKE wird eine erteilte oder entzogene Berechtigung wieder zurückgenommen. Dies bedeutet, dass an der Stelle keine Aussage über die Berechtigung getroffen wird. Ein Benutzer oder eine Rolle wird dann die entsprechende Berechtigung über die Mitgliedschaft in einer Rolle sozusagen erben.
Wenn Sie nochmals Abbildung 6.35 betrachten, so wurde die Berechtigung dort für diejenigen Rollen bzw. Benutzer explizit erteilt, bei denen Sie das Zutrittssymbol vorfinden. Den dort dargestellten Rollen und Benutzern mit dem Verbotssymbol wurde die Berechtigung entzogen. Für die Rollen und Benutzer ohne Symbol wurde keine Aussage getroffen, diese befinden sich sozusagen im Zustand Revoked. Abschließend soll nun noch ein Beispiel für die Konfiguration von Berechtigungen mit dem Management Studio präsentiert werden. Wir werden hierfür das Schema Kochen unserer Datenbank OmasKochbuch verwenden. Nach dem Öffnen des Eigenschaftsformulars für das Schema wird zunächst auf die Seite Berechtigungen gewechselt. Wenn Sie die obigen GRANT-, DENY- und REVOKE-Anweisungen ausgeführt haben, sehen Sie eine Darstellung wie in Abbildung 6.36. Die für den Benutzer DavidBowman erteilte UPDATE-Berechtigung für das Schema ist in der Spalte Erteilen für die Berechtigung Update zu erkennen. Im oberen Bereich des Fensters können Sie über den Button Hinzufügen weitere Rollen oder Benutzer, für die eine Konfiguration vorgenommen werden soll, in die Liste der Benutzer oder Rollen aufnehmen. Die untere Liste bietet eine Auswahl aller verfügbaren Berechtigungen für das aus-
269
6 Administration für Datenbankentwickler
Abbildung 6.36 Zugriffsberechtigungen für ein Schema konfigurieren
gewählte Objekt in diesem Fall ein Schema an. Über die Spalten Erteilen oder Verweigern haben Sie die Möglichkeit, die entsprechende Berechtigung zu vergeben bzw. zu entziehen. Erteilen entspricht hierbei GRANT, Verweigern bedeutet DENY. Wenn weder Erteilen noch Verweigern ausgewählt ist, so ist diese Berechtigung im Status REVOKE. Wenn Sie die Spalte Mit Erteilung auswählen, so darf der entsprechende Benutzer eine erteilte Berechtigung weitergeben. Wenn es sich um eine Rolle handelt, so gilt dies für alle Mitglieder dieser Rolle. Seht nützlich ist der Button Effektive Berechtigungen. Beim Klick auf diesen Button wird die Berechtigungshierarchie durchsucht. Anschließend werden in einem Fenster alle tatsächlichen Berechtigungen für den ausgewählten Benutzer oder die Rolle aufgelistet.
6.4
Überwachung von SQL Server SQL Server bietet für den Administrator umfangreiche Möglichkeiten zur Systemüberwachung an. Einen ersten Einblick in die hierfür vorhandenen Mittel haben Sie bereits in Kapitel 3 erhalten, in dem gezeigt wurde, wie Berichte für Server bzw. Datenbanken erzeugt werden können. In diesem Abschnitt werden Sie eine Auswahl der zur Verfügung stehenden Möglichkeiten zur Überwachung von SQL Server kennenlernen. Hierbei werden wir wie auch sonst an
270
6.4 Überwachung von SQL Server vielen Stellen sowohl das Management Studio als auch T-SQL für die Abfrage von Informationen verwenden. Die Thematik ist enorm spannend, interessant und überdies entsprechend umfangreich. Daher ist auch hier wieder die Beschränkung auf einige wesentliche Aspekte erforderlich. Mit den an dieser Stelle präsentierten Informationen werden Sie aber in die Lage versetzt, nach Belieben tiefer in die Materie einsteigen zu können. In Kapitel 12 werden wir das Thema Überwachung dann nochmals aufgreifen und untersuchen, wie die in SQL Server gespeicherten Informationen für eine Optimierung von TSQL-Abfragen interpretiert werden können.
6.4.1
Was kann überwacht werden?
SQL Server enthält Systemobjekte, in denen der Verlauf der Serveraktivitäten gespeichert wird. Diese Systemobjekte sind in den meisten Fällen Sichten, aber auch Funktionen und gespeicherte Prozeduren. Insgesamt können Sie unter Verwendung dieser Systemobjekte Informationen zu den folgenden Bereichen erhalten: Datenbankobjekte. Hierunter fallen einerseits statische Informationen wie zum Beispiel die Struktur von Tabellen. Auf der anderen Seite können auch dynamische Informationen, wie beispielsweise die Verwendung von Indizes, abgefragt werden. Spiegelungen. Wenn Sie eine Datenbankspiegelung für eine Hochverfügbarkeit Ihrer Datenbank eingerichtet haben, so können Sie die Parameter dieser Konfiguration natürlich auch abfragen. Common Language Runtime (CLR). Die Integration der CLR in SQL Server erlaubt die Erstellung von Datenbankobjekten in einer .NET-Programmiersprache. SQL Server ermöglicht die Ausgabe von Informationen über die entsprechenden Datenbankobjekte und Assemblies. Wir werden die Thematik CLR Integration in Kapitel 10 behandeln. Endpunkte. SQL Server kann Endpunkte für die Kommunikation mit der Außenwelt bereitstellen. Solche Endpunkte werden zum Beispiel bei der Spiegelung oder beim Einsatz des Service Brokers verwendet. In diesem Buch finden Sie in Kapitel 11 weiterführende Informationen über die Erstellung von http-Endpunkten mit SQL Server. Partitionen. Für große Tabellen oder Indizes können Partitionen erstellt werden, wodurch eine Aufspaltung dieser Objekte in mehrere physikalische Dateien möglich ist. Falls Sie Partitionierung verwenden, bekommen Sie von SQL Server auch Informationen zum Aufbau dieser Partitionen und der zugehörigen Steuerungsmechanismen. Ausführungsumgebung. Hierunter fällt einerseits die Ausführung von T-SQL-Anweisungen, andererseits können auch Informationen zu Verbindungen erhalten werden. Datenbanken und Speicher. Es ist möglich, den Speicherplatzbedarf von Datenbanken, Datenbankdateien oder auch von einzelnen Tabellen bzw. Indizes abzufragen. Sie können weiterhin Informationen über den E/A-Durchsatz je Datenbank erhalten. Service Broker. Bei Einsatz des Service Brokers werden spezielle Datenbankobjekte wie beispielsweise Nachrichtentypen, Warteschlangen oder Verträge erzeugt, über
271
6 Administration für Datenbankentwickler die der Server natürlich auch Auskunft geben kann. Der Service Broker ist Gegenstand des Kapitels 9. Sicherheit. Hiermit sind zum einen Informationen über angelegte Anmeldungen oder Datenbankbenutzer gemeint. Erteilte bzw. entzogene Berechtigungen auf Anweisungsund Objektebene können ebenso abgefragt werden. Transaktionen. Dieser Bereich betrifft zum Beispiel Informationen zu Sperren und Blockierungen oder zu aktiven Snapshots (in der entsprechenden Isolierungsstufe) Server und Betriebssystem. Hierunter fallen zum Beispiel Wartezustände des Servers oder auch dessen Hauptspeicherverwendung. Ebenso können (insgesamt über 800) Leistungsindikatoren abgefragt werden, die zum Beispiel Auskunft über durchschnittliche Wartezeiten zum Erlangen einer bestimmten Sperre oder die Anzahl von Deadlocks geben. Ablaufverfolgung und Ereignisse. SQL Server enthält Sichten, Funktionen und Prozeduren zur Erstellung und Auswertung von Ablaufverfolgungen. In Kapitel 12 wird diese Thematik eingehender behandelt.
6.4.2
Überwachung mit dem SQL Server Management Studio
Das SQL Server Management Studio ist das zentrale Arbeitsinstrument für den Administrator. Als solches bietet es natürlich Möglichkeiten sowohl zur Konfiguration des Servers als auch für die Überwachung desselben. Über das Kontextmenü der SQL Server-Instanz können Servereinstellungen angezeigt und geändert werden. Dies haben wir bereits in Abschnitt 6.3.1 für die Konfiguration der SQL Server-Authentifizierung getan. Schauen Sie sich bitte nochmals Abbildung 6.26 an, um sich zu erinnern. Das Management Studio bietet weitere Möglichkeiten zur Überwachung der aktuellen oder vergangenen Serveraktivitäten: SQL Server-Protokolle SQL Server protokolliert bestimmte Ereignisse in Textdateien. Diese Dateien finden Sie im Verzeichnis:
Jede auf einem Computer installierte SQL Server-Instanz besitzt hierbei ein eigenes Protokollverzeichnis. Die Angabe von .1 im obigen Pfad steht gerade für die erste SQL Server-Instanz oder die Standardinstanz. Bei jedem Neustart von SQL Server wird eine neue Protokolldatei mit dem Namen ERRORLOG in diesem Verzeichnis erzeugt, in die dann von der laufenden Instanz Einträge aufgenommen werden. Dies ist also das aktuelle Protokoll. Darüber hinaus archiviert SQL Server sechs weitere Protokolldateien mit Namen ERRORLOG.1
ERRORLOG.6. Der Name ERRORLOG ist hierbei etwas verwirrend, denn die Dateien enthalten nicht nur Fehler, sondern auch Informationen. Sie können die Dateien einfach mit einem Texteditor öffnen. Komfortabler ist die im Ordner Verwaltung\SQL Server-Protokolle des Objekt-Explorers angebotene Möglichkeit zur Betrach-
272
6.4 Überwachung von SQL Server
Abbildung 6.37 SQL Server-Protokolle
tung der Protokolle (Abbildung 6.37). Dort können Sie sowohl die archivierten als auch das aktuelle Protokoll zur Ansicht auswählen. Nach einem Doppelklick auf ein Protokoll öffnet sich der Protokolldatei-Viewer (Abbildung 6.38). In diesem Fenster können Sie zum Beispiel auch andere Protokolldateien (die Sie eventuell archiviert haben) laden oder bestimmte Protokolleinträge suchen bzw. nach diesen Filtern. Die Abbildung zeigt die Protokolleinträge für die automatische Wiederherstellung der Datenbanken beim Serverstart.
Abbildung 6.38 Das aktuelle SQL Server-Protokoll
Aktivitätsmonitor Der Aktivitätsmonitor gibt Informationen über alle derzeit offenen Verbindungen zu einer SQL Server-Instanz aus. Sie finden den Aktivitätsmonitor von SQL Server im Ordner Verwaltung (siehe nochmals Abbildung 6.37). Im Monitor selber können Sie zwischen
273
6 Administration für Datenbankentwickler
Abbildung 6.39 Prozessinformation im Aktivitätsmonitor
drei unterschiedlichen Sichten auf die aktuellen Aktivitäten wählen, indem Sie die entsprechende Seite im linken Bereich des Fensters auswählen (Abbildung 6.39). Die Abbildung zeigt die aktuelle Aktivität der existierenden Verbindungen. Die Prozess-ID in der linken Spalte enthält nur Prozesse, deren ID größer als 50 ist. Dies sind gerade alle Benutzerverbindungen. IDs bis 50 sind für die interne Verwendung durch SQL Server-eigene Prozesse reserviert. In der Spalte Status ist zu sehen, dass keiner der Prozesse aktiv ist alle befinden sich im Status sleeping. Die Prozess-ID 51 ist die vom Objekt-Explorer verwendete Verbindung. Auch der Aktivitätsmonitor selber taucht natürlich in der Liste der Prozesse auf, er hat hier die Prozess-ID 52. Der Aktivitätsmonitor kann auch Sperren auf Datenbankobjekten und Serverressourcen darstellen. Für eine entsprechende Anzeige können Sie eine der anderen beiden Seiten Sperren nach Prozess oder Sperren nach Objekt öffnen. Dies ist besonders dann nützlich, wenn bestimmte Verbindungen nicht mehr reagieren, um herauszufinden, warum bzw. von wem eine solche Verbindung gerade blockiert wird. Berichte Bereits in Kapitel 3 haben Sie die Möglichkeiten der Berichtserzeugung für unterschiedlich Serverbereiche und Datenbanken kennengelernt. Für eine SQL Server-Instanz selber stehen ebenfalls Berichte zur Verfügung. Falls Sie eine SQL Server-Instanz überwachen müssen, so kann ich Ihnen nur nahelegen, sich mit den bereitgestellten Berichten auseinanderzusetzen.1 Diese Berichte sind wirklich sehr nützlich und informativ. Abbildung 6.40 zeigt einige der verfügbaren Berichte. 1
274
Ab dem Service Pack 2 von SQL Server haben Sie auch die Möglichkeit, eigene Berichte hinzuzufügen.
6.4 Überwachung von SQL Server
Abbildung 6.40 Verfügbare Berichte für eine SQL Server-Instanz (Auszug)
Mit der Bezeichnung erste Abfragen sind hier stets diejenigen Abfragen gemeint, die den meisten Ressourcenverbrauch jeweils bezogen auf die erwähnte Ressource haben. Abbildung 6.41 zeigt ein Beispiel für den Bericht Leistung Erste Abfragen nach durchschnittlicher EA. Dieser Bericht enthält im oberen Bereich eine grafische Übersicht über die erzeugten logischen und physikalischen Lese- bzw. Schreibvorgänge. Unterhalb der beiden Diagramme finden Sie dann eine detaillierte Übersicht.
Abbildung 6.41 Beispielbericht: Leistung Erste Abfragen nach durchschnittlicher EA
275
6 Administration für Datenbankentwickler
6.4.3
Überwachung mit dem SQL Server Profiler
Der bereits in Kapitel 3 erwähnte SQL Server Profiler ist für den Datenbankentwickler ein wichtiges Instrument für die Fehlersuche. Der Profiler ermöglicht es Ihnen, genau zu beobachten, in welcher Form vom Client abgeschickte Anfragen tatsächlich am Server ankommen. Mit dem Profiler können Sie unter anderem die Ausführungsdauer von Abfragen oder die von Abfragen erzeugte CPU-Last protokollieren. Beim Start von SQL Server wird übrigens immer eine Ablaufverfolgung ausgeführt, die allerdings nur minimale Informationen sammelt sozusagen als Flugschreiber. Die Daten dieser Protokollierung finden Sie ebenfalls im LOG-Verzeichnis der SQL Server-Instanz. Dies sind die Dateien mit der Endung .trc (für Trace). Beim Doppelklick auf eine solche Datei wird diese im Profiler geöffnet und kann dann dort durchsucht werden. Wir werden den Profiler an dieser Stelle nicht weiter behandeln, in Kapitel 12 aber nochmals auf ihn zurückkommen.
6.4.4
Überwachung mit dem Windows-Systemmonitor
Nach der Installation von SQL Server stehen Ihnen im Windows-Systemmonitor über 800 zusätzliche Indikatoren in 23 Kategorien zur Verfügung, die Sie überwachen und protokollieren können. Hierzu zählen zum Beispiel die Größe von Datenbankdateien, die Nutzung der CPU, die Anzahl von Sperren und Deadlocks oder die Trefferquote im Datencache von SQL Server. Abbildung 6.42 zeigt den Systemmonitor in Aktion.
Abbildung 6.42 Der Windows-Systemmonitor zur Überwachung von SQL Server-Indikatoren
276
6.4 Überwachung von SQL Server Etwas weiter unten werden Sie sehen, wie Sie die Leistungsindikatoren auch ohne Verwendung des Systemmonitors abfragen können. In Kapitel 12 werden wir den Systemmonitor zusammen mit dem Profiler verwenden, um Abfragen zu finden, die eine hohe Serverlast erzeugen.
6.4.5
Verwendung von T-SQL zur Überwachung
SQL Server enthält insgesamt einige hundert Systemobjekte, mit denen Informationen zum Server abgefragt, aber auch geändert werden können. Diesbezüglich gibt es eine gute und eine schlechte Nachricht. Die gute Nachricht ist, dass Sie bei Beherrschung dieser Objekte ein enorm mächtiges Instrument zur Untersuchung von Server- und Datenbankobjekten in der Hand halten. Die schlechte Nachricht ist natürlich, dass diese Thematik genauso komplex wie nützlich ist. Ich kann Ihnen aber nur wärmstens empfehlen, sich mit den zur Verfügung stehenden Systemsichten und Prozeduren einmal auseinanderzusetzen. Für die Systemsichten finden Sie eine sehr gute Übersicht im PDF-Format auf der Microsoft Web Site (Googeln Sie einfach SQL Server 2005 System Views). Das entsprechende Dokument wird auch auf der Website zu diesem Buch zu finden sein. Allerdings können sich mit der Auslieferung von Service Packs natürlich Änderungen ergeben, sodass Sie die jeweils aktuelle Version vorzugsweise von der Microsoft-Website herunterladen sollten. Im folgenden Teil kann wieder nur eine Einführung in die Verwendung der Systemsichten bzw. -prozeduren gegeben werden. Später im Kapitel 12 werden wir dann nochmals Systemsichten einsetzen, um Abfragen zu untersuchen. Kataloginformationen Natürlich kann SQL Server auch Auskunft über Datenbanken und Datenbankobjekte geben. Hierzu zählen zum Beispiel die Auflistung der überhaupt existierenden Datenbankobjekte oder der Aufbau von Tabellen, also etwa Informationen über Tabellenspalten oder Einschränkungen. Für die Abfrage dieser sogenannten Metadaten oder Kataloginformationen stehen Ihnen in jeder Datenbank spezielle Systemsichten in zwei unterschiedlichen Schemas zur Verfügung: INFORMATION_SCHEMA. Der ANSI-Standard schreibt für die Abfrage von Metadaten die Implementierung dieses Schemas vor. In dem Schema finden Sie entsprechende Sichten für derartige Abfragen (Abbildung 6.43).
277
6 Administration für Datenbankentwickler
Abbildung 6.43 INFORMATION_SCHEMA-Sichten
Die INFORMATION_SCHEMA.TABLES-Sicht gibt zum Beispiel Informationen für die in der Datenbank enthaltenen Tabellen zurück, INFORMATION_SCHEMA.COLUMNS liefert diese Information für die Tabellenspalten. Beide Sichten können durch einen JOIN verknüpft werden:
Einen Ausschnitt aus dem Ergebnis der Abfrage zeigt Abbildung 6.44.
278
6.4 Überwachung von SQL Server
Abbildung 6.44 Spalten der Tabelle Extras.Kalender
Das Schema sys. Ebenfalls in jeder Datenbank finden Sie das Schema sys. In diesem Schema ist unter anderem eine Reihe von Systemsichten enthalten, die eine SQL Server-eigene Sicht auf die Metainformationen liefern. So gibt es dort zum Beispiel die Sichten und , die auch zur Abfrage von Informationen über Tabellen und die enthaltenen Spalten benutzt werden können. Darüber hinaus liefern die in diesem Schema enthaltenen Sichten auch Informationen über physikalische Parameter, wie zum Beispiel die Verwendung von Betriebssystemdateien von Datenbanken, was mit den INFORMATION_SCHEMA-Sichten die nur eine logische Sicht auf Datenbankobjekte gestatten nicht möglich ist. Systemprozeduren In Kapitel 2 wurden bereits kurz gespeicherte Prozeduren erwähnt, die T-SQL-Code unter einem Namen im Server ablegen. Der enthaltene Code kann dann einfach durch den Aufruf des Prozedurnamens ausgeführt werden. Im anschließenden Kapitel 7 werden wir eigene gespeicherte Prozeduren entwickeln. SQL Server enthält auch eine Reihe von Systemprozeduren, die ebenfalls zur Abfrage von Serverinformationen, aber auch zum Verändern von Parametern, also zur Serverkonfiguration verwendet werden können. Diese Prozeduren sind ebenfalls im Schema sys einer Datenbank enthalten. Die folgende Abfrage legt eine Datenbank mit den Standardeinstellungen an und gibt anschließend die Anzahl der enthaltenen gespeicherten Systemprozeduren zurück:
279
6 Administration für Datenbankentwickler Das Ergebnis der obigen Abfrage (SQL Server Developer Edition mit Service Pack 2) ist 1261! Es erübrigt sich wohl fast zu erwähnen, dass an dieser Stelle nicht alle gespeicherten Systemprozeduren behandelt werden, sondern dass wir uns auf eine kleine Auswahl beschränken müssen. Ehrlich gesagt, bin ich bislang auch noch niemandem begegnet, der von sich selber behaupten konnte, alle dieser Prozeduren zu kennen. Es gibt natürlich Prozeduren, die in der täglichen Arbeit sehr häufig eingesetzt werden. Hierzu zählen vor allem die folgenden (in alphabetischer Reihenfolge): sp_configure. Mit dieser Prozedur können serverweite Optionen eingestellt und abgefragt werden. Die Prozedur wird folgendermaßen aufgerufen:
Der Parameter option bezeichnet die Option, für welche die Einstellung geändert oder abgefragt werden soll. Über den zweiten Parameter wert wird die neue Einstellung für die Option angegeben. Beide Parameter sind optional. Wird wert nicht angegeben, dann erhalten Sie die aktuelle Einstellung für die Option. Wenn Sie option ebenfalls weglassen, so bekommen Sie als Ergebnis die Einstellung für alle Optionen. Abbildung 6.45 zeigt einen Ausschnitt aus dem Ergebnis des Prozeduraufrufs ohne Parameter.
Abbildung 6.45 Auszug aus dem Ergebnis des Aufrufs von sp_configure
In der Spalte config_value sehen Sie den jeweils konfigurierten Wert. Die Spalte run_value zeigt den gerade gültigen Wert. Nach dem Ändern einer Option muss die Funktion aufgerufen werden, damit die Änderung wirksam wird. config_value wird dadurch sozusagen in run_value übernommen. Etwas weiter unten finden Sie ein Beispiel hierfür. Bei einigen wenigen Änderungen ist es sogar erforderlich, den SQL Server-Dienst anschließend neu zu starten, damit der konfigurierte Wert wirksam wird. kennt auch eine Option show advanced options, mit der festgelegt
werden kann, dass erweiterte Optionen ebenfalls angezeigt oder geändert werden dürfen. Die obige Abbildung zeigt das Ergebnis des Prozeduraufrufs mit dem Wert 1 für show advanced options. Ist diese Option auf 0 gesetzt, so würde nur eine einge-
280
6.4 Überwachung von SQL Server schränkte Liste angezeigt. Die Bezeichnung show advanced options ist ein wenig irreführend, da auch keine erweiterten Optionen geändert werden dürfen, wenn der Wert für diese Option 0 ist. sp_help. Mit dieser Prozedur können Informationen zu Datenbankobjekten abgefragt werden. Die Prozedur erwartet hierfür als einzigen Parameter den Namen des Datenbankobjektes. Dieser Name kann zum Beispiel eine Tabelle, ein Schema oder auch eine gespeicherte Prozedur sein. Die von der Prozedur erzeugte Ausgabe variiert je nach Objekttyp. So werden zum Beispiel für eine Tabelle deren Struktur, der Primärschlüssel, Informationen über existierende Indizes und existierende Referenzen ausgegeben. Wenn das Datenbankobjekt eine Prozedur ist, so erhalten Sie Auskunft über den Besitzer der Prozedur, ihre Erstellungszeit sowie die von der Prozedur erwarteten Parameter. Da ebenso eine Prozedur ist, können Sie diese Informationen natürlich auch für bekommen:
Das Ergebnis des Prozeduraufrufs sieht so aus, wie in Abbildung 6.46 gezeigt.
Abbildung 6.46 Ergebnis des Aufrufs von sp_help 'sp_help'
Es gibt ungefähr noch 90 weitere sp_help-Prozeduren zur Abfrage von Informationen zu Datenbankobjekten, aber auch Servereinstellungen Hierzu gehören zum Beispiel die folgenden:
Liefert Informationen zu Datenbanken.
Liefert Informationen über den Code von gespeicherten Prozeduren, benutzerdefinierten Funktionen oder Sichten.
Gibt Informationen über Indizes zurück.
Liefert Informationen über registrierte Server.
Es lohnt sich auf jeden Fall, in der Online-Dokumentation nach sp_help zu suchen und die gefundenen Prozeduren einfach einmal auszuprobieren. sp_spaceused. Diese Prozedur gibt Informationen über den von einer Tabelle oder Sicht verwendeten Speicherplatz zurück. Eine Sicht belegt nur dann Speicherplatz, wenn ein Index für diese Sicht erzeugt wurde. Indizes werden wir in Kapitel 12 behandeln. Abbildung 6.47 zeigt die Ausgabe der Prozedur für den Aufruf:
281
6 Administration für Datenbankentwickler
Abbildung 6.47 Ergebnis des Aufrufs von
sp_who. Die Prozedur gibt Informationen zu existierenden Verbindungen zu SQL Server zurück. Der Prozedur kann auch ein Anmeldename als Parameter übergeben werden, um nur Informationen für Verbindungen zu erhalten, die unter dieser Anmeldung geöffnet wurden. Wenn Sie ACTIVE als Anmeldenamen übergeben, dann werden nur die gerade aktiven Verbindungen ausgegeben. Abbildung 6.48 auf der nächsten Seite zeigt einen Ausschnitt aus dem Ergebnis für den Aufruf ohne Parameter. Es ist zu erkennen, dass auch SQL Server-interne Prozesse in der Ausgabe enthalten sind. Dies sind die Zeilen mit einem Wert kleiner 50 in der Spalte spid. xp_cmdshell. Nein, dies ist kein Schreibfehler. Die Prozedur heißt tatsächlich so. SQL Server stellt auch eine Reihe von sogenannten erweiterten gespeicherten Prozeduren zur Verfügung, deren Namen allesamt mit xp_ beginnen. Diese Prozeduren sind nicht in T-SQL, sondern in einer Programmiersprache wie C oder C++ implementiert.
Abbildung 6.48 Ausschnitt aus dem Ergebnis des -Aufrufs
Die Entwicklungsumgebung Visual Studio 6.0 enthält eine entsprechende Projektvorlage für die Entwicklung solcher Prozeduren, die innerhalb von DLLs existieren und vom Server ausgeführt werden. Mit können Sie Kommandos auf der Windows-Systemebene ausführen, also zum Beispiel so etwas:
Als Ergebnis bekommen Sie eine Liste aller Dateien im Verzeichnis C:\. ermöglicht auch dies:
kann also auch für Angriffe auf den Server bzw. sogar das gesamte Netzwerk missbraucht werden. Aus diesem Grund ist die Verwendung von nach der In-
282
6.4 Überwachung von SQL Server stallation von SQL Server zunächst nicht gestattet. Wenn Sie einsetzen möchten, so müssen Sie dieses Feature explizit aktivieren. Dies kann zum Beispiel über den folgenden Aufruf von geschehen:
Alternativ kann die Aktivierung auch mit der SQL Server-Oberflächenkonfiguration vorgenommen werden. Erweiterte gespeicherte Prozeduren werden nur noch aus Gründen der Abwärtskompatibilität unterstützt. Für Neuentwicklungen sollten Sie in jedem Fall auf die Möglichkeit der .NET-Integration zurückgreifen, so wie dies in Kapitel 10 besprochen wird. Die recht kurze Aufzählung soll an dieser Stelle zunächst abgeschlossen werden. In den folgenden Kapiteln werden wir noch das eine oder andere Mal auf Systemprozeduren zurückkommen und dann auch einige weitere kennenlernen. Dynamische Verwaltungssichten (Dynamic Management Views) SQL Server protokolliert sämtliche Aktionen und führt dadurch eine Historie, zum Beispiel über durchgeführte Abfragen, den Ressourcenverbrauch innerhalb von Verbindungen oder etwa aufgetretene Blockierungen innerhalb von Transaktionen. Diese Protokolldaten können über sogenannte dynamische Verwaltungssichten abgefragt werden. SQL Server stellt insgesamt über 70 dieser Sichten zur Verfügung von denen manche in Wirklichkeit allerdings keine Sichten, sondern Funktionen sind. Die dynamische Verwaltungssichten ermöglichen die Abfrage von Informationen aus allen im Punkt 6.4.1 aufgezählten Bereichen. Bevor wir einige dieser Sichten und Funktionen betrachten, soll aber noch Folgendes erwähnt werden: 1. Bei jedem Neustart von SQL Server beginnt die Protokollierung der dynamischen Informationen von vorne. Anders gesagt: Die existierenden Protokolle werden gelöscht, wenn der SQL Server-Dienst beendet wird. 2. SQL Server sorgt dafür, dass durch die dynamische Protokollierung der Server nicht übermäßig belastet wird. Viele der Protokolle belegen ihrerseits Serverressourcen, wie zum Beispiel Hauptspeicher. Wenn die Protokollierung zu viele dieser Ressourcen benötigt, so werden ältere Einträge aus dem Protokoll entfernt. Die verfügbare Historie wird dadurch also nicht unbedingt bis zum letzten Serverstart zurückreichen. 3. Die bereits an mehreren Stellen erwähnten Berichte, die Ihnen im Management Studio für die Überwachung zur Verfügung stehen, benutzen ebenfalls sehr häufig die dynamischen Verwaltungssichten. Die Namensgebung für dynamische Verwaltungssichten folgt einem bestimmten Muster. Zunächst einmal sind alle dynamischen Verwaltungssichten im Schema sys jeder Datenbank gespeichert. Alle Namen beginnen stets mit dm_. Schauen Sie sich die Systemsichten einer beliebigen Datenbank ruhig einmal an, und filtern Sie den Ordner nach dm_ (Abbildung 6.49 auf der nächsten Seite). Der nächste Teil des Namens (also derjenige Teil
283
6 Administration für Datenbankentwickler nach dem ersten Unterstrich) steht dann für den Bereich, für den die Sicht Informationen liefert. Beispiele für diesen Teil sind etwa db für Datenbank, os für Betriebssystem, exec für Abfrageausführung oder auch tran für Transaktionen. Wir werden auf die dynamischen Verwaltungssichten in den verbleibenden Kapiteln dieses Buches noch des Öfteren zurückkommen. An dieser Stelle sollen nur einige Beispiele aufgeführt werden, um Ihnen das Potenzial dieser Sichten zu zeigen. So gibt es zum Beispiel die Sicht , in der alle Leistungsindikatoren von SQL Server protokolliert werden. Wie bereits gesagt, sind dies insgesamt etwa 800. In Kapitel 5 haben wir diese Sicht bereits verwendet, um die Anzahl der aufgetretenen Deadlocks abzufragen. Um
Informationen
über
den
Datendurchsatz
zu
erhalten,
kann
die
Sicht
verwendet werden. In dieser Sicht werden die Bytes
für Lese- und Schreiboperationen jeder Datenbankdatei summiert. Die folgende Abfrage ermittelt ein entsprechendes Ergebnis:
Abbildung 6.49 Dynamische Verwaltungssichten (Auszug)
284
6.4 Überwachung von SQL Server Die Systemsicht enthält Informationen über die Zuordnung von Datenbankdateien zu Datenbanken. Diese Sicht kann zur obigen Abfrage hinzugefügt werden, um den E/A-Durchsatz für Datenbanken und Dateien zu erhalten:
Die Abfrage gibt auch die Wartezeit zurück, die Prozesse auf den Abschluss von E/AOperationen warten mussten. Angezeigt wird weiterhin der Typ der Datenbankdatei (Protokoll oder Daten). Ein Beispiel für ein Ergebnis zeigt Abbildung 6.50.
Abbildung 6.50 E/A-Durchsatz je Datenbankdatei
Ein abschließendes Beispiel soll noch zeigen, wie dynamische Verwaltungssichten verwendet werden können, um Informationen über aktive Verbindungen abzufragen, also festzustellen, was gerade auf dem Server los ist. Hierzu kann die Sicht verwendet werden, in der Information über aktive Verbindungen geführt werden. Die Sicht enthält die Information über die Aktivitäten der aktiven Verbindungen. Beide Sichten können folgendermaßen miteinander verknüpft werden:
285
6 Administration für Datenbankentwickler Die Abfrage filtert nur Benutzerverbindungen heraus, die eine SessionID von größer 50 besitzen. Ein Beispiel für ein Ergebnis sehen Sie in Abbildung 6.51:
Abbildung 6.51 Aktivität der aktuellen Benutzerverbindungen
Die obige Abfrage kann nützlich sein, wenn der Server ausgelastet ist. Unter Umständen ist es möglich, den Prozess, der die Last erzeugt, zu identifizieren. Systemfunktionen SQL Server bietet eine Reihe von Systemfunktionen für die Überwachung von Server- und Datenbankeinstellungen. Die für diese Zwecke wichtigsten Funktionen wurden in Kapitel 4 bereits vorgestellt und werden daher an dieser Stelle nicht nochmals präsentiert.
6.5
Zusammenfassung In diesem Kapitel haben Sie eine Einführung in die Aufgaben eines SQL ServerAdministrators erhalten. Sie wissen jetzt in Grundzügen, wie Datensicherungen organisiert werden und wie Sie im Fehlerfall eine Wiederherstellung vornehmen können. Ganz nebenbei haben Sie hierbei auch gesehen, wie der SQL Server-Agent für eine zeitplangesteuerte Auftragsausführung eingesetzt werden kann. Sie kennen nun die zur Verfügung stehenden Möglichkeiten für den Schutz von SQL Server und Datenbanken vor unbefugten Zugriffen und können sowohl SQL ServerAnmeldungen als auch Datenbankbenutzer einrichten. Der letzte Teil dieses Kapitels beschäftigte sich mit der Überwachung des Servers durch das SQL Server Management Studio bzw. Abfragen der SQL Server-Systemobjekte. Noch ein abschließendes Wort zu den Systemsichten: Wenn Sie tiefer in die Thematik einsteigen möchten, so ist es eine sehr gute Idee, den Code von gespeicherten Systemprozeduren zu inspizieren. Viele dieser Prozeduren bieten letztlich eine Schnittstelle zur Abfrage oder Veränderung von Systemsichten, verwenden diese Sichten also intern. Wenn Sie sich den Code zum Beispiel von oder ansehen, so können Sie genau erkennen, wo die Informationen letztlich herkommen. Hierzu können Sie zum Beispiel einfach
aufrufen.
286
7 T-SQL-Programmierung Nachdem in Kapitel 4 die Grundlagen von T-SQL behandelt wurden, wollen wir in diesem Kapitel nun in die erweiterten Möglichkeiten von T-SQL einsteigen und sehen, welche Sprachelemente T-SQL für die Programmierung bietet.
7.1
Variablen Wie alle Programmiersprachen, so erlaubt auch T-SQL die Verwendung von Variablen in Ausdrücken. Eine T-SQL-Variable beginnt mit dem Zeichen @.
7.1.1
Deklaration von Variablen
Variablen müssen vor ihrer ersten Verwendung deklariert werden. T-SQL erlaubt nur getypte Variablen, d. h., bei der Deklaration muss der Typ der Variablen angegeben werden1. Als Datentypen stehen Ihnen hierbei die bekannten T-SQL-Typen zur Verfügung. Die Deklaration einer Variablen wird durch das Schlüsselwort DECLARE vorgenommen. Hierbei können in einer DECLARE-Anweisung auch mehrere Deklarationen stehen, die dann durch Komma getrennt sein müssen:
Eine Variable ist nur in dem T-SQL-Stapel gültig, in dem sie deklariert wurde. Das Stapeltrennzeichen GO erklärt also auch alle Variablen-Deklarationen des abgeschlossenen Stapels für ungültig.
1 Wir
wollen hier auch SQL_VARIANT als einen speziellen Datentyp ansehen.
287
7 T-SQL-Programmierung
7.1.2
Wertzuweisung an Variablen
T-SQL bietet keine Möglichkeit, einer Variablen bereits bei der Deklaration einen Standardwert zuzuweisen. Unmittelbar nach der Deklaration ist der Wert einer Variablen unabhängig vom verwendeten Datentyp NULL, die Variable ist also zunächst leer. Damit eine Variable sinnvoll verwendet werden kann, muss ihr natürlich ein Wert zugewiesen werden können. Hierfür gibt es in T-SQL prinzipiell zwei Möglichkeiten: Wertzuweisung durch SET Der Wert einer Variablen kann durch die Anweisung SET wie folgt festgelegt werden:
Es ist nicht möglich, etwa so wie bei DECLARE, mehrere Zuweisungen in einer SETAnweisung auszuführen. Jede Zuweisung muss also in einer separaten SET-Anweisung erfolgen. Möglich ist auch die Zuweisung eines Wertes aus einer Abfrage an eine Variable durch SET. Hierfür darf die Abfrage allerdings nur eine einzige Zeile und auch nur eine Spalte liefern. Hier ist ein Beispiel für eine derartige Zuweisung:
Die SELECT-Anweisung muss also in runde Klammern eingeschlossen werden und darf wie gesagt nur eine Zeile zurückliefern, ansonsten wird die Zuweisung nicht funktionieren. Wenn Sie in der obigen Anweisung die WHERE-Bedingung weglassen, erhalten Sie folgende Fehlermeldung:
Das obige T-SQL-Skript zeigt auch, wie der Wert einer Variablen über eine SELECTAnweisung im Ergebnisbereich ausgegeben werden kann. Es ist auch möglich, eine Wertzuweisung an eine Variable basierend auf dem Wert der Variablen vorzunehmen. Eine Variable kann also sowohl auf der linken als auch auf der rechten Seite des Zuweisungsoperators der SET-Anweisung stehen. Dadurch kann zum Beispiel der Wert einer Integer-Variablen erhöht werden:
Diese Möglichkeit kann in UPDATE-Anweisungen für die Vergabe einer laufenden Nummerierung genutzt werden. Eine Update-Anweisung verwendet auch eine SET-Anweisung zum Zuweisen neuer Spaltenwerte. In einer solchen SET-Anweisung können natürlich ebenso Ausdrücke mit Variablen verwendet werden. Schauen Sie sich bitte einmal das folgende Skript an, das eine Tabelle erzeugt und anschließend einige Zeilen hinzufügt:
288
7.1 Variablen
Die angelegte Tabelle Namen enthält eine Spalte Nummer, deren Werte nach der obigen INSERT-Anweisung allesamt 0 sind. Um nun diese Spalte als laufende Nummer einzurichten, kann das folgende Skript verwendet werden:
Im Skript wird eine spezielle UPDATE-Syntax benutzt, die zwei Zuweisungsoperatoren in der SET-Anweisung verwendet. Hierdurch wird der Wert der Spalte Nummer, beginnend bei 1 und dann um jeweils den Wert 1 inkrementiert, gesetzt. Einen Ausschnitt aus dem Inhalt der Tabelle zeigt Abbildung 7.1.
Abbildung 7.1 Laufende Nummer
Wertzuweisung durch SELECT Auch die SELECT-Anweisung ermöglicht die Zuweisung von Werten an Variablen. Die generelle Syntax einer solchen Anweisung sieht so aus:
289
7 T-SQL-Programmierung Es ist nicht möglich, die Zuweisung mit einer Datenabruf-Operation zu verbinden, um eine Ergebnismenge an den Client zurückzugeben. Versuchen Sie dies, so erhalten Sie eine Fehlermeldung. Wenn also Variablenzuweisungen erfolgen, so muss dies stets für alle Spalten der SELECT-Anweisung geschehen. Im Gegensatz zu SET kann die SELECT-Anweisung allerdings mehrere Zeilen zurückgeben. Die Variable enthält dann nach der Anweisung den Wert des angegebenen Ausdrucks für die letzte Zeile. Folgendes funktioniert also:
Die Variable enthält nach der Zuweisung den Wert der letzten zurückgelieferten E-Mail-Adresse. Interessant hierbei ist, dass die Variable auch während der Abarbeitung der SELECT-Anweisung auf die entsprechenden Werte des Ausdrucks gesetzt wird, also sozusagen alle Werte nacheinander durchläuft. Dies kann man sich in einigen Situationen zunutze machen, um zum Beispiel eigene Aggregate zu berechnen. Das folgende Skript nutzt ebenfalls dieses Verhalten aus, um alle Nachnamen von Angestellten, durch Semikolon getrennt, in eine Zeichenkette zu übertragen:
In der SELECT-Anweisung wird an die Variable stets der existierende Wert dieser Variablen, an denen noch der gerade abgefragte Wert der Spalte LastName angehängt wird, zugewiesen. Hierbei wird auch ein Semikolon als Separator eingefügt. Nach der Deklaration der Variablen ist der Wert dieser Variablen zunächst NULL. Die erste Berechnung des Spaltenausdrucks wird dadurch nur den Wert der Spalte LastName in die Variable eintragen, die dadurch einen Wert ungleich NULL enthält. Alle weiteren Auswertungen werden dann ein Semikolon und den Spaltenwert an den bereits existierenden Wert der Variablen anfügen. Die ersten Zeichen des Ergebnisses der abschließenden PRINT-Anweisung sehen so aus:
7.1.3
Besondere Variablentypen
Neben den bereits aus Tabellendeklarationen bekannten Variablentypen können für T-SQLVariablen auch noch weitere Datentypen verwendet werden. Hierzu zählen insbesondere die folgenden:
290
7.1 Variablen CURSOR Eine Cursor-Variable kann ein Abfrageergebnis aufnehmen, das zeilenweise durchlaufen werden kann. Wir werden diese Variablen etwas weiter unten noch behandeln und dort sehen, wie man solche Variablen erzeugt und verwendet. TABLE Eine Variable vom Typ TABLE enthält eine Tabelle. Stellen Sie sich dies einfach wie eine temporäre Tabelle vor, die Sie in Ihrem Skript mit Daten füllen und abfragen können. Dies funktioniert zum Beispiel so:
Variablen vom Typ TABLE sind insbesondere für die Verwendung mit der OUTPUTKlausel interessant. Sie haben dadurch die Möglichkeit, die von einer T-SQL Anweisung eingefügten, geänderten oder gelöschten Zeilen zwischenzuspeichern. Ein Beispiel für diese Verfahrensweise sehen Sie im folgenden Code:
Im obigen Skript wird zunächst eine Tabelle zu Testzwecken angelegt. In diese Tabelle werden dann einige Zeilen eingefügt und wieder gelöscht. Die gelöschten Zeilen werden hierbei in einer TABLE-Variablen protokolliert. Es ist möglich, TABLE-Variablen in einem Cursor zu verwenden. Cursor werden etwas weiter hinten in diesem Kapitel behandelt. Auch TABLE-Variablen werden wir später in diesem Abschnitt noch benötigen, wenn wir benutzerdefinierte Funktionen besprechen.
291
7 T-SQL-Programmierung
7.2
Dynamisches SQL In Kapitel 4 haben Sie gesehen, wie gespeicherte Prozeduren mit der Anweisung EXEC(UTE) aufgerufen werden. EXEC kann auch zum Ausführen von dynamischem SQL verwendet werden. Hierzu muss die SQL-Anweisung als Zeichenkette existieren, die dann an EXEC übergeben wird. Dies funktioniert zum Beispiel so:
Die runden Klammern sind hierbei obligatorisch, um EXEC mitzuteilen, dass es sich nicht um eine gespeicherte Prozedur, sondern um eine als Zeichenkette vorliegende SQL-Anweisung handelt. Es ist auch möglich, die Anweisung in einer Zeichenketten-Variablen zu speichern und auszuführen:
Bitte beachten Sie das zweite EXEC-Kommando. Für dieses Kommando sind die runden Klammern um die Variable optional, da es sich beim auszuführenden Kommando um eine gespeicherte Prozedur handelt. Für das erste EXEC-Kommando hingegen sind die Klammern Pflicht. Microsoft empfiehlt für die Ausführung von dynamischem SQL die Verwendung der gespeicherten Prozedur . Diese Prozedur kann für dynamisches SQL auch Parameter verarbeiten, die wie folgt angegeben werden:
Die Benutzung von ist vielleicht etwas gewöhnungsbedürftig, weil die Deklaration der für die Parameter verwendeten Variablen in einem Parameter der Prozedur vorgenommen wird (dies ist der zweite Parameter der Prozedur). Der SQL ServerAbfrageoptimierer ist aber in der Lage, für mit ausgeführtes SQL auch wieder verwendbare Abfragepläne zu erzeugen und im Prozedurcache abzulegen, was für dynamisches SQL, das mit EXEC ausgeführt wird, nicht immer gilt. Dies trifft insbesondere zu, wenn die Abfrage mit unterschiedlichen Parametern aufgerufen wird. Bei Verwendung von wird in diesem Fall eine Abfrage mit verschiedenen Parametern aufgerufen, bei EXEC müssen verschiedene Abfragen erzeugt werden.
292
7.3 Ablaufsteuerung Wir werden die Arbeitsweise des Abfrageoptimierers in Kapitel 12 nochmals etwas genauer untersuchen. Bitte beachten Sie, dass die Prozedur alle Parameter vom Typ NVARCHAR erwartet.
7.3
Ablaufsteuerung Wie jede Programmiersprache, so kennt auch T-SQL Anweisungen zur Ablaufsteuerung, also zum Beispiel zur bedingten oder wiederholten Ausführung von Anweisungen. Die zur Verfügung stehenden Möglichkeiten sind im Vergleich zu Client-Programmiersprachen wie beispielsweise C# zwar begrenzt, dennoch sollten Sie diese natürlich beherrschen.
7.3.1
Anweisungsblöcke
In T-SQL können mehrere Anweisungen zu einem Block zusammengefasst werden. In einem solchen Block enthaltene Anweisungen werden dann beispielsweise für eine bedingte Ausführung oder auch für Schleifen verwendet. Ein Anweisungsblock beginnt mit dem Schlüsselwort BEGIN und endet mit dem Wort END, sieht also zum Beispiel so aus:
Bitte beachten Sie, dass eine in einem BEGIN-END-Block deklarierte Variable nicht nur innerhalb des Blockes gültig ist. Es gilt das weiter oben Gesagte, dass eine Variable vom Beginn der Deklaration bis zum Ende des T-SQL-Stapels ihre Gültigkeit behält.
7.3.2
Bedingte Ausführung von Anweisungen
Bereits in Kapitel 4 haben Sie die CASE-Anweisung kennengelernt, welche die bedingte Ausführung von Code ermöglicht. T-SQL verfügt auch über eine IF-ELSE-Anweisung, mit der ebenfalls eine bedingte Ausführung von T-SQL-Kommandos erreicht werden kann. Wenn die in der IF-Klausel angegebene Bedingung wahr ist, so wird die nach dieser Klausel angegebene Anweisung ausgeführt, anderenfalls wird die im ELSE-Zweig enthaltene Anweisung gestartet. Eine Anweisung kann hierbei auch ein mit BEGIN/END angegebener Block sein. Außerdem gilt, dass der ELSE-Zweig optional ist. Es folgt ein einfaches Beispiel für den Einsatz von IF/ELSE:
293
7 T-SQL-Programmierung
Das obige Beispiel ist sicherlich nicht besonders sinnvoll, es soll aber auch lediglich die Verwendung von IF verdeutlichen. Die einzelnen Bedingungen werden hier in runde Klammern gesetzt, was zwar nicht notwendig ist, aber die Lesbarkeit der Anweisung doch etwas erhöht. In den einzelnen IF-Zweigen werden Blöcke verwendet, die je nach Bedingung ausgeführt werden oder nicht. Wenn ein solcher Block nur eine einzige Anweisung enthält, so darf der Einschluss dieser Anweisung in BEGIN/END auch weggelassen werden. Das Beispiel könnte also auch so geschrieben werden:
Verdeutlicht wird im Beispiel auch der Einsatz einer geschachtelten IF-Anweisung, die sich ähnlich wie eine CASE-Anweisung verhält. Tatsächlich könnte das Skript daher auch so formuliert werden:
7.3.3
Wiederholte Ausführung von Anweisungen
T-SQL kennt eine einzige Anweisung zur wiederholten Anweisungsausführung, also für die Erstellung von Schleifen. Hierfür steht Ihnen die WHILE-Anweisung zur Verfügung, die Sie wie folgt verwenden können:
Die Anweisung wird so lange ausgeführt, wie die Auswertung der Bedingung den Wert wahr ergibt. In der Regel wird statt einer einzigen Anweisung ein Anweisungsblock verwendet werden, in dem dann auch Zustände verändert werden, welche die Auswertung der
294
7.3 Ablaufsteuerung Bedingung beeinflussen. Dadurch wird dafür gesorgt, dass die Schleife endlich ist, also irgendwann abbricht. Die folgende Schleife gibt alle Datumswerte vom 01.01.2000 bis zum 31.12.2002 im Meldungsfenster aus:
Die Schleifenbedingung sorgt dafür, dass die Schleife so lange ausgeführt wird, bis der Wert von das Jahr 2003 erreicht. (Dabei wird von der impliziten Typkonvertierung Gebrauch gemacht.) Im Anweisungsblock der Schleife wird jeweils ein Tag zur Variablen addiert. Dadurch wird irgendwann der 01.01.2003 erreicht, die Schleife wird dann beendet. CONTINUE Durch die Anweisung CONTINUE innerhalb einer WHILE-Schleife werden die in der Schleife hinter CONTINUE stehenden Anweisungen nicht ausgeführt. Stattdessen wird sofort zur nächsten Prüfung der Schleifenbedingung übergegangen. Sollen zum Beispiel nur die Datumswerte von Montag bis Freitag ausgegeben werden, kann die obige Schleife so geändert werden:
CONTINUE wird hier für die Wochentage Samstag und Sonntag ausgeführt. Dadurch wird die PRINT-Anweisung übersprungen und die Ausführung der Schleife mit der Prüfung der Schleifenbedingung fortgesetzt. BREAK Die BREAK-Anweisung innerhalb einer Schleife beendet die Ausführung dieser Schleife unmittelbar. Es folgt sofort ein Beispiel hierzu. Sowohl für CONTINUE als auch für BREAK gilt, dass diese Anweisungen nur für die Schleife ausgeführt werden, in der sie aufgerufen werden. Schleifen können auch ineinan-
295
7 T-SQL-Programmierung der geschachtelt sein. In einem solchen Fall führt ein BREAK in einer inneren Schleife nicht zum Beenden aller also auch der äußeren Schleifen, sondern bricht nur die innere Schleife ab. Schleife zum Füllen der Kalender-Tabelle Die in Kapitel 4 als Beispiel erzeugte Tabelle Kalender soll nun mit Daten gefüllt werden. Hierzu kann die folgende Schleife dienen:
In der obigen WHILE-Schleife wird für jedes Datum zwischen dem 01.01.2000 und dem 31.12.2000 eine INSERT-Anweisung ausgeführt. Für ein Ende-Datum 31.12.2007 sind dies insgesamt 2922 INSERT-Anweisungen. Die folgende Schleife erledigt dasselbe, allerdings mit nur 13 INSERTs:
Der Trick ist, dass die Tabelle Kalender anhand von bereits existierenden Zeilen erweitert wird. Hierdurch wird die Zeilenanzahl in der Tabelle mit jedem INSERT innerhalb der WHILE-Schleife verdoppelt. Der Ausdruck
296
7.4 Cursor in der SELECT-Anweisung des allgemeinen Tabellenausdrucks berechnet jeweils einen Datumswert, der um genau die Anzahl von Tagen hinter dem maximalen in der Tabelle enthaltenen Datum liegt, wie das in der Zeile enthaltene Datum hinter dem minimalen in der Tabelle enthaltenen Datum liegt. Interessant ist die Abbruchbedingung der Schleife (1=1), die hier immer den Wert wahr ergibt. Dadurch wird die Schleife eigentlich zur Endlosschleife, die also niemals verlassen wird. Der Schleifenabbruch wird durch die BREAK-Anweisung am Ende der Schleife erreicht, die dann ausgeführt wird, wenn der Wert @@ROWCOUNT 0 ist. Dies ist genau dann der Fall, wenn durch die INSERT-Anweisung keine weiteren Zeilen mehr eingefügt wurden.
7.4
Cursor SQL ist eine mengenorientierte Sprache. SELECT-Anweisungen geben Ergebnismengen zurück, Anweisungen zu Datenänderungen verändern genau genommen ebenfalls Mengen, auch wenn eine solche Menge oftmals nur eine einzige Tabellenzeile ist. Durch Cursor existiert eine Möglichkeit, eine Ergebnismenge nicht als Gesamtheit zu bearbeiten, sondern zeilenweise zu durchlaufen. Hierfür kann ein Cursor eine Ergebnismenge aufnehmen, die dann in der Folge aus ihm zeilenweise ausgelesen oder auch verändert werden kann. Sie sollten nach Möglichkeit versuchen, für Ihre Abfragen zum Erhalten von Ergebnissen oder zum Ändern von Tabellendaten, mengenorientierte SQL-Anweisungen zu verwenden. Hierauf kommen wir etwas weiter unten nochmals zurück. Wenn die entsprechenden Abfragen allerdings sehr umfangreich werden, so ist es durchaus möglich, dass Sie hierbei auf Schwierigkeiten stoßen. Ursache dafür kann zum Beispiel einfach mangelnde Erfahrung im Umgang mit T-SQL sein. Natürlich ist es auch denkbar, dass die auszuführende Abfrage oder Aktualisierung nicht mit einer mengenorientierten Abfrage erledigt werden kann. Cursor bieten in so einem Fall einen Ausweg an. Sie ermöglichen eine prozedurale Datenverarbeitung, indem sie Ihnen eine Ergebnismenge zeilenweise zur Verfügung stellen. SQL Server erlaubt zum Beispiel das Versenden von E-Mails durch die in der Datenbank msdb enthaltene gespeicherte Systemprozedur . Stellen Sie sich vor, Sie sollten eine E-Mail für eine bestimmte Werbeaktion an alle Kunden aus dem Bereich Nord-Ost versenden. Die Abfrage der E-Mail-Adressen für die entsprechenden Kunden stellt sicher kein Problem dar, aber wie soll die Prozedur zum Versenden der Mails für jeden betreffenden Kunden per SQL-Skript aufgerufen werden? In so einem Fall hilft ein Cursor, über den für jeden Kunden der Prozeduraufruf gestartet werden kann.
7.4.1
Cursor erzeugen
Cursor werden in einer Variablen gespeichert und müssen durch DECLARE deklariert werden. In der DECLARE-Anweisung wird auch die dem Cursor zugrunde liegende
297
7 T-SQL-Programmierung SELECT-Anweisung angegeben. Eine einfache Cursor-Deklaration sieht zum Beispiel so aus:
Im Skript wird ein Cursor contactCursor deklariert. Auffällig ist zunächst einmal, dass der Name des Cursors nicht mit einem @ beginnt. Ein nach dem obigen Muster deklarierter Cursor ist keine echte Variable, er besitzt gegenüber einer Variablen einen entscheidenden Unterschied: Der Cursor lebt so lange, wie die Verbindung, in der er erstellt wurde, besteht. Bei Variablen ist dies nicht so, diese existieren nur innerhalb des Stapels, in dem sie deklariert wurden. Nach dem Schlüsselwort FOR folgt die SELECT-Anweisung, die später die Daten (Zeilen) des Cursors liefert. Bei der Deklaration eines Cursors können wahlweise weitere Optionen angegeben werden, die das Verhalten und die Verwendungsmöglichkeiten des Cursors spezifizieren. Diese Optionen werden zwischen CURSOR und FOR eingefügt. Es ist auch möglich, das Cursorverhalten über Optionen zu steuern, die zwischen dem Cursor-Namen und dem Schlüsselwort CURSOR angegeben werden. Klingt verwirrend? Nun, die Ursache vor diese Deklarationsvielfalt ist, dass SQL Server sowohl die Cursor-Deklaration nach dem ANSI 92-Standard als auch eine SQL Server-eigene Cursor-Deklaration ermöglicht. Und diese beiden Möglichkeiten unterscheiden sich wie folgt: ANSI 92-Syntax:
T-SQL-Syntax:
Die für beide Möglichkeiten zur Verfügung stehenden Optionen werden in der folgenden Tabelle 7.1 näher erläutert. Tabelle 7.1 Optionen für die Deklaration eines Cursors
298
Syntax
Option
Erklärung
ANSI 92
Bewirkt, dass der Cursor beim Öffnen eine Kopie der abgefragten Daten in der tempdb erzeugt. Anforderungen an den Cursor werden von dieser Kopie bearbeitet. Verwenden Sie diese Option mit Vorsicht, da die tempdb in SQL Server 2005 einen PerformanzEngpass hervorrufen kann.
7.4 Cursor Syntax
T-SQL
Option
Erklärung
Wenn angegeben, stehen alle Navigationsmöglichkeiten für den Cursor zur Verfügung. Die Position des Cursors kann also beliebig verändert werden.
Der Cursor lässt keine Aktualisierungen zu.
Über diese Klausel können aktualisierbare Spalten für den Cursor definiert werden. Cursor können über eine spezielle UPDATESyntax aktualisiert werden, die weiter unten noch besprochen wird.
Diese Option führt dazu, dass der Cursor nur innerhalb des Stapels, in dem er deklariert wurde, gültig ist.
Ein globaler Cursor bleibt in der gesamten Verbindung gültig, lebt also auch außerhalb des T-SQL-Stapels, in dem er ursprünglich deklariert wurde, weiter.
Ein mit dieser Option erstellter Cursor erlaubt nur das Abrufen von Zeilen vom Anfang zum Ende, mit der Schrittweite eins, also Zeile für Zeile. Kann nicht mit der Option SCROLL zusammen verwendet werden.
SCROLL hat hier die gleiche Bedeutung wie in der ANSI 92-Syntax. Diese Option kann nicht zusammen mit FORWARD_ONLY verwendet werden.
STATIC ist identisch mit INSENSITIVE aus der ANSI 92-Syntax.
Ein dynamischer Cursor liest die abzurufenden Daten erst beim Navigieren. Dadurch werden nach der Cursorerstellung vorgenommeine Datenänderungen auch beim Lesen von Zeilen aus dem Cursor wiedergegeben. Der Cursor verhält sich also so ähnlich wie eine Sicht.
Ein so erzeugter Cursor spiegelt nach seiner Erzeugung vorgemeinemene Aktualisierungen an den zugrunde liegenden Tabellendaten wider. Von anderen Verbindungen in dieser Zeit hinzugefügte Daten sind jedoch nicht sichtbar. Erreicht wird dieses Verhalten durch die Erzeugung einer Kopie der Schlüsseldaten des Cursors dies sind die Spalten, die eine Zeile eindeutig identifizieren in der tembdb.
Dies ist eine Kurzschreibweise für die beiden Optionen FORWARD_ONLY und READ_ONLY. Ein solcher Cursor benötigt nur wenige Ressourcen und ist relativ schnell. Diese Option können Sie nicht zusammen mit SCROLL oder FOR UPDATE verwenden.
Erstellt einen Cursor, der nicht aktualisiert werden kann.
Sorgt dafür, dass eine vom Cursor gelesene Zeile gesperrt wird, also von anderen Verbindungen nicht aktualisiert werden kann, solange der Cursor auf der Position dieser Zeile steht. Diese Option darf nicht mit FAST_FORWARD oder STATIC zusammen angegeben werden.
299
7 T-SQL-Programmierung Syntax
Option
Erklärung
Von Cursor gelesene Zeilen werden nicht gesperrt. Wenn über den Cursor Daten aktualisiert oder gelöscht werden, so wird ein entsprechender Versuch fehlschlagen, wenn die Zeile seit dem Einlesen in den Cursor von einer anderen Verbindung geändert wurde. Diese Option kann nicht zusammen mit FAST_FORWARD verwendet werden.
Erzeugt eine Warnung im Falle einer impliziten Typkonvertierung bei der Übertragung von Cursor-Spalten in Benutzervariablen.
Bewirkt dasselbe Verhalten wie in der ANSI 92-Syntax
Wie Sie im obigen Beispiel sehen können, ist die Angabe von Optionen nicht erforderlich. SQL Server erzeugt Cursor mit den folgenden Standardoptionen: Als STATIC und FAST_FORWARD deklarierte Cursor sind READ_ONLY. DYNAMIC- und KEYSET-Cursor sind OPTIMISTIC. Wenn die SELECT-Anweisung keine Aktualisierungen ermöglicht (zum Beispiel wegen fehlender Berechtigungen), so ist der Cursor READ_ONLY. Der Cursor selber kann übrigens so wie temporäre Tabellen in der tempdb erzeugt werden. Hierzu stellen Sie dem Namen des Cursors einfach ein # voran, wie dies in Kapitel 4 für temporäre Tabellen erklärt wurde. Cursor-Variablen SQL Server erlaubt eine Deklaration von Variablen des Typs CURSOR:
An eine derart deklarierte Variable kann mit der SET-Anweisung ein Cursor zugewiesen werden:
Die bei der Zuweisung verwendete Deklaration des Cursors muss hierbei der T-SQLSyntax genügen, Sie können also nicht die ANSI 92-Optionen verwenden. Außerdem ist ein in einer T-SQL-Variablen gespeicherter Cursor lokal, er gilt also nur, solange auch die Variable gültig ist: im Stapel, in dem die Variable deklariert wurde. Cursor-Variablen können zum Beispiel als Parameter an gespeicherte Prozeduren übergeben werden.
7.4.2
Einen Cursor durchlaufen Daten abrufen
Nachdem Sie nun wissen, wie ein Cursor erzeugt wird, wird sich dieser Abschnitt mit dem zeilenweisen Abrufen von Daten aus dem Cursor befassen.
300
7.4 Cursor Öffnen eines Cursors Die Deklaration eines Cursors allein reicht nicht aus, um anschließend Daten aus dem Cursor abrufen zu können. Zuvor muss der Cursor noch geöffnet werden. Erst beim Öffnen des Cursors wird die dem Cursor zugrunde liegende SQL-Anweisung ausgeführt. Die für den Cursor erforderlichen Ressourcen wie zum Beispiel eine temporäre Tabelle in der tempdb werden ebenfalls durch das Öffnen des Cursors angefordert und belegt. Die Anweisung OPEN öffnet einen Cursor:
An die OPEN-Anweisung wird einfach der Name des zu öffnenden Cursors übergeben. Daten einer Zeile abrufen Aus einem geöffneten Cursor kann eine Zeile mit der Operation FETCH abgerufen werden. Der FETCH-Operator hat die folgende allgemeine Form:
Eine FETCH-Anweisung kennt also verschiedene Optionen, von denen viele auch einfach weggelassen werden können. In der einfachsten Form können Sie FETCH so verwenden:
Die obige Anweisung wird einfach die nächste Zeile aus dem Cursor lesen und im Ergebnisbereich ausgeben. Mit der etwas weiter oben angegebenen Deklaration des Cursors contactCursor erhalten Sie dieses Ergebnis:
Wenn Sie sich vorstellen, dass die Zeilenposition im Cursor nach dem Öffnen des Cursors vor der ersten Zeile liegt, so liefert die obige FETCH-Anweisung also das Ergebnis der nächsten Zeile. Hierbei wird diese Zeile zur aktuellen Zeile. Ein erneutes FETCH liefert also das Ergebnis der zweiten Zeile:
Dies können Sie fortsetzen, bis alle Zeilen aus dem Cursor gelesen wurden. Etwas weiter unten werden wir so etwas unter Verwendung einer WHILE-Schleife programmieren. Zunächst aber sollen die zur Verfügung stehenden Optionen für die Positionierung im Cursor und das Abrufen von Daten erläutert werden. Sehen Sie sich hierzu bitte Tabelle 7.2 an.
301
7 T-SQL-Programmierung Tabelle 7.2 Positionieren mit FETCH FETCH-Option
Erklärung
Gibt die nächste Zeile zurück, positioniert also den Zeilenzeiger eine Zeile weiter. Eine Ausnahme von dieser Verfahrensweise existiert allerdings dann, wenn FETCH NEXT der erste Datenabruf vom Cursor ist. In diesem Fall wird die erste Zeile zurückgegeben. NEXT ist der Standard, wenn für FETCH keine Positionsangabe erfolgt.
Positioniert auf die vorhergehende Zeile und gibt diese Zeile zurück.
Macht die erste Zeile des Cursors zur aktuellen Zeile und gibt diese Zeile zurück.
Macht die letzte Zeile des Cursors zur aktuellen Zeile und gibt diese Zeile zurück.
n
Ist der Wert n positiv, so wird vom Anfang des Cursors beginnend die n-te Zeile zurückgegeben. Wenn n negativ ist, so beginnt die Zählung am Ende des Cursors. Die zurückgegebene Zeile wird zur aktuellen Zeile des Cursors. n kann hier auch eine Variable sein.
n
Wenn n positiv ist, so wird die n-te Zeile nach der gerade aktuellen Zeile zurückgegeben. Ist n negativ, so erfolgt die Positionierung rückwärts. Die zurückgegebene Zeile wird wiederum zur aktuellen Zeile. Auch hier kann n eine Variable sein.
Zu beachten ist hierbei, dass nicht jeder Cursor alle Positionierungen unterstützt. So erlaubt zum Beispiel ein Cursor, der in der ANSI 92-Syntax nicht mit der Option SCROLL deklariert wurde, nur die Option NEXT. Mit der FETCH-Anweisung ist es auch möglich, die aus dem Cursor gelesenen Spalten in Variablen zu übertragen. Hierzu werden die Variablen einfach in der INTO-Klausel der FETCH-Anweisung in der Reihenfolge angegeben, in der die Spalten in der SELECTAnweisung stehen. Für unseren contactCursor sieht das zum Beispiel so aus:
Zu beachten ist, dass beim Verwenden der INTO-Klausel alle in der SELECT-Anweisung enthaltenen Spalten empfangen werden müssen. Sie benötigen also genau so viele Variablen, wie Ihre SELECT-Anweisung Spalten hat. Im Meldungsbereich des Ausgabefensters sehen Sie nach der Ausführung des obigen Skriptes diesen Text:
Das Zwischenspeichern der Daten einer Zeile in Variablen und die anschließende Verwendung dieser Variablen, zum Beispiel für Berechnungen oder Datenänderungen, ist sicherlich die übliche Verfahrensweise beim Einsatz eines Cursors. Eine einfache Ausgabe von Zeilen, so wie dies über FETCH ohne INTO geschieht, kann in der Regel auch ohne einen Cursor aber dafür erheblich schneller erledigt werden.
302
7.4 Cursor Schließen eines Cursors Nachdem die Verarbeitung der Cursor-Zeilen abgeschlossen ist, wird der Cursor durch die CLOSE-Anweisung geschlossen. Durch diese Anweisung werden eventuell noch existierende Zeilensperren aufgehoben, und das Ergebnis der dem Cursor zugrunde liegenden Abfrage wird vom Cursor getrennt. CLOSE wird wie OPEN einfach durch Angabe des Cursor-Namens aufgerufen:
Ein durch CLOSE geschlossener Cursor kann durch OPEN jederzeit wieder geöffnet werden. Dies funktioniert wenn es sich um einen globalen Cursor handelt, der ja für die Dauer der Verbindung gültig ist auch außerhalb des gerade aktuellen T-SQL-Stapels. Beim erneuten Öffnen wird die zugrunde liegende Abfrage ausgeführt, und der Satzzeiger steht vor der ersten Zeile. CLOSE kann nur auf zuvor über OPEN geöffnete Cursor angewendet werden. Ist der Cursor bereits geschlossen, so erhalten Sie beim erneuten Versuch, den Cursor zu schließen, eine Fehlermeldung. Von Cursor verwendete Ressourcen freigeben Die endgültige Freigabe aller vom Cursor belegten Ressourcen erledigt die Anweisung DEALLOCATE:
Ist der Cursor noch offen, so wird er durch DEALLOCATE auch geschlossen. Die CursorVariable ist nach DEALLOCATE nicht mehr verfügbar. Status eines Cursors Durch die Anweisung FETCH kann der Zeilenzeiger im Cursor verändert werden. Es bleibt die Frage zu klären, wie man die aktuelle Position dieses Zeigers abfragen kann. Wichtig ist diese Information insbesondere für einen Fall: Wenn der Cursor vollständig durchlaufen wurde, der Zeilenzeiger also hinter dem Ende der letzten Zeile steht, soll das Durchlaufen des Cursors beendet werden. Leider ist es nicht möglich, die aktuelle Position des Zeilenzeigers abzufragen. Es gibt jedoch zwei Funktionen, welche die Statusinformationen zu einem Cursor liefern: @@CURSOR_ROWS. Nach dem Öffnen eines Cursors gibt diese Funktion die Anzahl von Zeilen im Cursor zurück. Bei einem dynamischen Cursor gibt diese Funktion immer den Wert 1 zurück. @@FETCH_STATUS. Diese Funktion liefert Informationen über den Status der letzten FETCH-Operation. Diese Funktion ist global für die Verbindung und enthält das Ergebnis für die jeweils letzte ausgeführte FETCH-Operation der Verbindung. Hierbei können die folgenden drei Werte zurückgegeben werden:
303
7 T-SQL-Programmierung @@FETCH_STATUS
Erklärung
0
Die letzte FETCH-Anweisung war erfolgreich.
-1
Die letzte FETCH-Anweisung war fehlerhaft. Möglicherweise wurde versucht, von einer Position zu lesen, die außerhalb des Ergebnisses liegt.
-2
Die Zeile ist nicht vorhanden. Dies kann zum Beispiel passieren, wenn in einen KEYSET-Cursor versucht wird, eine mittlerweile gelöschte Zeile zu lesen.
Die Funktion @@FETCH_STATUS kann also verwendet werden, um festzustellen, ob das zeilenweise Durchlaufen des Cursors beendet werden darf, weil alle Zeilen gelesen wurden. Hierzu wird eine WHILE-Schleife verwendet:
Im obigen Beispiel wird die FETCH-Operation in einer Endlosschleife ausgeführt. Unmittelbar nach jedem FETCH wird die Anweisung
ausgeführt. Dadurch wird die WHILE-Schleife beendet, sobald die FETCH-Operation fehlgeschlagen ist. Der Code geht hierbei davon aus, dass ein FETCH nur dann nicht funktioniert, wenn alle Zeilen gelesen wurden, was natürlich nicht so ganz korrekt ist. Im Beispiel ist auch zu sehen, wie ein Cursor nach dem Schließen erneut geöffnet und verwendet werden kann.
304
7.4 Cursor Nach der Ausführung des Skriptes erhalten Sie im Meldungsbereich des Ergebnisses diese Ausgabe, die beim ersten Durchlaufen des Cursors erzeugt wird:
Im Abfrageergebnis sehen Sie diese Ausgabe (vom zweiten Cursor-Durchlauf):
7.4.3
Daten aktualisieren
Wie Sie sicherlich bereits bei der Besprechung der für die Cursor-Deklaration zur Verfügung stehenden Optionen vermutet haben, ist es auch möglich, Daten über einen Cursor zu aktualisieren. Für einen solchen aktualisierbaren Cursor steht eine spezielle UPDATESyntax in der Form:
zur Verfügung. Um zum Beispiel alle Vornamen des im vorherigen Beispiel verwendeten Cursors auf Gabriel zu setzen, kann der Cursor so verwendet werden:
305
7 T-SQL-Programmierung Wenn Sie das Skript betrachten, werden Sie feststellen, dass die zu aktualisierende Spalte FirstName nicht im Cursor enthalten ist. Dies ist für ein UPDATE tatsächlich nicht erforderlich, der Cursor soll für die UPDATE-Operation lediglich die Zeile liefern. Das Interessante hierbei ist, dass hierfür keine Zeilenidentifikation wie etwa ein Primärschlüssel benötigt wird. Da die Daten aus dem Cursor zeilenweise ausgelesen werden, wird das UPDATE immer die gerade ausgewählte Zeile verwenden. Die Zeilenidentität kommt sozusagen aus dem Cursor.
7.4.4
Hinweise zur Verwendung von Cursorn
Cursor durchkreuzen den mengenorientierten Ansatz von SQL. Bitte verwenden Sie Cursor daher sparsam, als letzte Möglichkeit, wenn Ihnen keine andere Lösung einfällt. Nach Möglichkeit sollten Sie stets die mengenorientierten SQL-Anweisungen einsetzen. Dies ist vor allem auch deshalb wichtig, weil Cursor viele Ressourcen benötigen, die den SQL Server eventuell unnötig unter Stress setzen. Ist ein Cursor dennoch nicht zu vermeiden, so versuchen Sie bitte, die folgenden Hinweise zu beachten: Die SELECT-Anweisung des Cursors sollte nur die Spalten zurückgeben, die tatsächlich benötigt werden. Außerdem sollten Sie eine Einschränkung der Ergebnismenge bereits in der WHERE-Klausel der SELECT-Anweisung vornehmen und Zeilen nicht erst beim Durchlaufen des Cursors übergehen. Nach Möglichkeit verwenden Sie bitte READ ONLY-Cursor. Dies vermeidet unnötige Sperren. Für die meisten Fälle reicht ein FAST_FORWARD- oder zumindest ein FORWARD_ONLY-Cursor aus, der lediglich ein FETCH NEXT unterstützt. Dieser Cursor-Typ benötigt am wenigsten Ressourcen. Cursor, die in der tempdb erzeugt werden (STATIC, KEYSET, INSENSITIVE), können sehr langsam sein, da die tempdb auch von anderen Verbindungen gleichzeitig benutzt wird. Unter Umständen kann Ihr in der tempdb erzeugter Cursor auch die Ursache für ein verschlechtertes Laufzeitverhalten anderer Anwendungen sein, die ebenfalls temporäre Objekte in der tempdb erstellen.
7.5
Gespeicherte Prozeduren Im bisherigen Verlauf dieses Buches haben wir bereits mehrfach gespeicherte (System- Prozeduren verwendet. Sie wissen bereits, dass in diesen Prozeduren T-SQL-Code gespeichert ist und wie man diesen Code über den Aufruf der Prozedur ausführt. In diesem Abschnitt wollen wir nun eigene gespeicherte Prozeduren erstellen.
306
7.5 Gespeicherte Prozeduren
7.5.1
Warum gespeicherte Prozeduren?
Gleich zu Beginn dieses Kapitels soll die Frage geklärt werden, wozu gespeicherte Prozeduren überhaupt nützlich sind. Wofür eigentlich T-SQL-Code im Server speichern? Immerhin könnten die gespeicherten Anweisungen ja auch direkt von Client-Anwendungen ausgeführt werden. Bei der Verwendung von gespeicherten Prozeduren ergeben sich die folgenden Vorteile Gespeicherte Prozeduren verbergen die logische Struktur der Datenbank vor dem Anwender. Dies ist sicherlich der herausragende Vorteil beim Einsatz von gespeicherten Prozeduren. Durch den Aufruf einer Prozedur, die Daten ändert oder abfragt, muss eine Anwendung nicht die Struktur der Datenbank also etwa die enthaltenen Tabellen oder Sichten kennen. Es wird eine zusätzliche Abstraktionsebene, eine Datenzugriffsschicht implementiert, die zum Beispiel eine Änderung der Datenbankstruktur ermöglicht. Solange die Schnittstelle einer gespeicherten Prozedur nicht verändert wird, müssen Client-Anwendungen daraufhin nicht angepasst werden. Prozeduren können komplexe Programmlogik enthalten. Diese Programmlogik ist auf dem Server also zentral gespeichert. Bei einer Anpassung einer Prozedur sind automatisch alle Client-Anwendungen betroffen. Hierfür müssen weder die ClientAnwendungen geändert noch etwa der SQL Server neu gestartet werden. Für ClientAnwendungen gilt dies allerdings nur dann, wenn die Schnittstelle der Prozedur also die Struktur der Ein- und Ausgabe nicht verändert wird. Für jede T-SQL-Anweisung wird vom Abfrageoptimierer ein Ausführungsplan erstellt, der in einem speziellen Pufferspeicher gespeichert wird. Dieser Ausführungsplan bestimmt, wie eine Abfrage ausgeführt wird. Die Erstellung des Planes erfolgt auf der Grundlage der logischen und physikalischen Struktur der Datenbank. Ähnliche SQLAnweisungen verwenden hierbei denselben Plan. In Kapitel 12 werden wir den Abfrageoptimierer nochmals untersuchen. An dieser Stelle soll nur erwähnt werden, dass für eine gespeicherte Prozedur bei ihrem erstmaligen Aufruf ein solcher Plan erstellt wird, der dann für weitere Aufrufe verwendet wird. Da das Erstellen eines Planes ein CPUintensiver Vorgang ist, ergibt sich bei der Verwendung gespeicherter Prozeduren ein unter Umständen erheblicher Performanzgewinn gegenüber der einzelnen Ausführung der in der Prozedur enthaltenen Anweisungen, für die dann jeweils eigene Ausführungspläne verwendet werden müssten.
7.5.2
Erstellung von gespeicherten Prozeduren
Die Erstellung einer gespeicherten Prozedur erfolgt durch CREATE PROCEDURE, deren allgemeine Syntax so aussieht:
die
Anweisung
Nach der Anweisung CREATE PROCEDURE kann eine Parameterliste folgen, die Sie wahlweise in runde Klammern einschließen können. Jede Parameterdeklaration ist hierbei
307
7 T-SQL-Programmierung wie eine Variablendeklaration aufgebaut, besteht also aus einem Parameternamen und dem Datentyp. Etwas weiter unten werden wir noch erweiterte Optionen für Parameter behandeln. Nach dem Schlüsselwort AS folg dann der eigentliche Rumpf der Prozedur. Hier können Sie (nahezu) beliebige T-SQL-Anweisungen verwenden, also im Prinzip alles, was wir bisher besprochen haben. Im Anweisungsteil können Daten über SELECT abgefragt oder über UPDATE verändert werden. Es ist möglich, Cursor zu verwenden und diese in Schleifen abzuarbeiten. Die Erzeugung temporärer Tabellen für das Einsammeln von Informationen, die dann am Ende der Prozedur ausgegeben werden, ist eine weitere, gern verwendete Verfahrensweise beim Einsatz von gespeicherten Prozeduren. Natürlich stehen Ihnen in Prozeduren auch explizite Transaktionen zur Verfügung, die Sie nach Bedarf bestätigen oder bei Auftreten eines Fehlers auch zurücksetzen können. Hierzu dürfen oder besser: sollten Sie in Ihren Prozeduren entsprechende Fehlerbehandlungen, etwa mit TRY/CATCH, vorsehen. Im folgenden Abschnitt wird zunächst eine einfache gespeicherte Prozedur entworfen. Im weiteren Verlauf werden wir dann ganz allmählich zu umfassenderen Beispielen übergehen. Eine einfache Prozedur Unsere erste Prozedur soll den Namen, die Nummer und den Listenpreis aller Produkte einer bestimmten Kategorie zurückgeben. Diese Prozedur besteht aus einer einzigen SELECT-Anweisung und kann so erzeugt werden:
Die Prozedur erwartet den Namen der Produktkategorie als Parameter und verwendet diesen Parameter in der WHERE-Klausel. Der Anweisungsblock für die Prozedur wurde aus in BEGIN/END gekapselt. Dies ist nicht unbedingt erforderlich und erfolgt nur aus Gründen der besseren Lesbarkeit. Aus demselben Grund wurde der Parameter in runde Klammern eingeschlossen. Diese Konvention werden wir in allen folgenden Beispielen beibehalten. Nach dem Erstellen der Prozedur kann diese zum Beispiel so aufgerufen werden, um die Daten aller Produkte für die Kategorie Bikes zu erhalten:
Hierbei ist zu beachten, dass die Parameter nicht in Klammern gesetzt werden dürfen.
308
7.5 Gespeicherte Prozeduren Die erstellte Prozedur wird im Objekt-Explorer unter Programmierbarkeit/Gespeicherte Prozeduren angezeigt (Abbildung 7.2).
Abbildung 7.2 Gespeicherte Prozeduren der Datenbank AdventureWorks
In diesem Ordner sehen Sie auch einige weitere Prozeduren, welche die Datenbank AdventureWorks von Haus aus mitbringt. Wenn Sie eine Prozedur verändern möchten, so können Sie diese Prozedur mit dem Kommando
entfernen und anschließend über CREATE PROCEDURE neu erzeugen. Möglich ist auch die Verwendung des Kommandos ALTER PROCEDURE für die Änderung der in einer Prozedur enthaltenen Anweisungen oder der Parameterliste. Der Objekt-Explorer bietet hierfür eine recht bequeme Möglichkeit an. Aus dem Kontextmenü einer Prozedur können Sie einfach die Option Ändern auswählen (Abbildung 7.3).
309
7 T-SQL-Programmierung
Abbildung 7.3 Ändern einer gespeicherten Prozedur
Daraufhin wird der Quelltext der Prozedur in einem neuen Abfrage-Editorfenster geöffnet, das bereits eine vorbereitete ALTER PROCEDURE-Anweisung enthält (Abbildung 7.4).
Abbildung 7.4 Ändern der Prozedur getProductList
310
7.5 Gespeicherte Prozeduren Um zum Beispiel die Liste der Produkte nach Produktname sortiert auszugeben, fügen Sie eine entsprechende ORDER BY-Klausel zur Abfrage hinzu, so wie in der Abbildung dargestellt. Anschließend kann ALTER PROCEDURE einfach ausgeführt werden, um die Prozedur zu ändern. Seien Sie bitte vorsichtig, wenn Sie die Schnittstelle einer Prozedur also zum Beispiel die Parameterliste verändern. SQL Server prüft in so einem Fall nicht, ob die Prozedur an anderen Stellen aufgerufen wird. Für diese Aufrufe kann dann eventuell die Parameterliste nicht mehr korrekt sein, was erst zur Laufzeit also dann, wenn der ungültige Aufruf der Prozedur erfolgt bemerkt wird.
7.5.3
Verwenden von Parametern
Die Prozedur getProductList erwartet einen Eingabeparameter, der ausgewertet wird, um nur Produkte einer bestimmten Kategorie auszuwählen. In diesem Abschnitt wollen wir untersuchen, welche weitergehenden Möglichkeiten es für die Definition und Übergabe von Parametern gibt. Standardwerte festlegen Beim Erzeugen oder Ändern einer Prozedur können Sie bei der Angabe der Parameterliste auch Standardwerte für Parameter festlegen. Hierfür wird der Zuweisungsoperator = wie folgt verwendet:
Der Parameter besitzt dadurch den Standardwert 'Bikes'. Ein Parameter mit einem Standardwert muss beim Aufruf einer Prozedur nicht angegeben werden. Wird der Parameter weggelassen, so erhält er in der Prozedur den bei der Deklaration angegebenen Standardwert. getProductList kann nun also auch so aufgerufen werden, um eine Liste aller 'Bikes' zu erhalten:
Möglich ist auch die Angabe der Schlüsselwortes DEFAULT für einen Parameter, wenn für diesen Parameter der Standardwert verwendet werden soll, also so etwa:
Wir wollen die Prozedur getProductList nun so ändern, dass eine absteigende Sortierung nach dem Listenpreis erfolgt, die teuersten Produkte einer Kategorie also zuerst genannt werden. Außerdem soll über einen zweiten Parameter die Anzahl der auszugebenden Zeilen angegeben werden. Hierdurch können dann zum Beispiel die drei teuersten Produkte der Kategorie Components ausgegeben werden. Dieser zweite Parameter soll den Standardwert 3 erhalten. Die folgende ALTER PROCEDURE-Anweisung ändert die Prozedur entsprechend:
311
7 T-SQL-Programmierung
Übergabe von Parametern durch die Position Da die Prozedur nun für beide Parameter Standardwerte enthält, kann der Aufruf der Prozedur auf verschiedene Arten erfolgen. Beide Parameter können wahlweise angegeben oder weggelassen werden. Möglich sind zum Beispiel diese Prozeduraufrufe:
Für die beim Prozeduraufruf übergebenen Parameter gilt hierbei Folgendes: Die Position, an welcher der Parameter in der Liste steht, bestimmt den Wert des Prozedurparameters. Man kann dies auch so formulieren: Wenn der zweite Parameter angegeben werden soll, so muss auch der erste Parameter angegeben werden. Für mehr als zwei Parameter kann dies allgemeiner formuliert werden: Soll ein bestimmter Parameter angegeben werden, so müssen alle in der Parameterliste vorher stehenden Parameter ebenfalls beim Prozeduraufruf angegeben werden. Danach stehende Parameter können weggelassen werden, wenn diese einen Standardwert enthalten. Benannte Parameterübergabe Insbesondere bei Prozeduren mit vielen Parametern, die Standardwerte enthalten, kann die positionsbezogene Parameterübergabe lästig sein, wenn zum Beispiel nur ein Parameter sagen wir der neunte abweichend vom Standardwert vergeben werden soll. In diesem Fall müssen die ersten acht Parameter ebenfalls angegeben werden. Falls diese acht Parameter die Standardwerte erhalten sollen, so müssen Sie acht Mal DEFAULT beim Aufruf der Prozedur angeben. In so einem Fall kann die Übergabe von Parametern auch per Name erfolgen. Hierzu wird eine spezielle Syntax beim Prozeduraufruf verwendet, bei der die Namen der Parameter mit einem Zuweisungsoperator verwendet werden, um nur bestimmte Parameter zu setzen. Möchten Sie zum Beispiel die Prozedur getProductList mit dem Wert 5 für den zweiten Parameter aufrufen und für den ersten Parameter den Standardwert belassen, können Sie auch schreiben:
312
7.5 Gespeicherte Prozeduren Hier wird der Wert für den Parameter explizit gesetzt. Hier folgt noch einmal eine Zusammenfassung zur Parameterübergabe: Parameter ohne Standardwert müssen immer angegeben werden. Für Parameter mit Standardwert kann über DEFAULT der Standardwert festgelegt werden. Parameter mit Standardwert erhalten diesen Wert, wenn sie beim Prozeduraufruf weggelassen werden. Für Parameter mit Standardwert muss bei der positionsbezogenen Übergabe immer ein Wert angegeben werden, wenn für nachfolgende Parameter ein Wert angegeben wird. Eine Parameterliste muss also stets nach links aufgefüllt werden. Sie können die positionsbezogene Übergabe nicht mit der benannten Parameterübergabe zusammen verwenden, sondern müssen sich für eine Form entscheiden. Ausgabeparameter Die bislang verwendeten Parameter ließen nur die Übertragung von Informationen in eine Richtung zu, sie waren allesamt Eingabeparameter. SQL Server gestattet auch die Verwendung von Ausgabeparametern. Diese Parameter müssen in der Parameterliste durch die Option OUTPUT deklariert werden. Unserer Prozedur getProductList soll ein dritter Parameter hinzugefügt werden. Über diesen Parameter soll die Anzahl der tatsächlich ausgegebenen Zeilen von der Prozedur zurückgeliefert werden, die ja von der im Parameter übergebenen Anzahl abweichen kann. Hierzu wird die Prozedur so geändert:
Die abschließende SET-Anweisung setzt den Wert des Ausgabeparameters (der übrigens auch als Eingabeparameter verwendet werden kann, also genau genommen ein Ein- und Ausgabeparameter ist). Für den Empfang eines Ausgabeparameters wird eine spezielle Methode des Prozeduraufrufs verwendet:
313
7 T-SQL-Programmierung Hier wird der Wert des Ausgabeparameters in einer zuvor deklarierten Variablen empfangen. Hierzu muss auch beim Aufruf der Prozedur die OUTPUT-Klausel angegeben werden.
7.5.4
Der Rückgabewert einer gespeicherten Prozedur
Eine gespeicherte Prozedur liefert immer auch einen Rückgabewert vom Typ INT. Wenn Sie nichts anderes angeben, so ist dieser Wert einfach 0. Über die Anweisung RETURN können Sie auch einen anderen Rückgabewert setzen, der zum Beispiel Informationen zum Prozedurverlauf enthalten kann. Der zurückgegebene Wert muss vom Datentyp INT sein und kann beispielsweise verwendet werden, um einen Fehlercode zurückzugeben. Die Prozedur getProductList kann die Anzahl der ausgegebenen Zeilen auch über einen Rückgabewert liefern. Hierzu wird die Prozedur so modifiziert:
Um den Rückgabewert einer Prozedur entgegenzunehmen, muss der Prozeduraufruf so aussehen:
7.5.5
Die gespeicherte Systemprozedur sp_procoption
Bislang haben wir gespeicherte Prozeduren stets manuell aufgerufen. SQL Server bietet jedoch auch die interessante Möglichkeit, dass gespeicherte Prozeduren automatisch beim Start des SQL Server-Dienstes ausgeführt werden können. Hierzu muss eine Prozedur für die automatische Ausführung registriert werden, was durch die gespeicherte Systemprozedur folgendermaßen erledigt werden kann:
Der Parameter gibt den Namen der Prozedur an, für welche die automatische Ausführung ein- oder ausgeschaltet werden soll. Für den Parameter können Sie die Zeichenketten true, on oder 1 zum Aktivieren bzw. false, off oder 0 zum Deaktivieren der automatischen Ausführung verwenden.
314
7.5 Gespeicherte Prozeduren Damit die automatische Ausführung funktioniert, müssen folgende Voraussetzungen erfüllt sein: Eine Startprozedur muss in der master-Datenbank gespeichert werden und darf keine Parameter enthalten. Die Server-Option scan for startup procs muss eingeschaltet sein. Dies kann einfach über einen Aufruf von vorgenommen werden:
Startprozeduren bieten vielfältige Möglichkeiten, wenn Ihre Anwendungen Datenbankoder Serverobjekte benötigen, die beim Start von SQL Server initialisiert werden müssen. So haben Sie zum Beispiel die Möglichkeit, Tabellen in der tempdb zu erzeugen, die dann von Ihren Anwendungen benutzt werden können. Die Datenbank tempdb wird ja bei jedem Start von SQL Server neu erstellt, sodass diese Datenbank unmittelbar nach dem Start immer leer ist. Eine Startprozedur kann dann zu dieser leeren tempdb Objekte hinzufügen und diese initialisieren. Wenn Sie die WAITFOR-Anweisung verwenden, können Startprozeduren sogar Endlosschleifen abarbeiten, wie die folgende Prozedur zeigt, die zyklische Datensicherungen durchführt:
315
7 T-SQL-Programmierung
Die Prozedur ist in der SQL Server Express Edition nützlich, zu deren Umfang kein SQL Server-Agent gehört. Dadurch ist eine durch den Agenten gesteuerte, zyklische Ausführung von Datensicherungen in dieser Edition nicht möglich. Durch die obige Prozedur kann hier Abhilfe geschaffen werden. In dieser Prozedur laufen Datensicherungen in einer Endlosschleife. Jeden Morgen um 05:00 Uhr erfolgt eine vollständige Sicherung der Datenbank AdventureWorks auf ein Sicherungsmedium, das zuvor im Verzeichnis c:\SqlBackup erzeugt wird, wenn es noch nicht existiert. Hierbei wird für jeden Wochentag ein eigenes Sicherungsmedium erzeugt. Um 06:00 werden dann die Systemdatenbanken ebenfalls in dieses Sicherungsmedium gesichert. Im Anschluss an die vollständigen Sicherungen wird dann von ca. 07:00 bis 22:00 Uhr das Transaktionsprotokoll der Datenbank AdventureWorks gesichert. Danach beginnt erneut der Durchlauf der äußeren Schleife, um 05:00 ist dann also der nächste Tag an der Reihe. Die Prozedur wird niemals beendet. Damit diese Prozedur beim Start von SQL Server (Express) automatisch gestartet wird, muss sie durch den Aufruf von hierfür registriert werden:
Diese Prozedur ist die meiste Zeit mit der Ausführung der Anweisung WAITFOR also mit Warten beschäftigt, was kaum Serverressourcen belegt.
7.5.6
EXECUTE und INSERT
Wenn eine gespeicherte Prozedur eine Ergebnismenge also eine Tabelle zurückgibt, so kann dieses Ergebnis auch in einer Tabelle empfangen werden. Hierzu existiert eine spezielle INSERT-Syntax, die so aussieht:
Schauen Sie sich hierzu bitte das folgende Skript an, in dem die von der Prozedur erzeugte Ausgabe in eine zuvor angelegte temporäre Tabelle eingefügt wird:
316
7.5 Gespeicherte Prozeduren Ein Nachteil bei dieser Verfahrensweise ist, dass die Tabelle für den Empfang des Ergebnisses mit der richtigen Struktur bereits existieren muss. Es gibt keine Möglichkeit, nur bestimmte Spalten oder Zeilen aus dem von der Prozedur zurückgegebenen Ergebnis auszuwählen. Es müssen also stets alle Zeilen und Spalten empfangen werden. Wenn die Tabelle für die Aufnahme des Ergebnisses nicht explizit angelegt werden soll oder kann, so hilft ein kleiner Trick. Verwenden Sie in diesem Fall die Funktion OPENQUERY, welche die Ausführung von Abfragen auf sogenannten Verbindungsservern ermöglicht. Hierzu erwartet die Funktion als ersten Parameter den Namen des Verbindungsservers, der allerdings auch der lokale Server sein darf. Für den Server muss die Möglichkeit dieser Art des Datenzugriffs zuvor explizit aktiviert werden, was über die gespeicherte Systemprozedur erfolgen kann:
Das von OPENQUERY gelieferte Ergebnis kann wie eine Tabelle verwendet werden. Dadurch ist es auch möglich, nur bestimmte Zeilen oder Spalten zu verwenden:
7.5.7
Erweitertes Beispiel: PDF-Dateien einlesen
Zum Abschluss dieses Kapitels soll nun noch ein etwas umfangreicheres Beispiel einer gespeicherten Prozedur präsentiert werden. Wir wollen eine gespeicherte Prozedur entwickeln, die PDF-Dateien aus einem Verzeichnis in eine Tabelle überträgt. Diese Tabelle soll das folgende Format aufweisen:
Sie enthält also zwei Spalten, die den Dateinamen der PDF-Datei und den eigentlichen Inhalt der Datei in einer Spalte vom Typ VARBINARY(MAX) aufnehmen sollen.
317
7 T-SQL-Programmierung Die zu erstellende Prozedur soll ein Verzeichnis als Eingabeparameter entgegennehmen und dann alle in diesem Verzeichnis existierenden PDF-Dateien mit Namen und Inhalt in die Tabelle einfügen. Kern der Prozedur ist die Funktion OPENROWSET. Diese Funktion ermöglicht einerseits die Ausführung von Abragen auf Verbindungsservern. OPENROWSET erlaubt auch das Einlesen von Dateien aus dem Dateisystem. Von dieser zweiten Möglichkeit wollen wir hier Gebrauch machen. Dazu wird OPENROWSET in der folgenden Form verwendet:
Das obige Skript liest eine Textdatei ein und gibt den Inhalt dieser Datei im Meldungsfenster aus. Die OPENROWSET-Funktion erwartet zwei Parameter. Der erste Parameter ist der Name der Datei, der auf das Schlüsselwort BULK folgen muss. Der zweite Parameter spezifiziert das Format der Datei. Im Beispiel wird SINGLE_CLOB verwendet. Das C in dieser Bezeichnung steht für Character also eine Zeichenkette. Für unsere einzulesenden PDF-Dateien wird später der Datentyp SINGLE_BLOB verwendet werden. Das B steht hier für Binary also ein Binärdatenformat. Das von OPENROWSET zurückgegebene Resultat kann wie eine Tabelle zum Beispiel in einer SELECT-Anweisung verwendet werden. Auch dies zeigt das obige Beispiel. Nun aber genug der Vorrede, hier kommt der Code für die gespeicherte Prozedur PdfEinlesen: Listing 7.1 PDF-Dokumente in eine Tabelle einlesen
318
7.5 Gespeicherte Prozeduren
Zu Beginn des Skriptes wird zunächst die Tabelle PDFFiles neu erzeugt. Am Anfang der Prozedur wird der Inhalt des übergebenen Verzeichnisses für die Dateimaske *.PDF in eine Tabellenvariable übertragen. Dies erfolgt durch den Aufruf der erweitert gespeicherten Systemprozedur , die hier prinzipiell so verwendet wird:
Die Option /B für das Kommando DIR bewirkt nur die Ausgabe der Dateinamen ohne zusätzliche Informationen wie Größe oder Datum. Vor dem Aufruf von muss noch die entsprechende Option aktiviert werden, die den Aufruf dieser Prozedur gestattet. Direkt nach dem Aufruf wird diese Aktivierung dann wieder zurückgenommen. Für die Aktivierung wird die gespeicherte Systemprozedur verwendet. Da nicht jede SQL Server-Anmeldung die Berechtigung zum Aufruf dieser Prozedur besitzt, muss dem aufrufenden Benutzer die entsprechende Berechtigung erteilt werden. Hierzu kann der Benutzer beispielsweise in die Serverrolle serveradmin aufgenommen werden. Die Abarbeitung der gefundenen PDF-Dateien, deren Namen sich nun in der temporären Tabelle befinden, erfolgt durch einen Cursor. Innerhalb des Cursors wird für jede gefunde-
319
7 T-SQL-Programmierung ne Datei die Funktion OPENROWSET verwendet, um den Dateiinhalt in die Tabelle PDFFiles zu übertragen. OPENROWSET erwartet hierbei als ersten Parameter den Namen einer Datei als Zeichenkette. Leider kann hier keine Variable verwendet werden, die den Dateinamen enthält, der Name muss direkt angegeben werden. Aus diesem Grund verwendet die Prozedur dynamisches SQL und konstruiert für jede Datei eine eigene INSERT-Anweisung mit OPENROWSET. Wenn Sie diese Zeile auskommentieren
so erhalten Sie den Text der INSERT-Anweisung im Meldungsfenster zur Kontrolle. Die fertige Prozedur kann so verwendet werden:
7.6
Benutzerdefinierte Funktionen Benutzerdefinierte Funktionen bieten eine weitere Möglichkeit, T-SQL-Code auf dem Server zu speichern. Damit sind Funktionen Prozeduren recht ähnlich, und doch existieren einige erhebliche Unterschiede: Funktionen gestatten keine Seiteneffekte, die Datenänderungen durchführen. UPDATE-, INSERT- oder DELETE-Anweisungen sind in Funktionen also nicht erlaubt. Funktionen gestatten die Verwendung von Parametern, erlauben aber keine Ausgabeparameter. Auch für Funktionsparameter können Standardwerte angegeben werden. Da es in einem Funktionsaufruf generell nicht möglich ist, Parameter wegzulassen es müssen also immer alle Parameter angegeben werden , muss explizit DEFAULT als Parameterwert angegeben werden, wenn der Standardwert verwendet werden soll. Eine Funktion muss immer explizit ein Ergebnis über die RETURN-Anweisung zurückgeben. Dieses Ergebnis kann wie bei Prozeduren auch ein Wert vom Typ INT sein. Funktionen können jedoch Rückgabewerte beliebiger Datentypen an den Aufrufer zurückgeben. SQL Server unterscheidet zwischen Funktionen, die einen Skalarwert zurückgeben, und solchen Funktionen, die eine Tabelle als Ergebnis liefern.
7.6.1
Skalarwertfunktionen
Ein Skalarwert ist ein einzelner Wert eines einfachen Datentyps, also zum Beispiel ein INT, VARCHAR oder DATETIME. Eine Funktion, die einen Skalarwert zurückliefert, wird folgendermaßen erzeugt:
320
7.6 Benutzerdefinierte Funktionen
Eine Funktionsdefinition beginnt also mit CREATE FUNCTION und dem Funktionsnamen. Optional können dann Parameter angegeben werden, die genauso wie bei Prozeduren deklariert werden (mit der Ausnahme, dass keine Ausgabeparameter verwendet werden dürfen). Die runden Klammern hinter dem Funktionsnamen sind hier obligatorisch. Sie müssen auch hingeschrieben werden, wenn die Funktion keine Parameter entgegennimmt. Die anschließende RETURNS-Klausel bestimmt den Datentyp des Funktionsergebnisses. Nach dem Schlüsselwort AS beginnt dann der eigentliche T-SQL-Code für die Funktion. Eine Funktion muss eine abschließende RETURN-Anweisung enthalten, mit welcher der Funktionswert zurückgegeben wird. Dieser Wert muss vom in der RETURNS-Klausel angegebenen Datentyp sein oder zumindest implizit in diesen Datentyp konvertiert werden können. Lassen Sie uns hierzu ein einfaches Beispiel betrachten. SQL Server kennt keinen reinen DATE-Datentyp, in dem nur das Datum ohne Uhrzeit gespeichert ist. Wir wollen eine Funktion erstellen, die den Zeitanteil aus einem DATETIME-Wert entfernt, also ein DATETIME zurückgibt, in dem die Uhrzeit immer 00:00 ist. Hierzu wird der Funktion ein DATETIME-Wert übergeben. Diese Funktion könnte so aussehen:
Die Funktion enthält drei Anweisungen zwischen BEGIN und END, um zu zeigen, dass an dieser Stelle beliebig viele Anweisungen stehen können. Für die obige Funktion wäre auch einfach eine RETURN-Anweisung der Form
ausreichend gewesen. Erstellte Funktionen in einer Datenbank können Sie im Objekt-Explorer unter Programmierbarkeit/Funktionen sehen. Die gerade erstellte Skalarwertfunktion dbo.Datum finden Sie dort im Ordner Skalarwertfunktionen (Abbildung 7.5). In diesem Ordner sind auch einige weitere Funktionen vorhanden, die in der Beispieldatenbank standardmäßig enthalten sind. Die Funktion dbo.Datum kann nun zum Beispiel so aufgerufen werden:
321
7 T-SQL-Programmierung
Abbildung 7.5 Skalarwertfunktionen der Datenbank AdventureWorks
Beim Funktionsaufruf gibt es eine Besonderheit: Das Schema muss immer angegeben werden. Für Funktionsaufrufe wird also keine automatische Namensauflösung durchgeführt. Unsere Funktion wurde im Schema dbo erstellt, muss also stets durch dbo.Date aufgerufen werden. Außerdem ist es immer erforderlich, die runden Klammern für den Funktionsaufruf anzugeben. Dies gilt auch, wenn die Funktion keine Parameter hat, wie im nächsten Beispiel zu sehen sein wird. Wie oben erwähnt, dürfen Funktionen keine Datenänderungsoperationen durchführen. Das Abrufen von Daten, also die Ausführung von SELECT-Anweisungen, ist selbstverständlich möglich. In Abschnitt 7.1.2 haben wir eine durch Semikolon getrennte Liste aller Angestellten erzeugt. Dies soll nun nochmals innerhalb einer Funktion für alle Vertriebsmitarbeiter erfolgen. Hier ist die Funktion dbo.VBListe, die diese Aufgabe erledigt:
322
7.6 Benutzerdefinierte Funktionen Aus Gründen der Übersichtlichkeit wird in der Funktion ein allgemeiner Tabellenausdruck verwendet, der für alle Vertriebsmitarbeiter Vor- und Nachname aneinanderhängt. Die von diesem Tabellenausdruck zurückgegebenen Namen werden dann in einer Variablen von Typ VARCHAR(MAX) verkettet und schließlich über diese Variable zurückgegeben. Die Funktion kann zum Beispiel so aufgerufen werden:
Hier ist ein Ausschnitt aus dem Ergebnis:
7.6.2
Tabellenwertfunktionen
Tabellenwertfunktionen geben eine Tabelle an den Aufrufer zurück. Bei Tabellenwertfunktionen unterscheidet SQL Server generell zwischen zwei Typen. Zum einen gibt es die sogenannten Inline-Tabellenwertfunktionen, die nur aus einer einzigen SELECTAnweisung bestehen dürfen. SQL Server kennt jedoch auch Tabellenwertfunktionen, die im Anweisungsblock mehrere T-SQL-Anweisungen enthalten können. Inline-Tabellenwertfunktionen Eine Inline-Tabellenwertfunktion besitzt die folgende Struktur:
Wir wollen an dieser Stelle eine Funktion erstellen, welche die letzten Bestellungen für einen Vertriebsmitarbeiter zurückgibt. Die Funktion nimmt zwei Parameter entgegen. Zum einen wird die SalesPersonID des Vertriebsmitarbeiters übergeben, für den die Daten abgefragt werden sollen. Über einen zweiten Parameter wird angegeben, wie viele Zeilen zurückgegeben werden sollen. Diese Funktion kann als Inline-Tabellenwertfunktion erstellt werden, die so aussieht:
Der Aufruf einer Tabellenwertfunktion unterscheidet sich vom Aufruf einer Skalarwertfunktion. Da eine Tabellenwertfunktion eine Tabelle zurückgibt, kann (muss) die Funktion auch wie eine solche verwendet werden, also zum Beispiel so:
um für den Vertriebsmitarbeiter mit der SalesOrderID 284 die letzten fünf Bestellungen abzufragen.
323
7 T-SQL-Programmierung Stellen Sie sich eine Tabellenwertfunktion einfach wie eine Sicht mit Parametern vor. Eine Tabellenwertfunktion kann mit dem APPLY-Operator zusammen verwendet werden. Um zum Beispiel die jeweils letzten drei Bestellungen aller Angestellten abzufragen, ist es möglich, die Funktion dbo.LastOrders über APPLY so aufzurufen:
Diese Abfrage basiert auf dem APPLY-Beispiel aus Kapitel 4, das hier nochmals diesmal eben unter Verwendung einer Tabellenwertfunktion aufgegriffen wird. Tabellenwertfunktionen mit mehreren Anweisungen Eine Tabellenwertfunktion kann auch mehrere Anweisungen enthalten und erst am Ende eine Tabelle zurückgeben. In so einem Fall muss die RETRUNS-Klausel der CREATE FUNCTION-Anweisung die Deklaration einer Tabellenvariablen enthalten. In dieser Variablen werden während der Funktionsausführung Daten gespeichert, die dann an den Aufrufer (als Tabelle) zurückgegeben werden. Generell sieht eine solche Tabellenwertfunktion so aus:
Insbesondere darf die RETURN-Anweisung einer solchen Funktion also keinen Ausdruck enthalten. Durch RETURN wird einfach die bei RETURNS sozusagen erstellte Tabelle zurückgegeben. Wir wollen die Funktion dbo.LastOrders nun nochmals erstellen, diesmal als Tabellenwertfunktion mit mehreren Anweisungen. Dies kann ungefähr so aussehen:
324
7.7 Trigger
Im Funktionskopf wird eine Tabelle definiert, in die dann innerhalb der Funktion Zeilen hinzugefügt werden. Die abschließende RETURN-Anweisung gibt dann diese Tabelle zurück. Der Aufruf dieser Funktion kann genauso erfolgen wie im Beispiel weiter oben, also so:
7.7
Trigger Trigger sind ein spezieller Typ von gespeicherten Prozeduren. Sie unterscheiden sich von Prozeduren nur in wenigen aber wesentlichen Punkten: Trigger können keine Parameter verarbeiten. Trigger können nicht manuell aufgerufen werden. Ein Trigger wird bei seiner Definition mit einem Ereignis verknüpft, bei dessen Eintreten der Trigger automatisch ausgeführt wird. Solch ein Ereignis kann zum Beispiel eine Datenänderungsoperation, wie INSERT oder UPDATE, sein. Trigger dieser Art werden auch als DML-Trigger bezeichnet, da sie für Anweisungen ausgeführt werden, die Datenmanipulationen vornehmen. Neu in SQL Server 2005 ist die Möglichkeit, auch Ereignisse für Änderungen an Metadaten, also zum Beispiel an Datenbankstrukturen, mit Triggern verbinden zu können. Diese sogenannten DDL-Trigger können also mit Anweisungen aus dem Bereich Datendefinition verknüpft werden. Innerhalb von Triggern haben Sie Zugriff auf spezielle Objekte zur weiterführenden Information über das auslösende Ereignis. Bei DML-Triggern existieren zum Beispiel die beiden virtuellen Tabellen DELETED und INSERTED, die Informationen über die Datenänderungen liefern. In DDL-Triggern kann ein spezielles Ereignisobjekt über eine Systemfunktion abgefragt werden. Für die in diesem Abschnitt verwendeten Beispiele soll zunächst eine Tabelle Kontakt erzeugt werden, die drei zufällig ausgewählte Zeilen aus der Tabelle Person.Contact enthält:
Zur Spalte ContactID der abgerufenen Spalte wird hier einfach der Wert 0 addiert, um zu verhindern, dass die IDENTITY-Eigenschaft der Spalte in die angelegte Tabelle übernommen wird.
325
7 T-SQL-Programmierung
7.7.1
DML-Trigger
Ein DML-Trigger kann für INSERT-, UPDATE- oder DELETE-Operationen auf einer Tabelle oder auch Sicht definiert werden. Dies erfolgt über die Anweisung CREATE TRIGGER, welche die folgende Syntax hat:
Für kann INSERT, UPDATE oder DELETE angegeben werden, um die Ausführung des Triggers für eine entsprechende Datenänderung auszulösen. Es ist auch Möglich, mehrere Ereignisse durch Komma getrennt anzugeben, wodurch der Trigger dann für all diese Ereignisse ausgeführt wird. Im eines DML-Triggers haben Sie Zugriff auf die bereits in Kapitel 4 bei OUTPUT besprochenen Tabellen INSERTED und DELETED. Über diese Tabellen kann der Zustand vor und nach der Datenänderungsoperation abgefragt werden. In einem Trigger, der durch eine INSERT-Anweisung gefeuert wurde, ist die Tabelle DELETED leer. Gleiches gilt für die Tabelle INSERTED und Trigger, die durch ein DELETE ausgelöst wurden. Ein nach dem obigen Muster erstellter Trigger wird nach der Ausführung der entsprechenden SQL-Anweisung gestartet. Der Trigger wird also erst aufgerufen, wenn die gesamte Anweisung abgearbeitet wurde, und nicht etwa vor Ausführung der Anweisung oder für jede Zeile, die von der SQL-Anweisung betroffen ist. Das bedeutet, dass in den Tabellen INSERTED und DELETED durchaus auch mehrere Zeilen enthalten sein können. Dies sollten Sie beim Erstellen von Triggern unbedingt bedenken. INSERT-Trigger Es soll nun ein einfacher INSERT-Trigger für die gerade erzeugte Tabelle Kontakt erstellt werden. Dieser Trigger soll lediglich die hinzugefügten Zeilen, die ja in der INSERTED-Tabelle stehen, zurückgeben. Ein solcher Trigger kann folgendermaßen erstellt werden:
Der so erstellte Trigger existiert nicht als eigenständiges Datenbankobjekt. Der Trigger hängt immer an der Tabelle, für die er erstellt wurde. Im Objekt-Explorer wird dies auch deutlich. Der Trigger wird im Ordner für die jeweilige Tabelle, dort unter Trigger, angezeigt (Abbildung 7.6).
326
7.7 Trigger
Abbildung 7.6 Der INSERT-Trigger für die Tabelle Kontakt
Wir wollen nun natürlich ausprobieren, ob der Trigger tatsächlich beim Hinzufügen einer Zeile gefeuert wird, fügen also einfach eine Zeile hinzu:
Die Transaktion ist hier nur dazu da, die Tabelle im ursprünglichen Zustand zu belassen. Für unsere in diesem Abschnitt durchgeführten Versuche mit Triggern werden wir ausschließlich explizite Transaktionen verwenden, die stets zurückgesetzt werden. Nach der Ausführung der INSERT-Anweisung wird automatisch der Trigger gestartet. Die im Trigger ausgeführte SELECT-Anweisung liefert dieses Resultat:
Die INSERTED-Tabelle enthält im Trigger also eine einzige Zeile mit den angegebenen Spaltenwerten. UPDATE-Trigger Im zweiten Schritt soll nun ein UPDATE-Trigger hinzugefügt werden. Dies ist der einzige Trigger-Typ, in dem sowohl die INSERTED- als auch die DELETED-Tabelle mit Werten gefüllt sind. Daher geben wir im Trigger die Zeilen beider Tabellen aus:
327
7 T-SQL-Programmierung
Die Zeilen der beiden Tabellen werden einfach durch einen UNION ALL-Operator aneinandergehängt. In der ersten Spalte unterscheiden sich die Werte der beiden SELECTAnweisungen. Hier wird für die INSERTED-Tabelle die Zeichenkette Neue Werte ausgegeben, für die DELETED-Tabelle erfolgt die Ausgabe der Zeichenkette Alte Werte. Auch diesmal soll natürlich überprüft werden, ob der Trigger ausgeführt wird. Hierzu verwenden wir diese Abfrage, die alle Nachnamen ändert:
Im Ausgabebereich werden Sie dadurch so etwas erhalten:
Sie sehen den Inhalt der beiden Tabellen INSERTED und DELETED und können erkennen, dass der Nachname für alle Zeilen auf Schönfeld geändert wurde. Interessant ist, dass auch die folgende Anweisung den Trigger auslöst, obwohl hierbei keine Daten verändert werden:
Die Tabellen INSERTED und DELETED enthalten in diesem Fall identische Zeilen. Bedenken Sie dies bitte, wenn Sie Trigger verwenden. Entscheidend für die automatische Ausführung eines Triggers ist die Anweisung selber in diesem Fall UPDATE und nicht etwa die durchgeführte Datenänderung. DELETE-Trigger Zum Schluss unserer Experimente soll nun noch ein DELETE-Trigger hinzugefügt werden, der die Zeilen der Tabelle DELETED zurückgibt:
328
7.7 Trigger
Die folgende Anweisung bewirkt die Ausführung des Triggers:
Als Ausgabe werden alle gelöschten Zeilen angezeigt. Dies sind da keine Zeilenfilterbedingung angegeben wurde alle Zeilen der Tabelle Kontakt:
Auch der DELETE-Trigger wird immer gefeuert, nachdem eine DELETE-Anweisung ausgeführt wurde. Hierbei ist es unerheblich, ob tatsächlich Zeilen gelöscht wurden. Der Trigger startet also auch nach dieser Anweisung:
Sie erhalten vom Trigger eine leere Ergebnismenge, da ja nichts gelöscht wurde. Das Entfernen aller Zeilen der Tabelle ist auch über TRUNCATE TABLE möglich:
Hierbei wird der Trigger allerdings nicht ausgeführt. TRUNCATE ist eben kein DELETE. Es ist nicht möglich, für TRUNCATE einen Trigger zu erstellen. Trigger kombinieren Bislang haben wir jeweils einen Trigger für jede der drei möglichen DML-Anweisungen INSERT, UPDATE und DELETE erzeugt. SQL Server gestattet jedoch auch die Definition von Triggern für mehr als eine DML-Anweisung. Hierfür können Sie die Ereignisse, die den Trigger auslösen sollen, einfach durch Komma getrennt aufzählen. Unsere bisher erzeugten Trigger verhalten sich alle gleich. Sie geben jeweils nur den Inhalt der Tabellen INSERTED und DELETED aus. Hierfür kann auch ein einzelner Trigger verwendet werden, der für alle drei DML-Anweisungen angelegt wird:
329
7 T-SQL-Programmierung
Die oben präsentierten Ergebnisse für die einzelnen DML-Anweisungen werden auch durch diesen Trigger erzeugt, der immer den Inhalt beider Tabellen zurückgibt. Da die DELETED- bzw. INSERTED-Tabellen für INSERT- bzw. DELETE-Anweisungen keine Zeilen enthalten, funktioniert die Abfrage im Trigger in jedem Fall. Es ist auch möglich, mehrere INSERT-, DELETE- oder UPDATE-Trigger für eine Tabelle zu erzeugen. In so einem Fall kann die Aufrufreihenfolge der Trigger entscheidend sein. Wenn Sie zum Beispiel fünf UPDATE-Trigger zu unserer Tabelle Kontakt hinzugefügt haben, ist es möglicherweise wichtig festzulegen, in welcher Reihenfolge diese Trigger ausgeführt werden sollen. Hier bietet SQL Server nur die Möglichkeit, über die gespeicherte Systemprozedur anzugeben, welcher Trigger zuerst und welcher zuletzt ausgeführt werden soll. Dazwischen ist die Reihenfolge undefiniert. Am besten vermeiden Sie also eine solche Situation. Es ist immer auch möglich, mit nur einem Trigger je Ereignis auszukommen, aus dem ja zum Beispiel auch Funktionen oder Prozeduren aufgerufen werden können, wenn der Trigger ansonsten vielleicht zu komplex werden würde. Trigger deaktivieren/aktivieren In manchen Fällen kann es erforderlich sein, einen oder alle Trigger zeitweise außer Kraft zu setzen. Natürlich können Sie in so einem Fall den Trigger mit DROP TRIGGER löschen und anschließend über CREATE TRIGGER neu erstellen. SQL Server bietet jedoch auch die Möglichkeit, Trigger zu deaktivieren. Für DML-Trigger können Sie hierbei zwischen der Anweisung ALTER TABLE oder DISABLE TRIGGER wählen. Ein DISABLE TRIGGER-Kommando zur Deaktivierung des Triggers tr_all_kontakt sieht beispielsweise so aus:
Es müssen also der Name des Triggers und ebenso der Name der Tabelle angegeben werden. Über ein entsprechendes ENABLE TRIGGER kann der existierende Trigger wieder aktiviert werden:
Es ist auch möglich, alle Trigger einer Tabelle mit nur einem Kommando außer Kraft zu setzen und wieder zu aktivieren, Hierzu verwenden Sie in der obigen Syntax einfach ALL anstelle des Trigger-Namens:
330
7.7 Trigger
INSTEAD OF-Trigger Die bisher erstellten Trigger wurden allesamt nach der Ausführung der auslösenden Anweisung gestartet. SQL Server kennt einen zweiten Trigger-Typ, der anstelle von DMLAnweisungen ausgeführt wird: INSTEAD OF-Trigger. Einen INSTEAD OF-Trigger können Sie für jede der drei DML-Anweisungen genau einmal pro Tabelle erstellen. Außerdem können INSTEAD OF-Trigger auch für Sichten erzeugt werden, was eine Aktualisierung von Sichten ermöglicht, deren Zeilen ansonsten nicht geändert werden dürften. Bei einem INSTEAD OF-Trigger wird die eigentliche Anweisung also nicht ausgeführt. Stattdessen wird der Trigger aufgerufen, der dann seinerseits entsprechende Aktualisierungskommandos für die Tabelle absetzen kann. Ein INSTEAD OF-Trigger kann zum Beispiel verwendet werden, um zusätzliche Integritätsbedingungen zu überprüfen, die vor dem Einfügen, Ändern oder Löschen von Zeilen überprüft werden sollen und die nicht mittels deklarativer Integritätsbedingungen angegeben werden können. Abbildung 7.7 zeigt ein Beispiel für eine UPDATE-Anweisung.
Abbildung 7.7 INSTEAD OF UPDATE-Trigger
Im oberen Zweig ist die Ausführung aus der Sicht des Benutzers dargestellt, der ein UPDATE-Kommando ausführt. Für diesen Benutzer ist nicht ersichtlich, dass in Wirklichkeit ein Trigger, also Programmcode, aufgerufen wird, der zusätzliche Überprüfungen vornimmt und dann erst ein UPDATE-Kommando auf der Tabelle ausführt. Durch diese Ausführung wird der Trigger übrigens nicht erneut ausgeführt, eine Rekursion wird also unterbunden.
331
7 T-SQL-Programmierung
7.7.2
Hinweise zur Verwendung von Triggern
Auf den ersten Blick werden Ihnen Trigger vielleicht als eine fantastische und faszinierende Möglichkeit in der Datenbankentwicklung erscheinen. Dies mag sicherlich für gewisse Einsatzbereiche auch zutreffend sein. Setzen Sie Trigger aber bitte vorsichtig und sparsam ein. Trigger basieren auf Seiteneffekten, laufen also im Hintergrund mehr oder weniger unkontrolliert ab. Die Ausführung von Triggern hat eventuell entscheidende Auswirkunken auf das Laufzeitverhalten Ihres Systems. Eine UPDATE-Anweisung, die eigentlich nur ein paar Zeilen aktualisieren soll, kann über einen Trigger letztlich eine Transaktion erzeugen, die den Server an den Rand seines Leistungsvermögens bringt. Halten Sie Trigger daher in jedem Fall kurz, und versuchen Sie nicht, Anwendungslogik, die besser in Ihren Applikationen oder vielleicht in gespeicherten Prozeduren enthalten sein sollte, in Triggern unterzubringen. Große Trigger werden in der Regel auch große Transaktionen mit vielen Sperren zur Folge haben, und dies kann recht dramatische Auswirkungen auf das Laufzeitverhalten haben. Bedenken Sie hierbei bitte, dass ein Trigger je Anweisung ausgeführt wird. Dies kann dazu führen, dass die Tabellen INSERTED und DELETED sehr groß werden. Wenn Sie diese Tabellen im Trigger abarbeiten wollen, so werden Sie häufig auf einen Cursor zurückgreifen, was wiederum sehr ressourcenintensiv ist. Sinnvoll sind Trigger zum Beispiel dann, wenn Sie aus Performanzgründen eine Denormalisierung Ihrer logischen Datenbankstruktur vorgenommen haben. Es ist zum Beispiel denkbar, dass Sie redundante Daten speichern, etwa eine Spalte GesamtUmsatz in einer Tabelle mit Kundendaten. Der Gesamtumsatz für einen Kunden wird sich immer aus den für diesen Kunden existierenden Rechnungen herleiten lassen, die Spalte in der KundenTabelle ist folglich redundant und existiert wahrscheinlich nur aus Performanzgründen. Über Trigger können Sie den in dieser Spalte enthaltenen Wert auf dem Laufenden halten, wenn Rechnungen hinzugefügt, geändert oder gelöscht werden. In einigen Fällen kann es darüber hinaus auch sinnvoll sein, Trigger für die Aufrechterhaltung der Datenintegrität zu verwenden, wenn dies mit deklarativen Mitteln nicht möglich ist. Vorrangig sollten Sie jedoch immer versuchen, für die Wahrung der Integrität die vorhandenen Einschränkungen auf Tabellen- oder Spaltenebene zu verwenden. Wenn Sie sich unsere obigen Beispiele nochmals ansehen, werden Sie vielleicht ein weiteres Problem erkennen. Ein Trigger wird immer dann ausgeführt, wenn eine entsprechende Anweisung ausgeführt wurde, und nicht etwa erst zum Ende der Transaktion. Sie können sich dies so vorstellen, dass Trigger Daten aus dem Transaktionsprotokoll lesen. Dadurch sind in Triggern prinzipiell Dirty Reads oder auch Phantom Reads möglich. Es kann also sein, dass ein Trigger Daten verarbeitet, die danach von einer Transaktion als ungültig erklärt werden, so wie es in all unseren Beispielen der Fall gewesen ist. Seien Sie auch darauf vorbereitet, dass Trigger eventuell rekursiv aufgerufen werden können. Es ist möglich, dass ein Trigger seinerseits Änderungen an Tabellen vornimmt, die wiederum Trigger auslösen. Hierdurch kann sich unter Umständen eine Kette von TriggerAufrufen ergeben, die in einer Rekursion endet. Wenn Sie den Aufruf von Triggern aus
332
7.7 Trigger anderen Triggern heraus unterbinden wollen, können Sie die Einstellung nested triggers der gespeicherten Systemprozedur entsprechend setzen:
7.7.3
DDL-Trigger
Neu in SQL Server 2005 ist die Möglichkeit, auch Trigger für Änderungsoperationen an Server- oder Datenbankobjekten erzeugen zu können. Diese Trigger werden unter dem Begriff DDL-Trigger zusammengefasst und lassen sich wie gesagt in zwei Kategorien unterteilen, die Server- oder Datenbankereignisse überwachen. Für unsere Experimente in diesem Abschnitt soll eine Tabelle Logbuch erzeugt werden, in der wir über Trigger bestimmte Ereignisse auf der Datenbank AdventureWorks, aber auch auf dem gesamten Server protokollieren wollen. Diese Tabelle wird in der masterDatenbank wie folgt erstellt:
Die dritte Spalte der Tabelle, in der die Details der aufgetretenen Ereignisse gespeichert werden, ist von Datentyp XML. Bitte stören Sie sich nicht daran, der XML-Thematik ist das gesamte folgende Kapitel 8 gewidmet. SQL Server kennt insgesamt ca. 90 Ereignisse, welche die Auslösung eines DDL-Triggers bewirken können. Wir werden an dieser Stelle nicht alle Ereignisse besprechen. Eine vollständige Auflistung finden Sie natürlich in der Online-Dokumentation. Glücklicherweise ist die Namensgebung für die Ereignisse so gewählt, dass allein der Ereignisname genug Information über das entsprechende Ereignis enthält. Dadurch ist es sehr einfach, aus der Vielzahl von zur Verfügung stehenden Ereignissen das benötigte auszuwählen, wie die folgenden beiden Abschnitten zeigen werden. DDL-Trigger für Datenbankereignisse Ein Trigger für die Überwachung von Datenbankereignissen wird auf der entsprechenden Datenbank durch eine Anweisung der folgenden Form erstellt:
steht hierbei für ein zu überwachendes Datenbankereignis. SQL Server ver-
fügt über insgesamt 75 unterschiedliche Ereignisse, von denen so wie in der obigen Syn-
333
7 T-SQL-Programmierung tax angedeutet auch mehrere mit einem Trigger verknüpft werden können. Der Name eines Ereignisses ist hierbei so aufgebaut wie die DDL-Anweisung, für die der Trigger ausgelöst werden soll. Zwischen den einzelnen Schlüsselwörtern wird dabei anstelle des Leerzeichens ein Unterstrich verwendet. So gibt es zum Beispiel ein Ereignis CREATE_TABLE, das die Erzeugung von Tabellen überwacht. Die folgende Auflistung enthält einige weitere willkürlich ausgewählte Ereignisse, die verdeutlichen sollen, wie Ereignisnamen konstruiert werden: CREATE_USER DROP_INDEX ALTER_PROCEDURE DROP_FUNCTION CREATE_SCHEMA Durch die Art der Namensgebung ist die Auswahl von Ereignissen also recht einfach. Der folgende Trigger soll nun auf der Datenbank AdventureWorks erstellt werden:
Dieser Trigger wird immer ausgelöst, wenn eine Tabelle auf der Datenbank angelegt, geändert oder gelöscht wird. Er protokolliert die Aktion, durch die er gefeuert wurde, in der zuvor angelegten Tabelle Logbuch der master-Datenbank. Hierzu wird die Funktion EVENTDATA() verwendet, die alle Informationen über das Ereignis im XML-Format enthält. Etwas weiter unten werden wir den Rückgabewert dieser Funktion noch genauer betrachten. Für die Auslösung von DDL-Triggern können Sie übrigens nicht nur bestimmte Ereignisse, sondern auch Gruppen von Ereignissen verwenden. Eine Ereignisgruppe fasst bestimmte Ereignisse unter einem Namen zusammen. Die vollständige Liste der Ereignisgruppen und der Zuordnung von Ereignissen zu diesen Gruppen finden Sie in der OnlineDokumentation unter dem Stichwort DDL-Trigger, Ereignisgruppen zum Auslösen. Unser Trigger verwendet die Ereignisse CREATE_TABLE, ALTER_TABLE und DROP_TABLE. Für diese drei Ereignisse existiert die Ereignisgruppe DDL_TABLE_EVENTS. Bei der Erstellung des Triggers hätten wir also statt:
auch dies schreiben können:
334
7.7 Trigger Im Objekt-Explorer finden Sie den erstellten Trigger unter Programmierbarkeit/Datenbanktrigger in der entsprechenden Datenbank (Abbildung 7.8).
Abbildung 7.8 Der erzeugte DDL-Trigger im Objekt-Explorer
Nun, da der Trigger existiert, wollen wir natürlich ausprobieren, ob er auch funktioniert. Hierzu wird mit dem folgenden Skript einfach eine Tabelle erstellt, geändert und anschließend wieder gelöscht:
Das Skript verwendet die drei vom Trigger überwachten DDL-Anweisungen CREATE TABLE, ALTER TABLE und DROP TABLE. Der Trigger sollte also drei entsprechende Einträge in das Logbuch eingefügt haben. Dies können wir durch die folgende Abfrage überprüfen:
Die Abfrage sollte etwa ein Ergebnis wie Abbildung 7.9 in gezeigt liefern.
Abbildung 7.9 Inhalt der Tabelle Logbuch
Die dritte Spalte enthält alle Detaildaten für das jeweils protokollierte Ereignis im XMLFormat. Bei einem Klick auf den in dieser Spalte angezeigten Link wird das entsprechende
335
7 T-SQL-Programmierung XML-Dokument im Editor geöffnet. Für das erste Ereignis (CREATE TABLE) sieht dieses Dokument ungefähr so aus:
Es ist sehr schön zu erkennen, dass das Protokoll wirklich detailliert ist. Sie finden zum Beispiel Angaben über den Benutzer, der die Anweisung zu welcher Zeit auf welcher Datenbank ausgeführt hat, und auch den Text der Anweisung selber. Zum Schluss dieses Abschnitts soll nun der Trigger wieder von der Datenbank entfernt werden. Dies geschieht durch die Anweisung DROP TRIGGER, wobei die Zusatzklausel ON DATABASE angegeben werden muss, um anzuzeigen, dass es sich um einen DDLTrigger auf der Datenbank handelt:
DDL-Trigger für Serverereignisse Ein DDL-Trigger für die Überwachung von Serverobjekten wird durch die folgende Syntax erstellt:
Die Anzahl der Ereignisse, die für den Server überwacht werden können, ist längst nicht so hoch wie die für Datenbanken zur Verfügung stehenden. Insgesamt gibt es zwölf Ereignisse für die Überwachung des SQL Servers selber. Auch hier ist es so, dass die Namen für die Ereignisse eine Auswahl erleichtern, da sie direkt die DLL-Anweisung widerspiegeln. Zum Beispiel können Sie die folgenden Ereignisse verwenden: DROP_DATABASE CREATE_LOGIN Ebenso wie für Datenbankereignisse stehen auch für Serverereignisse Ereignisgruppen zur Verfügung, in denen bestimmte Ereignisse gebündelt sind. So gibt es zum Beispiel die Er-
336
7.7 Trigger eignisgruppe DDL_SERVER_LEVEL_EVENTS, welche die Ereignisse CREATE_DATABASE, ALTER_DATABASE und DROP_DATABASE enthält. Als Beispiel soll hier ein DDL-Trigger erzeugt werden, der das Löschen von SQL ServerLogins untersagt. Hierzu wird im Trigger einfach die Transaktion abgebrochen und eine Meldung ausgegeben. Dieser Trigger kann so erzeugt werden:
Natürlich werden auch DDL-Trigger für den Serverbereich im Objekt-Explorer angezeigt. Sie finden den erzeugten Trigger dort im Ordner Serverobjekte/Trigger (Abbildung 7.10).
Abbildung 7.10 Der erzeugte DDL-Trigger für den Server
Versuchen wir nun einmal, die oben erzeugte Anmeldung DavidBowman zu löschen, um zu sehen, ob der Trigger funktioniert:
Im Meldungsbereich der Ausgabe sollten Sie daraufhin Folgendes sehen:
Der erste Teil dieser Meldung wurde durch die PRINT-Anweisung des Triggers erzeugt. Die Meldung 3609 resultiert aus dem ROLLBACK TRANSACTION innerhalb des Triggers.
337
7 T-SQL-Programmierung Was nun noch fehlt, ist das Löschen des Triggers. Ein für den Server erstellter Trigger wird ebenfalls durch eine spezielle DROP TRIGGER-Syntax entfernt, die so aussieht:
In Kapitel 9 werden wir nochmals auf die Überwachung von Datenbanken und Servern zurückkommen, dort unter Verwendung des SQL Server Service Brokers.
7.8
Zusammenfassung Dieses Kapitel beschäftigte sich mit T-SQL als Programmiersprache. Sie wissen nun, welche Elemente die Sprache für die Ablaufsteuerung enthält und wie Sie T-SQL-Variablen einsetzen. In einem weiteren Abschnitt haben Sie erfahren, wie durch den Einsatz der Cursor eine zeilenbasierte Verarbeitung von Abfrageergebnissen vorgenommen werden kann. Ihnen ist jetzt auch geläufig, wie gespeicherte Prozeduren und benutzerdefinierte Funktionen in T-SQL erzeugt und verwendet werden. Schließlich wurde im letzten Abschnitt dieses Kapitels noch erklärt, welche Möglichkeiten Trigger für die automatische Ausführung von Code bieten und wie Trigger zur Überwachung von Servern und Datenbanken verwendet werden können.
338
8 XML-Integration XML hat sich mittlerweile als offener und universeller Standard für den Datenaustausch etabliert. Ich möchte an dieser Stelle gar nicht groß die Vor- und Nachteile von XML erörtern. Sicherlich gibt es für den Einsatz von XML mehr oder weniger geeignete Anwendungsgebiete, eine entsprechende Untersuchung oder Bewertung vorzunehmen ist aber nicht Gegenstand dieses Buches. Stellen wir zunächst einfach nur fest, dass der SQL Server seit der Version 2000 die Möglichkeit bietet, XML-Daten zu verarbeiten. Diese Möglichkeiten wurden nun mit dem SQL Server 2005 nochmals ganz erheblich erweitert. Aus diesem Grund gehört die Thematik XML-Integration natürlich in ein Buch über die Datenbankentwicklung mit dem SQL Server 2005 unbedingt hinein. In diesem Kapitel werden Sie zunächst erfahren, wie Sie Abfrageergebnisse mit der FOR XML-Klausel auf einfache Art in ein XML-Format überführen können. Daran anschließend wird die OPENXML-Funktion vorgestellt, welche die Konvertierung von XMLDokumenten in ein Tabellenformat ermöglicht. Sie erfahren dann etwas über den in der Version SQL Server 2005 hinzugekommenen nativen XML-Datentyp und die Möglichkeiten, XML-Dokumente oder -Fragmente abzufragen und zu verändern.
8.1
Tabellen im XML-Format darstellen: FOR XML FOR XML ist eine Klausel, die einfach zu einer SELECT-Anweisung hinzugefügt werden kann. Allein durch die Angabe dieser Klausel veranlassen Sie die Konvertierung der Ergebnismenge aus dem Tabellen- in das XML-Format. Die Art und Weise dieser Konvertierung können Sie hierbei in vielfältiger Weise bestimmen. indem Sie die FOR XMLKlausel durch eine Reihe von Optionen ergänzen. Eine (vereinfachte) SELECTAnweisung unter Verwendung von FOR XML hat die folgende Struktur:
339
8 XML-Integration Die FOR XML-Klausel ist seit der Version SQL Server 2000 verfügbar. Mit der neuen Version SQL Server 2005 sind einige nützliche Optionen für die bereits existierenden FOR XML-Klauseln RAW, AUTO und EXPLICIT hinzugekommen. Herausragend ist aber vor allem die Erweiterung um die neue Klausel FOR XML PATH. Diese Klausel bietet eine einfache Möglichkeit, das zurückgelieferte XML-Dokument oder -Fragment in vielfältiger Art zu gestalten. In den Kapiteln 8.1.4 und 8.1.6 wird dies näher erläutert. Generell gibt es also vier verschiedene FOR XML-Klauseln, die natürlich auch die zurückgelieferten XML-Daten in unterschiedlichen Formaten erstellen und die ihrerseits wiederum durch weitere Optionen ergänzt werden können. Wie in der obigen Syntax zu erkennen ist, sind dies die Klauseln:
In den folgenden Abschnitten werden wir uns zunächst mit diesen Klauseln beschäftigen, bevor wir dann in Abschnitt 8.1.5 auf die zusätzlich zur Verfügung stehenden Optionen eingehen. Für unsere einführenden Beispiele verwenden wir dabei zunächst die Tabelle Sales.SalesOrderHeader sowie die Sicht Sales.vSalesPerson der Datenbank AdventureWorks. Die in Listing 8.1 gezeigte SELECT-Anweisung verknüpft die Sicht mit der Tabelle über einen INNER JOIN und gibt einige Zeilen zurück. Listing 8.1 Beispielanweisung zur Auswahl einiger Bestellungen
Die Abfrage soll eine Liste der Vertriebsmitarbeiter und der Bestellungen, die diese Mitarbeiter initiiert haben, zurückliefern. Die Sicht Sales.vSalesPerson wird verwendet, um die Vertriebsmitarbeiter abzufragen, die Tabelle Sales.SalesOrderHeader enthält eine Liste aller aufgenommenen Bestellungen mit einer Referenz auf den zuständigen Vertriebsmitarbeiter. Die Tabelle und die Sicht werden über die in beiden enthaltene Spalte SalesPersonId verknüpft.
340
8.1 Tabellen im XML-Format darstellen: FOR XML Interessant ist hier die Verwendung der Funktion in der Unterabfrage. generiert bei jedem Aufruf einen eindeutigen GUID. Durch die Kombination der in der SELECT-Anweisung angegebenen TOP 20-Klausel mit der Sortierung nach werden 20 zufällig ausgewählte Zeilen aus der Tabelle Sales.SalesOrderHeader zurückgeliefert. Probieren Sie dies ruhig einmal aus, indem Sie die Unterabfrage:
separat ausführen. Auf diese Weise schränken wir unsere Ergebnismenge auf nur 20 Bestellungen ein, die wie gesagt zufällig ausgewählt werden. Ohne diese Einschränkung würden jeweils einige tausend Zeilen zurückgeliefert werden, was die Darstellung innerhalb dieses Buches sehr erschweren würde. Das Ergebnis der Abfrage aus Listing 8.1 sieht ungefähr so aus, wie in Abbildung 8.1 zu sehen.
Abbildung 8.1 Zufällig ausgewählte Bestellungen (nicht alle 20 Zeilen dargestellt)
8.1.1
FOR XML RAW
Ergänzen Sie die SELECT-Anweisung aus Listing 8.1 einfach um die Klausel FOR XML RAW:
341
8 XML-Integration
Sie werden in etwa ein Ergebnis erhalten, das so aussieht, wie in Listing 8.2 gezeigt. Listing 8.2 Auszug aus dem Ergebnis von FOR XML RAW
Das erhaltene XML-Ergebnis verdeutlicht die einfachen Konvertierungsregeln von FOR XML RAW: Jede Zeile der Ergebnismenge wird in ein -Element konvertiert. Innerhalb einer werden die Spaltenwerte als Attribute dargestellt. Für Spalten mit NULL-Werten wird kein Attribut in die aufgenommen. Schauen Sie sich die XML-Rückgabe aus Fehler! Verweisquelle konnte nicht gefunden werden. noch einmal an. Für das erste -Element (Vertriebsmitarbeiter Alberts) fehlt das zweite Attribut Territory. Dieses Attribut erscheint wirklich nur in den
-Elementen, in denen auch ein Wert in der Territory-Spalte der Ergebnismenge existiert. Ist Territory NULL, so wird das Attribut also einfach nicht aufgenommen. Wir werden später sehen, wie man dieses Verhalten ändern kann.
8.1.2
FOR XML AUTO
Wir wollen nun FOR XML AUTO zu unserer SELECT-Anweisung aus Listing 8.1 hinzufügen: Listing 8.3 FOR XML AUTO
342
8.1 Tabellen im XML-Format darstellen: FOR XML
Das Ergebnis unterscheidet sich in einigen Merkmalen von dem von FOR XML RAW gelieferten, wie Listing 8.4 zeigt Listing 8.4 Ergebnis von FOR XML AUTO
Eine Untersuchung dieses Ergebnisses verdeutlicht die Arbeitsweise von FOR XML AUTO: Spalten der Ergebnismenge werden als Attribute dargestellt. Jede Zeile der Ergebnismenge wird in ein XML-Element konvertiert. Die Bezeichnung für dieses Element ist hier allerdings nicht einfach . Sie ergibt sich aus den in der Abfrage verwendeten Tabellen bzw. wie in unserem Beispiel dem verwendeten Aliasnamen. Die XML-Ausgabe bildet den JOIN anhand einer hierarchischen Darstellung ab. Beachten Sie hierbei bitte, dass das Abfrageergebnis in der richtigen Reihenfolge zurückgegeben werden muss, damit der Wechsel zu einer neuen Ebene auch nur dann erfolgt, wenn sich die Daten in der äußeren Tabelle der Verknüpfung ändern. Im oben dargestellten Beispiel wird dies durch die Klausel garantiert. NULL-Werte in Attributen werden wiederum weggelassen. Schauen Sie sich das -Element für das erste ausgegebene Element (Alberts) an. Auch hier fehlt wiederum die Angabe für Territory. Der entscheidende Vorteil von FOR XML AUTO gegenüber FOR XML RAW liegt sicherlich in der hierarchischen Darstellung, die automatisch aus dem JOIN abgeleitet wur-
343
8 XML-Integration de. Bei einer Abfrage mit lediglich einer Tabelle unterscheiden sich die durch XML RAW bzw. XML AUTO erzeugten Ausgaben lediglich in der Bezeichnung des Zeilenelementes voneinander, wie Tabelle 8.1 verdeutlicht. Tabelle 8.1 FOR XML RAW und FOR XML AUTO Abfrage
XML-Ergebnis
8.1.3
FOR XML EXPLICIT
FOR XML EXPLICIT bietet die größte Flexibilität, wenn es darum geht, Abfrageergebnisse in das XML-Format zu überführen. Während die hierarchische Struktur der XMLDaten bei FOR XML AUTO implizit anhand der verknüpften Tabellen ermittelt wird, eröffnet FOR XML EXPLICIT Ihnen die Möglichkeit, diese Struktur explizit anzugeben. Die Strukturierung erfolgt hierbei anhand der Sortierung der Ergebnismenge sowie durch die Auswertung von speziellen Spalten und Spalten-Aliasnamen. Bei der Verwendung von FOR XML EXPLICIT werden Sie in fast allen Fällen mehrere Abfragen konstruieren im Allgemeinen wird eine Abfrage für jede Hierarchieebene oder jedes Element benötigt und diese werden dann durch UNIONs miteinander verbinden. Sie müssen Ihren Abfragen zwei Spalten hinzufügen, die Informationen über die aktuelle und die übergeordnete Hierarchieebene enthalten. Die erste Spalte muss Tag heißen und einen Integer-Wert zurückliefern, der einfach als eine Art Handle auf die von dieser Abfrage zurückgegebenen Zeilen verwendet wird. Die zweite Spalte muss Parent benannt werden. Auch diese Spalte ist vom Typ INTEGER. Parent gibt die TagNummer des übergeordneten Abfrageergebnisses an. Auf diese Art und Weise wird gewissermaßen eine Art einfach verkettete Liste erzeugt, in der jedes untergeordnete Abfrageergebnis auf das jeweils übergeordnete verweist. Die verwendeten Spalten-Aliasnamen müssen die folgende generelle Struktur aufweisen:
Die einzelnen Bestandteile eines solchen Spalten-Aliasnamens erfüllen hierbei die folgende Funktion:
344
8.1 Tabellen im XML-Format darstellen: FOR XML ElementName Dies ist der Name des zu generierenden XML-Elementes. Für die Erzeugung eines -Elementes geben Sie also einfach Adresse an. TagNummer Die TagNummer enthält einen Integer-Wert, der dem Element zugewiesen wird. Dieser Wert wird zusammen mit den Werten der Spalten Tag und Parent für die Erzeugung der Hierarchie verwendet. AttributName Hier geben Sie den Namen des Attributes an, der für die Spalte im angegebenen ElementName verwendet wird. Die Angabe ist nicht in allen Fällen erforderlich, wie weiter unten noch deutlich werden wird. Direktive Durch die Angabe einer Direktive können Sie zusätzliche Informationen für die Konvertierung spezifizieren. Eine Direktive ist optional und kann unter anderem die folgenden Bezeichnungen enthalten: Element Bei Angabe dieser Direktive wird der Wert der Spalte nicht als Attribut, sondern als eigenständiges XML-Element ausgegeben. Der Name dieses XML-Elementes wird hierbei durch die unter AttributName angegebene Bezeichnung bestimmt. Wird kein AttributName angegeben, so wird der Spaltenwert als Elementtext dargestellt. Die folgende Tabelle veranschaulicht dies nochmals für einen Spaltenwert Blythe: Direktive
Beispielausgabe
Hide Diese Direktive bewirkt, dass die Spalte nicht in das XML-Ergebnis aufgenommen wird. Das kann nützlich sein, wenn nach einer Spalte sortiert werden muss, die nicht in der XML-Ausgabe erscheinen soll. Cdata Die CDATA-Direktive unterbindet die Inhaltskodierung der Spalte. Der Spaltenwerte wird in eine CDATA-Sektion eingeschlossen. Aber nun genug der Vorrede, lassen Sie uns mit einem einfachen Beispiel beginnen, um das gerade Gesagte zu verdeutlichen. Nehmen wir an, Sie möchten das Abfrageergebnis der in Listing 8.1 gezeigten SELECT-Anweisung, das in Abbildung 8.1 zu sehen ist, unter Verwendung von FOR XML EXPLICIT in die folgende XML-Darstellung konvertieren:
345
8 XML-Integration Listing 8.5 FOR XML EXPLICIT: Vertriebsbeauftragte und Bestellungen
Dies ist im Prinzip die Darstellung aus Listing 8.4, wie sie von FOR XML AUTO zurückgeliefert wurde. Einziger Unterschied besteht hier in der Darstellung der Vertriebsbeauftragten, die in Listing 8.5 auch dann erscheinen, wenn für sie keine Bestellungen gefunden wurden. Das XML-Ergebnis soll also zwei Hierarchieebenen enthalten: Eine für die Vertriebsmitarbeiter und eine zweite, welche die Bestellungen für diese Vertriebsmitarbeiter enthält. Listing 8.6 zeigt die hierfür konstruierte Abfrage. Listing 8.6 Beispiel für FOR XML EXPLICIT
346
8.1 Tabellen im XML-Format darstellen: FOR XML Die gesamte Abfrage besteht aus zwei separaten SELECT-Anweisungen. Die erste Anweisung gibt alle Vertriebsmitarbeiter für die Hierarchieebene 1 zurück. In der zweiten Anweisung werden dann für die Ebene 2 alle Bestellungen geholt (auch hier erfolgt wieder nur eine zufällige Auswahl aus den vorhandenen Bestellungen entsprechend Listing 8.1). Schauen Sie sich die Spalten Tag und Parent an, welche die Verkettung der Hierarchieebenen angeben. Ebenso interessant sind natürlich die in der ersten SELECTAnweisung angegebenen Aliasnamen für die Spalten. Hier wird festgelegt, wie die Spalten im XML-Ergebnis erscheinen sollen: Aliasname
XML-Darstellung
Durch die Hide-Direktive wird diese Spalte nicht in das XML-Ergebnis aufgenommen. Da aber nach der Spalte sortiert werden muss (siehe am Ende der SQL-Anweisung), muss eine entsprechende Spalte in der Ergebnismenge existieren.
Der Spaltenwert wird als Attribut mit der Bezeichnung Name in einem Element auf der Ebene 1 ausgegeben.
Der Spaltenwert wird als Attribut mit der Bezeichnung Territory in einem Element auf der Ebene 1 ausgegeben.
Der Spaltenwert wird als Attribut mit der Bezeichnung Number in einem Element auf der Ebene 2 ausgegeben.
Der Spaltenwert wird als Attribut mit der Bezeichnung Date in einem Element auf der Ebene 2 ausgegeben.
Abbildung 8.2 Die Abfrage aus Listing 8.6 ohne die Klausel FOR XML EXPLICIT
347
8 XML-Integration Schauen wir uns zunächst das Ergebnis der Abfrage ohne die FOR XML EXPLICITKlausel an. Betrachten Sie hierzu bitte Abbildung 8.2. Hier ist sehr schön zu sehen, wie die Spalten Tag und Parent wechseln und welche Daten letztlich in den einzelnen XML-Elementen landen werden. Nehmen Sie nun die Klausel FOR XML EXPLICIT zur Abfrage wieder hinzu, so ergibt sich das in Listing 8.5 gezeigte Ergebnis. Vergleichen Sie dieses XML-Ergebnis mit der Tabellendarstellung aus Abbildung 8.2, so dürfte deutlich werden, wie die Konvertierung vorgenommen wird. Zwei Dinge sollten Ihnen auffallen Auch bei FOR XML EXPLICIT erfolgt die XML-Ausgabe standardmäßig attributzentriert. Durch die Angabe der Direktive Elements kann dieses Verhalten aber sehr einfach geändert werden, und zwar für jede Spalte der Ergebnismenge. Ändern Sie zum Beispiel den Spalten-Alias in (vergessen Sie nicht, diese Änderung auch in der ORDER BY-Klausel vorzunehmen), so erhalten Sie das folgende Ergebnis:
NULL-Attribute werden nicht in die Ausgabe übernommen. Dies ist dasselbe Verhalten, das wir auch bei den Klauseln FOR XML RAW und FOR XML AUTO schon beobachtet haben. Im Moment stellt sich natürlich die Frage, was wir letztlich erreicht haben. Im Prinzip sieht unser XML-Ergebnis ja genauso aus, wie dies bereits unter Verwendung von FOR XML AUTO der Fall war, nur ist die Anwendung von FOR XML EXPLICIT wesentlich komplizierter. Für den hier konstruierten Fall ist diese Aussage sicherlich wahr. Nur ist es eben so, dass FOR XML EXPLICIT auch Darstellungen ermöglicht, die mit FOR XML AUTO nicht abzubilden sind. Allerdings muss man für die große Flexibilität von FOR XML EXPLICIT auch einen Preis zahlen nämlich die ebenso erhöhte Komplexität. Zwei weitere Beispiele sollen dies also die Aspekte Komplexität und Flexibilität verdeutlichen. Stellen Sie sich zunächst vor, dass Sie eine dritte Ebene zum XML-Ergebnis hinzufügen möchten, in der die einzelnen Positionen der Bestellung aufgeführt sind. In Listing 8.7 sehen Sie das gewünschte Ergebnis, Listing 8.8 zeigt, wie es geht. Listing 8.7 XML-Ausgabe mit Bestellpositionen
348
8.1 Tabellen im XML-Format darstellen: FOR XML
Listing 8.8 Abfrage für das Ergebnis aus Listing 8.7
349
8 XML-Integration Wir haben zur XML-Ausgabe einen Bereich [Item!...] hinzugefügt, dessen Daten durch eine eigene Abfrage (Abfrage für Ebene 3 in Listing 8.8) geholt werden. Diese Abfrage verknüpft die Tabellen Sales.SalesOrderHeader, Sales.SalesOrderDetail, Production.Product und Sales.vSalesPerson, um die Informationen über Bestellpositionen und auch das zur Position gehörende Produkt zu bekommen. Durch die Spalten Tag und Parent wird angegeben, dass die Ebene 3 der Ebene 2, welche die allgemeinen Bestelldaten enthält, untergeordnet ist. Schauen Sie sich nun bitte noch einmal das XML-Ergebnis aus Listing 8.7 an. In unserem letzten Beispiel wollen wir zwischen und eine noch separate Ebene einfügen, die dazu dient, alle Bestellungen mit den untergeordneten Positionen aufzunehmen. Die Ebene soll ein Attribut mit Namen Count enthalten, das die Anzahl der Bestellungen für den Vertriebsmitarbeiter angibt. Das Ergebnis soll also so aussehen, wie in Listing 8.9 gezeigt. Listing 8.9 Eine Ebene hinzufügen
Was wir benötigen, ist eine zusätzliche Ebene, die durch eine weitere SELECT-Anweisung erzeugt wird. Listing 8.10 zeigt den Quellcode der Abfrage. Listing 8.10 FOR XML EXPLICIT-Abfrage für das Ergebnis aus Listing 8.9
350
8.1 Tabellen im XML-Format darstellen: FOR XML
In der Ebene 2 wird über eine Unterabfrage die Anzahl der Bestellungen für die einzelnen Vertriebsmitarbeiter geholt. Diese Anzahl wird dann durch die Angabe des SpaltenAliasnamens in das Attribut Count des -Elementes eingetragen. Wie Sie bereits an unseren einfachen Beispielen sehen, können die erforderlichen FOR XML EXPLICIT-Konstrukte recht schnell ziemlich kompliziert werden. Für die Erzeugung von Strukturen, die noch mehr Elemente und eventuell auch mehr Ebenen enthalten, gilt dies natürlich umso mehr. Einerseits führt diese Komplexität natürlich dazu, dass die Abfragen alsbald einigermaßen unüberschaubar werden. Nicht zu vergessen ist hier natürlich auch die Ausführungsgeschwindigkeit, die ebenfalls stark negativ beeinträchtigt werden kann. Die über UNION zusammengeführten SELECT-Anweisungen werden ja separat ausgeführt. Hinzu kommt, dass die einzelnen SELECT-Anweisungen oftmals auch noch identische Verknüpfungen verwenden, letztlich also zumindest teilweise auch identische
351
8 XML-Integration (redundante) Daten zurückgeben eine Situation, welche die Ausführungsgeschwindigkeit natürlich nicht gerade beschleunigt. Wir werden im nächsten Abschnitt sehen, wie hier Abhilfe geschaffen werden kann.
8.1.4
FOR XML PATH
Die mit dem SQL Server 2005 hinzugekommene Klausel FOR XML PATH kann die recht komplexe Struktur von FOR XML EXPLICIT in den meisten Anwendungsfällen ersetzen. FOR XML PATH hat hierbei zwei herausragende Vorteile gegenüber FOR XML EXPLICIT: 1. Die Klausel ist wesentlich einfacher zu verstehen und zu benutzen als FOR XML EXPLICIT. 2. In vielen Fällen werden die durch FOR XML PATH verursachten Abfragekosten (normalerweise also die für die Abfrage benötigte Zeit) deutlich unter denen einer vergleichbaren FOR XML EXPLICIT-Abfrage liegen. Der Grund hierfür ist einfach der, dass die beim Einsatz von FOR XML EXLICIT benötigte Aufspaltung der Abfrage in mehrere SELECT-Anweisungen und die anschließende Zusammenführung der Teilergebnisse über UNIONs bei FOR XML PATH entfallen kann. Oftmals ist es möglich, bei FOR XML PATH mit nur einer SELECT-Anweisung auszukommen, wo bei FOR XML EXPLICIT mehrere Anweisungen erforderlich sind. Dadurch ist FOR XML PATH hier natürlich im Vorteil. Bei FOR XML PATH haben Sie die Möglichkeit, den Pfad zum XML-Element oder Attribut, in das der Wert der Spalte konvertiert werden soll, als Spalten-Alias anzugeben. Am besten lässt sich dies an einem ersten Beispiel erklären, das FOR XML PATH in seiner einfachsten Form verwendet. Nehmen Sie unsere Abfrage aus Listing 8.1 als Grundlage, und fügen Sie eine ORDER BY-Klausel sowie FOR XML PATH hinzu:
Das Ergebnis der Abfrage sehen Sie in Listing 8.11.
352
8.1 Tabellen im XML-Format darstellen: FOR XML Listing 8.11 Ergebnis der einfachen FOR XML PATH-Abfrage
Zu sehen sind die folgenden Konvertierungsregeln: Jede Zeile in der Ergebnistabelle wird in ein -Element konvertiert. Alle Spaltenwerte werden als Elementtexte dargestellt. Die Elementnamen werden durch den Spalten-Alias bestimmt. Elemente mit NULL-Werten werden wiederum ausgelassen (siehe Alberts, dieser hat kein -Element). Interessant wird FOR XML PATH allerdings erst durch die Möglichkeit, eine XMLHierarchie durch die Angabe von Spalten-Aliasnamen, die Pfade enthalten, erzeugen zu können. Listing 8.12 zeigt das Prinzip. Listing 8.12 FOR XML PATH mit Pfadangaben
Allein durch die geänderten Spalten-Aliasnamen wird die Ausgabe aus Listing 8.11 nun zu:
353
8 XML-Integration Listing 8.13 Explizite Pfadangabe
Erkennbar ist, dass die Hierarchie in der XML-Ausgabe so abgebildet wird, wie dies durch die Aliasnamen der Spalten angegeben wurde. Allerdings hat das XML-Format auch noch einige Nachteile. Die Darstellung jeder Zeile als ist sicherlich in den meisten Fällen unerwünscht. Außerdem wird für jede Zeile der Ergebnismenge eine neue erzeugt, was im vorliegenden Beispiel sicherlich ebenfalls nicht gewollt sein wird. Vielmehr wird es gewünscht sein, dass das -Element für jeden Vertriebsmitarbeiter nur einmal vorhanden sein soll. Unterhalb von sollen dann alle Bestellungen des Vertriebsmitarbeiters aufgeführt werden. Dies ist die Darstellung aus Listing 8.5. In Abschnitt 8.1.6 werden wir sehen, wie hier Abhilfe geschaffen werden kann. Sollen die Spaltenwerte als Attribute dargestellt werden, so kann man einfach dem Spaltenalias ein @ vor dem Elementnamen hinzufügen. Ändert man dies zum Beispiel in der Abfrage aus Listing 8.12 für die Orderhierarchie, erhält man die folgende Abfrage (Auszug):
Das Ergebnis aus Listing 8.13 ändert sich damit zu:
354
8.1 Tabellen im XML-Format darstellen: FOR XML FOR XML PATH bietet noch weitaus mehr Möglichkeiten, als in dieser kurzen Einführung behandelt wurden. Wie schon gesagt, werden wir in Kapitel 8.1.6 noch einmal auf die erweiterten Möglichkeiten zurückkommen. Vorerst ist es aber erforderlich, etwas über die weiterführenden Optionen, welche die einzelnen FOR XML-Klauseln bieten, zu erfahren. Dies ist Gegenstand des folgenden Abschnitts.
8.1.5
FOR XML-Optionen
In den einzelnen FOR XML-Klauseln kann die Angabe von zusätzlichen Optionen die Konvertierung entscheidend beeinflussen. Diese Optionen werden einfach an die FOR XML-Klausel angehängt und durch Komma voneinander getrennt. Zur Verfügung stehen die folgenden Möglichkeiten:
Durch die Angabe des Elementnamens direkt in Klammern nach der FOR XMLKlausel kann das standardmäßig einfach benannte Zeilenelement umbenannt werden. Beispiel: In Listing 8.12 wird die FOR XML PATH-Klausel so abgeändert:
Die XML-Ausgabe wird daraufhin wie folgt aussehen:
Diese Option bewirkt, dass alle von der Abfrage zurückgelieferten Binärdaten im Base64-codierten Format dargestellt werden. Wenn Sie Binärdaten im RAW- oder EXPLICIT-Modus abrufen, dann müssen Sie diese Option angeben, anderenfalls erhalten Sie eine Fehlermeldung.
Die TYPE-Option sorgt dafür, dass das Ergebnis einer FOR XML-Abfrage als XMLDatentyp zurückgegeben wird. Die Rückgabe einer FOR XML-Abfrage kann ansons-
355
8 XML-Integration ten in bestimmten Fällen einfach als Text interpretiert werden (dies war das Standardverhalten in SQL Server 2000). Meist wird dies nicht gewünscht sein; die TYPEOption hilft Ihnen in solchen Situationen. Wir werden die TYPE-Option im folgenden Abschnitt 8.1.6 noch benötigen. Dem XML-Datentypen ist dann das gesamte Kapitel 8.3 gewidmet.
Durch die Angabe dieser Option fügen Sie der XML-Ausgabe einen Wurzelknoten hinzu. Beispiel: Nehmen wir noch einmal Listing 8.12 und ändern die FOR XML PATHKlausel so ab:
Zur XML-Ausgabe wird der Wurzelknoten hinzugefügt:
Dem zurückgegebenen Dokument wird ein XML XDR-Schema als Inline-Schema vorangestellt. Diese Option ist sehr nützlich für die Entwicklung mit ADO.NET. Durch das vorangestellte Schema ist die Überführung in ein DataSet-Objekt mit wenig Aufwand möglich. ADO.NET-Entwicklung ist nicht Gegenstand dieses Buches, daher möchte ich hier auf [] verweisen, wo das Thema umfassend behandelt wird.
Durch diese Option wird an den Anfang der XML-Ausgabe ein W3C XML-InlineSchema (XSD) eingefügt.
Die Angabe des Namespace im Zusammengang mit XMLSCHEMA führt dazu, dass dieser als Wert für das targetNamespace-Attribut in das erzeugte XSD eingetragen wird.
Die Klauseln FOR XML RAW und FOR XML AUTO erzeugen standardmäßig eine sogenannte attributzentrierte Ausgabe, d. h., alle Spalten werden in XMLAttributen dargestellt. Über die ELEMENTS-Option können Sie dieses Verhalten ändern. Wird die Option angegeben, so erfolgt die Darstellung der Spaltenwerte in XML-Elementen, wobei der Spalten-Alias als Elementname verwendet wird.
356
8.1 Tabellen im XML-Format darstellen: FOR XML Beispiel:
Wir
ändern
die
FOR
XML-Klausel
in
Listing
8.3
ab
in:
In der XML-Ausgabe erscheinen die Spaltenwerte dadurch als eigenständige XMLElemente:
Die Option hat dieselbe Auswirkung wie ELEMENTS. Die Zusatzoption ABSENT weist nur noch einmal explizit darauf hin, dass Spalten, die NULL-Werte enthalten, nicht in die XML-Ausgabe aufgenommen werden. Wir haben allerdings schon gesehen, dass dies auch das Standardverhalten ist, ABSENT kann deshalb auch einfach weggelassen werden.
Die Zusatzoption XSINIL bewirkt die Aufnahme von Spalten mit NULL-Werten in das Ergebnis. Alle FOR XML-Anweisungen lassen solche Spalten standardmäßig einfach aus, wie wir in den Beispielen der vorangegangenen Kapitel gesehen haben. Beispiel: Nehmen Sie abermals die Abfrage aus Listing 8.3 als Grundlage, und ändern Sie die FOR XML-Klausel so:
Sie erhalten ein Ergebnis, das ungefähr so aussieht:
357
8 XML-Integration Schauen Sie sich das -Element für den Vertriebsmitarbeiter Jiang an. Dieses Element existiert nun, trotzdem der Wert der zugrunde liegenden Spalte NULL ist. Hier wird einfach ein spezielles Attribut xsi:nil hinzugefügt, dessen Wert auf true gesetzt wird. Zu beachten ist, dass nicht alle Optionen für alle Klauseln erlaubt sind. So ist zum Beispiel die Angabe der Option ELEMENTS für FOR XML EXPLICIT nicht sinnvoll, da ja hierfür eine Direktive in den Spalten-Aliasnamen existiert. Daher ist die ELEMENTS-Klausel für FOR XML EXPLICIT nicht zugelassen. Tabelle 8.2 enthält einen Überblick darüber, welche Optionen in welcher Klausel verwendet werden dürfen. Tabelle 8.2 Verfügbarkeit der Zusatzoptionen in den unterschiedlichen FOR XML-Klauseln:
8.1.6
Noch einmal FOR XML PATH
Mit den im vorangegangenen Abschnitt besprochenen Optionen ist nun alles Wesentliche zu FOR XML gesagt. Die nunmehr vorhandenen Kenntnisse wollen wir zur weiterführenden Betrachtung der Klausel FOR XML PATH die in Kapitel 8.1.4 bislang ja nur kurz erklärt wurde nutzen. Betrachten Sie bitte zunächst erneut die Abfrage aus Listing 8.10, deren Ergebnis Sie in Listing 8.9 sehen. Abbildung 8.3 zeigt nochmals einen größeren Ausschnitt aus dem erzeugten XML-Dokument.
358
8.1 Tabellen im XML-Format darstellen: FOR XML
Abbildung 8.3 Die Bestellinformationen im XML-Format
Wir wollen nun versuchen, dieses Ergebnis unter Verwendung von FOR XML PATH zu erhalten und anschließend überprüfen, ob dadurch die zwei wesentlichen Nachteile von FOR XML EXPLICIT behoben wurden, nämlich dass: die FOR XML PATH-Abfrage leichter zu verstehen ist und dass die FOR XML PATH-Abfrage weniger Kosten verursacht. Beginnen müssen wir natürlich mit der zugrunde liegenden Abfrage, welche die Daten für das in Abbildung 8.3 auszugsweise dargestellte XML-Dokument liefert. Abbildung 8.4 zeigt einen Ausschnitt aus der Ergebnismenge.
359
8 XML-Integration
Abbildung 8.4 Die Bestellübersicht in Tabellenform
In Tabelle 8.3 finden Sie eine Übersicht über die in der Ergebnismenge enthaltenen Spalten sowie die Quelltabellen und spalten, aus denen diese Ergebnisspalten hergeleitet werden. Die Tabelle zeigt außerdem, wie die XML-Darstellung aussehen soll. Tabelle 8.3 XML-Darstellung der Spalten aus Abbildung 8.4 Spaltenname
Tabelle
Spalte
XML-Darstellung
SalesPerson
Sales.vSalesPerson
LastName
Attribut Name im Element
OrdersCount
Sales.SalesOrderHeader
Aggregat. count(*) für alle Bestellungen eines Vertriebsmitarbeiters
Attribut Count im Element
OrderDate
Sales.SalesOrderHeader
OrderDate
Attribut Date im Element <Salesperson>
Product
Production.Product
Name
Attribut Product im Element
Quantity
Sales.SalesOrderDetail
OrderQty
Attribut Quantity im Element
360
8.1 Tabellen im XML-Format darstellen: FOR XML Spaltenname
Tabelle
Spalte
XML-Darstellung
UnitPrice
Sales.SalesOrderDetail
UnitPrice
Attribut UnitPrice im Element
Amount
Sales.SalesOrderDetail
OrderQty * UnitPrice
Attribut Amount im Element
Benötigt wird also eine Verknüpfung der Tabellen Sales.vSalesPerson, Sales.SalesOrderheader, Sales.SalesOrderDetail und Production.Product. Ein erster Versuch könnte zunächst einfach so aussehen, wie in Listing 8.14 gezeigt. Listing 8.14 Erster Versuch der FOR XML PATH-Abfrage
Die Abfrage beginnt mit einem Tabellenausdruck, der für jeden Vertriebsmitarbeiter die ID, den Namen und die Anzahl der vorhandenen Bestellungen ermittelt. Die anschließende SELECT-Anweisung verknüpft die benötigten Tabellen (und Sichten) und gibt die erforderlichen Spalten zurück. Die Spalten-Aliasnamen spiegeln hierbei direkt die in Tabelle 8.3 aufgeführte XML-Darstellung wider.
361
8 XML-Integration In der FOR XML PATH-Klausel wird über die Option angegeben, dass jedes erzeugte Element benannt werden soll. Außerdem wird durch bestimmt, dass das Wurzelelement heißt. Damit sollte unsere Abfrage nun das gewünschte Ergebnis liefern. Rufen diese Abfrage auf, so erhalten Sie allerdings ein Resultat, wie in Listing 8.15 gezeigt. Listing 8.15 Teilergebnis der Abfrage aus Listing 8.14
Dieses Ergebnis ist im Prinzip nicht verkehrt, es enthält korrekte Daten und bildet auch fast die gewünschte Hierarchie ab. Allerdings unterscheidet es sich doch in wesentlichen Aspekten von der Darstellung in Listing 8.9 und Abbildung 8.3. Gewünscht war die Ausgabe ja derart, dass unterhalb eines jeden Vertriebsmitarbeiters alle zugehörigen Bestellungen angeordnet werden sollen. Gleiches gilt für Bestellungen und die Bestellpositionen. Auch hier soll nicht für jede enthaltene Position ein separates -Element erzeugt werden. Erst beim Wechsel von Vertriebsmitarbeiter bzw. Bestellung in der Ergebnismenge soll jeweils ein neues Element oder eben erzeugt werden. Wie kann man nun so eine Darstellung unter Verwendung von FOR XML PATH erreichen? FOR XML PATH erlaubt hierfür die Schachtelung von XML-Ergebnissen. Es ist möglich, korrelierende Unterabfragen zu verwenden, die ihrerseits auch XML-Resultate zurückgeben. Eine jede Unterabfrage, die ein XML-Resultat zurückliefert, wird dann entsprechend in die XML-Hierarchie der Gesamtausgabe eingeordnet. Dies bedeutet, dass wir für unser Beispiel also drei ineinander verschachtelte Abfragen benötigen. Die innerste Abfrage wird die Bestellpositionen liefern und somit für die Darstellung der -Elemente sorgen. Diese Abfrage wird dann ihrerseits in eine äußere Abfrage eingebettet, welche die -Elemente erzeugt. Diese wiederum wird als Unterabfrage für die -Abfrage dienen.
362
8.1 Tabellen im XML-Format darstellen: FOR XML Beginnen wir mit der inneren Unterabfrage für die Bestellpositionen. Dies erledigt die in Listing 8.16 gezeigte FOR XML PATH-Abfrage, die hier zum Test nur die Positionen einer einzigen Bestellung zurückgibt. Listing 8.16 Innere Abfrage für die -Elemente
Diese Abfrage liefert ein Ergebnis, das in etwa so aussieht wie in Listing 8.17. Listing 8.17 Auszug aus dem Ergebnis von Listing 8.16
Das sieht also schon einmal gar nicht so schlecht aus. Im nächsten Schritt betten wir diese Abfrage nun in eine äußere Abfrage ein, die sowohl die -Elemente als auch das umgebende -Element erzeugen soll. Diese Abfrage ist in Listing 8.18, zunächst noch ohne die FOR XML PATH-Klausel, zu sehen. Auch diese Abfrage gibt zum Test nur die 20 Bestellungen eines einzigen Vertriebsmitarbeiters zurück. Listing 8.18 Äußere Abfrage für SalesOrderDetail
Schauen wir uns das Ergebnis der Abfrage an, so ist dies aber ein wenig überraschend (siehe Abbildung 8.5).
363
8 XML-Integration
Abbildung 8.5 Ergebnis der Abfrage aus Listing 8.18
Die -Elemente werden hier nicht als XML, sondern einfach als Text zurückgeliefert. Dies ist der Punkt, an dem die TYPE-Option für die innere Abfrage zur Erzeugung der -Elemente eingesetzt werden muss. Nur dann wird diese Abfrage auch als XML-Typ angesehen, was überhaupt erst die Erzeugung der entsprechenden Hierarchie im XML-Dokument anhand der korrelierenden Unterabfragen möglich macht. Die entsprechend korrigierte SELECT-Anweisung sehen Sie in Listing 8.19. Listing 8.19 Korrigierte Abfrage zur Erzeugung der - und -Elemente
Die innere Abfrage zur Erzeugung der -Elemente wurde hier durch die TYPEOption erweitert. Außerdem wurde zur äußeren Abfrage jetzt die FOR XML PATHKlausel hinzugefügt, für die ebenfalls Optionen angegeben wurden. Durch diese Optionen wird zum einen ein Wurzelelement erzeugt. Die einzelnen Ausgabezeilen werden durch die Option in separaten -Elementen dargestellt. Wir erhalten nun ein Ergebnis ähnlich dem in Listing 8.20.
364
8.1 Tabellen im XML-Format darstellen: FOR XML Listing 8.20 Ergebnis der korrigierten Abfrage aus Listing 8.19
Damit ist nun auch die Erstellung der - und -Hierarchie abgeschlossen. Es fehlt letztlich noch die äußere Abfrage zur Erstellung der - und -Elemente. Wir konstruieren diese Abfrage aus dem in Listing 8.14 enthaltenen Tabellenausdruck und nehmen gleichzeitig auch die Abfrage aus Listing 8.19 auf. Was herauskommt, ist schließlich die FOR XML PATH-Abfrage in ihrer endgültigen Form, die in Listing 8.21 zu sehen ist. Mit dieser Abfrage erhalten Sie nun also das Ergebnis aus Listing 8.9. Listing 8.21 Die endgültige FOR XML PATH-Abfrage zur Ermittlung der Bestellinformationen
365
8 XML-Integration Vielleicht fällt Ihnen auf, dass sich die Abfrage zur Erstellung der -Elemente geringfügig von der in Listing 8.19 unterscheidet. In der endgültigen Form wird hier kein Wurzelelement Orders angegeben. Stattdessen erhält die gesamte Unterabfrage den Alisanamen [Orders]. Nur dadurch ist es möglich, das Count-Attribut zur Hierarchie hinzuzufügen, ohne die gewünschte Hierarchie zu zerstören. Anderenfalls würden stets zwei -Elemente erzeugt werden, von denen eines lediglich das Count-Attribut enthält, während das andere das übergeordnete Element für die einzelnen -Einträge bildet. (Dies können Sie natürlich leicht überprüfen, indem Sie die innere Abfrage so gestalten wie in Listing 8.19.) Vergleich mit FOR XML EXPLICIT Abschließend wollen wir nun noch die Abfragen aus Listing 8.10 und Listing 8.21 hinsichtlich Lesbarkeit und Optimierung vergleichen. Was die Lesbarkeit betrifft, so ist dies sicherlich vor allem Ansichtssache. Ich persönlich favorisiere die Form aus Listing 8.21, weil sie kürzer und für mich einfacher zu verstehen ist. Es wird aber sicherlich auch genügend Leute geben, welche die Abfrage aus Listing 8.21 bevorzugen. Dies sei also jedem selbst überlassen, entscheiden Sie es für sich selbst
Bedenken Sie aber bitte, dass bei der Verwendung von FOR XML EXPLICIT für weitere Elemente oder Hierarchieebenen jeweils eigene SELECT-Anweisungen erstellt werden müssen. Eine entsprechende Anweisung kann dadurch sehr schnell zehn oder 15 SELECTs enthalten und sich im Ausdruck über mehrere Seiten erstrecken. Und was ist mit der Performance? Messen wir sie einmal. Lassen Sie sich sowohl für die Abfrage aus Listing 8.10 als auch für die Abfrage aus Listing 8.21 den geschätzten Ausführungsplan anzeigen (Menü Abfrage/Geschätzten Ausführungsplan anzeigen). Im Fenster unter der Abfrage wird der Ausführungsplan in grafischer Form angezeigt. Stören Sie sich bitte nicht an der Darstellung, wir werden später noch einmal auf Abfragepläne und ihre Interpretation zurückkommen. Wichtig ist jetzt erst einmal nur, dass der Knoten mit der Bezeichnung XML SELECT links oben in diesem Fenster über die für die gesamte Abfrage ermittelten Kosten Auskunft gibt. (Kosten sind in diesem Zusammenhang einfach als ein Synonym für Zeit zu verstehen.) Klicken Sie mit der rechten Maustaste auf den Knoten, und wählen Sie Eigenschaften (Abbildung 8.6).
Abbildung 8.6 Eigenschaften des XML SELECT-Knotens
366
8.2 XML-Daten in Tabellenform abbilden: OPENXML Im Eigenschaften-Fenster können Sie anschließend die geschätzten Unterstrukturkosten für diesen Knoten sehen, wobei alle untergeordneten Knoten in die Ermittlung einbezogen wurden. (Ein entsprechendes Informationsfenster erscheint auch dann, wenn Sie einfach nur mit dem Mauszeiger einen Moment auf dem Knoten verweilen.) Notieren Sie sich diese Kosten, und wiederholen Sie die Prozedur auch für die Abfrage aus Listing 8.21. Auf meiner Maschine ergibt sich das in Tabelle 8.4 gezeigte Verhalten, die FOR XML PATHAbfrage ist also geringfügig langsamer. Tabelle 8.4 EXPLCIT- und PATH-Abfragekosten für das Beispiel FOX XML EXPLICIT
FOR XML PATH
5,273
5,778
Wir sehen also, dass es schwierig sein kann, pauschale Aussagen über Lesbarkeit und Ausführungsverhalten, also Abfragekosten, zu treffen. Es muss hier wirklich von Fall zu Fall entschieden werden, ob die EXPLICIT- oder PATH-Klausel besser geeignet ist. Dies sollten Sie beim Entwickeln Ihrer FOR XML-Anweisungen bedenken. Für die Optimierung von Abfragen ist es generell eine gute Idee, unterschiedliche Varianten auszuprobieren und einfach zu messen, welche Variante sich besser verhält. Diese Aussage ist natürlich auch für Abfragen mit einer FOR XML-Klausel gültig
8.2
XML-Daten in Tabellenform abbilden: OPENXML Im vorangegangenen Abschnitt haben wir untersucht, welche Möglichkeiten es gibt, um Tabellendaten im XML-Format abzubilden. Die OPENXML-Funktion bildet hierzu gewissermaßen das Gegenstück. Mit ihrer Hilfe können XML-Dokumente1 in Tabellenform umgewandelt werden. Die generelle Syntax der OPENXML-Funktion sieht so aus:
Die drei Parameter der Funktion haben die folgende Bedeutung: docHandle Dieser Integer-Wert muss ein Handle auf ein XML-Dokument enthalten. Ein solches Handle wird von der gespeicherten Prozedur zurückgegeben und durch den Aufruf von wieder freigegeben. Wir werden in Kürze sehen, wie diese beiden Prozeduren verwendet werden. 1
Bitte beachten Sie, dass mit OPENXML wirklich nur gültige XML-Dokumente verarbeitet werden können. Für FOR XML gilt diese Einschränkung nicht, hier können auch XML-Fragmente zurückgegeben werden. Solche Fragmente können Sie mit OPENXML nur bearbeiten, wenn Sie diese zuvor in ein gültiges XML-Dokument einbetten.
367
8 XML-Integration xpath Der XPath-Ausdruck enthält eine Pfadangabe zum Knoten innerhalb des durch spezifizierten XML-Dokuments. Auch dies wird sogleich weiter unten an einem Beispiel deutlich werden. Allerdings können wir im Rahmen dieses Buches weder auf XPath noch auf die Weiterentwicklung XQuery (die wir weiter unten noch benötigen) detailliert eingehen. Über diese Thematiken gibt es eigene Bücher, Sie können die entsprechenden Spezifikationen aber auch auf der Website des W3C, unter , nachlesen. flags Über den -Parameter können Sie Einfluss darauf nehmen, auf welche Weise die XML-Daten in das (Tabellen-)Ergebnis konvertiert werden sollen. Der Wert 1 steht hier für eine attributzentrierte Abbildung, 2 bedeutet elementzentriert. Der Parameter ist optional, wird er nicht angegeben, so wird der Standardwert 0 angenommen, was ebenfalls eine attributzentrierte Abbildung bewirkt. Wenn Ihnen das im Moment zu kompliziert klingt, vergessen Sie es zunächst einfach, und kommen Sie noch einmal hierher zurück, sobald wir unser erstes Beispiel besprochen haben. WITH-Klausel Über die WITH-Klausel geben Sie die Spalteninformationen für Ihre Ergebnistabelle an. Sie haben hierfür zwei Möglichkeiten. Zum einen können Sie einfach Spaltendefinitionen durch Komma getrennt aneinanderreihen. Dies funktioniert genau so, wie Sie es bereits von der Tabellendefinition über CREATE TABLE kennen. Die WITHKlausel erlaubt Ihnen allerdings auch die Angabe eines Tabellennamens. Im zweiten Fall werden dann die Spalten dieser Tabelle als Muster für das Abfrageergebnis hergenommen. Aber nun genug der Vorrede, schauen wir uns ein erstes Beispiel an. Da wir für viele Codebeispiele im Rest dieses Kapitels die Abfrage aus Listing 8.21 verwenden werden, wollen wir aber zuerst eine Sicht für diese Abfrage erzeugen: Listing 8.22 Die Sicht vXmlOrderInfo
368
8.2 XML-Daten in Tabellenform abbilden: OPENXML
Das Ergebnis dieser Sicht ist eine einzige Zeile und auch nur eine Spalte vom Typ Text. Rufen Sie sich bitte auch noch einmal den Aufbau des erzeugten XML-Dokuments in Erinnerung, oder schauen Sie sich dieses noch einmal in Listing 8.9 bzw. Abbildung 8.3 an. Den von der Sicht zurückgegebenen Text können wir einfach in eine Variable vom Typ eintragen. Anschließend holen wir uns dann ein Handle für das in dieser Variablen enthaltene XML-Dokument. Mit diesem Handle können wir schließlich OPENXML aufrufen, bevor wir es am Ende wieder freigeben. Listing 8.23 zeigt den Code. Listing 8.23 Eine Abfrage mit OPENXML
In Abbildung 8.7 sehen Sie die ersten Zeilen der Ergebnismenge.
369
8 XML-Integration
Abbildung 8.7 Liste der von OPENXML zurückgegebenen Bestellpositionen
Die Pfadangabe enthält die Navigation zum -Element, durch die Angabe von 1 für den dritten Parameter werden die in diesem Element enthaltenen Attribute zurückgegeben. Schließlich sorgt dann noch die enthaltene WITH-Klausel dafür, dass die Attribute Product, Quantity, UnitPrice und Amount in die Ergebnistabelle aufgenommen werden und dort vom angegebenen Datentyp sind. Da es das -Element in der Hierarchie nur einmal gibt, führt diese Abfrage übrigens zum selben Ergebnis:
Der -Parameter wurde hier ebenfalls weggelassen, da 0 für die attributzentrierte Ausgabe ja der Standardwert ist. Weitere Beispiele Ersetzen Sie bitte die OPENXML-Abfrage aus Listing 8.23 durch die in diesem Abschnitt aufgeführten SELECT-Anweisungen, um die Beispiele nachzuvollziehen. Eine Liste der Namen aller im Dokument enthaltenen Vertriebsmitarbeiter gibt Ihnen zum Beispiel diese Abfrage zurück:
Versuchen Sie die Abfrage mit elementzentrierter Abbildung, indem Sie den Parameter 2 an dritter Stelle übergeben, wird die obige Anweisung nur Zeilen mit NULL-Werten zurückgeben. Dies ist zu erwarten gewesen, da innerhalb des -Elementes ja nur Attribute existieren.
370
8.2 XML-Daten in Tabellenform abbilden: OPENXML Sie können die Rückgabe von OPENXML wie eine Tabelle verwenden. So ist zum Beispiel auch die Benutzung von Aggregatfunktionen erlaubt, wie die folgende Anweisung zeigt, die aus dem -Element alle Beträge nach Kalenderjahr addiert:
Die Anweisung zeigt auch, dass es nicht erforderlich ist, alle Attribute eines Elements in der Ergebnismenge entgegenzunehmen. Im vorliegenden Beispiel werden nur die Attribute Date und Total verarbeitet, das Attribut Number wird übergangen. Das Ergebnis einer SELECT-Anweisung, welche die OPENXML-Funktion verwendet, kann natürlich auch als Eingabemenge für eine INSERT-Anweisung dienen, also etwa so:
Auf diese Weise können Sie direkt XML-Daten in Ihre Tabellen importieren. Es ist auch möglich, Spalten in der Ergebnismenge explizit mit einem Attribut zu verknüpfen. Schauen Sie sich das folgende Beispiel einmal an:
Hier werden die Spalten Number und Total in der Ergebnismenge aus den gleichnamigen Attributen des -Elementes gefüllt. Die erste Spalte (SalesPerson) hingegen wird durch die Angabe von mit dem Name-Attribut des -Elements verbunden. Einen Ausschnitt aus dem Ergebnis dieser Abfrage sehen Sie in Abbildung 8.8.
Abbildung 8.8 Explizite Spaltenverknüpfung
371
8 XML-Integration
8.3
Der XML-Datentyp SQL Server 2005 führt für XML-Daten einen neuen Datentyp ein, der nun erstmals auch die native Speicherung und Verarbeitung von XML-Daten ermöglicht. In der Vorgängerversion, SQL Server 2000, konnten zwar auch bereits XML-Daten gespeichert und bearbeitet werden, dort wurden diese XML-Daten jedoch stets als Text abgebildet. Sie können den neuen XML-Datentyp nun genauso verwenden wie alle andern T-SQL-Datentypen auch. So ist es zum Beispiel möglich, Tabellenspalten von Typ XML zu erstellen oder auch XML-Variablen zu deklarieren. In XML-Daten können Sie mithilfe der Abfragesprache XQuery Daten suchen und auch über eine spezielle Erweiterung von XQuery modifizieren. Um solche XQuery-Abfragen zu beschleunigen, ist es sogar möglich, Indizes auf Tabellenspalten zu erzeugen, die XML-Daten enthalten. Solche Indizes können sehr sinnvoll sein, da XML-Spalten bis zu 2 GB Daten enthalten dürfen. Wir haben den XML-Datentyp in den vorangegangenen Kapiteln übrigens bereits verwendet, ohne dies dort zu erwähnen. So gibt zum Beispiel die FOR XML-Abfrage aus Listing 8.21 ihr Ergebnis nicht nur im XML-Format, sondern tatsächlich als XML-Typ zurück. An einigen Stellen haben wir bei FOR XML-Abfragen über die TYPE-Option auch explizit eine Konvertierung des Abfrageergebnisses in den XML-Datentyp veranlasst. Um eine Tabellenspalte vom Typ XML zu erstellen, können Sie diese Spalte ganz genau so deklarieren, wie Sie das von den bekannten Spaltentypen bereits kennen, also zum Beispiel so: Listing 8.24 Die Testtabelle OrderInfo
Die zweite Spalte Orders ist vom Typ XML, darf keine NULL-Werte enthalten und hat den Standardwert '' Das Einfügen von Zeilen erfolg ebenfalls wie gewohnt:
Die erste INSERT-Anweisung gibt lediglich einen Wert für die Spalte SalesPerson an, hier wird für die Orders-Spalte also der Standardwert eingetragen. In der zweiten INSERT-Anweisung wird dann explizit ein XML-Dokument für die Orders-Spalte angegeben. Ist Ihnen dabei etwas aufgefallen? Nun, in der VALUES-Auflistung der INSERTAnweisung ist der Datentyp der zweiten Spalte zunächst ebenfalls VARCHAR. Die Konvertierung in den Typ XML erfolgt hier implizit dadurch, dass dieser Text in die XMLSpalte Orders eingetragen wird.
372
8.3 Der XML-Datentyp Kontrollieren wir nun einmal, was die Tabelle OrderInfo für Daten enthält:
Das Ergebnis sehen Sie hier:
Wie wir es erwartet haben, enthält die Spalte Orders also XML-Daten. Beim Klick auf die Spaltendaten wird der XML-Editor geöffnet, der das entsprechende XML-Dokument komplett anzeigt. Wie schon weiter oben gesagt, können Sie auch Variablen vom Typ XML deklarieren und in diese Variablen XML-Werte eintragen:
Bitte beachten Sie die zweite SET-Anweisung, die ebenfalls wieder eine implizite Typkonvertierung bewirkt. Die Sicht vXmlOrderInfo gibt genauso keine XML-, sondern Textdaten zurück. Dieser Text wird durch die SET-Anweisung implizit in den XML-Typ umgewandelt. Da wir die Sicht im Verlaufe dieses Kapitels noch mehrfach verwenden werden, soll diese jetzt aber so modifiziert werden, dass sie echtes XML zurückliefert. Ändern Sie hierzu Listing 8.22 so ab, dass zur FOR XML PATH-Klausel die TYPE-Option hinzugefügt wird, also so: Listing 8.25 Die geänderte Sicht vXmlOrderInfo
Der komplette Code wird hier nicht erneut aufgelistet. Die drei Punkte stehen für die SELECT-Anweisung, die sich ja gegenüber Listing 8.22 nicht geändert hat. Bevor wir nun mit diesem Kapitel fortfahren, soll unsere Tabelle OrderInfo zunächst noch mit Daten gefüllt werden. Hierfür verwenden wir die Funktion OPENXML und die geänderte Sicht vXmlOrderInfo. Hierbei nutzen wir eine Fähigkeit von OPENXML aus, nämlich die, dass diese Funktion auch Spalten vom Typ XML zurückgeben kann. Sie können
373
8 XML-Integration eine solche Spaltendeklaration, wie Sie vielleicht bereits vermuten, in der WITH-Klausel angeben. Das folgende T-SQL-Skript trägt die von der Sicht vXmlOrderInfo zurückgelieferten Zeilen in die Tabelle OrderInfo ein:
Beachten Sie bitte, dass die Prozedur an zweiter Stelle auch einen Parameter vom Typ XML verarbeiten kann. Unsere bisherigen OPENXMLBeispiele hatten hier zunächst nur den Datentyp NVARCHAR(MAX) verwendet. Die Funktion OPENXML wird hier durch den dritten Parameter (2) veranlasst, eine elementzentrierte Ausgabe zu erzeugen, anderenfalls wäre es natürlich nicht möglich, ein XMLDokument zurückzubekommen. Die obige INSERT-Anweisung trägt also für jeden Vertriebsmitarbeiter eine Zeile in unsere OrderInfo-Tabelle ein. Überprüfen wir dies einmal durch:
so erhalten wir das in Abbildung 8.9 gezeigte Ergebnis.
Abbildung 8.9 Inhalt der Tabelle OrderInfo
374
8.3 Der XML-Datentyp
8.3.1
Ungetypte XML-Daten
In der Spalte Orders der Tabelle OrderInfo können nun also Daten vom Typ XML abgelegt werden. Beim Versuch, Werte in die Spalte einzutragen, findet hierbei stets eine Überprüfung dahingehend statt, ob es sich bei diesen Werten auch um gültiges XML handelt. Ist dies nicht der Fall, so wird die Änderung der Spaltenwerte abgelehnt und eine Fehlermeldung erzeugt. Somit ist also sichergestellt, dass die Spalte nur gültige XML-Daten enthalten kann. Wenn in diesem Zusammenhang bislang von XML-Daten gesprochen wurde, so sind genau genommen stets XML-Fragmente gemeint gewesen. Eine XML-Spalte oder auch eine XML-Variable kann also in der Form, wie wir diese bisher verwendet haben, stets ein gültiges XML-Fragment aufnehmen. Schauen Sie sich die folgenden INSERT-Anweisungen an: Listing 8.26 Einfügen einiger Zeilen in die Orders-Tabelle
Die ersten drei Anweisungen fügen gültiges XML in eine neue Zeile ein, während die letzte INSERT-Anweisung ein ungültiges Fragment angibt und daher zu einer solchen Fehlermeldung führt:
Zunächst ist also einmal gesichert, dass unsere Tabelle nur gültige XML-Fragmente enthalten kann. Aber was ist mit den INSERT-Anweisungen 1, 2 und 3 aus Listing 8.26? Diese fügen zwar jeweils ein gültiges XML-Fragment in die Spalte Orders ein, mit dem -Dokument, für das wir die Spalte konzipiert haben, haben diese Fragmente allerdings überhaupt nichts gemein. Unsere Spalte Orders ist bisher eine sogenannte ungetypte XML-Spalte. Eine solche Spalte kann ein beliebiges XML-Fragment aufnehmen, es findet lediglich eine Prüfung auf die allgemeine Gültigkeit dieses Fragments statt. Eine Schemavalidierung, um sicherzustellen, dass die Spalte tatsächlich nur -Dokumente enthalten kann, wird nicht vorgenommen. Wie so etwas funktioniert, erfahren Sie im nächsten Abschnitt.
375
8 XML-Integration
8.3.2
Getypte XML-Daten
Für die Verwendung von getypten XML-Daten müssen Sie zunächst ein entsprechendes XML-Schema in der Datenbank erzeugen. So ein Schema enthält letztlich den Namensraum sowie die Definition der Anordnung von Elementen und Attributen für XMLDatentypen. Eine Schemadefinition erstellen Sie durch das Kommando CREATE XML SCHEMA COLLECTION. Für unser -Dokument sieht die Erstellung einer solchen Schemadefinition dann so aus:
Falls Sie Visual Studio 2005 besitzen, müssen Sie die Schemadefinition übrigens nicht von Hand eintippen. Der Kasten Erstellen eines XML-Schemas mit Visual Studio 2005 erklärt Ihnen, wie es geht. Andere XML-Editoren bieten hier ganz ähnliche Möglichkeiten. Haben Sie das Schema erstellt, können Sie es sich im Management Studio auch ansehen. Hierzu öffnen Sie den Zweig Programmierbarkeit/Typen/XML-Schemaauflistungen (Abbildung 8.10).
376
8.3 Der XML-Datentyp
Abbildung 8.10 Das erzeugte XML-Schema
Alternativ ist es auch möglich, die in einer Datenbank enthaltenen Schemadefinitionen über die Katalogsicht sys.xml_schema_collections abzufragen:
Was nun noch fehlt, ist die Zuordnung des erzeugten Schemas zur Orders-Spalte der OrderInfo-Tabelle. Die Syntax für die Deklaration einer XML-Spalte oder von Variablen mit Bezug auf ein existierendes Schema ist:
Das Schema wird also einfach in Klammern hinter dem Namen des Spaltenbezeichners bzw. der Variablen angegeben. Sie können dem Namen des Schemas alternativ den Bezeichner DOCUMENT voranstellen, wenn Sie nur gültige XML-Dokumente als Inhalt zulassen möchten. Anderenfalls sind auch XML-Fragmente erlaubt. Erstellen eines XML-Schemas mit Visual Studio 2005 Falls Sie ein Abfrageergebnis im XML-Format vorliegen haben, können Sie mit Visual Studio hieraus leicht ein XML-Schema erstellen. Öffnen Sie hierzu zunächst die XMLDaten im Editor, indem Sie einfach im Abfrageergebnis auf eine Spalte klicken. Nehmen Sie zum Beispiel das Ergebnis der Abfrage
und klicken Sie auf eine beliebige Zeile in der Spalte Orders. Es öffnet sich das zugehörige Orders-Dokument im XML-Editor. Speichern Sie dieses Dokument als XML-Datei. Öffnen Sie diese Datei anschließend mit Visual Studio, und klicken Sie im Menü auf XML/Schema erstellen:
377
8 XML-Integration
Es wird eine XSD-Datei erstellt und im Editor geöffnet. Diese Datei können Sie nun bearbeiten. Hierbei haben Sie die Wahl, ob Sie das XSD-Dokument direkt verändern oder dies über eine komfortable grafische Oberfläche erledigen. Zum Öffnen des grafischen Editors klicken Sie im Menü auf Ansicht/Designer:
Im Designer haben Sie dann die Möglichkeit, das Schema anzupassen:
Sind Sie mit der Bearbeitung fertig, speichern Sie das XSD-Schema oder kopieren es in das Abfragefenster des SQL Server Management Studios. In der Spalte Orders werden nur XML-Dokumente gespeichert, wir können die Spaltendeklaration also so angeben:
Lassen Sie uns nun noch unsere Tabelle entsprechend anpassen. Dazu verwenden wir ALTER TABLE wie folgt:
Wahrscheinlich werden Sie eine Fehlermeldung ähnlich dieser erhalten:
378
8.3 Der XML-Datentyp Diese Meldung besagt, dass die Spalte nicht geändert werden darf, da noch eine Einschränkung auf sie verweist. Dies ist die DEFAULT-Einschränkung aus unserer ursprünglichen Deklaration der Tabelle. Löschen Sie die Einschränkung einfach über
wobei Sie den Namen der Einschränkung bitte entsprechend Ihrer Fehlermeldung anpassen. Anschließend können Sie die obige ALTER TABLE-Anweisung zum Ändern der Orders-Spalte nochmals ausführen. Erhalten Sie nun immer noch eine Fehlermeldung, so liegt dies mit Sicherheit daran, dass die Spalte Orders noch Daten enthält, die nicht dem Schema OrdersSchema entsprechen. Dies wären die Zeilen, die über die in Listing 8.26 angegebenen INSERT-Anweisungen eingefügt wurden. Löschen Sie bitte die entsprechenden Zeilen, und versuchen Sie es erneut diesmal sollte dann alles funktionieren. Nachdem die Orders-Spalte nun in eine typsichere XML-Spalte umgewandelt wurde, können Sie einmal versuchen, die INSERT-Anweisungen aus Listing 8.26 auszuführen. Sie werden feststellen, dass dies nicht funktioniert, und entsprechende Fehlermeldungen erhalten. Wir haben jetzt also XML-Dokumente in unserer OrderInfo-Tabelle abgelegt und auch dafür gesorgt, dass diese Dokumente einem bestimmten XML-Schema entsprechen müssen. Die nächsten Abschnitte beschäftigen sich nun mit der Thematik, wie man Daten aus diesen Dokumenten in Abfragen verwenden kann und welche Möglichkeiten T-SQL für die Änderung von XML-Dokumenten bereithält. Der XML-Datentyp unterstützt hierfür eine Reihe von Funktionen, die wir uns nun einmal genauer ansehen werden.
8.3.3
query
Mit der query-Funktion können Sie XML-Daten durchsuchen. XQuery ist eine Weiterentwicklung von XPath, das wir ja in Grundzügen bereits bei der Besprechung der Funktion OPENXML kennengelernt haben. So wie SQL die Abfragesprache für relationale Datenbanksysteme ist, ist XQuery die Sprache zum Suchen in XML-Daten. XQuery ist also gewissermaßen das SQL für XML. Weiter oben wurde bereits erwähnt, dass eine detaillierte Beschreibung von XQuery nicht Gegenstand dieses Buches sein kann. Hierfür gibt es eigene Werke, siehe zum Beispiel [LEHN04]. Einige Grundlagen wird dieses Kapitel aber selbstverständlich vermitteln. Die query-Sytax ist zunächst recht simpel:
Die query-Funktion wird also auf einer Variablen oder einer Spalte vom Typ XML aufgerufen, wobei ihr ein XQuery-Ausdruck als Parameter übergeben wird. Dabei gibt die Funktion stets Daten im XML-Format zurück. Betrachten Sie bitte das folgende Skript:
379
8 XML-Integration Hier wird eine Variable @xmlDoc deklariert und dieser der Wert der ersten OrdersSpalte zugewiesen. Wir können nun mit XQuery-Abfragen starten, von denen einige Möglichkeiten in Tabelle 8.5 aufgeführt sind. Tabelle 8.5 Beispiele für XQuery-Ausdrücke Beispiel
Erklärung
1
Alle -Elemente
2
Alle -Elemente
3
Alle -Elemente des ersten Elementes
4
Alle -Elemente mit mehr als fünf Stück
5
Alle -Elemente des ersten Elementes mit mehr als zehn Stück
6
Alle -Elemente aus 2003
So können Sie also zum Beispiel mit dem XQuery-Ausdruck 4 alle Bestellpositionen ermitteln, die mehr als fünf Stück enthalten:
Zur Steuerung des Ablaufs von Abfragen erlaubt XQuery auch sogenannte FLOWRAnweisungen. Dies sind die Anweisungen for, let, order by, where und return, von denen in SQL Server 2005 allerdings nur for, where und return unterstützt werden. Die folgende Anweisung gibt alle -Elemente zurück, bei denen das Attribut Total größer als 10000 ist:
Mithilfe der Ablaufsteuerung können Sie die Ausgabe auch in ein anderes XML-Format überführen:
Hier werden auch wieder alle -Elemente gesucht, deren Total-Attribut größer als 10000 ist. Allerdings werden diese Elemente nicht einfach zurückgegeben, sondern in das folgende XML-Format überführt:
380
8.3 Der XML-Datentyp
Zum Abschluss der Einführung in XQuery sollen nun noch einige Beispielabfragen auf unserer Sicht vXmlOrderInfo (siehe Listing 8.25) ausgeführt werden. Die erste Abfrage gibt einfach alle -Elemente zurück:
Diese Abfrage gibt nur das -Element zurück, bei dem das Attribut Number gleich SO51739 ist:
Hier werden alle -Elemente geholt, die zu einem Vertriebsmitarbeiter gehören, der mehr als 450 Bestellungen aufgenommen hat () und die genau zehnmal in der Bestellung enthalten sind ():
Das nächste Beispiel gibt alle -Elemente zurück, die mehr als 70 Elemente enthalten. Die eingebaute Funktion erledigt das Zählen:
Diese Abfrage gibt von den Bestellungen, die mehr als 70 Positionen enthalten, jeweils die erste Position zurück:
SQL Server 2005 enthält zwei nützliche XQuery-Erweiterungen, die es Ihnen auch erlauben, Tabellenspalten in das XML-Ergebnis einzubinden sowie in T-SQL-Stapeln deklarierte Variablen in XQuery-Abfragen zu verwenden. Hierfür existieren die beiden XQueryFunktionen sql:column, bzw. sql:variable, die Sie zum Beispiel so einsetzen können:
381
8 XML-Integration Die obige Abfrage gibt alle Bestellungen zurück, wobei der Wert der Spalte SalesPerson aus der Tabelle OrderInfo als Attribut SalesPerson in das erzeugte -Element eingefügt wird. Dies erledigt der Ausdruck über die -Funktion. In jedes -Element werden dann nur diejenigen Elemente aufgenommen, deren Bestellmenge genau zehn ist. Diese Filterung wird durch den Vergleich des Quantity-Attributs mit der Variable vorgenommen, deren Wert durch die -Funktion zurückgegeben wird. ist natürlich besonders nützlich für den Einsatz in gespeicherten Prozeduren, die Parameter entgegennehmen. Über die Funktion können Sie diese Parameter dann auch in XQueryAbfragen verwenden. Einen Auszug aus dem Ergebnis der Abfrage sehen Sie hier:
Damit soll die Einführung in XQuery zunächst abgeschlossen werden. Wir werden XQuery-Ausdrücke allerdings im Verlaufe dieses Kapitels noch benötigen und dann jeweils darauf zurückkommen.
8.3.4
exist
Die exist-Funktion prüft auf die Existenz eines Knotens, ganz ähnlich wie die T-SQLFunktion exists auf die Existenz einer Zeile testet. Existiert der Knoten, gibt die Funktion den Wert 1 zurück, anderenfalls 0. Damit ist diese Funktion für die WHERE-Klauseln prädestiniert. Auch die exist-Funktion erwartet einen XQuery-Ausdruck als Parameter:
Mit diesen Informationen können wir zum Beispiel sofort schreiben:
Was in diesem Fall äquivalent ist zu:
Die folgende Abfrage gibt aus unserer OrderInfo-Tabelle die Namen aller Vertriebsmitarbeiter zurück, die mehr als 300 Bestellungen aufgenommen haben:
exist ist vor allem nützlich für Datenänderungsanweisungen, also DELETE und UPDATE. Die folgende Abfrage löscht aus der Tabelle OrderInfo alle Zeilen, für die wenigstens eine Bestellung existiert, die vor dem 01.01.2004 liegt:
382
8.3 Der XML-Datentyp
8.3.5
value
Die value Funktion ermöglicht Ihnen die Konvertierung von XML-Daten in einen SQL Server-Datentyp für die Ausgabe. Mit der value-Funktion können Sie also z. B. Attribute aus Ihren XML-Dokumenten als SQL Server-eigene Datentypen in der Ergebnismenge einer Abfrage darstellen. Die Funktion verarbeitet zwei Parameter:
Der erste Parameter gibt wiederum einen XQuery-Ausdruck an, der hier allerdings nur einen einzigen Wert, ein sogenanntes Singleton, zurückgeben darf. Dies kann z. B. ein bestimmtes Attribut oder der Text eines Elements sein. Der zweite Parameter spezifiziert den SQL Server-Datentypen, in dem die Darstellung erfolgen soll. Die folgende Anweisung ruft aus dem -Element das Attribut Count ab und konvertiert es in einen INTEGER-Wert:
Beachten Sie bitte, dass der XQuery-Ausdruck nur einen einzigen Skalarwert zurückliefern darf. Im obigen Beispiel ist dies der Fall, da jede Zeile der OrderInfo-Tabelle nur ein einziges -Element mit einem Count-Attribut enthält. Gibt es mehrere Elemente, so muss im XQuery-Ausdruck explizit angegeben werden, welcher Knoten angefragt werden soll, so wie in der folgenden Anweisung:
Hier wird vom XQuery-Ausdruck nur das Number-Attribut des ersten Elements zurückgeliefert und dieses dann als VARCHAR(30) ausgegeben. Das Ergebnis ist eine Tabelle, die jeweils nur die erste im XML-Dokument enthaltene Bestellnummer darstellt. Schauen Sie sich bitte einmal die folgende Abfrage für die Sicht vXmlOrderInfo an:
Die Anweisung gibt nur eine einzige Zeile zurück, und zwar den Totalwert des ersten -Elements. Unsere Sicht liefert ein XML-Dokument, wird hierauf die valueFunktion angewendet, so kann aus diesem Dokument auch nur ein Wert ausgelesen werden. value erlaubt übrigens keine Konvertierung in den XML-Datentyp, so etwas funktioniert also nicht:
was nicht weiter schlimm ist, denn dafür haben wir ja die query-Funktion.
383
8 XML-Integration Was Sie sich also wirklich einprägen sollten, ist, dass die Funktion nur einmal je Ausgabezeile aufgerufen wird und dann je Aufruf auch nur einen Wert zurückliefert. value wird also nicht etwa automatisch für alle gleichrangigen Elemente eines Knotens ausgeführt, sondern tatsächlich nur für das übergebene Singleton-Element. Wir werden im folgenden Abschnitt sehen, wie man mehrere Knoten in die Tabellenform überführen kann.
8.3.6
nodes
Die node-Funktion ist nützlich zum Zerlegen von XML-Dokumenten. Sie erwartet einen XQuery-Ausdruck als einzigen Parameter. Der Rückgabewert von node ist dann eine Ergebnismenge, wobei jede zurückgelieferte Zeile genau diejenigen Strukturknoten enthält, die durch den übergebenen XQuery-Ausdruck spezifiziert wurden. Klingt kompliziert? Keine Sorge, es wird gleich deutlich werden, wie das funktioniert. Zunächst folgt hier aber erst einmal die allgemeine Syntax der Funktion:
Wie schon gesagt, muss also auch an die nodes-Funktion ein XQuery-Ausdruck übergeben werden. nodes wird typischerweise in der FROM-Klausel einer Abfrage verwendet. Der Ausdruck bezeichnet dann den Namen einer Tabelle und einer Ausgabespalte, in der die Resultate der Funktion bereitgestellt werden. Stellen Sie sich diese Tabelle einfach als eine virtuelle Tabelle vor, die Sie dann in der Abfrage verwenden können, z. B. für JOINs. Schauen Sie sich bitte unser erstes Beispiel an. Hier werden aus dem XML-Dokument, das die Sicht vXmlOrderInfo aus Listing 8.22 zurückgibt, alle -Elemente extrahiert:
Die Rückgabe von vXmlOrderInfo wird zunächst in eine Variable eingetragen. Für das in dieser Variablen enthaltene XML-Dokument wird dann die nodes-Funktion aufgerufen, wobei durch den XQuery-Ausdruck angegeben wird, dass alle Knoten mit ihren untergeordneten Knoten (hier: ) zurückgegeben werden sollen. In der SELECT-Liste wird hier nur eine einzige Spalte ausgegeben: das komplette, vom Aufruf der nodes-Funktion in der Spalte der Tabelle zurückgelieferte -Element. Dieses Element wird durch die query-Funktion zurückgegeben. Ein Teilergebnis der obigen Abfrage sehen Sie hier:
384
8.3 Der XML-Datentyp
Möchten Sie nur die untergeordneten -Elemente erhalten, können Sie dies so erreichen:
Hier sehen Sie auch, dass die Angabe des Tabellennamens nicht zwingend erforderlich ist, wenn der Spaltenname eindeutig aufgelöst werden kann. Hier gibt es also keinen Unterschied zu normalen Abfragen. Das Ergebnis sieht dann so aus:
Genauso wie wir in den obigen beiden Beispielen die query-Funktion verwendet haben, um ganze Zweige zurückzugeben, können die vom nodes-Aufruf zurückgelieferten Zeilen mit XML-Elementen auch durch die value-Funktion verarbeitet und somit in SQL ServerDatentypen konvertiert werden. Dies ermöglicht die Darstellung als Ergebnismenge sicher das Haupteinsatzgebiet von nodes. Nehmen wir an, wir möchten aus unserem XMLDokument alle Bestellnummern mit den zugehörigen Beträgen abrufen. Diese Abfrage führt zum Ziel:
385
8 XML-Integration Listing 8.27 Die nodes-Funktion zur Überführung in ein relationales Format
Hier werden aus den -Elementen, die von der nodes-Funktion zurückgegeben werden, die Werte für die Attribute Number und Total ausgelesen. Dies ist das (Teil)Ergebnis:
Abbildung 8.11 Bestellnummern und Beträge aus dem XML-Dokument
Unsere bisherigen Beispiele sind zunächst davon ausgegangen, dass nodes für ein XMLDokument aufgerufen wird, das in einer Variablen () gespeichert war. Was aber, wenn dieser Zwischenschritt nicht gewünscht ist und nodes direkt für eine XML-Spalte aufgerufen werden soll? In diesem Fall hilft der APPLY-Operator weiter. Erinnern Sie sich bitte daran, dass der an APPLY übergebene Ausdruck für jede Zeile der äußeren Abfrage aufgerufen wird. Ein APPLY-Ausdruck kann natürlich auch der Aufruf der nodesFunktion sein. Somit könnten wir für Listing 8.27 also auch schreiben:
und würden dasselbe Ergebnis erhalten. Der APPLY-Operator ist besonders dann nützlich, wenn Tabellen mit mehreren Zeilen, die XML-Daten enthalten, abgefragt werden sollen. Unsere Sicht vXmlOrderinfo gibt ja nur ein einziges XML-Dokument zurück, aber was ist z. B., wenn das Ergebnis aus Abbildung 8.11 durch eine Abfrage der OrderInfo-Tabelle erhalten werden soll? Diese Tabelle enthält ja eine Zeile für jeden Vertriebsmitarbeiter, wobei dessen Bestellungen als XML-Dokument in der Spalte Orders abgelegt wurden (siehe Abbildung 8.9). In diesem Fall müssen Sie den APPLY-Operator verwenden:
386
8.3 Der XML-Datentyp Hier wird für jede Orders-Spalte in jeder Zeile der Tabelle OrderInfo die nodesFunktion aufgerufen, die dann alle -Knoten zurückgibt. Aus diesen Knoten werden die Werte für die Attribute Number und Total durch die value-Funktion in der SELECT-Liste abgerufen und ausgegeben. Selbstverständlich können Sie auch Aggregatfunktionen anwenden, um Berechnungen durchzuführen, also z. B. so:
Für die Gruppierung gibt es allerdings die Einschränkung, dass XML-Funktionen nicht in GRYOU BY-Klauseln verwendet werden dürfen. Nehmen wir an, dass der Gesamtbetrag aller eingegangenen Bestellungen nach Kalenderjahr summiert werden soll. Die folgende Abfrage funktioniert dann nicht:
Sie erhalten die folgende Fehlermeldung:
Hier können Sie sich jedoch leicht mit einem allgemeinen Tabellenausdruck behelfen:
Der Tabellenausdruck erledigt die eigentliche Abfrage in der äußeren SELECTAnweisung, danach erfolgt dann lediglich noch die Gruppierung. Vielleicht ist Ihnen bereits aufgefallen, dass Sie durch den Einsatz der XML-DatentypFunktionen die Funktion OPENXML ersetzen können. Nehmen wir an, wir möchten das Ergebnis aus Abbildung 8.8 erneut erzeugen, diesmal aber unter Verwendung der XMLTyp-Funktionen. Zusätzlich soll noch eine Spalte für die Anzahl der Bestellungen, die jeder Vertriebsmitarbeiter aufgenommen hat, hinzugefügt werden. Die folgende Abfrage führt zum Ziel:
387
8 XML-Integration Und was ist nun besser, OPENXML oder die XML-Typ-Funktionen? Eine pauschale Aussage ist sicherlich nicht möglich. Auch hier sollten Sie jeden Fall gesondert betrachten und evtl. ausprobieren, welche Variante zu bevorzugen ist. Zunächst einmal arbeitet OPENXML mit einem Dokumenten-Handle, der nicht zwingend aus einem XMLDatentypen erstellt werden muss. Wir haben in unseren Beispielen hier eine Variable vom Typ NVARCHAR(MAX) verwendet und dort unseren XML-Code gespeichert. Für Variable dieses Typs stehen die XML-Typ-Funktionen natürlich nicht zur Verfügung, hier können Sie also nur OPENXML einsetzen. Für die XML-Typ-Funktionen spricht natürlich ihre gegenüber OPENXML größere Flexibilität und Einfachheit, da man sich das explizite Erzeugen eines entsprechenden Dokumenten-Handles ja ersparen kann. Vergleichen Sie die Abfragen aus unserem letzten Beispiel in diesem Abschnitt einmal mit den OPENXML-Beispielen, die ja stets durch die Aufrufe von und eingerahmt werden. Allerdings ist durch das Erzeugen eines solchen Handles auch das Parsen der Dokumentstruktur erledigt. Wenn Sie also mehrere OPENXML-Aufrufe mit demselben Dokumenten-Handle innerhalb eines Stapels verwenden, ist zu erwarten, dass diese Aufrufe auf jeden Fall schneller als vergleichbare XML-Typ-Funktionen sind. Aber auch hier kann man sicherlich nur den einen Rat geben: Probieren Sie es einfach aus! Wir haben nun gesehen, wie unter Einsatz von XQuery XML-Daten abgefragt werden. Im nächsten Kapitel wird nun untersucht, auf welche Weise man XML-Daten verändern kann.
8.3.7
modify
Die modify-Funktion ermöglicht Ihnen das direkte Ändern von XML-Spalten oder -Variableninhalten. Die XML-Daten werden dabei sozusagen in place modifiziert. Hierfür bietet SQL Server 2005 eine Erweiterung zur XQuery-Sprache an, die sogenannte XML Data Manipulation Language oder kurz: XML DML. Unter Verwendung der XML DML können Sie Daten einfügen, ändern oder löschen. Dementsprechend kennt die XML DML also drei unterschiedliche Befehle insert, replace und delete, die in den folgenden Abschnitten beschrieben werden. insert Das insert-Kommando hat die folgende Syntax:
Tabelle 8.6 erklärt die Bestandteile von insert:
388
8.3 Der XML-Datentyp Tabelle 8.6 Optionen des XML DML-insert-Kommandos Option
Beschreibung
ausdruck1
Dies ist für einzufügende Knoten. Für diesen Ausdruck können Sie z. B. einfach XML-Text verwenden wie: Um einen Textknoten einzufügen, können Sie hier auch einen element-Ausdruck verwenden, z. B.: element Order{"4711"} Es ist auch möglich, einem bestehenden Knoten ein Attribut hinzuzufügen. Hierzu verwenden Sie einen attribute-Ausdruck so: attribute Status {"Delivered"}
as first
Wenn Sie diese Option angeben, werden die durch ausdruck1 angegebenen XMLDaten als erstes Element in die Hierarchie eingefügt.
as last
Durch diese Option fügen Sie die neuen XML-Daten hinten ein
into
ausdruck1 wird in ausdruck2 eingefügt.
after
ausdruck1 wird hinter ausdruck2 eingefügt.
before
ausdruck1 wird vor ausdruck2 eingefügt
ausdruck2
Dies ist ein XQuery-Ausdruck, der den Pfad zu einem existierenden Knoten beschreibt.
Für unsere folgenden Beispiele verwenden wir ein -Dokument aus unserer Tabelle OrderInfo, das wir so erhalten:
In dieses Dokument wollen wir nun Daten einfügen. Zunächst wird einfach ein neuer -Knoten über ein XML Literal hinzugefügt:
Das insert-Kommando fügt das als Literal angegebene XML-Element als erstes Element in die -Hierarchie ein, wie wir uns durch die anschließende SELECTAnweisung überzeugen können, deren Ergebnis Sie hier sehen:
Ein -Element kann auch durch den Einsatz eines element-Ausdrucks eingefügt werden:
389
8 XML-Integration Das neue Element wird hier als letztes -Element in die Hierarchie des ersten -Elements eingefügt. sieht nun so aus:
Für das Hinzufügen eines Attributes zum ersten -Element schreiben Sie z. B.:
Dieses Element hat sich nun so geändert:
Um einen neuen leeren -Knoten am Anfang, also vor dem ersten vorhandenen -Knoten, einzufügen, können Sie so verfahren:
Dies ist das Ergebnis:
replace Zum Ändern bestehender Daten verwenden Sie die replace-Funktion in der folgenden Form:
ausdruck1 ist ein XQuery-Ausdruck, der den Knoten angibt, der ersetzt werden soll. ausdruck2 gibt den Wert für den neuen Knoten an. Um z. B. den Wert des Attributs Status für das gerade an erster Stelle eingefügte -Element auf Delivered zu ändern, können Sie diese Anweisung verwenden:
Hier sehen Sie das Ergebnis:
390
8.3 Der XML-Datentyp
Möchten Sie den Text des im vorigen Abschnitts eingefügten Elements
von 0815 auf 4711 ändern, können Sie dies so erreichen:
Die -Funktion gibt den Elementtext zurück. Vom obigen XQuery-Ausdruck wird der Text des ersten -Elementes mit dem Text 0815 zurückgegeben. Dieser wird dann durch 4711 ersetzt. delete Über die delete-Methode können Sie Knoten aus XML-Daten entfernen. Die Methode hat folgende einfache Syntax:
ausdruck steht hier wiederum für einen XQuery-Ausdruck. Die delete-Anweisung löscht alle Knoten, die von diesem XQuery-Ausdruck zurückgegeben werden. Um unser -Element mit dem Status-Attribut wieder aus dem Dokument zu entfernen, können wir so verfahren:
Bitte bedenken Sie, dass die obige Anweisung den gesamten Knoten löscht und nicht etwa nur das Status-Attribut aus dem gefundenen -Element entfernt. Der XQueryAusdruck gibt einen -Knoten zurück (nämlich den ersten, bei dem das StatusAttribut den Wert Delivered besitzt). Die delete-Anweisung löscht dann genau diesen Knoten. delete kann auch mehrere Knoten auf einmal löschen. Zum Entfernen aller Elemente, die weniger als 50 untergeordnete -Elemente haben, verwenden Sie diese Anweisung:
Die Besprechung der XML-Datentyp-Funktionen ist damit abgeschlossen. Im nächsten Abschnitt wollen wir nun noch untersuchen, wie die Abfrageleistung von XQueryAbfragen durch den Einsatz von XML-Indizes verbessert werden kann.
8.3.8
XML-Indizes
Wie bereits weiter oben erwähnt, können XML-Spalten jeweils bis zu 2 GB Daten enthalten. Allein aus diesem Grund können XQuery-Abfragen auf XML-Spalten eine hohe Last
391
8 XML-Integration auf dem Server erzeugen. Um die Abfrageleistung zu verbessern, können Sie für XMLSpalten Indizes erstellen. Dies ist natürlich schon einmal eine gute Nachricht. Die Nutzung von XML-Indizes ist allerdings auch mit einigen Bedingungen verbunden, wie im Verlaufe dieses Kapitels noch deutlich werden wird. Damit ein Index auf einer XML-Spalte einer Tabelle verwendet werden kann, gelten zunächst einmal die folgenden Einschränkungen: Die Tabelle muss einen gruppierten Primärschlüssel besitzen. Dieser Primärschlüssel kann nicht modifiziert werden, wenn ein XML-Index existiert. Ist dies gewünscht, so müssen die XML-Indizes entfernt und anschließend nach der Veränderung des Primärschlüssels neu erstellt werden. Ein XML-Index kann nicht für eine Sicht erstellt werden. Unsere in den vorangegangenen Beispielen verwendete Sicht vXmlOrderInfo kann also nicht indiziert werden. Ein XML-Index kann nicht für eine berechnete Spalte erzeugt werden. Betrachten Sie die folgende Tabelle:
Die Spalte XmlName ist eine berechnete Spalte, für die kein XML-Index erzeugt werden kann. XML-Indizes benötigen eine Menge Speicherplatz, in der Regel mehr als die zugrunde liegende XML-Spalte. Generell können Sie für eine XML-Spalte mehrere Indizes erstellen, die Sie je nach den verwendeten XPath-Ausdrücken konstruieren. Hierbei können Sie zwei unterschiedliche Typen von XML-Indizes erzeugen: primäre und sekundäre. Primäre XML-Indizes Ein primärer XML-Index muss immer der erste Index für eine XML-Spalte sein. Stellen Sie sich diesen Index so vor, dass er alle Daten des XML-Dokuments enthält und die Basis für alle weiteren sekundären Indizes darstellt. Sie erstellen einen primären Index durch das Kommando:
Für unsere OrderInfo-Tabelle würde die Erstellung eines solchen Index mit Namen also so aussehen:
Damit eine Aussage darüber getroffen werden kann, wie nützlich dieser Index letztlich ist, wollen wir einige Abfragen aus den vorangegangenen Abschnitten verwenden und die Ab-
392
8.3 Der XML-Datentyp fragekosten vor und nach der Erstellung des Index messen. Die für diese Messungen verwendeten Abfragen sind in Listing 8.28 noch einmal aufgeführt:2 Listing 8.28 Für den Indextest verwendete Abfragen
Die folgende Tabelle zeigt das Ergebnis der Messungen: Wert Indexgröße
Ohne primären XML-Index 8KB
Mit primärem XML-Index 25.264 KB
Abfragekosten Abfrage 1)
10
1,5
Abfrage 2)
5.010
63
Abfrage 3)
2.514
32
Abfrage 4)
130
9,4
Abfrage 5)
10
1,1
Wie Sie sehen können, benötigt allein der XML-Index zunächst einmal ca. 50% mehr Speicherplatz als die eigentlichen Tabellendaten. Durch die allesamt enorm verbesserten Abfrageergebnisse wird man für diesen zusätzlichen Speicherverbrauch allerdings mehr als entschädigt. 2
Falls Sie die Messungen nachvollziehen sollten, werden Ihre Ergebnisse wahrscheinlich von den hier präsentierten abweichen, da diese natürlich von der verwendeten Hardware, Software und Konfiguration abhängen. Generell sollten Sie aber was den Trend betrifft eine Übereinstimmung feststellen.
393
8 XML-Integration Sekundäre XML-Indizes Sie können sekundäre XML-Indizes anlegen, die bestimmte XQuery-Ausdrücke noch besser unterstützen, als dies ein alleiniger primärer XML-Index kann. Sekundäre XMLIndizes können für drei unterschiedliche Einsatzfälle konzipiert werden: PATH Ein PATH-Index kann nützlich ein, wenn Sie XQuery-Abfragen verwenden, die den Pfad und den Wert an die Abfrage übergeben, um Knoten zurückzubekommen. Ein Beispiel für eine solche Abfrage ist . Einen PATHIndex erstellen Sie so:
PROPERTY Ein PROPERTY-Index ist sinnvoll, wenn Sie XQuery-Abfragen verwenden, die Knoten durch eine Pfadangabe spezifizieren. Eine solche Pfadangabe sieht z. B. so aus: /Orders/Order[1]. Ein PROPERTY-Index wird so erzeugt:
VALUE Der VALUE-Index ist immer dann hilfreich, wenn Sie Pfadangaben unpräzise verwenden. So ist z. B. die Angabe eines Pfades nicht präzise, da die Pfadangabe nicht vollständig ist. Sie erstellen einen VALUE-Index mit dieser Anweisung:
Auch für die sekundären Indizes wurden die obigen Messungen durchgeführt. In der folgenden Tabelle sehen Sie nochmals alle gemessenen Werte zusammengefasst: Wert Indexgröße
OHNE 8 KB
PRIMARY 25.264 KB
PATH
PROPERTY
43.984 KB
VALUE
ALLE
43.992 KB
43.992 KB
81.440 KB
Abfragekosten
394
Abfrage 1)
10
1,5
0,026
0,04
0,5
0,026
Abfrage 2)
5010
63
60
23
63
23
Abfrage 3)
2514
32
30
12
32
12
Abfrage 4)
130
9,4
9,4
6,8
6,3
6,3
Abfrage 5)
10
1,1
0,1
0,1
0,07
0,07
8.4 Zusammenfassung Es fällt auf, dass die mit dem primären Index bereits wesentlich verbesserten Abfragekosten hier nochmals teils deutlich unterboten wurden. Es lohnt sich also, über den Einsatz von XML-Indizes nachzudenken, wobei auch hier wieder gilt: Eine allgemeine Faustregel für die Anwendung ist mit Sicherheit nicht herzuleiten. Die beste Methode ist sicherlich das Probieren und Messen, wobei Sie als Grundlage für Ihre Versuche das in diesem Kapitel Gelernte verwenden können. Löschen von XML-Indizes Für das Löschen von XML-Indizes müssen Sie die neue DROP INDEX-Syntax verwenden. Statt:
schreiben Sie also korrekt:
8.4
Zusammenfassung In diesem Kapitel haben Sie gelernt, wie der SQL Server 2005 XML-Daten darstellen und verarbeiten kann. Wir haben zunächst die beiden Abbildungsschichten FOR XML und OPENXML untersucht, die Tabellendaten in XML-Form bzw. XML-Daten in Tabellenform überführen können. Anschließend wurde erklärt, wie der SQL Server durch den Datentyp XML echte XML-Integration ermöglicht. Hierbei sollte Ihnen klar geworden sein, wie tief verwurzelt XML im SQL Server inzwischen ist. Was Sie sicherlich im Verlaufe dieses Kapitels auch bemerkt haben, ist, wie komplex XQuery-Ausdrücke werden können. Wenn Sie den Einsatz von XML ernsthaft in Erwägung ziehen und sich mit XQuery bislang nur oberflächlich auskennen, werden Sie um eine tiefergehende Beschäftigung mit XQuery mit Sicherheit nicht herumkommen. Darum möchte ich Sie an dieser Stelle nochmals auf entsprechende weiterführende Literatur verweisen, siehe z. B. [LEHN04].
395
9 SQL Server Service Broker Der SQL Server Service Broker ermöglicht die asynchrone Verarbeitung von Daten. Der Einsatz des Service Brokers ist immer dann sinnvoll, wenn eine Online-Datenverarbeitung zu einer Lasterhöhung derart führt, dass der Server letztlich nicht mehr oder nur in ungenügender Zeit auf Anfragen reagieren kann. In einem solchen Fall kann durch die asynchrone Verarbeitung von Anfragen dafür gesorgt werden, dass ein SQL Server-System mehr Anfragen verarbeit, als es tatsächlich physikalisch kann. Hierzu werden Anfragen einfach in eine Warteschlange eingereiht, die dann Schritt für Schritt abgearbeitet wird, wobei ausgenutzt wird, dass das System in absehbarer Zeit auch wieder in einen Zustand geringerer Auslastung zurückkehrt. In diesem Zustand ist dann die Abarbeitung der Warteschlange(n) möglich. Für den Endbenutzer kann durch eine solche Verfahrensweise eine merkliche Reduzierung der Antwortzeiten erreicht werden. Unter Einsatz des Service Brokers kann ein Benachrichtigungssystem installiert werden, das den Zustand von Warteschlangen überwacht und beim Eintreffen von Ereignissen, die in diese Warteschlangen eingereiht werden, Aktionen auslöst. Der Service Broker kann aber auch Änderungen an Server- und Datenbankobjekten überwachen und beim Auftreten von solchen Änderungen Ereignisse erzeugen, verteilen oder protokollieren.
9.1
Architektur Der SQL Server Service Broker ist ein Framework, mit dessen Hilfe eine ereignisgesteuerte Datenverarbeitung organisiert werden kann. Der Service Broker ist keine eigene Anwendung, er stellt im Rahmen des Frameworks lediglich die zur Implementierung einer serviceorientierten Architektur erforderlichen Objekte und Methoden zur Verfügung. Bevor wir betrachten, wie das Framework des Service Brokers aufgebaut ist und welche Objekte und Methoden zur Verfügung stehen, soll daher zunächst eine kurze Einführung in serviceorientierte Architekturen gegeben werden.
397
9 SQL Server Service Broker
9.1.1
Service Oriented Architecture (SOA)
Serviceorientierte Architekturen bestehen aus Diensten, die nur lose auf der Basis von Nachrichtenaustauschmechanismen miteinander verbunden sind. Solche Dienste können zum Beispiel Web Services sein, die wir in Kapitel 11 noch behandeln werden. Ein Dienst oder Service ist hierbei die Implementierung einer genau umrissenen Aufgabe aus einer bestimmten Anwendungsdomäne. Die technische Implementierung eines Dienstes also etwa Programmiersprache, Datenbank oder Systemplattform ist hierbei nicht von Bedeutung. Wichtig ist nur, dass der Dienst einen Vertrag erfüllen muss. Hiermit ist gemeint, dass ein Dienst sicherstellen wird, dass er auf bestimmte Anfragen definiert reagiert. Ebenfalls per Vertrag geregelt ist die Kommunikation zwischen Dienstanbieter und Dienstnutzer, also zum Beispiel die verwendeten Schnittstellen und das Protokoll. Komponenten in einer serviceorientierten Architektur sind Nachrichten, Warteschlangen, Verträge, Dienste und Konversationen. Dienste Ein Dienst ist hier die softwaretechnische Umsetzung einer exakt definierten Aufgabe. Nachrichten Nachrichten sind die Informationsträger in einer Kommunikation. Das Nachrichtenformat sollte hierbei so gewählt werden, dass ein plattform- und implementierungsunabhängiger Nachrichtenaustausch möglich ist. Web Services zum Beispiel verwenden das Simple Object Access Protocol (SOAP) ein spezielles XML-Format als Nachrichtenformat, wodurch genau diese Unabhängigkeit sichergestellt wird. Warteschlangen Warteschlangen nehmen Nachrichten auf, die auf ihre Abarbeitung warten. Hierdurch wird eine asynchrone Verarbeitung von Daten ermöglicht. Der für eine Nachricht in einer Warteschlange verantwortliche Dienst kann diese Nachricht irgendwann auswerten bzw. abarbeiten. Verträge Verträge regeln die Kommunikation zwischen Diensten. In den Verträgen wird festgelegt, welche Dienste welche Nachrichten bzw. Nachrichtenformate austauschen können. Konversationen Eine Konversation ist der Austausch von Nachrichten, die zueinander in Beziehung stehen. Die Konversation muss zum Beispiel dafür sorgen, dass zueinander in Beziehung stehende Nachrichten in der erforderlichen Reihenfolge in die entsprechenden Warteschlangen eingereiht werden.
398
9.1 Architektur Die Datenverarbeitung in einem solchen serviceorientierten System basiert also letztlich auf dem asynchronen Austausch von Informationen zwischen Diensten. Ein Dienst wird im Rahmen einer Konversation die für ihn per Vertrag definierten Nachrichten aus Warteschlangen abholen. Nach der Verarbeitung der in diesen Nachrichten enthaltenen Informationen kann der Dienst auch selber Nachrichten generieren und diese wiederum im Rahmen einer Konversation in eine Warteschlange einreihen.
9.1.2
Service Broker-Kommunikation
In Abbildung 9.1 sehen Sie eine vereinfachte Darstellung der Service Broker-Architektur.
Abbildung 9.1 Service Broker-Architektur
Für den Nachrichtenaustausch zwischen Anwendungen werden Dienste, Warteschlangen, Dialoge und natürlich die Nachrichten selber benötigt. Abbildung 9.1 zeigt, dass eine Anwendung Nachrichten aus einer Warteschlange entgegennimmt, beim Senden einer Nachricht, die Nachricht aber nicht direkt in eine Warteschlange einreiht, sondern diese Aufgabe einem Dienst überlässt. Nachrichten werden zwischen Diensten ausgetauscht und von diesen in die zugeordnete Warteschlange eingereiht. Die Koordination der Nachrichtenreihenfolge wird dabei durch eine Komponente übernommen, die in der Terminologie des Service Brokers als Dialog bezeichnet wird. Ein Dialog ist somit für die Koordination der Konversation zwischen den beteiligten Diensten verantwortlich. Dialoge sichern die korrekte Reihenfolge von Nachrichten auch über Servergrenzen hinweg und sorgen dafür, dass Nachrichten nicht versehentlich mehrfach in Warteschlangen eingereiht bzw. aus diesen ausgelesen werden können. Eine sehr gute Analogie zur Arbeitsweise des Service Brokers liefert uns die normale Briefpost. Schauen Sie sich hierzu bitte Abbildung 9.2 an. In dem dort skizzierten Szenario kommunizieren zwei Kollegen durch den Austausch von Briefen per Postdienst.
399
9 SQL Server Service Broker
Abbildung 9.2 Informationsaustausch durch einen Postdienst
Beide Kollegen holen die Post aus ihren Briefkästen, um sie anschließend zu lesen und zu beantworten. Die Koordination dieses Informationsaustausches übernimmt dabei ein spezieller Zustelldienst, der im Prinzip so wie der Service Broker arbeitet. Die Zustellung und Bearbeitung der Briefe findet asynchron statt, während die Post unterwegs ist, können beide Kollegen andere Aufgaben erledigen. Tabelle 9.1 liefert nochmals eine Gegenüberstellung der Begriffswelt aus dem normalen Postdienst und dem Service Broker. Tabelle 9.1 Vergleich zwischen Postdienst und Service Broker Postdienst
Service Broker
Brief
Nachricht
Adresse
Dienst
Briefkasten
Warteschlange
Empfänger (Kollege)
Anwendung
Der Service Broker leistet gegenüber dem normalen Postdienst aber noch einiges mehr. Zunächst einmal wird dafür Sorge getragen, dass alle Nachrichten in die Warteschlange in genau der Reihenfolge eingereiht werden, in der sie gesendet worden sind. Dies ist in einem Briefkasten sicherlich nicht der Fall, hier liegen die eingegangenen Nachrichten ungeordnet vor. Es ist außerdem sichergestellt, dass jede Nachricht nur einmal übermittelt wird. Dabei können auch die Service Broker verschiedener SQL Server zusammenarbeiten, entfernte Warteschlangen werden also ebenfalls so unterstützt, dass sowohl die korrekte Nachrichtenübermittlung als auch die Nachrichtenreihenfolge jederzeit gewährleistet ist. Bei der Nachrichtenübermittlung und -verarbeitung können Transaktionen verwendet werden, auch wenn die Service Broker verschiedener SQL Server an der Kommunikation beteiligt sind. Die Transaktionssicherheit ist also auch über Servergrenzen hinweg gewährleistet.
400
9.1 Architektur
9.1.3
Service Broker-Komponenten
Die an einer serviceorientierten Architektur beteiligten Komponenten wurden bereits in Abschnitt 9.1.1 allgemein erörtert. Die dort beschriebenen Bestandteile sollen nun nochmals im Hinblick auf den Service Broker spezifiziert werden. Für eine Service BrokerKommunikation werden Nachrichten, Warteschlangen, Verträge, Dienste und Dialoge benötigt, die in den folgenden Absätzen näher erklärt werden. Nachrichten Nachrichten als Informationsträger des Service Broker-Datenaustausches können im Textformat, als Binardaten oder aber im XML-Format versendet werden. Sicherlich wird das bevorzugte Format XML sein. Für dieses Format kann der SQL Server Service Broker eine Überprüfung auf Korrektheit vornehmen. Überprüft wird in jedem Fall, dass eine XML-Nachricht wohlgeformt ist. Zusätzlich kann aber auch die Übereinstimmung mit einem angegebenen XSD-Schema kontrolliert werden. Service Broker-Nachrichten können eine Größe von bis zu 2 GB aufweisen. Warteschlangen SQL Server Service Broker-Warteschlangen enthalten eine Liste von zusammengehörenden Nachrichten. Jede Warteschlange muss mit einem zugehörigen Dienst verknüpft sein. Eine gesendete Nachricht wird in einer Warteschlange bereitgestellt, wo sie von einer Anwendung zur Bearbeitung abgeholt werden kann. Sie können sich eine Service BrokerWarteschlange als eine virtuelle Tabelle oder Sicht vorstellen, in der jede Zeile eine gespeicherte Nachricht repräsentiert. Verträge Verträge bestimmen, welche Nachrichten in welche Warteschlangen eingereiht werden dürfen. Damit eine Nachricht in einer Warteschlange bereitgestellt werden kann, muss ein entsprechender Vertrag zwischen dem Typ der Nachricht und dem Dienst einer Warteschlange bestehen. Um auf die Analogie mit dem Postdienst zurückzukommen: Auch mit dem Postdienst wird durch die Frankierung eines Briefes, also die Bezahlung in gewisser Weise ein Vertrag geschlossen. Die ausreichende Frankierung sorgt dafür, dass der Dienst den Brief (die Nachricht) in den Briefkasten (die Warteschlange) einwirft. Dienste Mit jeder Warteschlange muss ein entsprechender Dienst verbunden sein, der Nachrichten einstellen darf. Über die zwischen Nachrichtentyp und Dienst bestehenden Verträge wird dann letztlich spezifiziert, welche Nachrichtentypen der Dienst verarbeiten darf.
401
9 SQL Server Service Broker Dialoge Dialoge sind die Koordinatoren des Service Broker-Nachrichtenaustausches. Eine Konversation wird im Rahmen eines Dialogs organisiert. Schauen Sie sich noch einmal Abbildung 9.1 an. Der dort dargestellte Dialog koordiniert und überwacht den Nachrichtenaustausch zwischen den beiden in der Abbildung zu erkennenden Diensten. Ein Dialog stellt den geordneten Nachrichtenaustausch zwischen zwei Service BrokerDiensten sicher und sorgt dafür, dass Nachrichten in der korrekten Reihenfolge bereitgestellt werden. Diese Aufgabe ist keineswegs trivial immerhin können Nachrichten bis zu 2 GB groß sein. Eine bemerkenswerte Eigenschaft von Dialogen ist, dass diese auch über Servergrenzen hinweg agieren können. Die an einem Dialog beteiligten Service Broker unterschiedlicher SQL Server-Instanzen gewährleisten dabei sowohl die korrekte Reihenfolge der Nachrichten als auch die Einhaltung der Transaktionsprinzipien.
9.2
Erstellung von Anwendungen In diesem Abschnitt wird eine einfache Service Broker-Anwendung erstellt und natürlich auch verwendet. Sie erhalten zunächst eine Einführung in die zur Verfügung stehenden TSQL-Anweisungen, mit denen Sie Service Broker-Anwendungen erstellen können. Danach werden wir mit diesen Kommandos Schritt für Schritt eine Anwendung entwickeln und Nachrichten austauschen.
9.2.1
T-SQL-Kommandos für eine Service Broker-Kommunikation
Für die Erzeugung der im vorherigen Abschnitt aufgeführten Service BrokerKomponenten und für das Versenden von Nachrichten an einen Dienst bzw. das Auslesen von Nachrichten aus einer Warteschlange wurde T-SQL um eine Reihe von Kommandos erweitert. Eine Kurzübersicht der wichtigsten Anweisungen ist in Tabelle 9.2 zu sehen. Im folgenden Abschnitt werden wir dann für die Erstellung unserer Beispielanwendung diese Kommandos verwenden und dabei nochmals näher erklären. Tabelle 9.2 T-SQL-Anweisungen für die Service Broker-Kommunikation T-SQL-Anweisung
402
Beschreibung
Anlegen, Ändern oder Löschen von Nachrichtentypen
Anlegen, Ändern oder Löschen von Warteschlangen
Anlegen, Ändern oder Löschen von Verträgen, Verbinden von Nachrichtentyp und Dienst
9.2 Erstellung von Anwendungen T-SQL-Anweisung
Beschreibung
Anlegen, Ändern oder Löschen von Diensten
Starten eines neuen Dialogs, Beginn einer Konversation
Beenden einer Dialogkonversation
Empfangen einer Nachricht aus einer Warteschlange
Senden einer Nachricht an eine Warteschlange
9.2.2
Eine Service Broker-Beispielanwendung
In diesem Abschnitt soll ein Beispiel erstellt werden, das die grundsätzliche Arbeitsweise des Service Brokers verdeutlicht. Um den Einstieg in die Entwicklung von Service BrokerAnwendungen zu vereinfachen, wird das Beispiel sehr einfach gehalten und enthält nur die nötigsten, für eine Service Broker-Konversation benötigten, Elemente. Dennoch werden die wichtigsten Aspekte des Service Broker-Einsatzen veranschaulicht. So ganz nebenbei werden Sie in diesem Abschnitt auch erfahren, wie Sie SQL Server-E-Mail konfigurieren. Unsere Anwendung basiert auf einer fiktiven Kundendatenbank der Firma Initech, in der neuen Kunden die Möglichkeit geboten wird, sich über das Internet zu registrieren. Die Kunden müssen für eine Registrierung ihren Namen und ihre E-Mail-Adresse bekannt geben und werden dann mit einer speziellen Willkommens-E-Mail begrüßt. Initech rechnet damit, dass sich in kurzer Zeit Millionen von Kunden registrieren werden. Um den Server nicht mit dem Versenden von E-Mails zu überlasten, soll der Prozess der Registrierung und Begrüßung asynchron ablaufen. Beim Anlegen eines neuen Kunden sollen also zunächst nur die für die Begrüßungs-E-Mail erforderlichen Daten als Nachricht in eine Warteschlange eingereiht werden. Diese Warteschlange wird dann später abgearbeitet, wobei dann die entsprechenden E-Mails versendet werden. Für unser Beispiel benutzen wir diesmal nicht die Datenbank AdventureWorks, sondern legen eine neue Datenbank SBSample an, die eine Tabelle Kunde enthält:
403
9 SQL Server Service Broker
Der einzige Grund für das Verwenden einer eigenen Datenbank ist übrigens die einfache Möglichkeit, unsere erzeugten Testobjekte durch das Löschen dieser Datenbank entfernen zu können. Das obige Skript erzeugt die verwendete Beispieldatenbank SBSample und fügt eine Tabelle Kunde zur Datenbank hinzu. Am Ende des Skriptes wird für die Datenbank noch ein Datenbank-Hauptschlüssel zur Datenbank angelegt. Service Broker benötigt diesen Schlüssel für eine sichere, also verschlüsselte, Kommunikation. In einer neu erstellten Datenbank ist die Service Broker-Kommunikation standardmäßig erlaubt. Sie können dies durch die Anweisung
überprüfen, deren Ergebnis unmittelbar nach dem Erstellen der Datenbank mit den Standardoptionen so aussieht:
Um den Service Broker in einer Datenbank auszuschalten, verwenden Sie das Kommando ALTER DATABASE:
Über die Option enable_broker können Sie die Service Broker-Kommunikation auch wieder erlauben:
Erzeugen der Service Broker-Objekte Nach dem Erzeugen der Datenbank können nun die erforderlichen Service Broker-Objekte hinzugefügt werden. Für unsere Anwendung benötigen wir die folgenden vier Objekte: Einen Nachrichtentyp. Eine in die Warteschlange eingereihte Nachricht soll den Namen und die E-Mail-Adresse eines Kunden enthalten. Hierzu wird eine Nachricht im XML-Format erzeugt, die diese Informationen aufnimmt. Eine Warteschlange. In die Warteschlange werden neu registrierte Kunden aufgenommen, für die noch eine E-Mail-Benachrichtigung versendet werden muss. Einen Vertrag. Der Vertrag regelt, welche Nachrichten von wem gesendet werden dürfen. Dies wird weiter unten noch erläutert.
404
9.2 Erstellung von Anwendungen Einen Dienst. Unser einziger Dienst bestimmt, welcher Vertrag eingehalten werden muss, wenn Nachrichten in die (einzige) Warteschlange eingestellt werden. Das folgende Skript erzeugt die benötigten Objekte:
Es wird zunächst ein Nachrichtentyp //SBSample.org/KdEMail erzeugt und festgelegt, dass Nachrichten dieses Typs im XML-Format ausgetauscht werden müssen. Hier könnten Sie über die Option VALID_XML WITH SCHEMA COLLECTION <schema_name> auch spezifizieren, dass das Nachrichtenformat einem bestimmten XML-Schema genügen muss. Microsoft empfiehlt für die Bezeichnungen von Nachrichtentypen URL-Namen, um die Eindeutigkeit auch über Systemgrenzen hinweg zu gewährleisten (also zum Beispiel [http://meinefirma.com/nachrtyp/allgemein/KdMail]). Für unser einfaches Beispiel ist die gewählte Bezeichnung ausreichend. Im zweiten Schritt wird die Warteschlange erzeugt, welche die Nachrichten aufnehmen soll. Danach kann (in Schritt 3) der Vertrag erzeugt werden, der festlegt, wer Nachrichten vom Typ //SBSample.org/KdEMail versenden darf. Jeder Dienst muss letztlich einem Vertrag genügen, wobei der initiierende Dienst die Konversation mit der Angabe des Vertrages für die Konversation startet. (Das wird gleich weiter unten zu sehen sein.) Die Klausel SENT BY INITIATOR legt fest, dass nur der Initiator der Konversation Nachrichten dieses Typs in die angegebene Warteschlange einreihen darf. Andere Möglichkeiten wären: SENT BY TARGET. Nur der Empfänger, also das Ziel der Nachricht, darf Nachrichten dieses Typs versenden. SENT BY ANY. Sowohl der Initiator als auch der Empfänger können Nachrichten dieses Typs versenden. Der Nachrichtenaustausch in einer Service Broker-Konversation kann mit Nachrichten unterschiedlicher Typen vonstatten gehen. Über die SENT BY-Klausel können Sie angeben, welche Nachrichtentypen von wem benutzt werden dürfen. So ist es zum Beispiel denkbar, dass ein Empfänger auf Nachrichten eines bestimmten Typs in einem festgelegten Format also durch einen festgelegten Nachrichtentypen antworten muss. In einem solchen Fall
405
9 SQL Server Service Broker ist SENT BY nützlich. Wir verwenden hier nur einen Nachrichtentypen, der vom Initiator der Konversation in die Warteschlange eingereiht wird. Der Empfänger der Nachricht wird seinerseits keine Antwort in eine Warteschlange einstellen, sondern lediglich eine E-Mail versenden. Daher ist für unseren Vertrag SENT BY INITIATOR angegeben. In Schritt 4 wird abschließend der einzige Dienst erzeugt, der den Nachrichtenaustausch regelt. Wir benötigen hier nur einen Dienst, der beim Anlegen eines Kunden dessen Parameter in die Warteschlange überträgt. Insofern ist unsere Beispielanwendung ein Spezialfall der in Abbildung 9.1 dargestellten Struktur. Wir verwenden nur einen Dienst und eine Warteschlange, eine Konversation wird in unserem Fall daraus bestehen, dass der Dienst Nachrichten an sich selber versendet. Dies werden Sie in den nächsten Absätzen sofort sehen. Unser Dienst //SBSample.org/KdMailService verwendet die angegebenen Warteschlange NeueKunden und benutzt hierfür den in Schritt 3 erzeugten Vertrag //SBSample.org/KdInsert. Damit wird sichergestellt, dass bei einer Konversation über diesen Dienst nur Nachrichten vom Typ //SBSample.org/KdEmail durch den Initiator der Konversation in die Warteschlange NeueKunden eingestellt werden können. Die Erzeugung der erforderlichen Objekte ist nun abgeschlossen. Die Objektstruktur kann in Anlehnung an Abbildung 9.1 wie in Abbildung 9.3 gezeigt dargestellt werden.
Abbildung 9.3 Die erzeugten Service Broker-Objekte
Unser einziger Dienst //SBSample.org/KdMailService stellt Nachrichten in die NeueKunden-Warteschlange ein. Der Nachrichtenaustausch muss dabei den im Vertrag //SBSample.org/KdInsert angegebenen Optionen genügen. Insbesondere kann also nur der Initiator einer Konversation Nachrichten vom Typ //SBSample.org//KdEmail in die Warteschlange einreihen. Sie können die existierenden Objekte über entsprechende Katalogsichten abfragen, von denen einige in Tabelle 9.3 aufgelistet sind.
406
9.2 Erstellung von Anwendungen Tabelle 9.3 Katalogsichten für Service Broker-Objekte Katalogsicht
Beschreibung
Gibt alle erzeugten Nachrichtentypen zurück. Diese Sicht enthält sowohl die System-Nachrichtentypen als auch benutzerdefinierte Typen.
Listet alle existierenden Verträge auf.
Gibt die Informationen über Beziehungen zwischen Verträgen und Nachrichtentypen aus.
Listet die erzeugten Dienste auf.
Gibt die Beziehungen zwischen Diensten und Verträgen zurück.
Listet die existierenden Warteschlangen auf.
Selbstverständlich ist es auch möglich, das SQL Server Management Studio zu verwenden, um die für eine Datenbank angelegten Service Broker-Komponenten zu inspizieren. Hierzu finden Sie für jede existierende Datenbank den Ordner Service Broker (Abbildung 9.4).
Abbildung 9.4 Die erzeugten Service Broker-Objekte im Objekt-Explorer
407
9 SQL Server Service Broker Interessant ist, dass außer den von uns explizit erzeugten Objekten auch eine Reihe von anderen Service Broker-Komponenten enthalten sind. Diese Objekte werden standardmäßig zu jeder erzeugten Datenbank hinzugefügt und können sofort nach der Erstellung der Datenbank verwendet werden. Wir werden in Abschnitt 9.3 noch einmal hierauf zurückkommen. Warteschlangen können Sie ganz genauso wie normale Tabellen über SELECTAnweisungen abfragen. Probieren Sie einmal das folgende Kommando, um den Inhalt unserer Warteschlange NeueKunden anzuzeigen:
Sie werden ein Ergebnis erhalten, das im Moment noch leer ist. Es ist übrigens nicht möglich, den Inhalt von Warteschlangen über INSERT, UPDATE oder DELETEAnweisungen zu verändern. Grundsätzlich können Nachrichten in eine Warteschlange nur über die Benutzung der zugeordneten Dienste eingereiht und dann auch nicht mehr verändert werden. Das Löschen einer Nachricht aus der Warteschlange erfolgt dann während der Verarbeitung der Nachricht durch das Kommando RECEIVE. Weiter unten wird diese Verfahrensweise noch genauer erklärt. Nach der Erzeugung der erforderlichen Objekte können wir uns nun der Implementierung des eigentlichen Nachrichtenaustauschs widmen. Der Begriff Nachrichtenaustausch ist hierbei vielleicht etwas übertrieben, da in unserem einfachen Beispiel ein eigentlicher Austausch von Nachrichten nicht stattfindet. Es werden lediglich Nachrichten in die Warteschlange NeueKunden eingereiht, die dann aus dieser Warteschlange ausgelesen und verarbeitet werden. Eine Beantwortung der eingegangenen Nachricht erfolgt hierbei allerdings nicht, ein echter Nachrichtenaustausch findet also nicht statt. Eintragen von Nachrichten in die Warteschlange Wir müssen zunächst dafür sorgen, dass beim Anlegen eines neuen Kunden eine zugehörige Benachrichtigung in die Warteschlange aufgenommen wird. Dies kann zum Beispiel über einen INSERT-Trigger für die Tabelle Kunde erledigt werden, der so wie in Listing 9.1 gezeigt aufgebaut sein könnte. Listing 9.1 INSERT-Trigger für das Hinzufügen neuer Kunden zur Warteschlange
408
9.2 Erstellung von Anwendungen
Im Trigger wird nach der Deklaration der lokalen Variablen und des XML-Gerüstes für eine Nachricht zunächst ein Dialog geöffnet, der die Konversation koordiniert. Dies wird über die BEGIN DIALOG CONVERSATION-Anweisung erledigt, die eine eindeutige Dialog-ID zurückliefert. Diese ID wird zunächst einfach nur für die spätere Verwendung in die Variable eingetragen. Beim Öffnen des Dialoges wird auch angegeben, welche Dienste für den Nachrichtenaustausch als Sender und Empfänger arbeiten und welcher Vertrag erfüllt werden muss. In unserem einfachen Fall benachrichtigt der Dienst //SBSample.org/KdMailService sich sozusagen selber. Falls Ihnen dies umständlich er-
409
9 SQL Server Service Broker scheint, so bedenken Sie bitte, dass das Aufnehmen einer Nachricht in eine Warteschlange immer über einen zugehörigen Dienst erfolgen muss. Zunächst einmal haben wir also keine andere Möglichkeit, als diesen Dienst zu verwenden. Beachten Sie bitte auch, dass die Abwicklung der Konversation im Rahmen eines Dialoges Vorteile wie Transaktionssicherheit und Einhaltung der Übermittlungsreihenfolge bietet. Sie könnten zum Beispiel den gesamten Code des Triggers in einen BEGIN TRANSACTION
COMMIT TRANSACTION-Block verpacken und würden damit sicherstellen, dass die an den Dienst gesendeten Nachrichten korrekt übertragen werden auch wenn die Warteschlange, in die eine Nachricht eingereiht werden soll, in einer Datenbank auf einem entfernten Server liegt. Die Formatierung einer Nachricht erfolgt dann mithilfe eines Cursors, der alle eingefügten Zeilen nacheinander abarbeitet. Da als Nachrichtenformat XML ausgewählt wurde, können wir hier das in Kapitel 8 Gelernte anwenden und die XQuery-Funktion für die Änderung eines XML-Dokumentes verwenden. Eine mit diesem Codeabschnitt erstellte Nachricht kann zum Beispiel so aussehen:
Am Ende der WHILE-Schleife, welche die Zeilen des Cursors abarbeitet, erfolgt dann das eigentliche Senden der Nachricht. Hierfür wird das T-SQL-Kommando SEND verwendet, wobei außer dem eigentlichen Nachrichteninhalt auch angegeben wird, innerhalb welchen Dialogs die Nachricht versendet wird und wie der Nachrichtentyp ist. Beachten Sie hier bitte, dass die Anweisung SEND kein T-SQL-Schlüsselwort ist. Aus diesem Grund ist das angegebene Semikolon hier Pflicht, ansonsten gibt es einen Syntaxfehler. Wurden alle Zeilen durch den Cursor abgearbeitet, wird dann schließlich noch aufgeräumt. Hierbei wird über END CONVERSATION auch der zuvor geöffnete Dialog beendet. Das Beenden des Dialoges führt letztlich auch zum Erzeugen einer speziellen Nachricht, die ganz normal in die Warteschlange eingereiht wird. Sie werden dies einige Abschnitte weiter unten sofort sehen. Konnten Sie den Trigger erfolgreich erzeugen, fügen Sie nun bitte einige Zeilen zur Tabelle Kunden hinzu, etwa so: Listing 9.2 Hinzufügen von zwei Kunden
Nachdem die Zeilen in der Tabelle existieren, sollten nun auch entsprechende Einträge in der Warteschlange vorhanden sein. Dies können Sie, wie oben erwähnt, einfach durch ein SELECT überprüfen:
410
9.2 Erstellung von Anwendungen Abbildung 9.5 zeigt die ersten beiden Spalten des Ergebnisses:
Abbildung 9.5 Der Inhalt der Warteschlange nach zwei angelegten Kunden
Es ist sehr schön zu sehen, dass für jedes der beiden obigen INSERT-Kommandos zwei Nachrichten in der Warteschlange existieren. Die jeweils erste Nachricht enthält den Inhalt der Nachricht im XML-Format. Diese Nachricht ist vom erzeugten Typ //SBSample.org/KdEmail. Für jede END CONVERSATION-Anweisung wurde ebenfalls eine Nachricht in die Warteschlange aufgenommen. Diese Nachricht ist vom vordefinierten Typ http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog. Der Empfänger einer solchen Nachricht kann dann seinerseits entscheiden, ob er ebenfalls die Konversation beendet. Was nun noch fehlt, ist der Code zur Abarbeitung der in der Warteschlange existierenden Einträge. Hierfür wollen wir eine gespeicherte Prozedur verwenden, die diese Nachrichten einzeln nacheinander ausliest und E-Mails entsprechend des Nachrichteninhaltes versendet. Da diese Prozedur E-Mails versenden soll, muss jedoch zunächst die Datenbank-EMail-Funktionalität konfiguriert werden. Wenn diese Konfiguration für Ihren Server noch nicht durchgeführt wurde, können Sie so vorgehen, wie im folgenden Abschnitt beschrieben. Konfiguration von Datenbank-E-Mail Im Management Studio finden Sie im Server-Ordner Verwaltung den Eintrag Datenbank-E-Mail. Wählen Sie für diesen Eintrag aus dem Kontextmenü bitte die Option Datenbank-E-Mail konfigurieren (Abbildung 9.6).
Abbildung 9.6 Konfigurieren von Datenbank-E-Mail
Es wird ein Assistent gestartet, der Sie durch die erforderlichen Schritte geleitet. Nach dem Begrüßungsfenster können Sie auswählen, welche Aufgabe Sie durchführen möchten. Später können Sie existierende Konten und Profile ebenfalls über den gerade gestarteten As-
411
9 SQL Server Service Broker sistenten konfigurieren. Im Moment wählen Sie bitte die Option Datenbank-E-Mail durch Ausführung der folgenden Aufgaben einrichten (Abbildung 9.7).
Abbildung 9.7 Konfiguration eines Profils und Kontos für Datenbank-E-Mail
Im nächsten Schritt des Assistenten geben Sie bitte einen Namen für das zu erstellende Profil ein und wählen die Option Hinzufügen
, um ein SMTP-Konto hinzuzufügen. Es öffnet sich ein Fenster wie in Abbildung 9.8 gezeigt.
Abbildung 9.8 Ein Konto für Datenbank-E-Mail hinzufügen
412
9.2 Erstellung von Anwendungen Geben Sie in diesem Fenster bitte die Daten für das E-Mail-Konto an, das SQL Server für das Versenden von Mails verwenden soll. Im nächsten Fenster können Sie nun bestimmen, welche Profile SQL Server für das Versenden von E-Mails verwenden soll und ob das hinzugefügte Profil das Standardprofil für die Versendung von E-Mails mittels SQL Server sein soll (Abbildung 9.9).
Abbildung 9.9 Konfiguration des Profils
Wählen Sie hier bitte das angelegte Profil als Standardprofil aus.
Abbildung 9.10 Konfiguration von Parametern
413
9 SQL Server Service Broker Der nächste Schritt des Assistenten erlaubt Ihnen die Veränderung von Parametern, die zu Ihrem hinzugefügten E-Mail-Konto gehören (Abbildung 9.10). Belassen Sie es hier bitte bei den vorgewählten Standardeinstellungen, und schließen Sie den Assistenten ab, indem Sie auf der folgenden Seite die Option Fertig stellen auswählen. Um zu kontrollieren, ob Sie das Konto korrekt konfiguriert haben, können Sie nun mit dem angelegten Profil eine Test-E-Mail senden. Wählen Sie hierzu aus dem Kontextmenü für Datenbank-E-Mail die Option Test-E-Mail senden
(Abbildung 9.6). Geben Sie eine gültige E-Mail-Adresse zum Beispiel Ihre eigene als Empfänger an, und schicken Sie die Test-Mail ab. Sie sollten nun eine entsprechende E-Mail vom SQL Server erhalten und können mit dem folgenden Abschnitt fortfahren, in dem wir die Abarbeitung der Warteschlange vornehmen und dafür sorgen, dass aus den in der Warteschlange existierenden Nachrichten E-Mails versendet werden. Abarbeiten der Warteschlange Das Auslesen von Ereignissen aus der Warteschlange erledigt die Prozedur , deren Code in Listing 9.3 zu sehen ist. Listing 9.3 Code zum Abarbeiten der Warteschlange
414
9.2 Erstellung von Anwendungen
In der Prozedur werden zunächst die benötigten Variablen deklariert. Anschließend beginnt eine Endlosschleife, deren wichtigste Anweisung am Beginn steht:
Die Klausel RECEIVE
FROM ist eine Option, die für WAITFOR verwendet werden kann. RECEIVE liest Ereignisse aus einer Warteschlange und entfernt die gelesenen Ereignisse auch aus dieser Warteschlange. Das Auslesen von Ereignissen kann natürlich auch über SELECT erfolgen, nur können Sie dann das ausgelesene Ereignis nicht etwa über DELETE löschen. Somit ist RECEIVE die einzige Möglichkeit, um Einträge auch aus einer Warteschlange zu löschen. Im Beispiel wird über die Option TIMEOUT 500 angegeben, dass 500 ms auf das Eintreffen eines Ereignisses gewartet wird. Konnte ein Ereignis aus der Warteschlange gelesen werden, so werden die benötigten Informationen in die drei Variablen (der Inhalt der eigentlichen Nachricht), (der Nachrichtentyp) und (die ID des Dialoges, der die Konversation steuert) eingetragen. Nach dem Beenden der WAITFOR RECEIVE-Anweisung erfolgt zunächst der Test, ob überhaupt eine Nachricht gelesen werden konnte. Wenn nicht, ist die Warteschlange leer, und die Prozedur kann beendet werden. Anschließend wird der Nachrichtentyp überprüft. Zeigt der Typ an, dass der Dialog beendet wurde, so beendet die Prozedur ihrerseits ebenfalls die Konversation für die erhaltene und liest sofort die nächste Nachricht aus.
415
9 SQL Server Service Broker Nur wenn die erhaltene Nachricht von Typ //SBSample.org/KdEmail ist, wird aus dieser eine E-Mail generiert. Dies erledigt der Code, der innerhalb der Zeilen
steht. Am Ende dieses Code-Abschnitts wird eine E-Mail über den Aufruf der gespeicherten Prozedur verschickt. Die Parameter für den Aufruf dieser Prozedur werden vorher aus der im XML-Format vorhandenen Nachricht geholt. Bevor Sie ausprobieren, ob die Prozedur funktioniert, sollten Sie nun noch dafür sorgen, dass die E-Mail-Adresse der eingefügten Kunden existiert und dass Sie kontrollieren können, ob die versendeten E-Mails auch tatsächlich ankommen. Dies können Sie natürlich am einfachsten erledigen, wenn Sie in den INSERT-Anweisungen zum Hinzufügen von Testkunden nur Ihre eigene E-Mail-Adresse verwenden. Fügen Sie also einige Kunden hinzu, wie in Listing 9.2 gezeigt, und verwenden Sie bitte Ihre eigene E-Mail-Adresse. Anschließend rufen Sie die Prozedur aus Listing 9.3 so auf:
Nach einer Weile sollten in Ihrem Postfach die entsprechenden Benachrichtigungen eingegangen sein (Abbildung 9.11).
Abbildung 9.11 Beispiel für eine empfangene E-Mail-Nachricht
Sie können nach der Abarbeitung der Prozedur nochmals den Inhalt der Warteschlange überprüfen:
Die Warteschlange ist nun leer.
416
9.2 Erstellung von Anwendungen
9.2.3
Service Broker-Aktivierung
Am Ende des vorangegangenen Abschnitts haben Sie gesehen, wie man Ereignisse aus Warteschlangen ausliest und diese Ereignisse abarbeitet. Dies ist dort über den Aufruf der gespeicherten Prozedur geschehen. Die interessante Frage ist nun, wann diese Prozedur eigentlich aufgerufen werden soll? Sicherlich könnte man die Prozedur in zyklischen Abständen aufrufen und somit dafür sorgen, dass Begrüßungs-E-Mails an neue Kunden zum Beispiel grundsätzlich in der Nacht um 03:00 Uhr versendet werden. Möglicherweise ist diese Vorgehensweise aber für die Firma Initech inakzeptabel, weil Kunden bereits so bald wie möglich nach der Registrierung begrüßt werden sollen. In diesem Fall könnte die Prozedur natürlich auch öfter aufgerufen werden, zum Beispiel stündlich. Allerdings wäre es dadurch möglich, dass die Abarbeitung der Warteschlange versehentlich zu einem Zeitpunkt erhöhter Serverlast erfolgt, und das wäre genau dem entgegengesetzt, was Initech mit dem Einsatz des Service Brokers bezweckt hat. Schließlich soll der Server durch den Einsatz des Service Brokers ja entlastet werden, also Warteschlangen in Zeiten geringerer Last abarbeiten. Sie sehen also, dass eine Antwort auf die Frage, wann Ereignisse aus einer Warteschlange ausgelesen und verarbeitet werden sollen, nicht so einfach zu finden ist. Service Broker bietet hierfür den Mechanismus der Aktivierung an. Über die Service Broker-Aktivierung kann eine gespeicherte Prozedur mit einer Warteschlange verknüpft werden. Diese Prozedur wird immer dann aufgerufen, wenn Ereignisse in die Warteschlange eingestellt werden. Sie kann somit die Abarbeitung der Warteschlangen-Ereignisse vornehmen. Bei der Aktivierung überwacht der SQL Server die Länge der Warteschlange, um festzustellen, ob die Prozedur die Ereignisse schnell genug abarbeitet. Falls dies nicht der Fall ist, die Warteschlange also ständig wächst, so startet SQL Server automatisch weitere Prozesse, die ebenfalls Ereignisse aus der Warteschlange auslesen. Die folgende Anweisung ändert die Warteschlange NeueKunden aus unserem Beispiel dahingehend, dass die Service Broker-Aktivierung eingeschaltet wird:
Mit der Warteschlange NeueKunden wird hier unsere Prozedur verknüpft, die automatisch aufgerufen wird, sobald Ereignisse in die Warteschlange eingetragen werden. Die maximale Anzahl von Instanzen der Prozedur für das Abarbeiten der Warteschlange wird über die Klausel MAX_QUEUE_READERS auf drei festgelegt. Über die EXECUTE AS-Klausel wird bestimmt, unter welchem Benutzerkonto die gespeicherte Prozedur ausgeführt wird. SELF steht hier für den aktuellen Benutzer, also denjenigen, der die CREATE QUEUE-Anweisung ausführt. Andere Möglichkeiten sind EXECUTE AS OWNER, für die Ausführung der Prozedur als Besitzer der Warteschlange, oder die explizite Angabe eines Benutzers (zum Beispiel ). Selbstverständlich kön-
417
9 SQL Server Service Broker nen Sie auch bereits beim Erzeugen einer Warteschlange über CREATE QUEUE angeben, dass Sie die Aktivierung verwenden möchten. In jedem Fall muss die in der Option PROCEDURE_NAME angegebene Prozedur aber bereits existieren, also vorher erzeugt werden. Für unser Beispiel, in dem die Prozedur in der Systemdatenbank msdb aufgerufen wird, muss außerdem der Prozess, der die Warteschlange abarbeitet, das Recht zur Ausführung dieser Prozedur erhalten. Dies erreichen Sie für unsere Testzwecke einfach durch das folgende Skript:
Wenn Sie nun erneut Zeilen zur Tabelle Kunden hinzufügen, wird die Prozedur automatisch aufgerufen, und Sie werden nach einer kleinen Wartezeit Ihre Begrüßungs-Mail erhalten. Sie haben nun einen Einblick erhalten, wie man den SQL Server Service Broker verwenden kann, um eigene Benachrichtigungsanwendungen zu erstellen. Service Broker kann aber noch mehr, wie Sie vielleicht schon beim Anblick von Abbildung 9.4 vermutet haben. Jede erzeugte Datenbank verfügt über eine Reihe von Standard-Service BrokerObjekten, die Sie für die Überwachung von Ereignissen verwenden können, die Serveroder Datenbankobjekte verändern. Dies ist Gegenstand des folgenden Abschnitts.
9.3
Überwachung von Server- und Datenbankobjekten Wie bereits erwähnt, können Sie mit dem Service Broker auch Änderungen an Server- und Datenbankobjekten überwachen. Hierzu existieren in jeder Datenbank vordefinierte Service Broker-Komponenten. Eine solche Ereignisbenachrichtigung wird durch die Anweisung CREATE EVENT NOTIFICATION hinzugefügt. Das Kommando hat die folgende, allgemeine Syntax:
Über wird hier der Name der Benachrichtigung festgelegt. Über die Optionen ON und FOR geben Sie an, welche Ereignisse kontrolliert werden sollen. Sie können eine Reihe von Ereignissen überwachen, die Änderungen an Datenbank- und Serverobjekten betreffen. Welche Ereignisse überwacht werden können, hängt von der Art der erzeugten EVENT NOTIFICATION ab. Wird die Ereignisbenachrichtigung für den Server erzeugt (ON SERVER), so stehen Ihnen für die Option <event> andere Ereignisse zur Verfügung als bei einer Ereignisbenachrichtigung, die mit ON DATABASE für eine bestimmte Datenbank erzeugt wurde. Tabelle 9.4 gibt Ihnen hierzu nur einige Beispiele, eine komplette Übersicht finden Sie in der Online-Dokumentation.
418
9.3 Überwachung von Server- und Datenbankobjekten Tabelle 9.4 Ereignisse, die überwacht werden können Geltungsbereich
Ereignis
ON DATABASE
CREATE_USER CREATE_TABLE CREATE_PROCEDURE CREATE_INDEX GRANT_DATABASE CREATE_SCHEMA CREATE_TRIGGER
ON SERVER
CREATE_DATABASE CREATE_LOGIN
Bitte bedenken Sie bei der Betrachtung von Tabelle 9.4, dass Sie für CREATE ebenso DROP oder ALTER einsetzen können. Über die Option TO SERVICE wird schließlich noch angegeben, welcher Dienst in welcher Service Broker-Instanz für die Benachrichtigung verwendet wird. Hier ist interessant, dass die Nachricht nicht nur an die aktuelle Datenbank, sondern an eine beliebige Datenbank auf einem Server, der im Netzwerk erreichbar sein muss, gesendet werden kann. Dadurch bietet sich Ihnen die Möglichkeit, eine eigene Überwachungs-Datenbank anzulegen. An den Service Broker dieser Datenbank können Sie beliebige Ereignisse von verschiedenen Datenbanken oder Servern senden und zentral protokollieren. Insgesamt ist die Erstellung einer Ereignisbenachrichtigung sehr ähnlich zur Erstellung einer normalen Service Broker-Anwendung, wie wir sie in Abschnitt 9.2 entwickelt haben. Nehmen wir an, wir möchten protokollieren, wann zu unserer Datenbank SBSample Benutzer hinzugefügt oder aus ihr entfernt werden. Hierzu muss letztlich eine Ereignisbenachrichtigung für die Ereignisse CREATE_USER und DROP_USER auf der Datenbank angelegt werden. Zunächst einmal benötigt man auch hier eine Warteschlange, die wir sofort beim Erzeugen mit einer Aktivierungsprozedur verbinden. Vorher wird allerdings noch eine Tabelle angelegt, in der die Prozedur die Ereignisse protokolliert:
419
9 SQL Server Service Broker
Ereignisse werden grundsätzlich in einem speziellen XML-Format in die zugehörige Warteschlange eingetragen, dies wird weiter unten nochmals angesprochen. Die Prozedur liest jeweils ein Ereignis aus der erzeugten Warteschlange und trägt dieses Ereignis in die erzeugte Tabelle EventLog ein. Wir können nun einen Dienst hinzufügen, der die Ereignisse entgegennimmt:
Der Dienst EventHistorsService verwendet den Standardvertrag http://schemas.microsoft.com/SQL/Notifications/PostEventNotification, der in jeder Datenbank vorhanden ist. Zum Schluss kann nun die eigentliche Ereignisbenachrichtigung erstellt werden:
Die Ereignisbenachrichtigung wird dafür sorgen, dass für alle CREATE USER- und DROP USER-Kommandos über den angelegten Dienst ein Eintrag in die Warteschlange aufgenommen wird. Dieser Vorgang wird dazu führen, dass die Prozedur aufgerufen wird, die das Ereignis in unserer Tabelle EventLog festhält. Wir können nun ausprobieren, ob die Benachrichtigung funktioniert, indem wir für die Datenbank Benutzer hinzufügen und löschen. Dies kann zum Beispiel über das folgende Skript geschehen:
In der Tabelle werden die aufgetretenen Ereignisbenachrichtigungen protokolliert. Wenn Sie diese Tabelle abfragen, so erhalten Sie zwei Zeilen, die allerdings nur
420
9.4 Zusammenfassung recht spärlich Auskunft darüber geben, welche Ereignisse aufgetreten sind. Etwas genauere Information bekommt man aus unserer Tabelle EventLog:
Hier erhalten Sie ebenfalls zwei Zeilen als Ergebnis, jedoch werden diesmal die aufgetretenen Ereignisse im XML-Format ausgegeben. Für das Kommando CREATE USER sieht der Inhalt des XML-Dokumentes in etwa so aus:
In diesem Protokolleintrag finden Sie alle für das Ereignis relevanten Informationen, wie Datum und Uhrzeit, auslösende Anweisung, ausführender Benutzer, Server, Datenbank etc.
9.4
Zusammenfassung In diesem Kapitel haben Sie gelernt, wozu der SQL Server Service Broker verwendet werden kann und wie man mit dem Service Broker eine ereignisgesteuerte, asynchrone Datenverarbeitung organisiert. Dabei wurde Ihnen eine Einführung in die Architektur des Service Brokers gegeben und erläutert, welche Komponenten Service Broker enthält und wie man Service Broker-Anwendungen erstellen kann. Sie haben an einer sehr einfachen Beispielanwendung die wichtigsten Bestandteile von Service Broker eingesetzt und deren Zusammenwirken untersucht. Im zweiten Teil dieses Kapitels konnten Sie sehen, wie man den Service Broker zur Überwachung von Änderungen an Server- und Datenbankobjekten einsetzt. Service Broker ist eine sehr mächtige und komplexe Komponente von SQL Server, mit deren Hilfe zum Beispiel eine Massenstapelverarbeitung organisiert werden kann. Ein sorgfältig geplanter Einsatz des Service Brokers kann die Antwortzeiten Ihrer Datenbank drastisch senken und die Skalierung Ihrer Anwendung erleichtern.
421
10 Datenbankentwicklung mit .NET Die Integration der Common Language Runtime (CLR) in die SQL Server Engine ist sicher eines der spektakulärsten Features von SQL Server 2005. Für den Datenbankentwickler ergibt sich dadurch die Möglichkeit, SQL Server-Datenbankobjekte in einer .NETProgrammiersprache, wie C# oder VB.NET, zu erstellen. Dieses Kapitel wird Ihnen nach einer kurzen Einführung zeigen, wie Sie Datenbankobjekte in der Programmiersprache C# entwickeln können. Hierbei werden wir vor allem Visual Studio 2005 als Entwicklungsumgebung einsetzen. Sie werden aber auch sehen, wie Sie solche Objekte ohne die Verwendung dieser integrierten Entwicklungsumgebung erstellen können.
10.1
SQL Server und die .NET CLR Die Common Language Runtime (CLR) ist die Ausführungsumgebung für .NET. Prinzipiell wird .NET-Quellcode zunächst aus dem Quellcode einer Programmiersprache, wie zum Beispiel C# oder VB.NET, lediglich in eine Zwischensprache, die Intermediate Language (abgekürzt IL), übersetzt. Dieser IL-Code kann jedoch nicht direkt ausgeführt werden. Für die Ausführung des IL-Codes ist die CLR erforderlich, die zuvor gestartet werden muss. Der IL-Code wird dann zur Laufzeit durch den Just-In-Time (JIT)-Compiler der CLR zunächst in ausführbaren Maschinencode übersetzt und erst dann gestartet. Die CLR beherbergt (hosted) also sozusagen den eigentlichen Programmcode. Neben dem Laden und Ausführen von Programmen übernimmt die CLR die volle Kontrolle über den Programmablauf und erledigt dabei auch Aufgaben wie Speicherverwaltung, Garbage Collection und Zugriffskontrolle. Zu den bekannten CLR-Hosts gehören zum Beispiel ASP.NET und Windows und nun eben auch der SQL Server 2005, der ebenfalls in der Lage ist, IL Code-DLLs zu laden und
423
10 Datenbankentwicklung mit .NET zu starten. In der Terminologie des SQL Servers 2005 werden diese DLLs so wie bereits aus der .NET-Welt bekannt als Assemblys1 bezeichnet. Die CLR-Integration bietet eine Reihe von Möglichkeiten für die Datenbankentwicklung. Herausragend sind hierbei vielleicht die folgenden: Zugriff auf viele (aber nicht alle!) Objekte der .NET-Klassenbibliothek. Der Zugriff auf die Elemente des .NET Frameworks eröffnet natürlich fantastische Möglichkeiten. Das Programmiermodell, das Ihnen dadurch zur Verfügung steht, ist gegenüber reinem T-SQL erheblich umfangreicher und leistungsfähiger. Zu beachten ist allerdings, dass Ihnen nicht alle Klassen aus der vorhandenen Bibliothek zur Verfügung stehen. So haben Sie zum Beispiel keinen Zugriff auf Elemente der WindowsBenutzeroberfläche, wie sie in oder enthalten sind. Ausführung von managed Code direkt im SQL Server. Dieses Feature betrifft vor allem die Punkte Stabilität, Performance und Sicherheit. SQL Server als CLR-Host hat die volle Kontrolle über den ausgeführten Code und somit die Möglichkeit, diesen Code zu überwachen und bei Problemen einzugreifen. Integration von T-SQL und .NET. In .NET entwickelte Objekte werden vom SQL Server tatsächlich wie eigene Objekte angesehen. Sie können zum Beispiel Ihre in Assemblys existierenden Prozeduren und Funktionen im T-SQL-Code ganz genau so benutzen, als wenn diese zum SQL Server hinzugehören würden. Verwenden von Visual Studio 2005 als Entwicklungsumgebung. Visual Studio 2005 bietet eine einheitliche Entwicklungsumgebung für die Erstellung von Anwendungen, die nun auch das Entwickeln von Datenbankobjekten in einer .NETProgrammiersprache unterstützt. Dadurch können Entwickler, die bereits mit Visual Studio vertraut sind, fast unmittelbar also ohne größeren Lernaufwand mit der Entwicklung solcher Datenbankobjekte beginnen. Integriertes Debuggen. Die Integration in Visual Studio erstreckt sich auch auf das Debuggen von erstellten Datenbankobjekten, was natürlich eine enorme Hilfe bei der Entwicklung bedeutet. Die CLR-Integration erlaubt die Erstellung folgender Datenbankobjekte unter .NET: Gespeicherte Prozeduren Benutzerdefinierte Typen Benutzerdefinierte Funktionen Trigger Benutzerdefinierte Aggregate
1
424
Dies ist kein Schreibfehler, der Begriff wird in der deutschen Dokumentation zum SQL Server tatsächlich so verwendet.
10.2 Sicherheitsaspekte Bevor wir uns nun damit beschäftigen können, wie Assemblys erstellt und im SQL Server registriert werden, sind noch einige einleitende Bemerkungen zum Thema Sicherheit erforderlich.
10.2
Sicherheitsaspekte .NET-Programme bieten die Möglichkeit, über eine Reihe von Elementen aus der .NETKlassenbibliothek auch auf externe Ressourcen, wie z. B. das Dateisystem oder das Netzwerk, zugreifen zu können, was natürlich bezogen auf die Integration solcher Programme in den SQL Server ein Sicherheitsrisiko darstellt. Gespeicherte Prozeduren können beispielsweise auf Einstellungen oder Dateien eines im Netzwerk befindlichen Webservers zugreifen, was möglicherweise unterbunden werden soll. Die sorgfältige Einstellung der erforderlichen Sicherheitsoptionen für die Integration von .NET-Assemblys ist daher unerlässlich. SQL Server erlaubt nach der Installation zunächst nicht die Registrierung von Datenbankobjekten, die in einer .NET-Programmiersprache erstellt wurden. Wenn Sie solche Datenbankobjekte verwenden werden, so muss die CLR-Integration für den SQL Server zuvor explizit aktiviert werden. Hierzu können Sie das Programm zur SQL ServerOberflächenkonfiguration benutzen, das Sie im SQL Server-Startmenü, dort unter Konfigurationstools, finden. Wählen Sie nach dem Start des Programms die Option Oberflächenkonfiguration für Features und dann den Zweig CLR-Integration (Abbildung 10.1).
Abbildung 10.1 Aktivieren der CLR-Integration
425
10 Datenbankentwicklung mit .NET Alternativ können Sie die CLR-Integration auch über T-SQL durch die gespeicherte Prozedur aktivieren:
Nachdem der Server nun die Registrierung von Assemblys gestattet, wollen wir unsere erste Assembly entwickeln und bereitstellen.
10.3
Assemblys Betrachten Sie bitte den folgenden C#-Code: Listing 10.1 Eine erste Assembly
Der Code enthält eine Klasse , in der eine einzige Funktion enthalten ist, die einen Wert vom Typ zurückgibt. Der Datentyp ist im Namensraum enthalten und repräsentiert die SQL-Version einer Zeichenkette im .NET-Code. Sie können den obigen Code einfach mit Notepad eingeben und anschließend mit dem C#Compiler in eine DLL übersetzen. Nehmen wir an, Sie haben eine Datei SayHello.cs erzeugt, dann könnte der Compileraufruf so aussehen:
Durch den Aufruf des Compilers auf diese Weise wird die DLL SayHello.dll im selben Verzeichnis wie die Quelldatei SayHello.cs erzeugt. Herzlichen Glückwunsch! Sie haben soeben Ihre erste .NET-Assembly für den SQL Server erzeugt. Die Assembly enthält eine Funktion SayHelloTo, die einfach nur eine Zeichenkette mit einem Hallo-Text zurückgibt. Wie aber gelangt diese Funktion nun in den SQL Server? Eine mit dem .NET Framework erzeugte DLL muss im SQL Server 2005 als eine sogenannte Assembly registriert werden. Hierfür existiert das Kommando CREATE ASSEMBLY, das im einfachsten Fall so aufgerufen wird:
426
10.3 Assemblys
steht hier für den Namen der Assembly, wie er in der SQL Server-
Datenbank verwendet wird, für den exakten Pfad zur DLL, die den Assembly-Code enthält. Über die Klausel WITH PERMISSION SET wird die Berechtigungsebene für die Assembly eingestellt. Diese Berechtigungsebene spezifiziert die Sicherheitsoptionen in Bezug auf Ressourcenzugriffe aus der Assembly heraus. Der Standardwert für diese Einstellung also wenn die WITH PERMISSION_SET-Klausel weggelassen wird ist SAFE, wodurch keinerlei Zugriffe auf Ressourcen außerhalb SQL Server zugelassen werden. Soll zum Beispiel auf das Windows-Dateisystem zugegriffen werden, so müssen Sie bei der Registrierung der Assembly eine niedrigere Sicherheitsebene festlegen. Tabelle 10.1 erklärt die zur Verfügung stehenden Optionen. Tabelle 10.1 PERMISSION_SET-Optionen PERMISSION_SET
Erklärung
SAFE
Ein Zugriff auf externe Ressourcen ist nicht möglich.
EXTERNAL_ACCESS
Der Zugriff auf externe Ressourcen, wie zum Beispiel die Registry, das Dateisystem oder das Netzwerk, ist erlaubt.
UNSAFE
Es existieren keinerlei Einschränkungen. Insbesondere ist auch der Zugriff auf unmanaged Code erlaubt.
Für unsere gerade erstellte Assembly ist keinerlei Zugriff auf Ressourcen außerhalb SQL Server erforderlich, die (Standard-)Option SAFE ist daher die richtige Wahl. Der Aufruf zur Registrierung der Assembly kann also etwa so aussehen, vorausgesetzt die Assembly soll in der Datenbank AdventureWorks registriert werden:
Damit existiert die Assembly in der Datenbank AdventureWorks. Sie können dies überprüfen, indem Sie im Management Studio den Zweig Programmierbarkeit/Assemblys für diese Datenbank öffnen (Abbildung 10.2).
427
10 Datenbankentwicklung mit .NET
Abbildung 10.2 Die registrierte Assembly im Management Studio
Bei der Bereitstellung einer Assembly wird der gesamte Inhalt der Assembly-DLL in die Systemtabellen der Datenbank übertragen. Die DLL muss daher nur für den Zeitraum des CREATE ASSEMBLY-Kommandos zur Verfügung stehen. Sie können die registrierten Assemblys über die Anweisung:
abfragen. In der Spalte content sehen Sie den in der DLL enthaltenen Binärcode. Durch diese Verfahrensweise ist sichergestellt, dass Ihr DLL-Code, welcher der Ausführung der in der DLL enthaltenen Datenbankobjekte zugrunde liegt, auch in Datensicherungen berücksichtigt wird und vom Dateisystem abgekoppelt ist. Über den Systemview können Sie ebenfalls Informationen über die in einer Datenbank registrierten Assemblys erhalten:
Es ist übrigens auch möglich, eine Assembly mit dem Management Studio zu registrieren. Hierzu wählen Sie einfach aus dem Kontextmenü für Assemblys den Punkt Neue Assembly
und geben im anschließenden Dialog den Verweis auf die DLL ein. Es gibt eine dritte Möglichkeit, eine Assembly zu registrieren, wenn Sie ein Visual StudioDatenbankprojekt erstellt haben. Etwas später werden wir hierauf zurückkommen. Wo ist nun aber unsere Funktion ? Die Antwort auf diese Frage ist sicherlich ebenso überraschend wie einfach: Die Funktion existiert nicht. Die in einer Assembly enthaltenen Datenbankobjekte müssen in der Datenbank separat erzeugt werden. Hierbei werden sozusagen T-SQL-Wrapper für die in der Assembly enthaltenen Objekte erstellt. Hierzu können Sie die folgenden teilweise bereits bekannten Anweisungen zum Erzeugen von Prozeduren, Funktionen, Triggern, Typen und Aggregaten verwenden:
428
10.3 Assemblys CREATE PROCEDURE CREATE FUNCTION CREATE TRIGGER CREATE TYPE CREATE AGGREGATE Die einzelnen CREATE-Kommandos werden dabei stets eine Klausel EXTERNAL enthalten, um anzuzeigen, dass das entsprechende Objekt aus einer externen Assembly erzeugt werden soll. Für unsere Funktion sieht diese Erzeugung so aus:
Die Anweisung erzeugt eine T-SQL-Funktion, die einen Parameter vom Typ erwartet und einen Wert vom Typ zurückgibt. Diese Funktion wird in der Assembly , dort in der Funktion der Klasse implementiert. Dies wird durch die Option spezifiziert. Nach der Registrierung der Funktion können Sie diese im Zweig Programmierbarkeit/Funktionen/Skalarwertfunktionen im Management Studio auch sehen (Abbildung 10.3).
Abbildung 10.3 Die Funktion SayHelloTo
Die Funktion existiert nun in der Datenbank AdventureWorks, und wir können sie benutzen wie eine ganz normale T-SQL-Funktion:
Das Ergebnis der obigen SELECT-Anweisung sehen Sie in der folgenden Abbildung:
429
10 Datenbankentwicklung mit .NET
Für den Aufrufer der Funktion ist letztlich nicht ersichtlich, dass die Implementierung in einer .NET-Assembly erfolgt ist.
10.4
Der Namensraum Microsoft.SqlServer.Server Der Namensraum enthält Klassen, Schnittstellen und auch Compiler-Attribute, die Sie für die Programmierung Ihrer Datenbankobjekte verwenden können. Wichtig für den Datenbankentwickler sind hier vor allem die Klassen , , und . Die Klasse SqlContext kann als eine Art Verbindungsglied zwischen .NET-Programmcode und SQL
Server verstanden werden. Das wichtigste Objekt in dieser Klasse ist sicherlich das statische Objekt , das vom Typ ist. Die Klasse SqlPipe Die Funktion in der Klasse ermöglicht das Übertragen von Ergebnissen an den Aufrufer. ist mehrfach überladen, wodurch Sie sowohl einfachen Text als auch einzelne Datensätze vom Typ oder auch ein gesamtes Objekt zurückgeben können. In unseren Beispielen werden Sie sehen, wie dies funktioniert. Die Klasse SqlTriggerContext Sofern Sie einen Trigger erstellt haben, können Sie im Code des Triggers die Klasse benutzen, um abzufragen, welches Ereignis den Trigger ausgelöst hat. Hierzu steht Ihnen das statische Objekt vom Typ zur Verfügung. In Abschnitt 10.6.6 werden Sie sehen, wie Sie verwenden. Die Klasse SqlDataRecord Die Klasse repräsentiert eine einzelne Datenzeile. Sie können diese Zeile zum Beispiel mit der Funktion an den Client übertragen. Auch hierfür werden Sie in Kürze ein Beispiel sehen.
430
10.5 Datentypen der Namensraum System.Data.SqlTypes
10.5
Datentypen der Namensraum System.Data.SqlTypes Die .NET-eigenen Datentypen wie zum Beispiel oder sollten im Code für die Programmierung der Datenbankobjekte nicht verwendet werden. Für alle .NETDatentypen existieren im Namensraum Entsprechungen, die Sie in Ihrem Code auch benutzen sollten. Hauptgrund für diese Verfahrensweise ist die Möglichkeit, für SQL Server-Variablen auch den Wert NULL verwenden zu können, was für die .NET-Datentypen nicht möglich ist. Falls Sie also zum Beispiel einen Parameter vom Typ in einer in .NET entwickelten gespeicherten Prozedur empfangen, so werden Sie möglicherweise einen Laufzeitfehler erhalten, wenn Sie den Wert NULL für diesen Parameter übergeben. Die Namen der SQL Server-Datentypen sind relativ einfach aus den .NET-eigenen Datentypen abzuleiten. So ist der .NET-Datentyp zum Beispiel , wird zu usw. Tabelle 10.2 enthält eine komplette Übersicht hierzu. Tabelle 10.2 Gegenüberstellung der Datentypen
10.6
Datentyp in System.Data.SqlTypes
SQL Server-Datentyp
SqlBinary
binary, image, timestamp, varbinary
SqlBoolean
bit
SqlByte
tinyint
SqlDateTime
datetime, smalldatetime
SqlDecimal
decimal
SqlDouble
float
SqlGuid
uniqueidentifier
SqlInt16
smallint
SqlInt32
int
SqlInt64
bigint
SqlMoney
money, smallmoney
SqlSingle
real
SqlString
char, nchar, text, ntext, nvarchar, varchar
SqlXml
xml
Datenbankprojekte mit Visual Studio 2005 erstellen Nachdem nun die Grundlagen für die Erstellung von CLR-Datenbankobjekten behandelt wurden und auch bereits eine erste einfache Assembly erstellt sowie im SQL Server registriert wurde, wollen wir in diesem Abschnitt etwas komplexere Beispiele entwickeln. Hier-
431
10 Datenbankentwicklung mit .NET zu werden wir aber nicht mehr Notepad und die Kommandozeilenversion des C#Compilers verwenden, sondern die integrierte Entwicklungsumgebung Visual Studio 2005. Wir werden zunächst ein Visual Studio 2005-Datenbankprojekt erzeugen und diesem Projekt dann Prozeduren, Funktionen, Typen, Aggregate und Trigger hinzufügen. Hierzu erstellen wir zunächst ein neues Projekt und werden dann Schritt für Schritt unsere Datenbankobjekte addieren.
10.6.1 Erstellen eines Datenbankprojektes Nach dem Start von Visual Studio 2005 kann über das Menü Datei/Neu/Projekt
ein neues Projekt angelegt werden. Anschließend wählen wir Visual C#/Datenbank/SQL Server-Projekt als Projekttyp und geben für das Projekt den Namen CLRSamples sowie ein Verzeichnis an (Abbildung 10.4).
Abbildung 10.4 Ein SQL Server-Projekt anlegen
Die Projektvorlage SQL Server-Projekt steht Ihnen ab der Version Visual Studio Professional zur Verfügung. Wenn Ihre Visual Studio-Version zu klein ist, können Sie sich auf der Microsoft-Website eine 90-Tage-Testversion herunterladen. Nach dem Klick auf OK wird das Projekt angelegt. Es folgt nun die Aufforderung, eine Datenbankverbindung anzugeben. Dort tragen wir die Verbindungsinformationen zur AdventureWorks-Datenbank ein, unsere CLRDatenbankobjekte sollen in dieser Datenbank erstellt werden (Abbildung 10.5).
432
10.6 Datenbankprojekte mit Visual Studio 2005 erstellen
Abbildung 10.5 Angeben der Verbindung zur Datenbank
Über den Button Testverbindung kann hier noch überprüft werden, ob die Verbindungsinformationen korrekt sind. Nach dem Schließen des Dialoges mit OK existiert dann ein leeres SQL Server-Projekt (Abbildung 10.6).
Abbildung 10.6 Das erzeugte SQL Server-Projekt
433
10 Datenbankentwicklung mit .NET Das Projekt enthält bereits Einstellungen und Verweise, die für die spätere Erstellung und Bereitstellung einer Assembly im SQL Server erforderlich sind. Insbesondere ist auch ein SQL-Skript Test.sql enthalten, das wir später noch benötigen werden. Wir können nun beginnen, Datenbankobjekte zum Projekt hinzuzufügen, und beginnen mit einigen gespeicherten Prozeduren.
10.6.2 Erstellen von gespeicherten Prozeduren Eine gespeicherte Prozedur kann einfach über das Kontextmenü für das Projekt (Hinzufügen/Gespeicherte Prozedur
siehe Abbildung 10.7) erzeugt werden.
Abbildung 10.7 Eine gespeicherte Prozedur hinzufügen
Im anschließend erscheinenden Fenster wählen wir Prozeduren.cs als Namen für die zu erstellende Quellcodedatei. Visual Studio geht davon aus, dass jede Prozedur in einer eigenen Quellcodedatei angelegt wird. In jeder dieser Dateien wird standardmäßig eine Klasse StoredProcedures die mit der Option partial deklariert wird durch den für die Implementierung der Prozedur erforderlichen Code erweitert. Es ist jedoch auch möglich, mehrere Prozeduren in einer Klasse unterzubringen, das ist der Weg, den wir für unser Beispiel wählen. Daher auch der Name der Datei: Prozeduren.cs, die den Code für all unsere Prozeduren enthalten wird. Das erzeugte Gerüst für unsere Prozedur sieht folgendermaßen aus:
434
10.6 Datenbankprojekte mit Visual Studio 2005 erstellen
Der Quellcode enthält zunächst alle notwendigen Verweise zur .NET-Klassenbibliothek. Als Name der erzeugten Klasse wurde der Standard vergeben. Innerhalb dieser Klasse können Methoden, Eigenschaften und Funktionen erstellt werden. Es wurde bereits ein Gerüst für eine Prozedur erstellt, deren Name Prozeduren dem eingegebenen Dateinamen entspricht. Beachten Sie auch die Direktive , durch die veranlasst wird, dass bei der Bereitstellung der Assembly aus der Funktion Prozeduren eine gespeicherte Prozedur gleichen Namens erzeugt wird.. Wir können nun den Code für die Klasse schrittweise verändern und einige gespeicherte Prozeduren hinzufügen. Hello World Beginnen wollen wir natürlich mit dem Klassiker schlechthin:
Der Name der vom Assistenten erzeugten Prozedur wird in geändert und eine Programmzeile hinzugefügt, die den Text Hello World an den Client zurückgibt. Wenn die -Methode des -Objektes mit einer Zeichenkette als Parameter aufgerufen wird, so wird der Text dieser Zeichenkette im Meldungsfenster ausgegeben. Die Wirkungsweise ist also dieselbe wie beim T-SQL-Befehl PRINT. Um nun die Assembly und alle in ihr enthaltenen Objekte bereitzustellen, werden wir ein sehr nützliches und zweckmäßiges Feature von Visual Studio ausnutzen, das nämlich genau diese Operationen für uns erledigen kann. Hierzu sind nur ein paar Mausklicks erforderlich. Im Hauptmenü kann dies durch die Auswahl von Erstellen/CLRSamples bereitstellen erfolgen. Alle erforderlichen Registrierungen werden dann durch Visual Studio erledigt, wobei die bei der Erstellung des Projektes angegebene Verbindung zur AdventureWorks-Datenbank benutzt wird (Abbildung 10.8).
435
10 Datenbankentwicklung mit .NET
Abbildung 10.8 Bereitstellen der entwickelten Assembly
Nach der erfolgreichen Registrierung kann die gespeicherte Prozedur genau so aufgerufen werden wie jede andere gespeicherte Prozedur auch:
Im Meldungsfenster des Abfrageeditors wird dadurch der Text Hello World. ausgegeben. Rückgabe von Tabular Data Streams Wenn Ihre Prozedur ein Ergebnis an den SQL-Ergebnisbereich zurückgeben soll, so können Sie hierfür auch verwenden. Der folgende Code erstellt eine zweite Prozedur , in der ebenfalls der Text Hello World. an den Client zurückgegeben wird, nur diesmal als SQL-Ergebnis, also als Tabular Data Stream (TDS):
Hierbei nutzen wir aus, dass auch einen als Parameter akzeptiert. Wenn nun die Assembly erneut bereitgestellt wird, so existiert eine zweite Prozedur, , die nach der Ausführung den Text Hello World. im Ergebnisbereich ausgibt. Rufen Sie die Prozedur so auf:
dann erhalten Sie das folgende Ergebnis:
436
10.6 Datenbankprojekte mit Visual Studio 2005 erstellen
Sie können im Management Studio überprüfen, ob die Assembly und die beiden Prozeduren existieren, so wie bereits in Abbildung 10.3 gezeigt. Die gerade erstellten Prozeduren finden Sie im Zweig Programmierbarkeit/Gespeicherte Prozeduren der Datenbank AdventureWorks. Das nächste Beispiel zeigt, wie man mehrere Zeilen als TDS an den Client zurücksendet. Auch hierfür wird natürlich wieder die Funktion verwendet:
Im Code wird zunächst die Struktur der Tabelle erzeugt, die zurückgegeben wird. Diese Tabelle soll zwei Spalten RowNumber und TimeCreated enthalten, in die später in der for-Schleife die Zeilennummer und die aktuelle Uhrzeit (inklusive Datum) eingetragen werden. Dies geschieht innerhalb der Schleife durch die Aufrufe von
Der interessante Teil des Codes ist sicherlich die Technik, mit der ein Resultset zurückgegeben wird. Hierzu werden die Funktionen , und verwendet, die jeweils aufgerufen werden, um eine Übertragung zu beginnen, eine Zeile zum Client zu senden und die Übertragung wieder zu beenden.
437
10 Datenbankentwicklung mit .NET Nach der erneuten Bereitstellung der Assembly kann die Prozedur nun verwendet werden. Um zum Beispiel 1000 Zeilen zu erhalten, rufen Sie die Prozedur so auf:
Im Ergebnisbereich werden dann 1000 Zeilen angezeigt. Verwendung von Ausgabeparametern So wie in T-SQL-Prozeduren können Sie auch in mit .NET erstellten Prozeduren Ausgabeparameter verwenden. Hierzu werden die von der Prozedur empfangenen Parameter einfach als deklariert:
Nach der Bereitstellung der Assembly steht Ihnen die Prozedur zur Verfügung, die Sie wie gewohnt benutzen können:
Die SELECT-Anweisung gibt die erwarteten Werte zurück:
Die Kontextverbindung zum SQL Server Das folgende Beispiel zeigt, wie Sie innerhalb Ihres Codes eine Verbindung zum SQL Server herstellen:
Wenn Sie den obigen Code betrachten, werden Sie feststellen, dass Sie das aus der ADO.NET-Entwicklung bekannte Programmiermodell auch für die SQL Server-
438
10.6 Datenbankprojekte mit Visual Studio 2005 erstellen Programmierung verwenden können. Die bekannten Klassen für Verbindungen (), Kommandos () oder auch die Klasse können Sie bei der Entwicklung von SQL Server-Objekten genau so verwenden, wie Sie dies von der ADO.NET-Entwicklung her kennen. Eine Besonderheit im obigen Code ist der Aufbau einer Verbindung zum SQL Server. Durch die Angabe von context connection=true wird die sogenannte Kontextverbindung verwendet. Die Assembly wird ja im Kontext der Datenbank ausgeführt, in der sie registriert wurde. Das Benutzen der Kontextverbindung bewirkt die Verwendung der bereits vorhandenen Verbindung zur eigenen Datenbank. Sie könnten natürlich auch eine separate Verbindung zum Server herstellen, indem Sie den String mit der Verbindungsinformation entsprechend konfigurieren, also zum Beispiel so:
Dies würde allerdings eine zusätzliche Verbindung öffnen und somit unnötig Ressourcen verbrauchen. Außerdem würde die Kommunikation über eine solche externe Verbindung über zusätzliche Netzwerkschichten verlaufen und somit langsamer sein. Etwas kurios mag Ihnen der Aufruf von und für die Kontextverbindung erscheinen. Diese Verbindung ist ja immer offen und kann daher weder erneut geöffnet und schon gar nicht geschlossen werden. Der Grund dafür, dass zumindest das Öffnen der Verbindung im Code geschehen muss (ansonsten erhalten Sie einen Laufzeitfehler) ist einzig und allein der Umstand, dass Sie als Entwickler das von ADO.NET bekannte Programmiermodell verwenden sollen (oder besser: dürfen). In dieser Umgebung ist nun einmal das Öffnen einer Verbindung für die Ausführung von Kommandos auf dieser Verbindung erforderlich. Der Code zeigt auch, wie Sie ein -Objekt an den Client zurückgeben. Hierfür wird wiederum die Funktion verwendet. Die Prozedur führt die als Zeichenkette übergebene SQL-Anweisung aus und liefert das Ergebnis als TDS zurück. Nach der Bereitstellung der Assembly können Sie die Prozedur zum Beispiel so aufrufen, um alle Zeilen und Spalten aus der Tabelle Person.Contacts zu erhalten:
Zugriff auf externe Ressourcen Das folgende Beispiel zeigt, wie einfach es ist, durch den Einsatz der CLR-Integration sozusagen eine Verbindung zwischen T-SQL und Ressourcen des Betriebssystems herzustellen:
439
10 Datenbankentwicklung mit .NET Die Funktion speichert das im ersten Parameter übergebene XML-Dokument in einer Datei, deren Name als zweiter Parameter an die Funktion übergeben wird. Der Beispielcode zeigt auch die Verwendung der Direktive , um die Prozedur in der Datenbank mit dem Namen zu registrieren. Wenn nun die Assembly erneut bereitgestellt wird, kann die Prozedur anschließend aufgerufen und überprüft werden, ob eine entsprechende XML-Datei erstellt wird. Dies kann zum Beispiel so geschehen: Listing 10.2 Speichern eines XML-Dokumentes in einer Datei
Führen Sie den obigen Code aus, so werden Sie allerdings eine Fehlermeldung erhalten, die wie folgt beginnt:
Ursache hierfür ist, dass Visual Studio unsere Assembly mit der Option
registriert und der Assembly dadurch der Zugriff auf das Dateisystem des Computers verwehrt wird. Dies kann aber durch eine Anpassung der Assembly-Eigenschaften leicht geändert werden. Über das Menü Projekt/CLRSamples-Eigenschaften
wird das entsprechende Fenster geöffnet, in dem die Berechtigungsebene für die Assembly in der Registerkarte Datenbank festgelegt werden kann (Abbildung 10.9).
Abbildung 10.9 Festlegen der Berechtigungsebene
440
10.6 Datenbankprojekte mit Visual Studio 2005 erstellen Durch die Auswahl von Extern PERMISSION_SET=SAFE registriert.
wird
die
Assembly
mit
der
Option
Nach der Konfiguration der erforderlichen Berechtigungsebene können wir die Assembly nun erneut bereitstellen. Diesmal bekommen wir allerdings bereits beim Versuch, die Assembly zu registrieren, eine Fehlermeldung von Visual Studio (Abbildung 10.10).
Abbildung 10.10 Fehler bei der Registrierung
Die Fehlermeldung besagt letztlich, dass unsere AdventureWorks-Datenbank die Registrierung einer Assembly mit PERMISSION_SET=EXTERNAL_ACCESS nicht gestattet. Für unsere weiteren Experimente soll die Sicherheitsstufe für die Datenbank AdventureWorks daher zunächst herabgesetzt werden. Die Datenbank wird hierfür in den vertrauenswürdigen Modus versetzt. Dies kann durch die Anweisung:
erreicht werden. Durch diese Einstellung können unsere erstellten Module, wie zum Beispiel Prozeduren und Funktionen, dann auch auf Ressourcen außerhalb der Datenbank zugreifen. Normalerweise kann die Einstellung auf ON gesetzt werden, wenn bekannt ist, dass keine bösartigen Assemblys installiert wurden. Hauptsächlicher Zweck der Option TRUSTWORTHY ist es, dem Administrator eine einfache Möglichkeit zu bieten, den Zugriff auf externe Ressourcen bei Verdacht auf bösartige Assemblys schnell unterbinden zu können. Das Versetzen einer Datenbank in den nicht vertrauenswürdigen Modus verbietet den Zugriff auf externe Ressourcen aus allen Assemblys dieser Datenbank, sofern für diese Assemblys nicht spezielle Konfigurationen vorgenommen wurden. Welche Konfigurationen erforderlich sind, um Assemblys mit der Option PERMISSION_SET=EXTERNAL_ACCESS auch in Datenbanken mit der Einstellung TRUSTWORTHY OFF registrieren zu können, werden wir später in diesem Kapitel noch sehen. Wenn nun die Assembly erneut bereitgestellt und anschließend der Code aus Listing 10.2 nochmals ausgeführt wird, sollte die XML-Datei korrekt erzeugt werden. war das abschließende Beispiel für eine mit dem .NET Framework erstellte
gespeicherte Prozedur. Im nächsten Abschnitt werden wir nun benutzerdefinierte Funktionen zu unserer Assembly hinzufügen.
441
10 Datenbankentwicklung mit .NET
10.6.3 Erstellen von benutzerdefinierten Funktionen Wie bei T-SQL-Funktionen, so können auch unter .NET erstellte benutzerdefinierte Funktionen einen Skalarwert oder eine Tabelle zurückliefern. Auch .NET-Funktionen dürfen keinerlei Seiteneffekte enthalten, die Datenänderungen durchführen. UPDATE-, INSERToder DELETE-Anweisungen innerhalb einer benutzerdefinierten Funktion sind also nicht gestattet. Skalarwertfunktionen Bereits zu Beginn dieses Kapitels haben wir in unserem ersten Beispiel eine Skalarwertfunktion programmiert, die einfach eine Hello-Zeichenkette zurückgeliefert hat. Für den Fall, dass Sie sich hieran nicht mehr erinnern, schauen Sie sich bitte noch einmal Listing 10.1 an. Wir wollen nun zu unserem Projekt eine Skalarwertfunktion hinzufügen, die eine der Stärken der .NET-Integration herausstreicht. Die Funktion soll eine E-Mail-Adresse auf Korrektheit überprüfen. Hierzu fügen wir zu unserem Projekt zunächst eine neue Quellcodedatei Funktionen.cs hinzu. Dies kann wieder über das Kontextmenü des Projektes erledigt werden, so wie bereits in Abschnitt 10.6.2 und Abbildung 10.7 gezeigt, nur wählen wir diesmal anstelle von Gespeicherte Prozedur
den Punkt Benutzerdefinierte Funktion... aus. Nach Angabe des Dateinamens Funktionen.cs im Dialog Neues Element hinzufügen erhalten wir zunächst wieder ein Gerüst, diesmal für eine benutzerdefinierte Funktion. Der Name der Klasse wird auf belassen. Der Name unserer Funktion soll lauten, und die Funktion soll einen Skalarwert vom Typ zurückgeben. Die Funktion bekommt die zu überprüfende EMail-Adresse als übergeben. Der sehr kurze Quellcode für diese Funktion sieht so aus:
Die Funktion verwendet die Funktion des statischen Objektes im Namensraum und überprüft über das als zweiten Parameter angegebene Muster, ob der erste Parameter als E-Mail interpretiert werden kann. Über die Direktive wird angegeben, dass die Funktion als eine benutzerdefinierte Funktion veröffentlicht werden soll. Stellen Sie sich nun bitte einmal vor, Sie sollten dieselbe Funktionalität mit T-SQL programmieren. Garantiert werden Sie feststellen, dass diese Aufgabe nur mühsam gelöst werden kann, da T-SQL nur über sehr eingeschränkte Möglichkeiten für die Zeichenkettenverarbeitung verfügt. In diesem speziellen Fall lohnt sich die Nutzung der .NET-
442
10.6 Datenbankprojekte mit Visual Studio 2005 erstellen Integration ganz besonders, da die dort vorhandenen Mittel zur Verarbeitung von Zeichenketten gegenüber T-SQL erheblich umfangreicher und komfortabler sind. Nach der Bereitstellung des Projektes können wir unsere Funktion nun verwenden:
Wie erwartet, enthält die erste Spalte des Ergebnisses den Wert 0, in der zweiten Spalte wird 1 zurückgegeben. Tabellenwertfunktionen Die nächste Funktion soll eine Textdatei einlesen und den Inhalt dieser Datei als Tabelle zurückgeben. Diese Datei enthält Personendaten und sieht in etwa so aus:
Jede Zeile besteht aus den folgenden fünf durch ein Semikolon getrennten Spalten: 1. Eine eindeutige ID des Kontaktes 2. Vorname 3. Nachname 4. E-Mail-Adresse 5. Telefonnummer Eine benutzerdefinierte Funktion, die eine Tabelle zurückliefert, muss im Quellcode als deklariert werden. Aus diesem Grund benötigen wir in unserer Klasse einen Verweis auf . Da eine Datei eingelesen werden soll, wird außerdem eine Referenz auf benötigt. Die folgenden beiden -Direktiven müssen also zum Quellcode der Klasse hinzugefügt werden:
Wir können nun die Funktion entwerfen, welche die Kontaktdaten aus der Textdatei einliest und diese als Tabelle an den Client zurückgibt. Die Funktion an sich ist erstaunlich einfach aufgebaut:
443
10 Datenbankentwicklung mit .NET
Vor
der
Deklaration
der
eigentlichen
Funktion
wird
über
den
Parameter
der Attributfunktion
zunächst die Struktur der von der Funktion zurückgegebenen Tabelle deklariert. Beachten Sie bitte den anderen Parameter, , zunächst nicht, hierauf werden wir etwas weiter unten noch einmal eingehen. Die Funktion selber gibt einen Wert vom Typ zurück. Im Beispiel wird ein String-Array als konkrete Implementierung für dieses Interface verwendet. In dieses Array werden einfach alle in der Datei enthaltenen Zeilen eingetragen. Anschließend wird das Array zurückgegeben. Damit ist die Funktion bereits fertig codiert. Die Frage ist nun natürlich, auf welche mysteriöse Weise die Tabellenspalten erzeugt werden, und hier kommt die Funktion ins Spiel. Über den Parameter wird der Name einer speziellen Spaltenerzeugungsfunktion, in unserem Fall eben , angegeben. Diese Spaltenerzeugungsfunktion wird für jedes Element des von der eigentlichen benutzerdefinierten Funktion zurückgegebenen Arrays aufgerufen. Dabei erwartet die Spaltenerzeugungsfunktion genau so viele Ausgabeparameter, wie die zu erstellende Tabelle Spalten besitzt. Zusätzlich muss die gesamte Zeile, aus der letztlich die Spaltendaten erzeugt werden sollen, als erster Parameter an diese Funktion übergeben werden. Die Deklaration unserer Spaltenerzeugungsfunktion muss somit wie folgt aussehen:
Im ersten Parameter vom Typ werden die einzelnen Zeilen empfangen. In unserem Fall sind dies einfach alle Elemente des von der Funktion gelieferten Arrays, also Zeichenketten. Alle anderen Parameter repräsentieren die Tabellenspalten entsprechend der Deklaration der Tabelle. Diese Parameter müssen als Ausgabeparameter deklariert werden. Das eigentliche Füllen der Spalten aus den im Parameter übergebenen Informationen ist dann relativ simpel:
444
10.6 Datenbankprojekte mit Visual Studio 2005 erstellen
Nach der Bereitstellung der Assembly kann die Funktion nun zum Beispiel so benutzt werden:
Abbildung 10.11 zeigt ein Beispiel für das Ergebnis des Funktionsaufrufes.
Abbildung 10.11 Ergebnis der Funktion
10.6.4 Erstellen benutzerdefinierter Typen Benutzerdefinierte Typen eröffnen Ihnen die Möglichkeit, das SQL Server-Typsystem um eigene Typen zu erweitern. Solche Typen können Sie in Ihren Tabellen oder im T-SQLCode dann wie SQL Server-eigene Datentypen verwenden. Die Typen können nicht nur Daten, sondern auch Methoden beinhalten, erlauben also tatsächlich objektorientierte Programmierung. Denken Sie zum Beispiel noch einmal an den Datentyp XML, der ja letztlich auch Methoden zur Verarbeitung von XML-Daten anbietet. Wenn Ihnen diese Möglichkeit nun faszinierend erscheint und Sie sofort daran denken, Datentypen aus Ihrem Geschäftsumfeld zu implementieren, so möchte ich Sie an dieser Stelle genau davor warnen.
445
10 Datenbankentwicklung mit .NET Benutzerdefinierte Typen sollten überschaubar und handlich sein. Komplexe Typen sind generell keine gute Wahl für benutzerdefinierte Typen. Gute Beispiele für eigene Typen wären etwa: E-Mail-Adressen Komplexe Zahlen Geometrische Objekte, wie zum Beispiel Punkte oder Flächen IP-Adressen Geschlecht Nicht wirklich zu empfehlen wäre hingegen die Implementierung der folgenden Objekte als Typ: Rechnung Adresse Lieferschein Wir werden in diesem Abschnitt einen Typ entwickeln, der einen einfachen Datumswert also ohne eine Zeitkomponente repräsentiert. SQL Server kennt von Haus aus ja nur den Datentyp DATETIME, in dem sowohl Datum als auch Uhrzeit enthalten sind. Einen benutzerdefinierten Typ können Sie wie bereits in Abbildung 10.7 gezeigt wieder über das Projekt-Kontextmenü hinzufügen. Wir benennen die Datei Date.cs. Der Assistent erstellt auch diesmal ein Gerüst, wobei der enthaltene Typ bereits als deklariert ist. Generell ist die Entwicklung eigener Typen nicht ganz so einfach, wie dies bei Prozeduren und Funktionen der Fall ist dies wird Ihnen im Verlauf dieses Abschnitts sicherlich klar werden. Was sofort auffällt, ist, dass das erzeugte Gerüst bereits etwas mehr Code enthält als in den vorangegangenen Beispielen zu Prozeduren und Funktionen. Nach einigen Aufräumarbeiten und dem Entfernen unbenötigter Platzhalter-Methoden und -Variablen sieht der erzeugte Code etwa so aus, wie im folgenden Listing 10.3 gezeigt: Listing 10.3 Der erzeugte Code für einen benutzerdefinierten Typen
446
10.6 Datenbankprojekte mit Visual Studio 2005 erstellen
Ein benutzerdefinierter Typ muss mindestens die in Tabelle 10.3 aufgelisteten Methoden implementieren. Tabelle 10.3 Erforderliche Methoden für benutzerdefinierte Typen Methode
Beschreibung
ToString
Gibt eine String-Repräsentation des Typs zurück.
IsNull
Enthalten im INullable-Interface. Gibt true zurück, wenn der Typ NULL ist.
Null
Enthalten im INullable-Interface. Gibt die NULLRepräsentation des Typs zurück.
Parse
Den Typ aus einem String erzeugen.
Für die Implementierung benutzerdefinierter Typen ist außerdem ein grundlegendes Verständnis der Direktive
nötig. Insbesondere ist wichtig, dass über diese Direktive angegeben wird, wie die Speicherung einer Instanz des Typs im SQL Server erfolgen soll. Ein Datentyp muss serialisierbar sein. Um eine Instanz des Typs zum Beispiel in einer Spalte einer Tabelle ablegen zu können, muss der Datentyp in ein Binärformat überführt und auch aus einem solchen Format erstellt werden können. Für einfache Typen kann diese Serialisierung durch SQL Server-interne Mechanismen erledigt werden. Komplexe Typen, die ihrerseits interne Va-
447
10 Datenbankentwicklung mit .NET riablen enthalten, die keine feste Länge haben (hierzu zählen zum Beispiel Variablen von Typ ), erfordern die explizite Implementierung der Serialisierung. Im Einzelnen haben die Parameter der Direktive die in Tabelle 10.4 aufgeführte Bedeutung: Tabelle 10.4 Parameter der Attributfunktion SqlUserDefinedType Eigenschaft
Beschreibung
Format.Native
Der SQL Server übernimmt die Serialisierung dieses Datentyps. Diese Direktive kann verwendet werden, wenn der Datentyp sich nur aus Elementen fester Länge zusammensetzt. Hierzu zählen zum Beispiel bool, byte, int, double, SqlInt32 und auch SqlDateTime. Die Direktive MaxByteSize darf nicht benutzt werden.
Format.Unknown
Unbekanntes Serialisierungsformat. Dient nur als Platzhalter. Typen mit diesem Serialisierungsformat können nicht bereitgestellt werden.
Format.UserDefined
Die Serialisierung muss implementiert werden. Verwenden Sie hierzu die Methoden Read und Write des Interface IBinarySerialize. Erforderlich bei Benutzung von Typen variabler Länge wie string oder SqlString.
MaxByteSize
Gibt die maximale Größe des Typen in Byte an. Kann nur verwendet werden, wenn auch die Serialisierung implementiert wird, also nur bei Format.UserDefined.
IsFixedLength
Gibt true zurück, wenn alle Instanzen dieses Typen dieselbe Länge haben. Dies erleichtert SQL Server die Verwaltung und Serialisierung.
IsByteOrdered
Muss auf true gesetzt werden, wenn Vergleichsoperationen wie < oder > durchgeführt werden sollen. Auch erforderlich für ORDER BY und Aggregatfunktionen, die einen Vergleich benötigen, wie zum Beispiel MIN und MAX. Über das Interface IComparable kann eine Vergleichsfunktion CompareTo() implementiert werden. Möglich ist auch die Implementation eigener Vergleichsoperatoren.
ValidationMethodName
Gibt den Namen einer Methode an, die zur Gültigkeitsprüfung dieses Typs aufgerufen wird.
Name
Der Name des Typs im SQL Server.
Um den Typ zu vervollständigen, müssen zunächst die in Tabelle 10.3 genannten Methoden implementiert werden. Dies ist sozusagen die Pflicht. Voranstellen wollen wir aber die Implementierung der drei Datumskomponenten Tag, Monat und Jahr. Zu diesem Zweck werden einfach drei interne Variablen eingeführt und diese über entsprechende öffentliche Eigenschaften zugänglich gemacht. Im Code zur Zuweisung von Werten an diese
448
10.6 Datenbankprojekte mit Visual Studio 2005 erstellen Eigenschaften werden noch einige Gültigkeitsprüfungen vorgenommen. Heraus kommt ein Code, der aussieht wie in Listing 10.4: Listing 10.4 Implementierung der Eigenschaften Tag, Monat und Jahr
449
10 Datenbankentwicklung mit .NET
Wir können nun die Methoden , , und implementieren:
Hier folgt noch einmal die Erklärung, was diese Funktionen für Aufgaben erfüllen:
450
10.6 Datenbankprojekte mit Visual Studio 2005 erstellen . Die Funktion erzeugt eine String-Repräsentation des Daten-
typs im (deutschen) Format TT.MM.JJJJ. Ist der Wert NULL, so wird einfach die Zeichenkette NULL zurückgegeben. . Diese Nur-Lese-Eigenschaft muss den Wert true zurückgeben, wenn der
Wert des Typs NULL ist. Hierzu wird einfach überprüft, ob mindestens eine der internen Variablen für Tag, Monat und Jahr 0 ist. Ist dies der Fall, so soll der Wert als NULL interpretiert werden. . Die Nur-Lese-Eigenschaft gibt eine NULL-Repräsentation des Datentyps
zurück. Ein neu erzeugter Wert vom Typ ist immer NULL, weil die internen Variablen für Tag, Monat und Jahr allesamt 0 sind. Daher wird einfach ein neues zurückgegeben. . Die von erzeugte String-Repräsentation des Datentyps
enthält die einzelnen Datumskomponenten für Tag, Monat und Jahr, die einfach durch einen Punkt voneinander getrennt sind. ist hierzu die Umkehrfunktion: Die Datumskomponenten werden aus einem String erzeugt, der das Format TT.MM.JJJJ besitzt. Weiter unten werden Sie sehen, wie Sie die Funktion verwenden können. Eine erste Version unseres Typs ist nun fertig. Nach der erfolgreichen Bereitstellung der Assembly können wir diesen Typ zum Beispiel so verwenden:
Interessant ist das Ergebnis der SELECT-Anweisung:
In der Spalte D1, die ohne irgendwelche Formatierung bzw. Konvertierung ausgegeben wird, wird einfach die interne Repräsentation des Wertes angezeigt. Sowohl D2 als auch D3 geben dann die String-Repräsentation zurück, die über den Aufruf der Funktion erzeugt wird. Die erste Wertzuweisung an die Variable
erzeugt den Wert für über den impliziten Aufruf der Funktion . Der Datentyp kann auch in eigenen Tabellen verwendet werden:
451
10 Datenbankentwicklung mit .NET
In der INSERT-Anweisung wird das Datum als Zeichenkette angegeben, was wiederum bewirkt, dass die Erzeugung des Typs über den Aufruf der Funktion erfolgt. Für benutzerdefinierte Typen können auch Methoden erstellt werden. Gerade bei Datumswerten können Sie sich sicherlich eine Reihe nützlicher Methoden vorstellen, um zum Beispiel Berechnungen mit Datumswerten durchführen zu können. Wenn wir in die Tabelle Kalender in einer Schleife eine Reihe von Datumswerten etwa für einen Zeitraum von zwei Jahren einfügen möchten, so wäre es praktisch, eine Methode zur Verfügung zu haben, mit der eine Anzahl von Tagen zu einem Datum addiert werden kann. Eine entsprechende Funktion würde dann so aussehen:
gibt ein Datum zurück, das aus dem Datum der aktuellen Instanz durch die
Addition der übergebenen Anzahl von Tagen ermittelt wird. Versuchen Sie nun, die Assembly erneut bereitzustellen, so werden Sie allerdings einen Fehler erhalten:
Ursache für diesen Fehler ist, dass der Typ in unserer Tabelle Kalender verwendet wird. Daher kann er nicht gelöscht und dann erneut bereitgestellt werden. Dies ist ein generelles Problem mit benutzerdefinierten Typen, die keine Versionierung unterstützen. Ein einmal verwendeter Typ muss in dieser Form beibehalten werden. Natürlich können Sie spezielle Konvertierungsroutinen entwerfen, da ja alle Typen die String-Konvertierung in beide Richtungen ermöglichen. Sie könnten also etwa so vorgehen:
452
10.6 Datenbankprojekte mit Visual Studio 2005 erstellen
Auch wenn diese Verfahrensweise vielleicht ein wenig umständlich ist und sich außerdem verkompliziert, wenn für die zu ändernde Tabelle Fremdschlüssel oder Einschränkungen existieren, so ist es auf diesem Wege zumindest möglich, ein Update durchzuführen. Allerdings sollten Sie beachten, dass für Ihren T-SQL-Code keine Überprüfung stattfindet. Wenn Sie also die Namen von Methoden in Ihren Datentypen verändern, so wird der vorhandene T-SQL-Code nicht mehr funktionieren. Dies gilt auch für gespeicherte Prozeduren, die Methoden von benutzerdefinierten Typen verwenden. Wenn diese Methoden verändert werden, werden die Prozeduren fehlerhaft sein. Versuchen Sie also am besten, unterschiedliche Versionen benutzerdefinierter Typen zu vermeiden. Wir wollen unsere Tabelle Kalender einfach löschen:
und anschließend die Assembly bereitstellen. Danach können wir die neue Methode verwenden, um zur Tabelle Kalender mehrere Zeilen in einer Schleife hinzuzufügen: Listing 10.5 Verwendung von zum Hinzufügen von Zeilen zum Kalender
Die Tabelle Kalender enthält nun Einträge über mehrere Jahre. Die Zeilen dieser Tabelle können über eine SELECT-Anweisung abgefragt werden. Zusätzlich sollen die Datumswerte in umgekehrter Reihenfolge ausgegeben werden, das Ergebnis soll also sortiert werden:
Statt des erhofften Ergebnisses wird jedoch eine Fehlermeldung zurückgegeben:
453
10 Datenbankentwicklung mit .NET Ähnliche Fehlermeldungen gibt es beim Versuch, Vergleichsoperatoren auf den Typ anzuwenden, oder beim Verwenden von Aggregatfunktionen: Tabelle 10.5 Fehlermeldungen für Vergleichsoperationen T-SQL Skript
Fehlermeldung
Meldung 403, Ebene 16, Status 1, Zeile 3 Ungültiger Operator für Datentyp. Der Operator ist greater than, der Typ ist Date.
Meldung 6210, Ebene 16, Status 1, Zeile 1 Der benutzerdefinierte 'Date'-Typ ist nicht vollständig vergleichbar. Meldung 8117, Ebene 16, Status 1, Zeile 1 Der Operanddatentyp Date ist für den minOperator ungültig.
Wenn Sie Vergleichsoperationen benutzen möchten was sicherlich fast immer der Fall sein wird , so müssen Sie den Parameter der Attributfunktion auf den Wert true setzen. Über die Implementierung der Schnittstelle können Sie dann eine Funktion definieren, die zwei Instanzen Ihres benutzerdefinierten Typs vergleicht. Außerdem können Sie die erforderlichen Vergleichsoperatoren programmieren. Zunächst wird die Deklaration unseres Typs wie folgt erweitert:
Die Implementierung von kann dann so erfolgen:
454
10.6 Datenbankprojekte mit Visual Studio 2005 erstellen verwendet für Vergleiche die Hilfsfunktion , die eine Integer-
Repräsentation des Typs zurückgibt. Auf der Basis von können nun die Vergleichsoperatoren <, >, = und != definiert werden:
Damit ist unser einfacher benutzerdefinierter Typ nun fertig. Nach der erneuten Bereitstellung der Assembly eventuell muss vorher noch die vorhandene Tabelle Kalender gelöscht werden kann der Code aus Listing 10.5 noch einmal ausgeführt werden. Die Anweisungen aus Tabelle 10.5 werden nun korrekt funktionieren. Resümee Die vorangegangenen Ausführungen haben gezeigt, wie komplex die Entwicklung benutzerdefinierter Typen sein kann und welche Probleme Sie hierbei erwarten. Dabei ist der Typ ja noch lange nicht komplett, zum Beispiel fehlen eine ganze Reihe von Berechnungs- und Konvertierungsfunktionen, um etwa auch mit internationalen Datumsformaten arbeiten zu können. Implementierung der Serialisierung Aufgrund der Tatsache, dass der Typ für die interne Repräsentation nur einfache Datentypen (in diesem Fall ) verwendet, konnte für die Serialisierung der SQL Serverinterne Mechanismus benutzt werden. In diesem Abschnitt werden wir nun auch einen benutzerdefinierten Typ erstellen, bei dem dies nicht möglich ist, die Serialisierung also implementiert werden muss. Hierfür wird ein Typ, der eine E-Mail-Adresse repräsentiert, entwickelt. Diese Adresse wird intern als gespeichert. Da ein eine variable Länge aufweist, muss in diesem Fall die Serialisierung explizit implementiert werden. Die Deklaration des E-Mail-Typs enthält die notwendigen Direktiven, die angeben, dass eine eigene Serialisierung angegeben wird, und erklärt auch bereits, dass das Interface verwendet werden wird:
455
10 Datenbankentwicklung mit .NET
Über den Parameter wird hier außerdem angegeben, dass eine E-Mail maximal 100 Zeichen lang sein darf. Im Code für den E-Mail-Datentyp müssen die folgenden beiden Funktionen der Schnittstelle implementiert werden:
Diese beiden Funktionen sorgen durch die Verwendung der übergebenen Parameter und dafür, dass eine Instanz des E-Mail-Typs in eine Binärdarstellung überführt oder aus einem binären Format erzeugt werden kann. Den kompletten Code für den E-Mail-Typ zeigt Listing 10.6: Listing 10.6 Der benutzerdefinierte Typ Email
456
10.6 Datenbankprojekte mit Visual Studio 2005 erstellen
Die Benutzung dieses Typs erfolgt dann wie gehabt, also zum Beispiel so:
10.6.5 Erstellen von benutzerdefinierten Aggregaten Die CLR-Integration im SQL Server 2005 erlaubt auch die Erstellung benutzerdefinierter Aggregate. Solche Aggregate können einerseits die SQL Server-eigenen Aggregate ergänzen. Dadurch ist die Implementierung von Funktionalität, die mittels der StandardAggregate wie z. B. , oder nicht abgebildet werden kann, möglich. Besonders sinnvoll ist der Einsatz von benutzerdefinierten Aggregaten immer dann, wenn diese Aggregate mit benutzerdefinierten Typen rechnen. Das folgende Beispiel soll aus einer Liste von Datumswerten die maximale Differenz in Tagen ermitteln. Hierbei ist jedes in der Liste enthaltene Datum vom Typ , der im vorangegangenen Abschnitt gerade entwickelt wurde. Die Aggregatfunktion soll
457
10 Datenbankentwicklung mit .NET benannt werden. Um zum Beispiel für unsere Kalender-Tabelle die maxi-
male Tagesdifferenz zwischen zwei enthaltenen Werten zu ermitteln, kann folgende Anweisung verwendet werden:
Wir erzeugen zunächst ein neues Aggregat, zum Beispiel über das Menü Projekt/Aggregat hinzufügen
, und benennen es MaxDayDiff.cs. Wie gewohnt, wird durch den Assistenten wieder eine entsprechende Vorlage erstellt, in der bereits alle zu implementierenden Methoden deklariert sind. Diese Methoden müssen die in Tabelle 10.6 genannte Funktionalität beinhalten: Tabelle 10.6 Funktionen für benutzerdefinierte Aggregate Methode
Beschreibung
Init
Diese Methode wird für jede Aggregation einmal aufgerufen. Sie dient zur Initialisierung des Aggregat-Objekts.
Accumulate
Accumulate wird für jedes Element in der Aggregat-Liste zur Berechnung des Resultats aufgerufen.
Merge
Merge wird benötigt, wenn der Server mehrere Threads zur Berechnung (Parallelverarbeitung) verwendet. Die Methode muss die Daten der unterschiedlichen (parallelen) Instanzen zusammenführen.
Terminate
Diese Methode wird am Ende der Aggregation aufgerufen und muss das Ergebnis zurückgeben.
Auch für Aggregate muss im Übrigen eine Serialisierung sichergestellt werden. Wie schon bei den benutzerdefinierten Typen ist es auch hier möglich, die Serialisierung dem SQL Server zu überlassen, sofern die interne Darstellung des Aggregats dies erlaubt. In unserem Fall wird für die interne Darstellung unser Typ verwendet, für den die Serialisierung nicht gesondert implementiert zu werden braucht. Die Implementierung des Aggregates ist dann relativ geradlinig. Hierzu müssen einfach nur die in Tabelle 10.6 aufgeführten Methoden sozusagen gefüllt werden. Der fertige Quellcode sieht dann so aus: Listing 10.7 Der komplette Code für MaxDayDiff
458
10.6 Datenbankprojekte mit Visual Studio 2005 erstellen
Die Berechnung des Aggregats kann nun nach der erneuten Bereitstellung der Assembly durchgeführt werden. Bitte denken Sie daran, vor der Bereitstellung die eventuell noch vorhandene Tabelle Kalender zu löschen. Anschließend kann diese Tabelle neu erzeugt werden. Nach dem Hinzufügen einiger Zeilen mit zufällig ausgewählten Datumswerten kann dann berechnet werden:
459
10 Datenbankentwicklung mit .NET
10.6.6 Erstellen von Triggern Zum Abschluss soll zu unserem Projekt nun noch ein Trigger hinzugefügt werden. Für diesen Trigger erstellen wir zunächst eine Tabelle Kontakt, in die einfach 100 zufällig ausgewählte Zeilen aus der Tabelle Person.Contact eingefügt werden:
Nachdem die Vorbereitungen abgeschlossen sind, kann der Trigger nun über den Assistenten erzeugt werden. Dies geschieht über das Menü Projekt/Trigger hinzufügen
. Wie üblich wird eine Vorlage erstellt, die nun vervollständigt werden muss. Die Vorlage für Trigger ist zunächst einfach leer, hier kommt es nur darauf an, die Parameter für die Attributfunktion so einzustellen, dass der Trigger durch die gewünschten Ereignisse ausgelöst wird. Tabelle 10.7 enthält einen Überblick über diese Parameter und deren Verwendung. Tabelle 10.7 Eigenschaften von CLR Triggern Eigenschaft
Beschreibung
Name
Der Name des Triggers im SQL Server
Target
Bei DML-Triggern, also Triggern, die auf INSERT-, UPDATE- oder DELETEAnweisungen ausgelöst werden, steht hier der Name der Tabelle, für die der Trigger gefeuert wird. Für DDL-Trigger kann hier auch DATABASE angegeben werden.
Event
Das den Trigger auslösende Ereignis. Für DML-Trigger sind hier folgende Werte möglich:
Für DDL-Trigger auf Datenbankbasis können hier andere Ereignisse wie zum Beispiel oder angegeben werden.
460
10.6 Datenbankprojekte mit Visual Studio 2005 erstellen Sie können sowohl DDL- als auch DML-Trigger erzeugen, allerdings können nur DDLTrigger für Datenbankereignisse erstellt werden. In .NET programmierte Trigger für Serverereignisse sind nicht möglich. Unser Trigger soll einfach für das DELETE-Ereignis auf der Tabelle Kontakt gefeuert werden und die gelöschten Zeilen als TDS an den Client zurückgeben. Der fertige Code für den Trigger sieht dann so aus:
Wie gewohnt, haben Sie im Trigger Zugang zu den Tabellen DELETED und INSERTED, um Datenänderungen abzufragen. Der Code enthält so weit nichts Besonderes, zeigt aber, wie man den benutzt, um das auslösende Ereignis abzufragen. Neu ist auch die Methode der Klasse , mit der das Ergebnis an den Client zurückgegeben wird. Hierzu kann diese Methode einfach mit einer entsprechend präparierten Instanz der Klasse aufgerufen werden. Der Trigger kann nun nachdem die Assembly bereitgestellt wurde ausprobiert werden. Die folgende Anweisung entfernt einige Zeilen aus der Tabelle Kontakt:
Wie erwartet wird im Ergebnisbereich die Liste der gelöschten Zeilen ausgegeben. Übrigens: Falls Sie die Tabelle Kontakt löschen, so wird natürlich auch der zugehörige Trigger ShowDeletedContacts entfernt. Allerdings ist der Trigger noch in der Assembly
461
10 Datenbankentwicklung mit .NET enthalten. Wenn Sie die Tabelle Kontakt also erneut erstellen, so können Sie den in der Assembly ja noch existierenden Trigger einfach über die Anweisung
erstellen und dadurch die erneute Bereitstellung der gesamten Assembly durch Visual Studio umgehen. Andererseits können Sie mit Visual Studio keinen Trigger für eine nicht existierende Tabelle bereitstellen. Die im Parameter angegebene Tabelle muss also existieren, wenn die Assembly bereitgestellt wird, ansonsten erhalten Sie eine Fehlermeldung.
10.6.7 Debuggen von CLR-Objekten Einer der großen Vorteile der CLR-Integration in SQL Server 2005 ist die Möglichkeit des integrierten Debuggings. Jedes Visual Studio 2005-Projekt enthält ein T-SQL-Skript Test.sql, das Sie hierfür hernehmen können. Das Skript enthält bereits einige Kommentare, die seine Verwendung erläutern. Wir wollen an einem Beispiel sehen, wie dieses Skript verwendet wird, und wollen hierzu unsere in Abschnitt 10.6.2 erstellte gespeicherte Prozedur debuggen. Hierzu wird der Quellcode für das Skript Test.sql geändert, sodass das Skript nur diese eine Zeile enthält:
Anschließend wird im Kontextmenü für die Datei Test.sql genau dieses Skript als Standardskript zum Debuggen festgelegt (Abbildung 10.12).
Abbildung 10.12 Test.sql als Standardskript zum Debuggen festlegen
462
10.7 Noch einmal: Sicherheit Vergewissern Sie sich bitte, dass Sie Debug als aktuelle Konfiguration ausgewählt haben. Der Debugger kann dann, zum Beispiel über die Toolbar oder über die Taste F5, gestartet werden (Abbildung 10.13).
Abbildung 10.13 Start des Debuggers
Nach dem Erreichen des Haltepunktes können Sie in gewohnter Weise mit dem Visual Studio-Debugger arbeiten, also zum Beispiel F11 für Einzelschrittbearbeitung oder F10 für die prozedurweise Ausführung wählen. Sie können Variablen inspizieren und verändern, kurzum: Ihnen steht der komplette Funktionsumfang des Visual Studio-Debuggers zur Verfügung.
10.7
Noch einmal: Sicherheit In diesem Abschnitt kommen wir ein letztes Mal auf das wichtige Thema Sicherheit zurück. Zu Beginn dieses Kapitels haben wir die Datenbank AdventureWorks in den vertrauenswürdigen Modus versetzt, um aus der Assembly CLRSamples auch auf Ressourcen des Betriebssystems zugreifen zu können. Diese Einstellung soll nun wieder zurückgenommen werden:
463
10 Datenbankentwicklung mit .NET Versuchen wir nun einmal, eine Funktion aus der Assembly aufzurufen. Hierfür nehmen wir , die eine Textdatei einliest und deren Inhalt in Tabellenform zurückgibt (siehe auch Abschnitt 10.6.3):
Anstelle eines Ergebnisses wird nun allerdings eine Fehlermeldung ausgegeben, die so beginnt:
Offensichtlich verhindern also die Sicherheitsmechanismen des SQL Servers, dass diese Funktion ausgeführt werden darf. Dies gilt übrigens für alle Prozeduren und Funktionen der Assembly, so funktioniert der Aufruf von
nun ebenfalls nicht mehr, obwohl innerhalb der gespeicherten Prozedur überhaupt nicht auf externe Ressourcen zugegriffen wird. Im Folgenden wird ein Weg aufgezeigt, wie Assemblys in einer Datenbank, die sich nicht im vertrauenswürdigen Modus befindet, der Zugriff auf externe Ressourcen ermöglicht werden kann. Hierfür muss zunächst die Assembly signiert werden. Anschließend wird aus der Signatur der Assembly ein asymmetrischer Schlüssel in der master-Datenbank erstellt. Aus diesem Schlüssel kann dann eine SQL Server-Anmeldung erzeugt werden, der anschließend die Berechtigung EXTERNAL ACCESS ASSEMLBY erteilt wird. Die signierte Assembly kann danach mit der Sicherheitsoption EXTERNAL erneut bereitgestellt werden. Dadurch ist die Zugriffsberechtigung EXTERNAL nicht automatisch für alle Assemblys einer Datenbank erlaubt, wie dies bei einer Datenbank mit der Option TRUSTWORTHY ON der Fall ist, sondern kann für jede Assembly separat festgelegt werden, was natürlich die Sicherheit insgesamt erhöht. Die einzelnen Schritte sollen nun nochmals am Beispiel der Assembly CLRSamples erläutert werden. Signieren der Assembly Im ersten Schritt soll für die Assembly eine Signatur vergeben werden. Hierzu werden die Projekteigenschaften über das Menü Projekt/CLRSamples-Eigenschaften
geöffnet. Im Projekteigenschaften-Fenster existiert die Seite Signierung. Dort kann die Option Assembly signieren selektiert und anschließend die Option aus der Combobox ausgewählt werden (Abbildung 10.14).
464
10.7 Noch einmal: Sicherheit
Abbildung 10.14 Eine Signatur für eine Assembly erstellen
Im anschließend erscheinenden Fenster kann nun für den neuen Schlüssel ein Name und ein Kennwort vergeben werden (Abbildung 10.15).
Abbildung 10.15 Erstellen eines Schlüssels für die Signatur
Die Assembly kann nun erneut erstellt (nicht bereitgestellt!) werden. Dies können Sie über das Menü Erstellen/CLRSamples erledigen. Die Assembly ist dann signiert. Aus der Signatur der Assembly einen asymmetrischen Schlüssels in der master-Datenbank erstellen Dieser Schlüssel wird über das Kommando CREATE ASSEMBLY folgendermaßen erzeugt:
465
10 Datenbankentwicklung mit .NET Bitte beachten Sie, dass Sie den Pfad für die Assembly-DLL entsprechend anpassen. Möglicherweise müssen Sie zuvor noch einen Datenbankhauptschlüssel für die Datenbank master anlegen. Wenn Sie hierzu aufgefordert werden, können Sie dies durch die Anweisung
erledigen. Erstellen eines Logins aus dem asymmetrischen Schlüssel Aus dem im vorigen Punkt erstellten asymmetrischen Schlüssel kann nun eine SQL Server-Anmeldung erstellt werden. Hierzu kann, wie gewohnt, das Kommando CREATE LOGIN verwendet werden:
Die Assembly wird dann im Sicherheitskontext dieser Anmeldung ausgeführt. Für normale Benutzer ist diese Anmeldung nutzlos, da das Kennwort nicht bekannt ist. Es wird aus der Signatur der Assembly und dem Datenbankhauptschlüssel ermittelt und ist daher geheim. Erteilen der erforderlichen Berechtigung für das erzeugte Login Nachdem nun eine entsprechende SQL Server-Anmeldung existiert, muss dieser noch die Berechtigung EXTERNAL ACCESS ASSEMBLY erteilt werden:
Bereitstellen der Assembly Die Assembly kann nun im SQL Server registriert werden. Im einfachsten Fall erledigen Sie dies wieder durch den Aufruf Erstellen/CLRSamples bereitstellen aus dem Hauptmenü. Überprüfung Wenn die Funktion nun erneut aufgerufen wird, so funktioniert alles wie gewünscht:
Es wird keine Fehlermeldung mehr ausgegeben, sondern die Tabelle mit den Kontaktdaten. Möglicherweise mag Ihnen die geschilderte Verfahrensweise umständlich erscheinen, und Sie werden daher versucht sein, Ihre Datenbanken grundsätzlich mit der Option TRUSTWORTTY ON zu erstellen. Für reine Entwicklungsumgebungen ist gegen diese Methode sicherlich nichts einzuwenden. In Produktionsumgebungen sollten Sie aber ernst-
466
10.8 T-SQL oder .NET? haft darüber nachdenken, Assemblys, die Zugriff auf externe Ressourcen benötigen, zu signieren und dann mit entsprechenden Schlüsseln und Logins zu arbeiten, um keine unnötigen Sicherheitsrisiken einzugehen.
10.8
T-SQL oder .NET? Mit der Möglichkeit, nun auch Datenbankobjekte unter.NET erstellen zu können, entsteht natürlich die Frage, unter welchen Bedingungen der Einsatz von managed Code unter .NET Vorteile gegenüber T-SQL-Prozeduren oder -Funktionen bietet. Generell kann gesagt werden, dass für Datenzugriffe nach wie vor T-SQL die Sprache der Wahl sein sollte. Es ist zwar auch unter .NET möglich, über dynamisches SQL Daten abzufragen oder zu verändern, dies ist jedoch in der Regel mit Performanceeinbußen verbunden. Widerstehen Sie bitte auch der Versuchung, durch die Entwicklung komplexer Datentypen in .NET ein objektorientiertes oder objektrelationales Datenbanksystem zu entwickeln. Für Datentypen, die aus mehreren Attributen bestehen, sind in einem relationalen Datenbanksystem Tabellenzeilen vorgesehen. Halten Sie sich bitte an dieses Konzept. Nur dadurch kann SQL als universale Abfragesprache verwendet werden. Bei Verwendung benutzerdefinierter Datentypen für Objekte, wie z. B. Adressen, ist dies nicht mehr ohne Weiteres möglich. In einem solchen Fall müssten Sie Ihre Objekte dokumentieren und den Benutzern Ihrer Anwendung mitteilen, wie diese Objekte in SQL zu verwenden sind. Sie werden auch Probleme mit unterschiedlichen Versionen Ihrer Objekte zu lösen haben und in Kauf nehmen müssen, dass die Performance unter der selbst implementierten Serialisierung leidet. Der Einsatz von .NET ist in folgenden Fällen besonders sinnvoll bzw. zu empfehlen: Nutzen von in T-SQL nicht vorhandenen Möglichkeiten Erstellung einfacher benutzerdefinierter Typen Erstellung benutzerdefinierter Aggregate Vorteile durch den Zugriff auf die Klassen des .NET Frameworks Ersetzen von erweiterten gespeicherten Prozeduren Ersetzen von COM-Automatisierung (sp_OA_*) Einsatz von rechenintensiven Algorithmen Nutzung der Vorteile objektorientierter Entwicklung
467
10 Datenbankentwicklung mit .NET
10.9
Zusammenfassung In diesem Kapitel haben Sie erfahren, wie Sie SQL Server-Datenbankobjekte in einer .NET-Programmiersprache erstellen können. Nach einer Einführung in die generelle Architektur der CLR-Integration in SQL Server 2005 wurde eine Reihe von Beispielen entwickelt, die verdeutlicht haben, wie Sie Gespeicherte Prozeduren Benutzerdefinierte Funktionen Benutzerdefinierte Typen Benutzerdefinierte Aggregate Trigger in C# erstellen können. Dabei haben Sie vor allem gesehen, wie man Visual Studio 2005 zur komfortablen Entwicklung, zum Debuggen und zur Bereitstellung dieser Datenbankobjekte einsetzen kann, wissen aber auch, dass die Verwendung von Visual Studio 2005 keine Notwendigkeit ist. Das Kapitel hat auch die wesentlichsten Sicherheitsaspekte beim Einsatz von .NETAssemblys aufgezeigt. Abschließend gestatten Sie mir bitte noch eine Bemerkung zum präsentierten Beispielcode: Ihnen ist sicherlich aufgefallen, dass in allen Beispielen fast durchgängig auf eine Fehlerbehandlung verzichtet wurde. Dies ist lediglich aus Gründen der Übersichtlichkeit und nicht etwa aus Nachlässigkeit geschehen. Für Datenbankobjekte, die in Produktivsystemen eingesetzt werden sollen, muss natürlich eine entsprechende Ausnahmebehandlung implementiert werden.
468
11 Web Services mit dem SQL Server SQL Server 2005 erlaubt die Erstellung von nativen HTTP SOAP-Endpunkten. Der SQL Server ist in der Lage, auf HTTP SOAP-Anforderungen zu antworten, ohne dass die Internet Information Services als Vermittler notwendig sind. Dadurch können Web Services direkt mit dem SQL Server bereitgestellt werden. In diesem Kapitel werden Sie nach einer Einführung in die Bereitstellung von Web Services mittels SQL Server eine einfache Beispielanwendung erstellen. Diese Anwendung wird einen Webdienst verwenden, der zwei gespeicherte Prozeduren veröffentlicht.
11.1
Was sind Web Services? Prinzipiell implementiert ein Web Service eine bestimmte Funktionalität und stellt diese über den Aufruf einer zugehörigen URL im Internet zur Verfügung. Die Grundlagen für den Aufruf von Methoden eines Web Service werden durch die folgenden XML-Standards geschaffen: 1. Ein Web Service veröffentlicht eine Beschreibung seiner Methoden und Parameter in der Regel in einem speziellen XML-Format, der Web Service Description Language (WSDL). Diese Beschreibung kann normalerweise über die URL des Web Service abgefragt werden. Für den per Web Service veröffentlichten Suchdienst der InternetSuchmaschine Google lautet diese URL zum Beispiel:
2. Für den Austausch von Informationen, also die Anfrage an einen Web Service und dessen Antwort, wird das Simple Object Access Protocol (SOAP) verwendet. Dieses Protokoll verwendet ebenfalls ein spezielles XML-Format, in dem sowohl Anfragen an einen Web Service als auch dessen Antworten verpackt werden. 3. Universal Description, Discovery and Integration (UDDI) ist ein Verzeichnisdienst zur Registrierung von Web Services. Über diesen Dienst können Web Services bekannt gegeben und gesucht werden.
469
11 Web Services mit dem SQL Server Die Kommunikation über XML ermöglicht plattformunabhängige, heterogene ServiceArchitekturen, in denen Web Services in beliebigen Programmiersprachen erstellt und auf einer Vielzahl von unterschiedlichen Servern bereitgestellt werden können. Web ServiceClients können die veröffentlichten Dienste dann in unterschiedlichen Programmiersprachen und natürlich auch auf unterschiedlicher Hardware verwenden. Durch den Einsatz von Web Services kann letztlich eine Architektur von Softwaresystemen geschaffen werden, die als serviceorientiert bezeichnet wird. In einer solchen Architektur sind die beteiligten Komponenten oder Dienste nur lose miteinander gekoppelt. Der Fokus in einer serviceorientierten Architektur liegt auf der Bereitstellung und Nutzung von Diensten, die jeweils eine dokumentierte Schnittstelle anbieten, die für die Kommunikation benutzt werden muss. Die Kommunikation erfolgt hier auf der Basis von Verträgen, in denen festgelegt wird, welche Anfragen ein Dienst verarbeitet und in welcher Form der Dienst auf diese Anfragen antwortet.
11.2
SOAP-Endpunkte SQL Server kann auf der Basis des für Web Services verwendeten SOAP-Protokolls sogenannte Endpunkte bereitstellen. Über diese Endpunkte können die veröffentlichten Dienste von anderen Anwendungen oder Diensten benutzt werden. Der SQL Server ermöglicht damit die Veröffentlichung von SOAP-basierten Web Services unter Verwendung von HTTP. Haupteinsatzgebiet für diese Funktionalität ist sicherlich die Möglichkeit der Interaktion mit SQL Server-Datenbanken über das Internet. Mit SQL Server erstellte HTTPEndpunkte gestatten die Verwendung bzw. Veröffentlichung folgender Datenbankobjekte: Gespeicherte Prozeduren. Sie können beliebige gespeicherte Prozeduren bereitstellen. Diese Prozeduren können Ergebnismengen zurückgeben und Parameter verarbeiten. Benutzerdefinierte Funktionen. Ein Web Service kann benutzerdefinierte Funktionen veröffentlichen, die einen Skalarwert zurückgeben. An diese Funktionen können Sie ebenso Parameter übergeben. T-SQL-Anweisungen. Sie können die Ausführung beliebiger T-SQL-Anweisungen über einen Web Service erlauben, auch wenn dies sicherlich ein Risiko darstellt und Sie lieber Prozeduren oder Funktionen verwenden sollten. Prinzipiell können SQL Server-Prozeduren und -Funktionen aber auch beliebige Funktionalität beinhalten, die nicht unbedingt etwas mit Datenbankaktionen zu tun haben muss. Allein durch die in Kapitel 10 besprochene .NET-Integration können SQL ServerProzeduren und -Funktionen grundsätzlich ebenso Funktionalität bereitstellen, die nicht unbedingt etwas mit Datenbankzugriffen zu tun haben muss. Durch die Möglichkeit, auch solche Prozeduren in Web Services bereitzustellen, kann der SQL Server letztlich auch als ein Dienstanbieter für eine Vielzahl von unterschiedlichen Diensten verwendet werden auch wenn Services für Datenbankzugriffe sicherlich das Haupteinsatzgebiet sein werden.
470
11.2 SOAP-Endpunkte SQL Server ermöglicht die Steuerung von Zugriffen auf Datenbankobjekte, die via Web Service veröffentlicht werden. Hierauf werden wir weiter unten noch einmal zurückkommen.
11.2.1 Syntax zur Erstellung eines HTTP-Endpunktes Zur Erzeugung eines HTTP-Endpunktes wird das Kommando CREATE ENDPOINT verwendet. Dieses Kommando ist relativ komplex, mit einer Vielzahl von Klauseln und Optionen. Die generelle Struktur von CREATE ENDPOINT ist aber stets wie folgt:
Das Kommando besteht grundsätzlich aus zwei Bereichen: 1. Im Bereich, der mit AS HTTP beginnt, werden protokollspezifische Optionen wie der Authentifizierungsmodus, der zu verwendende HTTP-Port und die URL des Web Service spezifiziert. 2. Der Bereich FOR SOAP definiert dann die eigentlichen Web Services. Hier wird angegeben, unter welchem Namen SQL Server-Prozeduren und -Funktionen veröffentlicht werden sollen. Dabei können in einem Endpunkt auch mehrere Web Services definiert werden. Ein erstes einfaches Beispiel soll verdeutlichen, wie CREATE ENDPOINT verwendet wird. Schauen Sie sich hierzu bitte die folgende Anweisung an, deren HTTP- und SOAPOptionen weiter unten sofort erläutert werden: Listing 11.1 Ein einfacher Endpunkt
Im Beispiel wird ein Endpunkt AdventureWorksHR erzeugt. Dieser Endpunkt veröffentlicht eine Methode ManagerEmployees, welche die gespeicherte Prozedur uspGetManagerEmployees in der Datenbank AdventureWorks ausführt. Durch die Klausel STATE = STARTED wird angegeben, dass der Endpunkt unmittelbar nach Erstellung zur Verfügung stehen soll. Über die Option STOPPED kann hier auch angegeben werden, dass der Endpunkt nicht ausgeführt wird. Soll der Endpunkt gestartet werden, aber keine Verbindungen zulassen, geben Sie als Status DISABLED an.
471
11 Web Services mit dem SQL Server Im HTTP-Teil der Anweisung wird über die Optionen SITE und PATH angegeben, welche URL der Web Service verwenden wird. Wir werden auf den Aufbau der URL in Abschnitt 11.4 noch einmal genauer eingehen. Die PORTS-Option bestimmt, ob der Zugriff auf den Endpunkt über HTTP (CLEAR) oder HTTPS (SSL) erfolgt. In unserem Beispiel verwenden wir keine HTTP-Verschlüsselung, geben also CLEAR an. CLEAR_PORT ermöglicht die Angabe einer Portnummer für den Zugriff auf den Endpunkt. Bei Angabe von SSL als PORTS muss statt der Klausel CLEAR_PORT SSL_PORT angegeben werden. Die URL für den Web Service ist somit durch die Optionen SITE, PATH, PORTS und CLEAR_PORT spezifiziert als:
AUTHENTICATION gibt den Authentifizierungsmodus an, mit dem Benutzer identifiziert werden, die den Endpunkt verwenden. Das Beispiel benutzt hier die integrierte WindowsAuthentifizierung, weitere Möglichkeiten sind BASIC (HTTP 1.1-Authentifizierung mit Base64-Verschlüsselung), DIGEST (HTTP 1.1-Authentifizierung unter Verwendung von MD5), NTLM (der von Windows 95, 98 und NT unterstützte Algorithmus) und KERBEROS. Die FOR SOAP-Klausel beschreibt dann den eigentlichen Web Service. Über WEBMETHOD wird der Methodenname angegeben, mit dem der Web Service aufgerufen werden kann. NAME gibt hier den Namen der gespeicherten Prozedur an, die der Web Service ausführen wird. Beim Aufruf der Web-Methode ManagerEmployees wird also die gespeicherte Prozedur uspGetManagerEmployees in der Datenbank AdventureWorks aufgerufen. Über die FORMAT-Klausel kann angegeben werden, in welcher Art der Dienst Ergebnisse zurückliefert und ob Zeilenanzahl, Fehlermeldungen und Warnungen zurückgegeben werden sollen. Soll das Ergebnis direkt als System.Data.DataSet mit einer durch Visual Studio unter Verwendung des Proxyklassen-Generators erstellten Applikation verwendet werden, so muss hier ROWSET_ONLY angegeben werden. Im Beispiel verwenden wir ALL_RESULTS. Dadurch wird die Web-Methode ein Ergebnis liefern, das nicht nur das DataSet, sondern zusätzliche Informationen zu Zeilenanzahl und Fehlern liefert. Für die Verarbeitung des Ergebnisses in einer mit Visual Studio erstellten Anwendung ist dadurch nur ein geringfügig höherer Aufwand erforderlich. WSDL gibt an, ob der Endpunkt die WSDL-Dokumenterzeugung unterstützen soll. Die Angabe von DEFAULT steht für die Standardunterstützung. Alternativ kann hier NONE für keine Unterstützung oder der Name einer gespeicherten Prozedur angegeben werden, die eine eigenes WSDL-Dokument zurückgibt. Für die meisten Einsatzfälle sollte die DEFAULT-Option die richtige Wahl sein. Die DATABASE-Klausel gibt den Namen der Datenbank an, in deren Kontext der Endpunkt ausgeführt wird. Über NAMESPACE wird dann noch der Namensraum für den Endpunkt angegeben. Die Klausel ist optional, wenn Sie hier nichts spezifizieren, so wird als Standard verwendet. Sie können die existierenden Endpunkte aus dem Systemkatalog abfragen:
472
11.3 Reservierung von HTTP-Namensräumen Natürlich wird der Endpunkt auch im SQL Server Management Studio angezeigt, dort im Ordner Serverobjekte/Endpunkte/SOAP (Abbildung 11.1).
Abbildung 11.1 Anzeige des erzeugten Endpunktes im Management Studio
Für den oben erstellten Endpunkt AdventureWorksHR veröffentlicht SQL Server die Beschreibung der enthaltenen Web Services (hier ist es nur ein einziger) im Standard-WSDLFormat. Diese Beschreibung kann über die folgende URL abgefragt werden:
Wichtig ist auch, dass Sie verstehen, dass der SQL Server selber auf HTTP-Anforderungen zum erzeugten Endpunkt reagiert, die Internet Information Services (IIS) sind hierfür nicht notwendig. Die interessante Frage ist nun natürlich, wie man einen solchen Endpunkt in eigenen Applikationen einsetzen kann. Bitte gedulden Sie sich zunächst noch ein wenig, wir werden später in diesem Kapitel hierauf zurückkommen. Zuvor ist jedoch noch ein kurzer Exkurs zu Namensräumen und dem Aufbau der URL, die für den Aufruf der in einem Endpunkt enthaltenen Web Services verwendet wird, erforderlich.
11.3
Reservierung von HTTP-Namensräumen Vielleicht haben Sie sich bereits gefragt, auf welche Art und Weise der SQL Server URLZugriffe erlauben und kontrollieren kann. Um auf SQL Server-Objekte über HTTP zuzugreifen, muss offensichtlich irgendeine Verbindung zwischen SQL Server und den zugehörigen Komponenten des darunter liegenden Betriebssystems bestehen. Diese Verbindung wird über den Kernelmodus-HTTP-Treiber HTTP.sys hergestellt. Der HTTP.sysTreiber empfängt HTTP-Anforderungen von Clients und leitet diese Anforderungen entsprechend an SQL Server-Endpunkte weiter. Damit das funktioniert, muss SQL Server einen entsprechenden HTTP-Namensraum in HTTP.sys reservieren. Diese Reservierung kann auf zwei verschiedene Arten erfolgen: implizit und explizit. Generell müssen Sie
473
11 Web Services mit dem SQL Server hierbei beachten, dass für die Reservierung von HTTP-Namensräumen WindowsAdministratorprivilegien erforderlich sind es reicht also nicht aus, lediglich ein SQL Server-Administrator zu sein. Außerdem steht der Treiber HTTP.sys derzeit nur für die Betriebssysteme Windows Server 2003 und Windows XP (mit Service Pack 2) zur Verfügung, HTTP-Endpunkte mit SQL Server können also auch nur auf diesen Systemen erzeugt werden.
11.3.1 Implizite Reservierung Der Namensraum für einen Endpunkt wird immer dann implizit reserviert, wenn ein Endpunkt erzeugt wird, ohne dass eine explizite Reservierung existiert. In einem solchen Fall werden die entsprechenden Angaben für SITE, PATH und PORTS ausgewertet, um den Namensraum zu reservieren. Diese Reservierung ist allerdings nur so lange gültig, wie der SQL Server läuft. Unser Beispiel aus Listing 11.1 reserviert den Namensraum implizit. Während der Ausführung von SQL Server werden dadurch alle Anforderungen an eine URL, die so beginnt, zur SQL Server-Instanz weitergeleitet. Wird der SQL Server-Dienst beendet, so kann eine andere Anwendung den Namensraum übernehmen. Die SQL Server-Dokumentation sagt aus, dass nur Benutzer, die über lokale Administratorrechte auf dem PC verfügen, die Reservierung von HTTP-Namensräumen vornehmen dürfen. Für eine implizite Reservierung muss der angemeldete Benutzer, also derjenige, der CREATE ENDPOINT ausführt, auch ein lokaler Windows-Administrator sein. Die Berechtigung zur Ausführung von CREATE ENDPOINT besitzen zunächst einmal nach der Installation von SQL Server nur SQL Server-Administratoren1. Somit muss also ein Benutzer, der einen Endpunkt anlegen möchte, in der Regel sowohl Windows- als auch SQL Server-Administrator sein. In meiner SQL Server-Instanz2 war es allerdings auch erforderlich, die Anmeldung, unter welcher der SQL Server-Dienst ausgeführt wird, in die Gruppe der Administratoren aufzunehmen. Erst danach funktionierte die implizite HTTP-Namensraumreservierung. Nur allein dem angemeldeten Benutzer also demjenigen, der das CREATE ENDPOINTKommando ausführt Administratorrechte zu erteilen, war hier nicht ausreichend. In diesem Fall wurde stets die folgende, irreführende Fehlermeldung ausgegeben:
474
1
Die Berechtigung, CREATE ENDPOINT auszuführen, kann auch anderen Benutzern erteilt werden.
2
SQL Server Developer Edition mit Service Pack auf Windows XP mit Service Pack 2
11.3 Reservierung von HTTP-Namensräumen Diese Meldung ist definitiv nicht korrekt, da der lokale Administrator ja wohl über die entsprechende Berechtigung verfügen sollte. Falls Sie also ähnliche Probleme haben, versuchen Sie einfach einmal, die Anmeldung des SQL Server-Dienstes in die Gruppe der lokalen Administratoren aufzunehmen. Alternativ können Sie den SQL Server auch unter dem lokalen Systemkonto ausführen, das Konto hat ebenfalls die Berechtigung, einen beliebigen Namensraum zu binden, sofern dieser noch verfügbar ist.
11.3.2 Explizite Reservierung Durch die gespeicherte Prozedur kann auch eine explizite Reservierung eines HTTP-Namensraumes erfolgen. Diese Prozedur wird folgendermaßen verwendet:
Die Angabe der Portnummer hinter dem Servernamen ist hier obligatorisch. Die explizite Reservierung hat zwei Vorteile gegenüber einer impliziten Reservierung: 1. Für die Ausführung einer CREATE ENDPOINT-Anweisung mit einem bereits explizit reservierten Namensraum sind keine Windows-Administratorprivilegien erforderlich. Ist zum Beispiel der Namensraum explizit reserviert worden, so können Endpunkte für oder erstellt werden, ohne dass hierfür Windows-Administratorprivilegien erforderlich sind. 2. Ein explizit reservierter Namensraum ist auch dann noch gültig, wenn die Instanz von SQL Server nicht ausgeführt wird. Die Reservierung erfolgt übrigens hierarchisch, wie Ihnen eventuell oben in Punkt 1 bereits aufgefallen ist. Eine Reservierung für reserviert also ebenso alle untergeordneten Namensräume, wie zum Beispiel:
11.3.3 Löschen einer expliziten Reservierung Um die explizite Reservierung eines HTTP-Namensraumes zu löschen, verwenden Sie die gespeicherte Prozedur . Diese Prozedur wird ebenfalls mit einer URL als Parameter aufgerufen:
Auch hier wieder benötigt der aufrufende Benutzer Windows-Administratorprivilegien.
475
11 Web Services mit dem SQL Server
11.4
Aufbau der URL für einen Endpunkt Um zu ermitteln, welche URL Ihr Endpunkt verwendet, können Sie das CREATE ENDPOINT-Kommando untersuchen. Die Angaben für SITE, PATH, PORTS, CLEAR_PORT und SSL_PORT der HTTP-Klausel bestimmen letztlich die genaue Adresse. Nehmen wir das folgende CREATE ENDPOINT-Kommando als Beispiel:
Tabelle 11.1 gibt Auskunft darüber, wie sich die URL aus den einzelnen Optionen des CREATE ENDPOINT-Kommandos zusammensetzt. Tabelle 11.1 Aufbau der URL
11.5
Klausel
Teil der URL
Bemerkung
PATH
http://server:800/servicename/path
Dies ist der relative Teil der URL.
SITE
http://server:800/servicename/path
Über SITE wird der Name des Servers angegeben. Wenn SITE nicht angegeben wird, dann wird als SITE der Name des lokalen Computers verwendet.
PORTS = (CLEAR)
http://server:800/servicename/path
PORTS = (SSL)
https://server:800/servicename/path
CLEAR_PORT
http://server:800/servicename/path
SSL_PORT
https://server:800/servicename
Einen Web Service erstellen und verwenden Nun aber erst einmal genug der Theorie. In diesem Abschnitt wollen wir einen Web Service bereitstellen, der zwei gespeicherte Prozeduren aus der Beispieldatenbank AdventureWorks veröffentlicht. Hierzu erzeugen wir zunächst diese beiden einfachen Prozeduren, die dann später durch den Web Service bereitgestellt werden:
476
11.5 Einen Web Service erstellen und verwenden
Die Prozedur Person.pGetAllContacts gibt alle Zeilen aus der Tabelle Person.Contact zurück. Über die zweite Prozedur, Person.pUpdateContactEmail, kann die E-Mail-Adresse einer einzelnen Zeile in der Tabelle Person.Contact geändert werden. Hierzu muss die ContactID der zu ändernden Zeile an die Prozedur übergeben werden, die Prozedur erwartet also einen Parameter. Erzeugen Sie bitte die beiden Prozeduren in der Beispieldatenbank AdventureWorks.
11.5.1 Den AdWrksServicePoint-Endpunkt erzeugen Bevor der Endpunkt erzeugt wird, muss zunächst einmal festgelegt werden, welche URL dieser verwenden soll. Hier können Sie sich im Prinzip für eine beliebige URL auf Ihrem lokalen Computer entscheiden. Nehmen Sie zum Beispiel einfach diese:
Der Name localhost für den Server ist natürlich nur zu Testzwecken zu gebrauchen. Der Zugriff auf diese URL wird letztlich nur vom lokalen Computer möglich sein. In einem Produktivsystem müssen Sie selbstverständlich einen Servernamen verwenden, der auch über das Intra- oder Internet erreichbar ist. Für unser Beispiel ist die obige URL aber ausreichend. Die Reservierung des Namensraumes soll explizit vorgenommen werden. Dies wird, wie oben erläutert, über die gespeicherte Prozedur erledigt, die Sie mit der zu reservierenden URL aufrufen:
Nach der Reservierung können Sie nun den Endpunkt wie folgt erstellen:
477
11 Web Services mit dem SQL Server Die obige Anweisung erstellt den Endpunkt AdWrksServicePoint mit den folgenden Optionen: 1. ist die URL für den Endpunkt. 2. SQL Server verwendet die Windows-integrierte Sicherheit zur Authentifizierung von Benutzern, die Verbindungen zum Endpunkt herstellen möchten. 3. Der Endpunkt veröffentlicht diese beiden Web-Methoden: Web-Methode
Gespeicherte Prozedur
ContactList
AdventureWorks.Person.pGetAllContacts
UpdateContactEmailAddress
AdventureWorks.Person.pUpdateContactEmail
Es wird also ein Endpunkt erzeugt, der zwei Web-Methoden enthält. Die Methode ContactList soll ihr Ergebnis im ROWSET-Format liefern. Dadurch können wir später direkt ein DataSet mit diesem Ergebnis füllen. Das Ergebnis der Methode UpdateContactEmailAddress soll weitergehende Informationen zu Zeilenanzahl und Fehlern enthalten. 4. Der Web Service verwendet die Standardoptionen für die Beschreibung seiner Schnittstelle. Dies wird durch die Option WSDL = DEFAULT festgelegt. 5. Die beiden Methoden des Web Services werden im Kontext der Datenbank AdventureWorks ausgeführt. 6. Der Namensraum für den Web Service ist: .
Nach der erfolgreichen Erzeugung des Web Service können Sie sich das WSDLDokument mit der Schnittstellenbeschreibung über diese URL ansehen:
11.5.2 Sicherheitsaspekte Es gilt nun die Frage zu klären, wer sich überhaupt zum Endpunkt verbinden darf und wer die Berechtigung besitzt, die im Web Service enthaltenen Methoden aufzurufen. SQL Server sichert Web Services durch ein zweistufiges Berechtigungssystem ab. Zunächst einmal kann SQL Server-Anmeldungen die Berechtigung zum Verbinden zu existierenden Endpunkten erteilt oder verweigert werden. Zum Erteilen einer solchen Berechtigung verwenden Sie die Anweisung:
gibt den Namen des Endpunktes an, ist die SQL Server-
Anmeldung. Die Anweisung muss in der master-Datenbank ausgeführt werden. Für die SQL Server-Anmeldung mit CONNECT-Berechtigung zum entsprechenden Endpunkt muss in der Datenbank, in der Objekte für den Web Service benötigt werden, ein zugehöriger Benutzer existieren. Dieser Datenbankbenutzer muss die entsprechenden
478
11.5 Einen Web Service erstellen und verwenden Rechte für den Zugriff auf die Datenbankobjekte besitzen, die im Web Service verwendet werden. In unserem Beispiel wäre dies also die EXECUTE-Berechtigung für die beiden gespeicherten Prozeduren Person.pGetAllContacts und Person.pUpdateContactEmail in der Datenbank AdventureWorks. Lassen Sie uns nun die Konfiguration der erforderlichen Berechtigungen durchführen. Unser Endpunkt verwendet die integrierte Windows-Sicherheit. Aus diesem Grund legen wir zunächst in der Systemsteuerung einen Windows-Benutzer an, aus dem dann anschließend auch eine SQL Server-Anmeldung erzeugt wird. Legen Sie also bitte über die WindowsSystemsteuerung einen Windows-Benutzer an und nennen diesen zum Beispiel ServiceUser (Abbildung 11.2).
Abbildung 11.2 Anlegen eines Windows-Benutzers für den Zugriff auf den Web Service
Versuchen Sie nun einmal, sich als ServiceUser zum Endpunkt zu verbinden, indem Sie das WSDL-Dokument abrufen. Hierzu wählen Sie im Startmenü Ihren Browser mit der rechten Maustaste aus und wählen im Kontextmenü Ausführen als
. Wählen Sie im Fenster Ausführen als den gerade angelegten Benutzer ServiceUser aus, und geben Sie anschließend im Browser als URL die HTTP-Adresse für den Zugriff auf das WSDLDokument an:
Als Ergebnis werden Sie lediglich ein SOAP-Dokument erhalten, das eine Fehlermeldung zurückgibt. Abbildung 11.3 zeigt einen Auszug aus einem solchen Dokument.
479
11 Web Services mit dem SQL Server
Abbildung 11.3 Anmeldefehler im SOAP-Dokument
Es ist klar zu erkennen, dass kein Zugriff auf das angeforderte WSDL-Dokument erteilt wurde. Wir benötigen nun also noch eine SQL Server-Anmeldung, der das Recht erteilt wird, sich zum Endpunkt zu verbinden. Öffnen Sie zum Anlegen einer solchen Anmeldung bitte im SQL Server Management Studio eine neue Abfrage. Im ersten Schritt wird die erforderliche SQL Server-Anmeldung für den Benutzer ServiceUser erzeugt:
Ersetzen Sie in der obigen CREATE LOGIN-Anweisung bitte SqlServer durch den Namen Ihres Computers. Existiert die SQL Server-Anmeldung, so kann dieser nun das Recht zur Anmeldung an den Endpunkt erteilt werden. Dies geschieht durch das folgende Kommando:
Denken Sie bitte auch hier wieder daran, für SqlServer den Namen Ihres Computers einzutragen. Die SQL Server-Anmeldung existiert nun und besitzt das Recht zur Anmeldung an den Endpunkt AdWrksServicePoint. Was noch fehlt, ist ein entsprechender Datenbankbenutzer in der AdventureWorks-Datenbank, der die Berechtigung zur Ausführung der beiden gespeicherten Prozeduren besitzt. Um den Benutzer anzulegen und diesem die entsprechenden Rechte zuzuweisen, verwenden Sie das folgende Skript:
480
11.5 Einen Web Service erstellen und verwenden Bitte vergessen Sie auch diesmal nicht, SqlServer entsprechend anzupassen. Der Datenbankbenutzer ServiceUser, der unter demselben Namen auch als WindowsBenutzer existiert, besitzt nun das Recht, auf den Endpunkt zuzugreifen. Sie können dies überprüfen, indem Sie den weiter oben durchgeführten Versuch, das zum Web Service gehörige WSDL-Dokument als ServiceUser abzurufen, wiederholen. Das Dokument sollte nun korrekt und ohne Fehlermeldung angezeigt werden. Im folgenden Abschnitt werden wir nun sehen, wie der soeben erstellte Endpunkt verwendet werden kann. Am Ende des Abschnitts werden wir auch noch einmal auf die Sicherheit zurückkommen und sehen, wie der gerade angelegte Benutzer ServiceUser die im Endpunkt enthaltenen Methoden verwenden kann.
11.5.3 Den Web Service verwenden Der Endpunkt existiert nun und ermöglicht den Zugriff auf die AdventureWorksDatenbank über die beiden veröffentlichten gespeicherten Prozeduren. In diesem Abschnitt soll eine einfache Windows-Client-Anwendung erstellt werden, die den Web Service verwendet. Die Anwendung wird mit Visual Studio 2005 unter Verwendung von C# erstellt auch wenn es natürlich generell möglich ist, einen entsprechenden Client in einer beliebigen Entwicklungsumgebung und Programmiersprache zu entwickeln. Starten Sie bitte Visual Studio, und erzeugen Sie eine neue C#-Windows-Anwendung WSSample (Abbildung 11.4).
Abbildung 11.4 Eine Windows-Anwendung für die Verwendung des Endpunktes erstellen
Wenn Sie statt C# VB.NET bevorzugen, können Sie das Beispiel selbstverständlich auch in VB.NET erstellen.
481
11 Web Services mit dem SQL Server Unser Beispielprogramm soll die beiden Methoden benutzen, die der Endpunkt zur Verfügung stellt. Die Methode ContactList gibt alle vorhandenen Kontakte zurück. Wir wollen diese Kontakte einfach in einem DataGridView auflisten. Über die zweite veröffentlichte Methode, UpdateContactEmailAddress, soll die Anwendung die E-Mail-Adresse eines Kontaktes ändern. Visual Studio bietet die Möglichkeit, zur einfachen Verwendung der in einem Web Service enthaltenen Methoden eine Proxy-Klasse zu generieren. Hierzu muss einfach nur ein Webverweis zum Projekt hinzugefügt werden. Wählen Sie im Projektmappen-Explorer das Kontextmenü für den Ordner Verweise aus und klicken auf Webverweis hinzufügen
(Abbildung 11.5).
Abbildung 11.5 Einen Webverweis hinzufügen
Im Dialog Webverweis hinzufügen geben Sie einfach die URL für das WSDLDokument unseres Endpunktes an und klicken auf den Button Gehe zu. Ändern Sie bitte auch den Webverweisnamen von localhost auf einen passenderen Namen wie zum Beispiel AdventureWorks (Abbildung 11.6).
Abbildung 11.6 Den Webverweis zum Endpunkt hinzufügen
482
11.5 Einen Web Service erstellen und verwenden In der Beschreibung für den AdWrksServicepoint-Endpunkt können Sie die enthaltenen Methoden mit Parametern und Rückgabetypen sehen. Diese Informationen hat der Assistent aus dem WSDL-Dokument extrahiert. Klicken Sie nun auf den Button Verweis hinzufügen, so wird ein neuer Ordner Web References mit einem Verweis AdventureWorks zur Projektmappe hinzugefügt. Dieser Ordner enthält die vom Assistenten erzeugte Proxy-Klasse für den einfachen Zugriff auf die Methoden des Web Service. Sie können sich die erzeugte Proxy-Klasse ansehen, wenn Sie im Projektmappen-Explorer auf Alle Dateien anzeigen klicken:
In der Datei Reference.cs finden Sie den vom Assistenten erstellten Code. Die Anwendung soll eine Liste der Kontakte anzeigen sowie die Änderung von E-MailAdressen ermöglichen. Hierfür fügen Sie bitte ein DataGridView, zwei Buttons und zwei Textboxen zum automatisch erzeugten Formular Form1 hinzu, so wie in Abbildung 11.7 gezeigt.
Abbildung 11.7 Die Eingabeelemente des Formulars
483
11 Web Services mit dem SQL Server Es gilt nun, dafür zu sorgen, dass beim Betätigen des Buttons Kontakte laden der DataGridView mit den von der Methode ContactList zurückgegebenen Zeilen gefüllt wird und dass eine E-Mail-Adresse aktualisiert wird, wenn der Button Aktualisieren gedrückt wird. Den gesamten Quellcode für das Formular Form1 zeigt Listing 11.2. Listing 11.2 Der komplette Quellcode für das Formular
484
11.5 Einen Web Service erstellen und verwenden Der Code ist erstaunlich kurz, da der größte Teil der Arbeit durch die automatisch erzeugte Proxy-Klasse erledigt wird. Zu Beginn wird eine Variable definiert, die eine Instanz der Proxy-Klasse enthält:
Diese Variable wird im weiteren Verlauf für den Aufruf der durch den Web Service bereitgestellten Methoden verwendet. Wichtig ist auch das Festlegen des Authentifizierungsmodus im Konstruktor des Formulars:
Durch diese Programmzeile wird der angemeldete Windows-Benutzer für die Authentifizierung verwendet. Der Rest des Codes ist unkompliziert. Hier wird im Wesentlichen die Behandlung der Klickereignisse für die beiden Buttons erledigt. Die fertige Anwendung zur Laufzeit sehen Sie in Abbildung 11.8.
Abbildung 11.8 Die Anwendung zur Laufzeit
Wenn Sie die Anwendung starten, sorgen Sie bitte dafür, dass Ihre Windows-Anmeldung mit einer SQL Server-Anmeldung verknüpft ist, die SQL Server-Administratorprivilegien besitzt. Sofern Sie nach der Installation von SQL Server die automatisch erzeugte Anmeldung VORDEFINIERT\Administratoren nicht verändert haben und Mitglied der Gruppe Administratoren auf Ihrem Computer sind, ist dies automatisch der Fall. Holen Sie sich nach dem Start der Anwendung zunächst die in der Datenbank gespeicherten Kontakte
485
11 Web Services mit dem SQL Server durch Kontakte laden. Anschließend können Sie einen beliebigen Kontakt auswählen und dessen E-Mail-Adresse ändern. Es bleibt die Frage zu klären, ob unser angelegter Benutzer ServiceUser ebenfalls über die Berechtigung verfügt, die im Endpunkt enthaltenen Methoden auszuführen. Starten Sie hierzu bitte die Anwendung, indem Sie das Kontextmenü für die erzeugte ausführbare Datei (das .EXE-File) im Explorer öffnen und dort Ausführen als... wählen. Geben Sie im Dialog Ausführen als als Benutzer dann bitte ServiceUser ein und drücken OK. Testen Sie im Programm, ob Sie Kontakte anzeigen und E-Mail-Adressen ändern können. Dies sollte ohne Probleme funktionieren. Wir wollen nun dem Benutzer ServiceUser das Recht zur Ausführung der gespeicherten Prozedur Person.pUpdateContactEmail entziehen. Öffnen Sie ein neues Abfragefenster im SQL Server Management Studio, und geben Sie das folgende Kommando ein:
Versuchen Sie nun erneut, eine E-Mail-Adresse zu ändern, so werden Sie eine Fehlermeldung wie in Abbildung 11.9 gezeigt erhalten.
Abbildung 11.9 Fehlermeldung/Zugriffsverletzung
11.6
Zusammenfassung Dieses Kapitel hat Ihnen gezeigt, wie Sie SQL Server für eine native HTTP-Unterstützung einsetzen können. Sie haben gesehen, wie über die Erstellung von SOAP-Endpunkten Web Services direkt mit dem SQL Server bereitgestellt werden. Wie Sie für Ihre Web Services Namensräume reservieren und den Zugriff kontrollieren sowie die Sicherheitsoptionen konfigurieren, sollte Ihnen ebenfalls klar geworden sein. Am Ende des Kapitels ist dann noch deutlich gemacht worden, wie einfach die Benutzung eines Endpunktes in einer mit Visual Studio 2005 erstellten Anwendung ist.
486
12 Optimierung von Abfragen Zu Beginn dieses Kapitels soll zunächst einmal die gar nicht so triviale Frage gestellt werden, woran man eigentlich merkt, dass eine Abfrage zu lange benötigt, um ein Ergebnis zu liefern. In den vielen Fällen werden Sie durch Rückmeldungen von Anwendern darauf aufmerksam gemacht, dass eine Anwendung nicht wie erhofft oder gewohnt reagiert. Ich weiß nicht, warum dies so ist, aber ich persönlich kenne viele Entwickler, die dazu neigen, solche Beschwerden nicht allzu ernst zu nehmen. Dies kann einigermaßen fatal sein, da die Ausführungszeit sich für gewöhnlich exponentiell verschlechtert, was dazu führen kann, dass ziemlich plötzlich und unerwartet gar nichts mehr geht. Sie sollten natürlich versuchen, nicht optimale Abfragen zu entdecken, bevor Ihre Anwender dies tun. Oftmals ist es aber so, dass Sie schlechte Abfragen gar nicht so einfach aufspüren können. In vielen Fällen wird Ihre Datenbank Abfragen enthalten, bei denen zunächst nicht bemerkt wird, dass sie eigentlich unperformant sind. Dies kann zum Beispiel passieren, wenn eine Abfrage innerhalb einer halben Sekunde ein Ergebnis liefert. Dadurch wird eine subjektive Messung, zum Beispiel durch Anwender, keine Beschwerden hervorbringen. SQL Server ist aber eventuell extrem unter Stress, um die Abfragezeit von einer halben Sekunde zu erreichen. Etwas weiter hinten werden Sie hierzu ein Beispiel sehen. Wenn es sich nur um eine einzelne Abfrage handelt, so ist dies sicherlich kein Problem. Falls sich derartige Abfragen jedoch häufen, so wird SQL Server irgendwann die Grenzen seiner Belastbarkeit erreichen. Daher sollten Sie stets versuchen, jede Abfrage so optimal wie möglich zu entwerfen. Es ist durchaus möglich, dass durch eine Optimierung eine Verbesserung um Größenordnungen erreicht wird. Eine Optimierung ist also die Mühe wert! Natürlich wird jeder Datenbankentwickler bestrebt sein, von vornherein nur Code auszuliefern, der elegant, performant und effizient ist. Daher sollte das Messen der Leistung der entwickelten Abfragen auf jeden Fall ein Bestandteil der T-SQL-Entwicklung und des Testens sein. Hierbei ist es in der Regel nicht ausreichend, einfach nur eine subjektive Messung vorzunehmen, also lediglich die gefühlte Zeit zu messen, die vergeht, bevor das Abfrageergebnis zur Verfügung steht. Etwas genauer sollte man sich das System schon an-
487
12 Optimierung von Abfragen sehen, sonst können im späteren Echtbetrieb sehr unangenehme Überraschungen auftreten. Natürlich muss aber auch davon ausgegangen werden, dass es durchaus Probleme geben kann, die erst im laufenden Betrieb einer Datenbank auftreten werden, manchmal vielleicht erst nach einer recht langen Betriebsdauer von möglicherweise sogar einigen Jahren. Sicherlich gehört das Messen der Leistung und der Auslastung des Datenbankservers zu den typischen Aufgaben eines Systemadministrators. Allerdings wird der Administrator sich in der Regel an Sie als Entwickler wenden, wenn es darum geht, entdeckte Schwachstellen im T-SQL-Code auszumerzen. An dieser Stelle ist also irgendwie interdisziplinäre Arbeit angesagt. Als Datenbankentwickler sollten Sie zunächst einmal wissen, wie man von vornherein Abfragen entwirft, die kompakt und schnell sind. Darüber hinaus wird es auch Ihre Aufgabe sein, aufgespürte Engpässe in Zusammenarbeit mit dem Administrator zu beheben. In vielen Fällen ist es sogar so, dass Administrator und Entwickler ein und dieselbe Person sind. Ein Entwickler muss in der Lage sein, die ihm vom Administrator vorgelegten Ergebnisse in Form von grafischen oder textuellen Abfrageplänen, Ablaufverfolgungen oder Ausgaben des Systemmonitors zu interpretieren. Genau an dieser Stelle setzt das vorliegende Kapitel an. Die Optimierung von Abfragen und des Servers ist allerdings eine sehr komplexe Aufgabe, über die auch bereits eigene Bücher verfasst wurden (siehe zum Beispiel [BEN-06]). In diesem Kapitel werden Sie in die zugrunde liegenden Techniken eingeführt. Das Ziel ist es, Ihnen die vielfältigen Möglichkeiten des SQL Servers für die Überwachung und Optimierung von Abfragen aufzuzeigen.
12.1
Die Speicherverwaltung von SQL Server SQL Server speichert Datenbankinhalte letztlich in Dateien des Betriebssystems. Dies wurde bereits in den Kapiteln 2 und 4 erwähnt. An dieser Stelle soll nun etwas genauer darauf eingegangen werden, wie Datendateien aufgebaut sind. SQL Server-Datendateien sind Seiten aufgeteilt. Eine Seite ist die kleinste Verwaltungseinheit für die Ein- bzw. Ausgabe und hat eine Größe von 8 KB. In jeder Seite sind hierbei 96 Bytes für die Verwaltung der Seite selbst reserviert, sodass insgesamt 8060 Bytes für die eigentlichen Daten zur Verfügung stehen. Acht zusammenhängende Seiten werden als Block (englisch: Extent) bezeichnet. Ein Block ist also 64 KB groß und stellt eine Verwaltungseinheit für die Erweiterung von existierendem Speicher, zum Beispiel für Tabellen, dar. Wenn eine Tabelle mehr Speicher benötigt, so wird stets ein kompletter Block hinzugefügt. Hierbei gibt es allerdings eine Ausnahme: Eine Tabelle, die kleiner als 64 KB ist, kann sich einen Block auch mit anderen Tabellen teilen.
488
12.2 Ausführung von Abfragen durch SQL Server
12.2
Ausführung von Abfragen durch SQL Server SQL Server erstellt für jede Abfrage vor der Ausführung einen Ausführungsplan. Diese Aufgabe wird durch den Abfrageoptimierer von SQL Server erledigt. Der Optimierer ist ein kostenbasierter Optimierer, er versucht also, die Abfragekosten so gering wie möglich zu halten. Hierbei kennt der Optimierer allerdings nur einen einzigen Kostenfaktor: Die Zeit, die eine Abfrage benötigen wird, um ein Ergebnis zu liefern. Das einzige Ziel des Optimierers ist also die Minimierung der Ausführungszeit. Der Optimierer erstellt einen Abfrageplan einerseits anhand der physikalischen Struktur der beteiligten Datenbankobjekte. Andererseits führt SQL Server auch Statistiken, in denen durchgeführte Abfragen und die Verteilung von Tabellen- und Indexdaten protokolliert werden. Diese Statistiken werden in die Konstruktion eines Abfrageplanes ebenfalls mit einbezogen. Die Erstellung eines Abfrageplanes wird auch als Kompilieren der Abfrage bezeichnet. Das Kompilieren ist ein CPU-intensiver Vorgang. Daher werden einmal erstellte Pläne in einem Puffer zwischengespeichert. Der Abfrageoptimierer wird vor dem Kompilieren einer Abfrage zunächst diesen Puffer auch als Prozedurcache bezeichnet durchsuchen und einen dort existierenden Plan verwenden, wenn dieser zur Abfrage passt. Die Erstellung eines Abfrageplanes wird nur durchgeführt, wenn kein passender Plan im Prozedurcache gefunden wird. Im Verlauf dieses Kapitels werden wir noch des Öfteren auf Abfragepläne zurückkommen. Neben dem Prozedurcache verwendet SQL Server auch einen Datencache. In diesem Puffer werden von der Festplatte gelesene Daten zwischengespeichert. Falls diese Daten in einer weiteren Abfrage benötigt werden, so liest SQL Server die Daten aus dem Cache anstatt von der Festplatte. Das Einlesen von Daten in den Cache erfolgt hierbei nicht unbedingt seitenweise. Bei großen Tabellen kann SQL Server auch blockweise von der Festplatte lesen. Das Ziel hierbei ist, so wenig wie möglich Positioniervorgänge für die Schreib-/Leseköpfe der Festplatten zu benötigen, daher werden unter Umständen mehr Daten von der Platte gelesen als im Moment nötig. Hierdurch wird die Wahrscheinlichkeit dafür, dass benötigte Daten bereits im Cache existieren, erhöht; eine Technik, die als Read ahead, also Vorauslesen, bezeichnet wird. Der Datencache ist eine entscheidende Komponente für die Ausführungszeit von Abfragen. Immerhin geht das Lesen von Daten aus dem Hauptspeicher um wenigstens eine Größenordnung schneller als das Lesen von der Festplatte. Eine hohe Cache-Trefferquote ist daher auch eine wesentliche Voraussetzung für kurze Abfragezeiten. Die dynamische Systemsicht gibt unter anderem auch Informationen zur Cache-Trefferquote für die letzten paar tausend Seitenzugriffe zurück. Hierfür können Sie die folgende Abfrage verwenden:
489
12 Optimierung von Abfragen Das Ergebnis dieser Abfrage könne in etwa so aussehen, wie in Abbildung 12.1 gezeigt.
Abbildung 12.1 Datencache-Statistik
Die Zeile Buffer cache hit ratio enthält die Anzahl von Seiten, die aus dem Cache gelesen wurden. In der Zeile Buffer cache hit ratio base wird die Gesamtanzahl von SeitenSuchvorgängen im Cache ausgegeben. Die prozentuale Trefferquote ist der Quotient dieser beiden Werte, der durch die folgende Abfrage ermittelt werden kann:
Der zurückgegebene Wert sollte deutlich über 95% liegen. Ein kleinerer Wert ist ein sicheres Indiz dafür, dass SQL Server mehr Hauptspeicher benötigt.
12.3
Erstellen einer Testdatenbank Für die Experimente in diesem Kapitel soll zunächst eine kleine Testdatenbank erzeugt werden. Diese Datenbank besteht aus zwei Tabellen, die einfach mit Zeilen aus der Datenbank AdventureWorks gefüllt werden. Hierzu wird das folgende Skript verwendet:
490
12.4 Verwenden von Indizes
Erzeugt wird eine Datenbank AWPerformance, die zwei Tabellen enthält: BestellPosund BestellKopf. Diese Tabellen enthalten alle Zeilen und einige ausgewählte Spalten der Tabellen Sales.SalesOrderDetail und Sales.SalesOrderHeader der Datenbank AdventureWorks. Die Datenbank AWPerformance wird für unsere Experimente im weiteren Verlauf dieses Kapitels verwendet.
12.4
Verwenden von Indizes Indizes sind spezielle Datenstrukturen, durch die das Suchen von Daten erheblich beschleunigt werden kann. Jeder Index für eine Tabelle wird als ausbalancierter Baum gespeichert. Für die Abfrageoptimierung existieren diverse Möglichkeiten, welche die Abfragezeit letztlich um einen bestimmten Prozentsatz verkleinern Der Einsatz von Indizes zur Beschleunigung von Abfragen ragt hierbei jedoch heraus. Durch Indizes kann die Abfragezeit nicht nur um einige Prozent, sondern gleich um Größenordnungen verbessert werden. Daher ist es enorm wichtig, dass Sie Indizes verstehen und sinnvoll einsetzen. In diesem Abschnitt werden Sie erfahren, wie Indizes funktionieren und wann der Einsatz von Indizes sinnvoll ist, aber auch, wann durch Indizes keine Verbesserung zu erwarten ist.
12.4.1 Tabellen ohne Index Heap Ein Heap (deutsch: Haufen) ist eine Tabelle ohne gruppierten Index. Die Tabellendaten liegen einfach unsortiert und unorganisiert als eine Menge von Blöcken auf der Festplatte. SQL Server verwaltet eine Zuordnungstabelle, die Index Allocation Map (IAM), in der letztlich Zeiger zu den einzelnen Seiten eines Heaps stehen. Ein Zeiger enthält hierbei die Datei-ID der SQL Server-Datendatei und die Nummer des entsprechenden Blocks. Abbildung 12.2 zeigt ein Beispiel für eine als Heap organisierte Tabelle. Wenn Daten aus einem Heap benötigt werden, so muss der gesamte Heap gelesen werden, ein Vorgang, der im Englischen als Table Scan bezeichnet wird. Schauen Sie sich zum Beispiel diese Abfrage an:
Die Tabelle BestellPos existiert derzeit als Heap, die Daten dieser Tabelle sind also völlig ungeordnet. Um die geforderten Daten zu liefern, muss die Tabelle BestellPos zei-
491
12 Optimierung von Abfragen lenweise durchsucht werden. Hierzu wird die Tabelle seiten- oder blockweise gelesen. Dieses Lesen erfolgt entweder von der Festplatte oder aus dem Datencache, sofern die Tabellendaten dort bereits vorhanden sind. Für jede Zeile wird dann der angegebene Zeilenfilter ausgewertet. Die Tabelle wird also gescannt.
Abbildung 12.2 Heap
12.4.2 Gruppierte Indizes Ein gruppierter Index enthält alle Daten der Tabelle in den Blattseiten des Indexbaumes in sortierter Form. Abbildung 12.3 zeigt ein Beispiel für einen gruppierten Index auf der Spalte BestId der Tabelle BestellKopf. Der dargestellte Indexbaum hat hier eine Tiefe von zwei. Jeder einzelne Indexeintrag in einer Nicht-Blattseite (dies ist im Beispiel nur eine Seite) besteht aus dem Schlüssel selber sowie einem Zeiger auf eine untergeordnete Seite. Dieser Zeiger beinhaltet die Datei-ID der Daten- oder Indexdatei sowie die Seitennummer innerhalb der Datei. Auf der Blattseite des Index steht dann allerdings kein Verweis mehr. In dieser Seite sind tatsächlich die Tabellendaten gespeichert. Ein gruppierter Index enthält also die kompletten Tabellendaten.
492
12.4 Verwenden von Indizes
Abbildung 12.3 Gruppierter Index auf BestellKopf.BestId
Die Blattseiten sind hierbei als doppelt verkettete, sortierte Listen aufgebaut. Jede Seite enthält also einen Verweis sowohl auf die vorhergehende als auch auf die nachfolgende Seite. Angenommen, es soll die Zeile mit BestId = 55141 gefunden werden. Die Suche beginnt an der Wurzel des Indexbaumes. Dort wird zunächst festgestellt, dass der gesuchte Schlüsselwert zwischen 42703 und 55142 liegt. Dies führt zu einem zweiten Suchschritt auf der Seite 3:123, auf die durch den Index verwiesen wird. Auf dieser Seite wird der gesuchte Wert gefunden. Jeder Suchvorgang umfasst also nur so viele Schritte, wie der Indexbaum Stufen besitzt. Der dargestellte Indexbaum verfügt über zwei Ebenen, folglich sind für jede Abfrage, die den Index verwendet, zwei Suchschritte also das Lesen von zwei Seiten ausreichend. Da die Tabelle BestellKopf über 31000 Zeilen in insgesamt 191 Seiten enthält, würde eine entsprechende Abfrage ohne Index (ein Tabellen-Scan) 191 Lesevorgänge hervorrufen. Somit ist zu erwarten, dass der Index die Abfrage ungefähr um den Faktor 85 beschleunigt! Für viele Tabellen sind übrigens tatsächlich nur zweistufige Indizes vorhanden. Nur bei großen Tabellen oder sehr großen Indizes, die sich also über viele Spalten erstrecken, wird der Indexbaum dreistufig sein. Extrem große Tabellen können auch vierstufige Indizes hervorbringen. Woher kommt der Wert 191 für die Anzahl der Seiten dieser Tabelle? Sie können diesen Wert ungefähr ausrechnen, wenn Sie die durchschnittliche Zeilenlänge der Tabelle ermitteln und dann feststellen, wie viele Zeilen in eine Datenseite passen. Hierfür müssen Sie wissen, wie viel Speicherplatz die einzelnen Datentypen der Tabelle benötigen, und auch zusätzliche Bytes für die Verwaltung jeder Zeile berücksichtigen. Anschließend teilen Sie einfach die Anzahl der Zeilen in der Tabelle durch die Anzahl der Zeilen je Seite. Einfacher
geht
es
durch
eine
Abfrage
der
dynamischen
Verwaltungssicht
. In dieser Sicht (genau genommen ist es eine Funk-
tion) sind nicht nur Informationen zu Indizes, sondern auch zu Heaps enthalten. Die Funktion erwartet vier Parameter, die festlegen, für welche Tabelle oder für welchen Index die
493
12 Optimierung von Abfragen Information abgefragt wird und wie detailliert die Ausgabe sein soll. Ein Aufruf für die Tabelle BestellKopf kann zum Beispiel so erfolgen:
Diese Abfrage gibt die von der Tabelle BestellKopf in der Datenbank AWPerformance belegten Datenseiten zurück. In meiner AWPerformance-Datenbank sind das 191 Seiten. Wichtig für gruppierte Indizes ist, dass Sie sich Folgendes einprägen: Der gruppierte Index ist keine Kopie der Tabelle, er ist die Tabelle. Dieser Sachverhalt kann auch so ausgedrückt werden: Wenn für eine Tabelle ein gruppierter Index existiert, dann gibt es keine Tabelle mehr. Stattdessen können Sie sich nun den Index, der ja auf den Blattseiten alle Daten der Tabelle enthält, als die Tabelle selber vorstellen. Aus diesem Grund ist auch nur ein gruppierter Index je Tabelle erlaubt. Der Vollständigkeit halber soll hier noch erwähnt werden, dass auch für eine Sicht ein gruppierter Index erstellt werden kann.
12.4.3 Nicht gruppierter Index auf einem Heap Eine Tabelle ohne gruppierten Index also ein Heap kann nicht gruppierte Indizes besitzen. Auch in diesem Fall wird ein Indexbaum erzeugt, so wie in Abbildung 12.4 dargestellt. Es existiert jedoch ein wesentlicher Unterschied zum gruppierten Index: Die Blattseite des Index enthält nicht die Daten der entsprechenden Zeile. Stattdessen ist in der Blattseite des Index ein Verweis auf den tatsächlichen Speicherort in der Datendatei enthalten. Dieser Verweis besteht aus den Teilen Datei-ID, Blocknummer und Position innerhalb des Blockes. Im Vergleich zum gruppierten Index ist beim Lesen immer ein zusätzlicher Schritt erforderlich, der letztlich anhand des Verweises in die Datendatei die eigentlichen Daten holt. In Abbildung 12.4 ist dies am Beispiel einer Suche nach der Zeile mit dem Wert dargestellt. Die Suche beginnt wieder an der Wurzel des Index. Im ersten Schritt wird festgestellt, dass der gesuchte Wert zwischen und liegt. Dadurch wird auf der Seite 3:128 weitergesucht. Auf dieser Seite wird der gesuchte Wert gefunden. Es muss nun über den hier eingetragenen Verweis ein weiterer Lesevorgang im Datenbereich der Tabelle erfolgen, durch den die angeforderten Daten geholt werden. Insgesamt sind also drei Lesevorgänge erforderlich (gegenüber von zwei Lesevorgängen bei Verwendung eines gruppierten Index). Für eine Tabelle können insgesamt 249 nicht gruppierte Indizes erstellt werden.
494
12.4 Verwenden von Indizes
Abbildung 12.4 Nicht gruppierter Index BestellKopf.Nr auf einem Heap (Tabelle BestellKopf)
12.4.4 Nicht gruppierter Index auf einem gruppierten Index Einen Tabelle, die bereits über einen gruppierten Index verfügt, kann zusätzlich auch noch bis zu 249 nicht gruppierte Indizes enthalten. Für einen nicht gruppierten Index auf einem gruppierten Index besteht allerdings ein wesentlicher Unterschied zum nicht gruppierten Index auf einem Heap: Der nicht gruppierte Index auf einem gruppierten Index enthält in den Blattseiten keine Verweise auf den Speicherort. Stattdessen ist dort der Index-Wert des gruppierten Index eingetragen. Abbildung 12.5 verdeutlicht dies an einem Beispiel. Im oberen Beeich ist ein nicht gruppierter Index auf der Spalte Nr der Tabelle BestellKopf dargestellt. In den Blattseiten für diesen Index ist hier aber kein Verweis auf die Daten enthalten, sondern der Schlüsselwert des gruppierten Index. Der Grund hierfür ist recht einfach zu verstehen: Würde hier der Verweis auf den physikalischen Speicherort stehen, so müsste dieser Verweis jedes Mal beim Aktualisieren des gruppierten Index aktualisiert werden und zwar für alle nicht gruppierten Indizes der Tabelle. Erinnern Sie sich bitte daran, dass der gruppierte Index die Daten in sortierter Form abspeichert. Wenn sich also Schlüsselwerte ändern, so wird sich auch die Sortierreihenfolge verändern. Dadurch müssten in einem solchen Fall alle Speicherortangaben der nicht gruppierten Indizes angepasst werden. Die Verfahrensweise, im nicht gruppierten Index nur den Schlüsselwert zu speichern, umgeht dies, hat jedoch einen anderen Nachteil: Bei Verwendung eines nicht gruppierten Index auf einem gruppierten Index in einer Abfrage muss der gruppierte Index ebenfalls durchsucht werden.
495
12 Optimierung von Abfragen
Abbildung 12.5 Nicht gruppierter Index BestellKopf.Nr auf gruppiertem Index BestellKopf.BestId
In Abbildung 12.5 wird über einen nicht gruppierten Index auf der Spalte Nr der Tabelle BestellKopf gesucht. Diese Tabelle verfügt auch über einen gruppierten Index auf der Spalte BestId. Ein Suchvorgang läuft nun so ab, dass zunächst der Schlüsselwert des gruppierten Index ermittelt wird. Hierfür sind wieder zwei Lesevorgänge erforderlich. Danach wird der gruppierte Index mit dem gefundenen Schlüssel durchsucht, was nochmals zwei Lesevorgänge bewirkt. Insgesamt werden also vier Seiten gelesen. Gegenüber der Variante ohne gruppierten, aber mit nicht gruppiertem Index ist also ein Lesevorgang mehr erforderlich. Normalerweise fällt dies jedoch nicht weiter ins Gewicht, da die NichtBlattseiten des gruppierten Index während einer solchen Abfrage sowieso in den Datencache eingelesen werden. Der zusätzliche Lesevorgang auf einer solchen Indexseite ist daher meist zu vernachlässigen. Beachten Sie jedoch, dass beim Ändern eines Schlüsselwertes für einen gruppierten Index auch alle nicht gruppierten Indizes entsprechend geändert werden müssen. Für einen gruppierten Index sollten Sie daher stets Spalten auswählen, die nicht mehr geändert werden. Hier bietet sich sicherlich der Primärschlüssel als Kandidat an. Etwas weiter unten werden wir hierauf noch einmal zurückkommen.
496
12.4 Verwenden von Indizes Eine weitere Besonderheit ist die folgende: Wenn nicht gruppierte Indizes existieren und Sie einen gruppierten Index erstellen, so müssen die nicht gruppierten Indizes ebenfalls neu erstellt werden. Dies kann natürlich je nach Tabellen- und Indexgröße ein zeit- und ressourcenintensiver Prozess sein. Abdeckende (Covered) Indizes Schauen Sie sich bitte noch einmal Abbildung 12.4 an. Der dort dargestellte Suchvorgang basiert auf dieser Abfrage:
Die Werte für die beiden Spalten BestDatum und Wert müssen durch einen zusätzlichen Lesevorgang über den vorhandenen Verweis in die Datendatei gelesen werden. Angenommen, die Abfrage wäre so formuliert worden:
In diesem Fall könnte der zusätzliche Lesevorgang eingespart werden, da die einzige Spalte, die von der Abfrage zurückgegeben werden soll, ja bereits in den Indexdaten existiert. Tatsächlich erkennt der Abfrageoptimierer eine solche Situation und spart den zusätzlichen Lesevorgang ein, der auch als Lookup bezeichnet wird. Im obigen Beispiel wird der Vorteil dieser Verfahrensweise vielleicht gar nicht so deutlich, da die Abfrage ja nur eine einzige Spalte zurückgibt. Indizes können jedoch auch über mehrere Spalten aufgebaut werden. Wenn dann eine Abfrage erstellt wird, die den Index verwendet und nur Spalten zurückgibt, die im Index enthalten sind, ist der Vorteil sicherlich einzusehen. SQL Server erlaubt die Aufnahme zusätzlicher Spalten in einen Index, um sogenannte abdeckende Indizes zu erstellen. Solche Indizes enthalten Spalten, die eigentlich nicht zum Index gehören. Der Index ist also nicht etwa nach diesen Spalten sortiert und kann auch nicht herangezogen werden, um zum Beispiel den Baum nach diesen Spaltenwerten zu durchsuchen. Allerdings werden durch solche eingeschlossenen Spalten eben Lesevorgänge in den Datendateien eingespart. Eingeschlossene Spalten können selbstverständlich nur für nicht gruppierte Indizes verwendet werden. Der gruppierte Index enthält ja bereits alle Spalten. Auf abdeckende Indizes werden wir etwas weiter unten noch einmal zurückkommen.
12.4.5 Erzeugen von Indizes Nachdem Ihnen nun bekannt ist, wie Indizes funktionieren, fehlt natürlich noch die Information, wie Indizes erstellt werden. Hierfür bietet SQL Server prinzipiell zwei Möglichkeiten an: die manuelle Erzeugung von Indizes und die automatische Indexerstellung.
497
12 Optimierung von Abfragen CREATE INDEX Ein Index kann manuell durch CREATE INDEX erstellt werden. Diese Anweisung hat die folgende etwas vereinfachte Syntax:
Bei der Erstellung muss also ein Name für den Index vergeben werden. Außerdem erforderlich ist die Angabe der Tabelle, für die der Index erzeugt werden soll. In runden Klammern hinter dem Tabellennamen stehen dann durch Komma getrennt die Spalten, für die der Index erzeugt werden soll. Wenn Sie mehrere Spalten verwenden, beachten Sie bitte die Reihenfolge der Spalten. Als Faustregel können Sie sich merken, dass der Abfrageoptimierer nur die erste Spalte des Index verwendet, um zu entscheiden, ob eine Suche im Indexbaum sinnvoll ist. Der folgende Index auf der Tabelle BestellPos:
wird für diese Abfrage:
daher nicht verwendet, um den Indexbaum zu durchsuchen. Er kann durchaus verwendet werden, zum Beispiel für eine sequenzielle Suche, aber eben nicht für eine Indexsuche im eigentlichen Sinn. Zwischen den Schlüsselwörtern CREATE und INDEX können noch die folgenden Optionen angegeben werden: CLUSTERED. Es wird ein gruppierter Index erstellt. NONCLUSTERED. Es wird ein nicht gruppierter Index erstellt. Dies ist der Standard. UNIQUE. Es wird ein Index erstellt, der eindeutige Werte enthalten muss, also keine mehrfachen Spaltenwerte zulässt. Wenn Sie Indizes haben, bei denen dies zutrifft, so sollten Sie UNIQUE angeben, da es SQL Server die Verwaltung von Indizes erleichtert. Lassen Sie diese Option weg, so darf der Index mehrfache Schlüsselwerte enthalten. Für die Tabelle BestellKopf sollen nun zwei Indizes erstellt werden. Zunächst wollen wir einen eindeutigen gruppierten Index für die Spalte Nr erzeugen:
Es soll nun auch ein eindeutiger Index für die Spalte Id hinzugefügt werden:
Dieser Index wird als nicht gruppierter Index angelegt. Im Objekt-Explorer finden Sie Indizes im Ordner für die entsprechende Tabelle (Abbildung 12.6).
498
12.4 Verwenden von Indizes
Abbildung 12.6 Indizes für die Tabelle BestellKopf
Anzahl der Indexstufen Die dynamische Verwaltungssicht gibt Informationen über die existierenden Indizes von Tabellen zurück. Um zum Beispiel abzufragen, wie viele Stufen die beiden angelegten Indizes enthalten, kann die folgende SELECTAnweisung verwendet werden:
Die Angabe von 'detailed' im letzten Parameter bewirkt die Ausgabe von Informationen zu jeder Indexstufe. Für meine Tabellen und Indizes wird das folgende Ergebnis erzeugt:
Beide Indizes haben also eine Tiefe von 2. Auf der ersten Stufe ist jeweils nur die Wurzel des Indexbaumes enthalten. Die Stufe 2 enthält für den gruppierten Index 183 und für den nicht gruppierten Index 98 Seiten. Der Unterschied in der Seitenzahl erklärt sich natürlich dadurch, dass die Blattseiten des gruppierten Index (dies sind die Seiten auf der Stufe 0) ja alle Tabellendaten enthalten, während in den Blattseiten des nicht gruppierten Index lediglich der Schlüsselwert des gruppierten Index gespeichert ist. Automatische Erzeugung von Indizes SQL Server erzeugt automatisch Indizes, wenn Sie für eine Tabelle die folgenden Einschränkungen verwenden:
499
12 Optimierung von Abfragen PRIMARY KEY. Für den Primärschlüssel einer Tabelle wird standardmäßig ein gruppierter Index erstellt. UNIQUE. SQL Server erstellt automatisch einen nicht gruppierten Index für eine UNIQUE-Einschränkung. Der Grund hierfür ist natürlich, dass diese Indizes die Prüfung der Einschränkung extrem beschleunigen. Ohne einen entsprechenden Index müsste jeweils die Tabelle durchsucht werden, um die Einschränkung zu testen, was natürlich in der Regel erheblich länger dauert als die Suche über einen Index. Der Füllfaktor für einen Index Für einen Index kann bei der Erstellung ein sogenannter Füllfaktor angegeben werden, über den festgelegt wird, zu wie viel Prozent die Blattebene des Index mit Daten belegt sein soll. Wenn Sie erwarten, dass die im Index verwendeten Spalten häufig geändert werden, der Index also oft umstrukturiert werden muss, so kann es sinnvoll sein, einen niedrigen Füllfaktor zu verwenden, also Speicherplatz in den Blattseiten freizuhalten. Ein niedriger Füllfaktor beschleunigt das Ändern von Zeilen, da eine eventuelle Umordnung des Indexbaumes vermieden wird. Dafür benötigt ein solcher Index natürlich mehr Speicherplatz. Mehr Speicherplatz führt unter Umständen dazu, dass mehr Lesevorgänge erforderlich sind, wenn der Index verwendet wird. Bitte beachten Sie hierbei, dass der angegebene Füllfaktor lediglich beim Erstellen des Index verwendet wird. Eine spätere dynamische Verwaltung wird durch SQL Server nicht durchgeführt. Der Index-Füllfaktor wird über die WITH-Klausel bei CREATE INDEX folgendermaßen in Prozent angegeben:
Zusätzlich kann auch festgelegt werden, dass der angegebene FILLFACTOR ebenso für die Nicht-Blattseiten verwendet werden soll. Dies geschieht über die Klausel PAD_INDEX, die nur zusammen mit FILLFACTOR verwendet werden darf:
Wenn Sie den FILLFACTOR bei der Erstellung eines Index nicht angeben, so gilt eine serverweite Standardeinstellung. Diese Einstellung ist nach der Installation des Servers 100%. Sie kann durch die gespeicherte Systemprozedur abgefragt und geändert werden:
500
12.5 Messen der Leistung Einen Index neu erstellen In der WITH-Klausel der CREATE INDEX-Anweisung kann auch die Option DROP_EXISTING=ON angegeben werden, die bewirkt, dass ein Index neu erstellt wird. Der Index wird sozusagen in einem Schritt entfernt und neu aufgebaut. Um zum Beispiel den weiter oben erzeugten Index ix_BK_Id für die Tabelle BestellKopf mit einem Füllfaktor von 30% neu zu erstellen, können wir schreiben:
Wenn wir nun noch einmal überprüfen, wie viele Stufen die Indizes dieser Tabelle haben (durch Abfrage der dynamischen Verwaltungssicht , so wie weiter oben gezeigt), so kommt Folgendes heraus:
Der nicht gruppierte Index verfügt nun aufgrund des niedrigen Füllfaktors über wesentlich mehr Seiten und auch eine weitere Stufe. Das Durchsuchen des Indexbaumes erfordert für diesen Index jetzt bedingt durch die dritte Stufe einen zusätzlichen Lesevorgang.
12.4.6 Löschen von Indizes Die Anweisung DROP INDEX löscht vorhandene Indizes. Hierbei muss sowohl der Indexals auch der Tabellenname angegeben werden. Die folgende Anweisung löscht den erstellten Index ix_BK_Id auf der Tabelle BestellKopf:
12.5
Messen der Leistung Es ist natürlich leicht einzusehen, dass Abfragen, die für eine Optimierung in Frage kommen bzw. für die sich die Optimierung lohnen wird, zunächst einmal aufgespürt werden müssen. Das Messen der Abfrageleistung ist deshalb in der Regel der Ausgangspunkt für einen Optimierungsansatz. In diesem Abschnitt werden Ihnen verschiedene Möglichkeiten zur Kontrolle der Ausführung von Abfragen vorgestellt. Bevor fortgefahren wird, sollen aber zunächst alle Indizes in der Datenbank AWPerformace gelöscht werden. Hierzu können Sie das folgende Skript verwenden:
501
12 Optimierung von Abfragen
Das Skript konstruiert ein Kommando, das aus DROP INDEX-Anweisungen besteht, die durch ein Semikolon getrennt sind. Um die Indizes zu finden, wird die Systemsicht verwendet. Die bislang erstellten Indizes beginnen alle mit den Buchstaben ix. Aus werden daher auch nur diese Indizes abgefragt. Das abschließende EXEX führt dann alle DROP INDEX-Anweisungen mit einem Mal aus. Löschen von Prozedur- und Datencache Wenn Sie Messungen durchführen, so ist es wichtig, dass Sie stets dieselben Randbedingungen verwenden. Dies ist nicht immer einfach, da SQL Server sowohl Abfragepläne als auch Daten für eine erneute Verwendung zwischenspeichert. Dadurch wird eine wiederholte Ausführung einer Abfrage auf der Basis des Caches möglicherweise schneller sein. Wenn Sie also sichergehen wollen, dass die Rahmenbedingungen dieselben sind, so können Sie eine Abfrage mehrfach ausführen und dann die Messung der wiederholten Ausführung verwenden. Es ist jedoch auch möglich, sowohl den Daten- als auch den Prozedurcache zu leeren, bevor mit der Messung begonnen wird. Hierzu verwenden Sie das Kommando DBCC in der folgenden Form:
Seien Sie aber bitte vorsichtig mit diesen Anweisungen in Produktivumgebungen. Das Leeren der Puffer erfolgt für alle Datenbanken, also den gesamten Server. Dies kann die Performanz natürlich ganz empfindlich beeinträchtigen.
12.5.1 Messen mit der Stoppuhr Die Stoppuhr ist tatsächlich ein geeignetes Instrument zur Messung. Sie ist für den Datenbankentwickler bzw. -administrator möglicherweise genauso ein Universalwerkzeug wie es das Taschenmesser für den Handwerker ist. Allerdings brauchen Sie natürlich keine Stoppuhr, die mechanisch betätigt wird. Stattdessen können Sie einfach die integrierten Funktionen zur Abfrage und Konvertierung von Datum und Uhrzeit verwenden, um eine Messung durchzuführen. Hierzu wird einfach die Zeit am Beginn eines Skriptes festgehalten und am Ende eines Skriptes nochmals genommen. Die Differenz kann dann zum Beispiel ausgegeben oder auch in einer Tabelle gespeichert werden. Das folgende Skript demonstriert diese Verfahrensweise:
502
12.5 Messen der Leistung
Anstelle der abschließenden SELECT-Anweisung können Sie natürlich ebenso ein INSERT verwenden, um das Ergebnis in eine Protokolltabelle einzutragen, also etwa so:
Beachten Sie bitte hierbei, dass die Funktion GETDATE() einen DATETIME-Wert mit einer Genauigkeit von 3,33 ms liefert. Genauere Messungen sind mit dieser Methode also nicht möglich.
12.5.2 Statistische Werte SQL Server kann nach der Ausführung einer Abfrage statistische Werte zu Lesevorgängen sowie zu Ausführungszeiten ausgeben. Hierzu existieren die folgenden beiden Ausführungsoptionen für Abfragen, die wahlweise ein- oder ausgeschaltet werden können: SET STATISTICS IO Wenn Sie diese Option einschalten, erhalten Sie Informationen zu E-/A-Operationen jeder im Stapel enthaltenen Abfrage. Lassen Sie uns dies an einem Beispiel betrachten. Zunächst einmal soll hierfür der Datencache geleert werden. Anschließend wird eine SELECT-Anweisung ausgeführt, nachdem zuvor noch STATISTICS IO eingeschaltet wurde:
Im Meldungsbereich der Ausgabe finden Sie nach der Ausführung der Abfrage die Informationen über die E-/A-Statistik:
Die Ausgabe enthält Informationen darüber, wie oft auf eine Tabelle im Abfrageplan zugegriffen wurde (Scananzahl), wie viele Seiten vom Datencache gelesen wurden (logische Lesevorgänge) und wie viele Lesevorgänge von der Festplatte erforderlich waren (physikalische Lesevorgänge und Read-ahead-Lesevorgänge). Dieselbe Information wird dann
503
12 Optimierung von Abfragen auch noch einmal für LOB-Daten, also Large OBjects, wie zum Beispiel Spalten von Typ TEXT oder IMAGE ausgegeben. Das obige Beispiel enthält eine Menge physikalischer Lesevorgänge, weil wir den Datencache vorher geleert haben. Interessant ist auch, dass die Tabelle in einer Abfrage drei Mal verwendet wird (Scananzahl=3). Wie kann so etwas passieren? Der Optimierer entscheidet sich für eine parallele Ausführung der Abfrage, verwendet also mehrere Threads. Dies liegt ganz einfach daran, dass die Kosten für die Abfrage hierdurch minimiert werden konnten. Führen Sie die Abfrage nun noch einmal aus, dann ergibt sich folgende E-/A-Statistik (dies ist abhängig von der Hauptspeicherausstattung Ihres Computers):
Es werden nun keine physikalischen Lesevorgänge mehr benötigt, da sich bereits alle Daten im Cache befinden. Die Anzahl der logischen Lesevorgänge, also der Leseoperationen aus dem Cache, ist natürlich identisch. Schalten Sie STATISTICS IO durch diese Anweisung wieder aus:
SET STATISTICS TIME Diese Abfrageoption ermöglicht die Ausgabe von Informationen zur benötigten Abfragezeit. Um dies einmal auszuprobieren, soll die obige Abfrage nochmals ausgeführt werden, diesmal mit eingeschalteter Option STATISTICS TIME. Zusätzlich zum Datencache wird hier zuvor allerdings auch noch der Prozedurcache geleert:
Auf meiner Maschine sieht das Ergebnis so aus:
Die erste Ausgabe gibt Auskunft darüber, wie lange der Abfrageoptimierer zum Erstellen des Abfrageplans benötigt hat. Dies waren also insgesamt 81 ms. Die zweite Ausgabe enthält die Information für die Ausführung der Abfrage, die hier insgesamt ca. 30 Sekunden benötigt hat. 688 ms CPU-Zeit wurden für die Abfrageausführung beansprucht.
504
12.5 Messen der Leistung Wir wollen auch diesmal die SELECT-Anweisung erneut ausführen ohne jedoch zuvor den Daten- oder Prozedurcache zu leeren. Das Resultat ändert sich recht drastisch:
Für die Erstellung des Abfrageplanes wurde jetzt nur noch 1 ms benötigt, da der Plan aus dem Prozedurcache geholt werden konnte. Auch die Daten für das Ergebnis wurden aus dem Cache geholt, wodurch sich die Zeit für die Abfrage auf 194 ms verkürzt hat. Allein durch die Verwendung der Puffer für Abfragepläne und Daten ist die Abfrage ungefähr 150 Mal schneller geworden! Ein gutes Argument für ausreichend Hauptspeicherkapazität, nicht wahr? Wenn Sie sich darüber wundern, dass die CPU-Zeit größer als die verstrichene Zeit ist: STATISTICS TIME gibt die Summe aller CPU-Zeiten aus. In meinem PC verrichtet ein Dual-Core-Prozessor seine Arbeit, und die Abfrage verwendet offenbar beide CPUs. Zum Abschluss soll nun STATISTICS TIME wieder ausgeschaltet werden. Dies geschieht wie gewohnt:
12.5.3 Ausführungspläne Ein vom Optimierer erstellter Abfrageplan ist kein Geheimnis. SQL Server gestattet die Betrachtung von Abfrageplänen auf verschiedene Arten und ermöglicht so auch einen Einblick in die Arbeitsweise des Optimierers. Die Analyse und Interpretation von Ausführungsplänen ist eine recht komplexe Thematik. An dieser Stelle können nur die wesentlichen Grundlagen erläutert werden, die Sie als Entwickler in die Lage versetzen, sich tiefer in diese Materie einarbeiten zu können. Ich kann Ihnen nur wärmstens empfehlen, dies nach und nach zu versuchen, um zu verstehen, wie der Optimierer arbeitet. Die Zeit, die Sie hierfür investieren, ist wirklich gut angelegt. Bitte beachten Sie, dass die in diesem Abschnitt erstellten Ausführungspläne auf Ihrem Computer eventuell etwas anders aussehen können, wenn Sie die Beispiele nachvollziehen. Dies ist abhängig von der Hardware-Ausstattung und auch von der Server-Konfiguration. Grafische Ausführungspläne In Kapitel 3 haben Sie bereits ein Beispiel für einen grafischen Ausführungsplan gesehen. Ein solcher Plan gibt Auskunft darüber, wie eine Abfrage verarbeitet, wie sie physikalisch ausgeführt wird. Im Management Studio existieren für die Anzeige eines grafischen Ausführungsplanes zwei Möglichkeiten:
505
12 Optimierung von Abfragen Geschätzten Ausführungsplan anzeigen. Wenn Sie für eine im Editor stehende Abfrage den geschätzten Ausführungsplan anzeigen möchten, können Sie aus dem Menü das Kommando Abfrage/Geschätzten Ausführungsplan anzeigen wählen. Alternativ geht dies auch über den oben dargestellten Button in der Menüleiste. Hierbei wird die Abfrage nur analysiert und nicht ausgeführt. Diese Option ist zum Beispiel nützlich, wenn die eigentliche Abfrageausführung lange dauert Tatsächlichen Ausführungsplan anzeigen. Dies ist kein Kommando, sondern eine Option. Wenn Sie die Option einschalten (über die Menüleiste oder über Abfrage/Tatsächlichen Ausführungsplan einschließen), dann wird bei der Ausführung der Abfrage der Ausführungsplan in das Ergebnis eingeschlossen. Der Plan wird in einem separaten Reiter im Ergebnisbereich angezeigt. Wir wollen mit einem simplen Ausführungsplan beginnen, um zu zeigen, welche Information ein Ausführungsplan enthält. Hierzu wird im ersten Versuch diese Abfrage verwendet:
Lassen Sie sich den geschätzten Ausführungsplan anzeigen, wie oben beschrieben. Heraus kommt ein Plan, wie in Abbildung 12.7 dargestellt.
Abbildung 12.7 Ein einfacher Ausführungsplan
Ein Ausführungsplan wird als Graph dargestellt, der von rechts nach links und von oben nach unten gelesen wird. Die Knoten im Graph stehen für Operatoren, die Kanten (Pfeile) repräsentieren den Datenfluss.
506
12.5 Messen der Leistung Der Ausführungsplan für unsere Abfrage mit einer Tabelle ist recht simpel. Die Tabelle wird zunächst durch einen Tabellen-Scan durchsucht. An den SELECT-Operator werden vom Tabellen-Scan nur die Zeilen übergeben, welche die Zeilenfilterbedingung erfüllen. Für jeden Operator werden im Plan die anteiligen Kosten der gesamten Abfrage angezeigt. Im Beispiel erzeugt der Tabellen-Scan 100% der Abfragekosten. Wenn Sie den Mauszeiger über einen Operator bewegen, so erhalten Sie weiterführende Informationen zu diesem Operator. In der Abbildung ist diese Information für den TableScan-Operator dargestellt. Von den präsentierten Informationen sind insbesondere diese beiden wichtig: Geschätzte Unterstrukturkosten. Dieser Wert bezieht sich auf die Gesamtkosten des Knotens inklusive aller untergeordneten Knoten. Für den am weitesten links stehenden Knoten (im Beispiel der SELECT-Operator) sind die Unterstrukturkosten also die Gesamtkosten der Abfrage. Geschätzte Anzahl von Zeilen. Dies ist ein Schätzwert für die Zeilenanzahl, die vom Operator verarbeitet wird. Der Wert kann von der tatsächlichen Zeilenanzahl abweichen. In einem geschätzten Ausführungsplan (ein solcher Plan ist in der Abbildung dargestellt) ist nur dieser Schätzwert enthalten, der aus den existierenden Statistiken ermittelt wird. Wenn Sie den tatsächlichen Ausführungsplan einschließen, erhalten Sie zusätzlich eine Angabe über die tatsächliche Zeilenanzahl. Wenn diese beiden Werte im tatsächlichen Ausführungsplan voneinander abweichen, so ist dies ein sicheres Indiz dafür, dass die Statistiken veraltet sind und aktualisiert werden sollten. Wie Sie Statistiken aktualisieren, erfahren Sie etwas weiter hinten in diesem Kapitel. Sicher haben Sie sich bereits gefragt, was der Begriff Abfragekosten in diesem Zusammenhang bedeutet. Etwas weiter oben wurde bereits erwähnt, dass der Optimierer hier nur eine Interpretation kennt: die Zeit, welche die Abfrage für die Ausführung benötigt. Der für Kosten angezeigte Wert repräsentiert tatsächlich die geschätzte Anzahl von Sekunden, welche die Abfrage auf einem von Microsoft eingerichteten Referenzcomputer benötigen würde. Das zweite Beispiel verwendet eine Abfrage, in der auch ein JOIN enthalten ist:
Für diese Abfrage über zwei Tabellen erhalten wir den in Abbildung 12.8 gezeigten Ausführungsplan.
507
12 Optimierung von Abfragen
Abbildung 12.8 Ein Ausführungsplan mit einem JOIN Die Ausführung beginnt in der rechten oberen Ecke, also mit einem Tabellen-Scan der Tabelle BestellKopf. Der (INNER) JOIN wird durch den HASH MATCH-Operator abgebildet, der für jede Zeile der äußeren Tabelle BestellKopf einen weiteren Tabellen-Scan auf der Tabelle BestellPos durchführt, um die Zeilen zurückzugeben, welche die JOINBedingung erfüllen. Dieser Tabellen-Scan macht 99% der Kosten für die gesamte Abfrage aus. HASH MATCH ist eine bestimmte Methode, einen JOIN physikalisch auszuführen. Wie der Name andeutet, arbeitet dieser Operator hierfür mit einer Hash-Tabelle. Die Abbildung verdeutlicht ein weiteres nützliches Feature des grafischen Ausführungsplanes: Die Stärke der Pfeile, welche den Datenfluss anzeigen, ist ein Gradmesser für die Anzahl der Zeilen, die an den jeweiligen Operator übergeben werden. Die kurze Einführung in die Analyse von grafischen Ausführungsplänen soll an dieser Stelle zunächst abgeschlossen werden. Später in diesem Kapitel kommen wir noch einmal auf Ausführungspläne zurück und werden sehen, wie man diese Pläne für die Optimierung verwenden kann. XML-Ausführungspläne: SET SHOWPLAN_XML Der grafische Ausführungsplan ist ein extrem nützliches Hilfsmittel für die Abfrageanalyse und -optimierung. Allerdings enthält der Ausführungsplan in dieser Form nicht alle Informationen. (Hierfür werden Sie weiter unten ein Beispiel sehen.) SQL Server bietet auch die Möglichkeit, einen ausführlichen Ausführungsplan im XML-Format anzusehen. Hierzu existiert die Einstellung SET SHOWPLAN_XML. Wenn Sie diese Option auf ON setzen, dann wird der Ausführungsplan zunächst als Link auf ein XML-Dokument angezeigt. Beim Klick auf den Link wird dieses Dokument im Editor geöffnet. Zu beachten ist, dass SET SHOWPLAN_XML die einzige Anweisung in einem Stapel sein muss. Außerdem wird eine Abfrage nicht ausgeführt, wenn diese Option ON ist. Wir wollen für die obige Abfrage den Ausführungsplan im XML-Format einsehen. Dazu kann das folgende Skript verwendet werden:
508
12.5 Messen der Leistung
Das Ergebnis des Skript-Laufes ist nur der Link auf den Abfrageplan, der etwa so aussieht:
Nach einem Klick auf den angebotenen Link wird der Ausführungsplan angezeigt. In Listing 12.1 sehen Sie einen Auszug aus dem Plan. Dort wird der Tabellen-Scan auf der Tabelle BestellPos beschrieben. Listing 12.1 Auszug aus einem XML-Ausführungsplan
Natürlich haben Sie die Möglichkeit, den erzeugten Plan zu speichern und mit eigenen Analysewerkzeugen oder zum Beispiel selbst entwickelten XSLT-Stylesheets zu untersuchen. Nützlich ist ein Ausführungsplan im XML-Format auch zum Austausch von Informationen mit Kollegen oder etwa einem technischen Support. Gespeicherte Ausführungspläne Vielleicht haben Sie sich bereits gefragt, ob die im Prozedurcache gespeicherten Ausführungspläne auch eingesehen werden können. So etwas ist tatsächlich möglich. Im Puffer existierende Pläne können durch eine dynamische Verwaltungssicht (hier ist es eine Funktion) im XML-Format angezeigt werden.
509
12 Optimierung von Abfragen Hierfür verwenden Sie die Funktion , an die ein Handle für den Plan übergeben werden muss, der ausgegeben werden soll. Dieses Handle können Sie über die dynamische Verwaltungssicht ermitteln. enthält auch eine Spalte mit einem Handle für die entsprechende T-SQL-Anweisung. Mit diesem Handle kann der Text der Anweisung aus der Funktion abgefragt werden. Eine entsprechend konstruierte Abfrage sieht dann so aus:
Über die WHERE-Klausel werden hier nur Abfragen zurückgegeben, die in irgendeiner Form den Text Bestellkopf enthalten. Wenn Sie die Beispiele in der hier präsentierten Reihenfolge ausprobiert haben, dann ist dies nur eine Abfrage. Neben dem Ausführungsplan und dem Abfragetext wird hier auch noch die Ausführungszeit der Abfrage zurückgegeben. Das Resultat sieht dann etwa so aus:
Auch hier wird der Ausführungsplan als Link auf ein XML-Dokument zurückgegeben. Einen XML-Ausführungsplan grafisch anzeigen Wenn Sie sich zum Beispiel den vom vorherigen Beispiel gelieferten XMLAusführungsplan anzeigen lassen, dann existiert leider zunächst keine Möglichkeit, diesen Plan auch grafisch auszugeben. Hier hilft jedoch ein kleiner Trick: Das SQL Server Management Studio ist standardmäßig mit Dateien der Endung .SQLPLAN verknüpft. Solche Dateien werden vom Management Studio als Ausführungspläne interpretiert und beim Öffnen in grafische Form angezeigt. Öffnen Sie also ein XML-Dokument mit einem Ausführungsplan durch einen Klick auf den Link. Anschließend speichern Sie dieses Dokument als .SQLPLAN-Datei (Abbildung 12.9). Die so gespeicherte Datei können Sie anschließend im Management Studio öffnen, zum Beispiel einfach durch Doppelklick im Explorer oder über das Menü Datei/Öffnen/Datei.... Dadurch wird der Ausführungsplan im Management Studio in grafischer Form angezeigt, so wie in Abbildung 12.7 zu sehen. Eine solche Vorgehensweise bietet eine hervorragende Möglichkeit zum Austausch und zur gemeinsamen Analyse von Ausführungsplänen.
510
12.5 Messen der Leistung
Abbildung 12.9 Einen Ausführungsplan als SQLPLAN speichern
12.5.4 Der SQL Server Profiler Der SQL Server Profiler wurde bereits in Kapitel 3 erwähnt. Dieses Werkzeug ist eine nahezu unschätzbare Hilfe bei der Fehlersuche und natürlich auch für die Optimierung. Der Profiler protokolliert SQL Server-Aktivitäten in einer sogenannten Ablaufverfolgung (englisch: Trace). Hierbei können Sie detailliert auswählen, welche Ereignisse in diese Ablaufverfolgung aufgenommen werden sollen. Der Einsatz des Profilers erfolgt hauptsächlich aus drei Gründen: Fehlersuche. Der Profiler kann über eine grafische Benutzeroberfläche die an den Server gesendeten Abfragen darstellen. Falls eine Client-Anwendung unerklärbare Fehler produziert, so kann der Profiler sehr hilfreich sein, da Sie in der Oberfläche genau verfolgen können, in welcher Form und auch von wem Abfragen an den Server übermittelt werden. Dadurch ist eine Lokalisierung des Fehlers oftmals sehr einfach. Lernen. Da der Profiler SQL-Anfragen protokolliert, ist er auch sehr hilfreich, wenn Sie versuchen, SQL Server zu verstehen. So können Sie zum Beispiel vordefinierte Berichte im Management Studio erstellen, während eine Ablaufverfolgung läuft. Im Protokoll sehen Sie dann, welche System- und dynamischen Verwaltungssichten aufgerufen werden, um den Bericht zu erstellen. Optimierung. Die vom Profiler erzeugte Ablaufverfolgung kann auch für eine Untersuchung des Laufzeitverhaltens von Abfragen zum Zwecke der Optimierung verwendet werden. Diese Möglichkeit wird im folgenden Teil näher beschrieben. In Kapitel 3 wurde bereits erklärt, wie Sie den Profiler prinzipiell verwenden. In diesem Kapitel wollen wir nun also sehen, wie der Profiler speziell für die Optimierung eingesetzt werden kann.
511
12 Optimierung von Abfragen Beginnen wollen wir mit einer wesentlichen Feststellung: Die aus der Physik bekannte Heisenberg´sche Unschärferelation, die im Prinzip besagt, dass jede Messung das Ergebnis verfälscht, gilt auch für SQL Server und den Profiler. Mit anderen Worten: Eine Ablaufverfolgung benötigt ihrerseits ebenso Ressourcen auf dem Server, verschlechtert also das Laufzeitverhalten des Servers. Wenn Sie den Profiler verwenden, um das Laufzeitverhalten Ihrer Abfragen zu untersuchen, sollten Sie diesen Faktor unbedingt ernst nehmen. Der Profiler kann das Laufzeitverhalten von SQL Server ganz erheblich beeinflussen. Daher beachten Sie bitte die folgenden Ratschläge: 1. Verwenden Sie eine serverseitige Ablaufverfolgung, verzichten also auf die grafische Oberfläche des Profilers. Die grafische Repräsentation erzeugt enorme Netzwerk- und auch Server-Last. Das in diesem Abschnitt präsentierte Beispiel zeigt, wie es gemacht wird. 2. Nehmen Sie in Ihr Protokoll so wenige Ereignisse wie möglich auf. Verwenden Sie nur die wirklich benötigten Ereignisse und Ereignisspalten. 3. Speichern Sie die Ablaufverfolgung in einer Datei. Der Profiler ermöglicht auch das Speichern in einer Tabelle einer beliebigen Datenbank. Am wenigsten Kosten verursacht die Speicherung in einer Datei des Betriebssystems. Diese Datei kann dann später in eine Tabelle konvertiert werden. Etwas weiter unten werden Sie sehen, wie dies funktioniert. Angenommen, es existieren Probleme mit dem Laufzeitverhalten in der Datenbank AWPerformance. (So wie die physikalische Struktur dieser Datenbank im Moment aussieht, ist dies kein Wunder. Aber dazu später mehr
) Sie entscheiden sich dafür, eine serverseitige Ablaufverfolgung mitlaufen zu lassen, um diese später untersuchen zu können und herauszufinden, wodurch die Probleme erzeugt werden. Zum Erstellen einer entsprechenden Ablaufverfolgung starten Sie bitte zunächst den Profiler. Starten Sie auch eine neue Ablaufverfolgung. Lesen Sie bitte nochmals in Kapitel 3 nach, wenn Sie nicht wissen, wie Sie hierfür vorgehen sollen. In die Ablaufverfolgung sollen nur die für eine Optimierung erforderlichen Ereignisse aufgenommen werden. Glücklicherweise müssen wir diese Ereignisse nicht erst zusammensuchen. Eigens für diesen Zweck gibt es die Vorlage Tuning, die wir für die Ablaufverfolgung verwenden wollen (Abbildung 12.10).
512
12.5 Messen der Leistung
Abbildung 12.10 Neue Ablaufverfolgung mit der Vorlage Tuning
Starten Sie die Ablaufverfolgung anschließend durch den Button Ausführen. Wenn die Ablaufverfolgung läuft, wählen Sie sofort aus dem Menü Exportieren/Skript für Ablaufverfolgungsdefinition erstellen/Für SQL Server 2005
(Abbildung 12.11).
Abbildung 12.11 Skript für eine serverseitige Ablaufverfolgung erstellen
Anschließend geben Sie für die Speicherung des Skriptes einen Dateinamen an. Wir haben nun ein T-SQL-Skript erzeugt, das eine serverseitige Ablaufverfolgung erstellt. Der Profiler kann jetzt wieder beendet werden. Das erzeugte Skript sieht ungefähr so aus wie in Listing 12.2. Listing 12.2 Serverseitiges Skript für eine Tuning-Ablaufverfolgung
513
12 Optimierung von Abfragen
Im Skript muss zunächst noch eine Anpassung vorgenommen werden. Das obige Listing enthält ziemlich am Anfang eine Zeile, in welcher der Name der Protokolldatei spezifiziert wird. (Diese Zeile ist im Listing fett gekennzeichnet.) Geben Sie hier einen Dateinamen an, und achten Sie dabei darauf, dass es sich um einen serverseitigen Dateinamen handelt.
514
12.5 Messen der Leistung Die Dateiendung wird automatisch auf TRC (für Trace) gesetzt. Die entsprechende Programmzeile könnte also so aussehen:
Die gespeicherte Systemprozedur erzeugt eine serverseitige Ablaufverfolgung und gibt ein Handle auf diese Ablaufverfolgung in einem Ausgabeparameter zurück. Dieser Handle wird im Parameter empfangen. Anschließend werden über sequenzielle Aufrufe der Prozedur die Ereignisse und Ereignisspalten für die Ablaufverfolgung konfiguriert:
Die Ablaufverfolgung wird dann durch den Aufruf von:
gestartet. Zum Schluss wird dann noch die erzeugte Trace-ID ausgegeben. Diese ID wird später zum Beenden der Ablaufverfolgung benötigt. Selbstverständlich hätte das Skript auch manuell erstellt werden können. Hierzu müssten die einzelnen Trace-Flags aber bekannt sein. Sie werden mir sicher dahingehend zustimmen, dass es fast unmöglich ist, die Flags für die Vielzahl der unterschiedlichen Ereignisse alle im Kopf zu haben. Auch das Heraussuchen aus der Online-Dokumentation wäre wohl recht mühsam. Die Verwendung des Profilers als Skript-Generator ist hier sicherlich der bequemere Weg, der außerdem natürlich auch weniger fehleranfällig ist. Ändern Sie nun bitte den Dateinamen für das Protokoll, und führen Sie das Skript aus. Zum Schluss des Skriptes wird eine Trace-ID ausgegeben. Diese ID hat üblicherweise den Wert 2. Die ID mit dem Wert 1 ist bereits durch das normalerweise immer beim Start von SQL Server automatisch ausgeführte Minimal-Trace belegt. Dieses Minimal-Trace können Sie sich wie eine Art Flugschreiber für SQL Server vorstellen. Nun, da die Ablaufverfolgung ausgeführt wird, wollen wir einige Abfragen starten, die in das Protokoll aufgenommen werden sollen:
515
12 Optimierung von Abfragen Nachdem die Abfragen ausgeführt wurden, kann die Ablaufverfolgung angehalten werden. Dies wird ebenfalls durch die gespeicherte Systemprozedur erledigt. Hierfür benötigen Sie die Trace-ID:
Die Protokolldatei sollte nun im angegebenen Verzeichnis existieren. Sie können diese Datei zum Beispiel durch einen Doppelklick in den Profiler laden und sich die enthaltenen Einträge ansehen. Eine interessante Möglichkeit zur Analyse der Ablaufverfolgung eröffnet die Funktion . Durch diese Funktion wird der Inhalt einer Ablaufverfolgungsdatei in eine Tabelle umgewandelt. Diese Tabelle kann dann sehr bequem für eine Analyse verwendet werden. Die folgende Anweisung stellt alle Daten der Protokolldatei in Tabellenform dar:
Der zweite Parameter bezeichnet hier die Anzahl der einzulesenden Dateien, da es möglich ist, dass Ablaufverfolgungen auf mehrere Dateien aufgeteilt werden. Natürlich ist es auch möglich, eine Tabelle zu erzeugen, die dann im weiteren Verlauf benutzt werden kann. Hierzu verwenden Sie einfach SELECT
INTO:
Mit dieser erzeugten Tabelle wollen wir nun unsere problematischen Abfragen heraussuchen. Dazu wird die Ausgabe einfach absteigend nach der Spalte Duration sortiert:
Die Zeitangaben in der Duration-Spalte sind in µs. Daher wird hier noch eine Umrechnung durchgeführt. Außerdem werden nur Zeilen herausgefiltert, die auch eine relevante Ausführungszeit enthalten. Ein mögliches Resultat der obigen Abfrage sehen Sie in Abbildung 12.12. Es ist sehr schön zu erkennen, dass die ersten drei Zeilen insgesamt fast 90 Sekunden benötigt haben, der Rest kann hier sicherlich vernachlässigt werden.
516
12.5 Messen der Leistung
Abbildung 12.12 Ergebnis der Ablaufverfolgung
Nachdem Sie nun die problematischen Abfragen herausgefunden haben, können Sie weitere Analysen durchführen, wobei Sie sich dann eben auf diese Abfragen konzentrieren.
12.5.5 Windows-Systemmonitor Der unter Windows vorhandene Systemmonitor kann ebenfalls zur Überwachung der SQL Server-Leistung eingesetzt werden. Hierzu stehen Ihnen nach der Installation von SQL Server ca. 800 zusätzliche Leistungsindikatoren zur Verfügung. Diese Indikatoren sind dieselben, die Sie auch durch die dynamische Verwaltungssicht abfragen können. Wenn Sie den Systemmonitor starten, können Sie also zum Protokoll entsprechende SQL Server-Leistungsindikatoren addieren (Abbildung 12.13).
Abbildung 12.13 SQL Server-Leistungsindikatoren des Systemmonitors
517
12 Optimierung von Abfragen Sie haben dann wie gewohnt die Möglichkeit, diese Indikatoren in der grafischen Benutzeroberfläche des Systemmonitors zu beobachten (Abbildung 12.14).
Abbildung 12.14 Der Systemmonitor bei der Arbeit
12.5.6 Verbindung von Ablaufverfolgung und Logfile Ein neues und gleichermaßen mächtiges Feature von SQL Server 2005 ist die Möglichkeit, eine Ablaufverfolgung mit einem System-Logfile verbinden zu können. Hierzu müssen Sie eine SQL Server-Ablaufverfolgung und eine Protokollierung des Systemmonitors gleichzeitig aufzeichnen. Diese beiden Aufzeichnungen können dann später im SQL Server Profiler gemeinsam dargestellt werden. Um ein System-Logfile zu erstellen, starten Sie den Systemmonitor, und wählen Sie im Ordner Leistungsprotokolle den Punkt Neue Protokolleinstellungen aus dem Kontextmenü (Abbildung 12.15). Benennen Sie im sich öffnenden Fenster anschließend die Einstellungen, zum Beispiel SQL Server.
518
12.5 Messen der Leistung
Abbildung 12.15 Protokolleinstellungen für SQL Server-Leistungsindikatoren erstellen
Sie haben anschließend die Möglichkeit, Leistungsindikatoren diesem Protokoll hinzuzufügen. Wählen Sie hier einige aus, zum Beispiel die Prozessorzeit. Geben Sie nun noch einen Namen für die Protokolldatei an, und wählen Sie für das Anzeige-Intervall bitte einen geringen Wert, sagen wir zwei Sekunden. Dieser geringe Wert ist nur für unser Beispiel, für eine echte Protokollierung werden Sie sicherlich ein größeres Intervall wählen, da die Protokollierung ansonsten zu ausführlich werden würde. Ist die Konfiguration abgeschlossen, starten Sie die Protokollierung aus dem Kontextmenü (Abbildung 12.16).
Abbildung 12.16 Starten der Protokollierung
519
12 Optimierung von Abfragen Unmittelbar danach starten Sie bitte auch noch einmal die SQL Server-Ablaufverfolgung aus Listing 12.2. Führen Sie nun bitte wiederum einige Abfragen aus, damit die beiden laufenden Protokolle entsprechende Einträge erhalten. Danach können Sie beide Protokollierungen beenden. Wie bereits etwas weiter oben erwähnt, ist es möglich, eine SQL ServerAblaufverfolgungsdatei einfach durch Doppelklick in den Profiler zu laden. Wenn Sie dies für die erstellte Ablaufverfolgung einmal tun, können Sie anschließend das vom Systemmonitor erzeugte Log-File über das Menü Datei/Leistungsdaten importieren hinzufügen. Dadurch werden die Ergebnisse beider Protokollierungen in einem Fenster angezeigt (Abbildung 12.17).
Abbildung 12.17 Verbindung von Ablaufverfolgung und Log-File
Die Anzeige der beiden Protokolle ist hierbei synchron. Wenn Sie also im oberen Bereich eine Abfrage selektieren, so wird in der Grafik im unteren Teil automatisch der zugehörige Punkt in der Zeit hervorgehoben. Umgekehrt funktioniert dies ebenso. Durch einen Mausklick im grafischen Bereich wird die zu dieser Zeit gehörende Anfrage in der oberen Liste selektiert. So können Sie zum Beispiel sehr schön verfolgen, bei welchen Abfragen die Prozessorlast hoch war.
520
12.6 Beispiele für die Optimierung
12.6
Beispiele für die Optimierung Nachdem Sie nun einige interessante Möglichkeiten zum Messen der Abfrageleistung kennen, wollen wir an einigen kleinen Beispielen zeigen, wie Abfragen durch recht einfache Mittel optimiert werden können. Beispiel 1 Angenommen, Sie möchten die Spalten LiefNr, Preis und Menge der Tabelle BestellPos abfragen, wobei Sie sich nur für Zeilen interessieren, deren LiefNr mit 33F beginnt. Der erste Entwurf einer entsprechenden Abfrage könnte so aussehen:
Aus Bequemlichkeitsgründen werden hier einfach alle Spalten zurückgegeben. Dies hat natürlich den Vorteil, dass die Abfrage immer die gerade aktuellen Spalten zurückgibt. Wenn sich die Tabellenstruktur ändert, muss die Abfrage nicht angepasst werden. Bevor nun die Abfrage ausgeführt wird, soll noch der tatsächliche Ausführungsplan in grafischer Form eingeschlossen werden. Dies kann nicht durch eine entsprechende SETAnweisung erfolgen, sondern muss über das Menü oder die Menüleiste geschehen. Wie Sie sehen, wurde außerdem die Option STATISTICS IO für die Dauer der Abfrage eingeschaltet. Wir werden alle Abfragen in diesem Abschnitt mit der Option STATISTICS IO ausführen, auch wenn dies in den folgenden Beispielen nicht nochmals explizit erwähnt wird. Der Abfrageplan (Abbildung 12.18) enthält sicherlich keine Überraschung. Es wird einfach ein Tabellen-Scan durchgeführt, der die geforderten Zeilen herausfiltert. Die Verwendung der Option SET STATISTICS IO ON bei der Abfrageausführung liefert das folgende Ergebnis:
Entscheidend ist hier der Wert für die logischen Lesevorgänge, der ja angibt, wie viele Seiten tatsächlich gelesen wurden, um die Daten der Abfrage zurückzuliefern. Bei einem Tabellen-Scan sind dies alle Seiten der Tabelle. Unser Ziel ist es, diesen Wert zu minimieren.
521
12 Optimierung von Abfragen
Abbildung 12.18 Der erste Abfrageplan für Beispiel 1
Hier noch einmal die Zusammenfassung der Ergebnisse: Totale Abfragekosten: 67,61 Logische E-/A-Operationen (gelesene Seiten): 91089 Dieses Ergebnis ist sicher unbefriedigend. Ich hoffe, dass Sie mir hierin uneingeschränkt zustimmen. Mit einer solch schlechten Abfrageleistung sollten Sie sich nicht zufriedengeben. Wir wollen nun überlegen, was unternommen werden kann, um die Abfrage zu optimieren. An erster Stelle steht hierbei sicher die Beseitigung des Tabellen-Scans, der ja 100% der Abfragekosten verursacht. Hierzu wird ein nicht gruppierter Index auf der Spalte LiefNr eingeführt, die wir ja in der WHERE-Klausel verwenden. Der Optimierer kann diesen Index verwenden, um statt eines Tabellen-Scans ein Durchsuchen des Indexbaumes durchzuführen, was natürlich erheblich schneller geht und auch weniger E-/A-Operationen verursacht. Dieser Index wird so angelegt:
Wenn die Abfrage nun erneut ausgeführt wird, dann sieht der Ausführungsplan so aus, wie in Abbildung 12.19 zu sehen. Über einen Index Seek werden aus den Blattseiten des Index die entsprechenden Verweise auf die Tabellendaten geholt. Über diese Verweise werden dann die Daten durch einen Row ID Lookup geholt. Es ist dieser Lookup, der die Abfragekosten bestimmt. Die E-/A-Statistik sieht nun so aus:
Die logischen Lesevorgänge haben sich also ungefähr um den Faktor 13000 verringert!
522
12.6 Beispiele für die Optimierung
Abbildung 12.19 Ausführungsplan bei Verwendung eines Index
Es folgt wieder die Zusammenfassung der Messergebnisse: Totale Abfragekosten: 0,09 Logische E-/A-Operationen (gelesene Seiten): 7 An dieser Stelle könnten wir eigentlich aufhören. Das Ergebnis ist immerhin um einige Größenordnungen besser als zu Beginn. Es geht jedoch noch besser! Wir können die Spalten, die von der Abfrage zurückgegeben werden, in den Index einschließen. Dadurch wird der RID-Lookup eingespart, weil dann alle Daten direkt aus dem Index geholt werden können. Da der Indexbaum eine Tiefe von drei hat, werden dann nur noch drei logische Lesevorgänge benötigt. Wir ändern den Index also so:
Es muss nun allerdings auch die Abfrage entsprechend angepasst werden, indem die nicht benötigten Spalten ausgeschlossen werden. Mit anderen Worten: In der Spaltenliste der SELECT-Anweisung werden nur die interessierenden Spalten aufgezählt:
Für diese Abfrage mit dem gerade erzeugten Index ergibt sich dann ein Ausführungsplan, wie er in Abbildung 12.20 zu sehen ist. Nun ist tatsächlich nur noch ein Durchsuchen des Indexbaumes erforderlich. Alle Daten, welche die Abfrage zurückliefern soll, können aus dem Index geholt werden. Dies ist nur möglich, weil wir den Platzhalter (*) aus der Spaltenliste entfernt haben. Ansonsten wäre nach wie vor ein zusätzlicher Lookup-Schritt erforderlich gewesen, um die Daten für Spalten zu holen, die nicht in den Index eingeschlossen sind.
523
12 Optimierung von Abfragen
Abbildung 12.20 Abfrageplan mit abdeckendem Index
Die E-/A-Statistik hat sich nochmals verbessert und zeigt nur noch die bereits im Voraus ermittelten drei logischen Lesevorgänge:
Das Resultat sieht nun also so aus: Totale Abfragekosten: 0,003 Logische E-/A-Operationen: 3 Die Verbesserung gegenüber der ursprünglichen Version ist tatsächlich ziemlich drastisch, nicht wahr? Die erforderlichen E-/A-Operationen wurden ungefähr um den Faktor 30000 gesenkt. Auch die Abfragekosten sind nun ca. 22000 Mal geringer! Interessant ist aber auch der Vergleich zwischen abdeckendem und nicht abdeckendem Index: Durch den abdeckenden Index wurden die Kosten nochmals um den Faktor 30 reduziert und die erforderlichen E-/A-Operationen um mehr als die Hälfte vermindert. Es ist ziemlich offensichtlich, dass für die Tabelle BestellPos ein Index auf der Spalte LiefNr hilfreich bzw. erforderlich ist. Für den abdeckenden Index, der ja immerhin nochmals eine Kostenreduzierung um den Faktor 30 bewirkt hat, gilt dies allerdings nicht. Dass dieser Index eine Verbesserung bewirkt, konnte nur durch eine sorgfältige Analyse der E-/A-Statistik und des Ausführungsplanes ermittelt werden. Beispiel 2 Dieses Beispiel verwendet eine Abfrage, die zwei Tabellen miteinander verknüpft:
Auch hier soll wieder mit einer Messung der E-/A-Operationen und dem Inspizieren des Ausführungsplanes begonnen werden. Der tatsächliche Ausführungsplan ist in Abbildung 12.21 zu sehen. Mit den Erfahrungen aus dem vorangegangenen Beispiel sehen wir sofort, dass offensichtlich Indizes erforderlich sind, um die Tabellen-Scans durch eine Suche im Index zu ersetzen. Hierzu müssen zwei Indizes hinzugefügt werden, die den JOIN unterstützen.
524
12.6 Beispiele für die Optimierung
Abbildung 12.21 Ausführungsplan für die Abfrage über zwei Tabellen
Die E-/A-Statistik zeigt an, dass für beide beteiligten Tabellen alle Datenseiten gelesen wurden:
Es folgt wieder die kurze Zusammenfassung der Messung: Totale Abfragekosten: 68,37 Logische E-/A-Operationen: ca. 92000 Es sollen nun Indizes erstellt werden, damit die Abfrageleistung verbessert wird. Wenn Sie sich die Abfrage ansehen, so werden Sie drei mögliche Kandidaten für Indizes entdecken: BestellKopf.BestId. Dieser Index wird den JOIN unterstützen. Außerdem ist die Spalte BestId ein geeigneter Primärschlüssel für die Tabelle. Daher erzeugen wir für diese Spalte einfach eine PRIMARY KEY-Einschränkung. SQL Server erstellt dadurch automatisch einen eindeutigen Index für diese Spalte. BestellKopf.Nr. Die Spalte wird in der WHERE-Klausel als Filter verwendet und ist daher ein guter Kandidat für einen Index. Dies ist die Lektion aus dem vorangegangenen Beispiel. Da die Nummer eindeutig sein muss, sollte hier ein UNIQUE-Index verwendet werden. BestellPos.BestId. Auch für diese Spalte ist sicherlich ein Index sinnvoll, da die Spalte in der JOIN-Bedingung verwendet wird. Dies geht unmittelbar aus dem gezeigten Ausführungsplan hervor. Dort ist zu sehen, dass für jede gefundene Zeile in der Tabelle BestellKopf ein Tabellen-Scan in der Tabelle BestellPos ausgeführt wird. Ein Index auf der Spalte BestId kann diesen Tabellen-Scan möglicherweise verhindern. Allerdings werden wir keinen Index erzeugen, der nur diese Spalte verwendet. Auch die Tabelle BestellPos soll natürlich einen Primärschlüssel bekommen. Dieser Primärschlüssel ist ein aus den Spalten BestId und BestPosId zusammengesetzter Schlüssel.
525
12 Optimierung von Abfragen Das folgende Skript übernimmt die Erstellung der drei Indizes:
Nach der Erstellung der Indizes sieht der Abfrageplan wesentlich besser aus (Abbildung 12.22).
Abbildung 12.22 Abfrageplan mit Verwendung der erstellten Indizes
Der Plan zeigt, dass die Abfrage zunächst mit einem Index-Seek auf dem zur erzeugten UNIQUE-Einschränkung gehörenden Index der Tabelle BestellKopf beginnt. Dieser Suchvorgang gibt eine Zeile zurück. Für diese Zeile muss anschließend noch der Wert für die Spalte BestDatum aus dem gruppierten Index der Tabelle BestellKopf geholt werden. Auch hierfür kann ein (Clustered) Index-Seek verwendet werden. Der nächste Nested Loops-Operator (eine Stelle weiter links) holt dann noch alle Zeilen aus der Tabelle BestellPos, und zwar hier nur die Spalte Menge. Auch hierfür kann ein (Clustered) Index-Seek benutzt werden, da die Spalte BestId, nach der in der Tabelle gesucht wird, in diesem gruppierten Index an erster Stelle steht. Der Vollständigkeit halber werden hier auch noch die E-/A-Statistiken gezeigt:
526
12.6 Beispiele für die Optimierung Das Ergebnis der optimierten Abfrage ist also wie folgt: Totale Abfragekosten: 0,011 Logische E-/A-Operationen: 10 Auch hier ist die Verbesserung sehr drastisch. Wenn Sie den zweiten Ausführungsplan nochmals genauer betrachten, sehen Sie aber eventuell auch, dass es noch ein wenig besser geht. Der Clustered Index-Seek auf der Tabelle BestellKopf erfolgt ausschließlich, um die Spalte BestDatum zu lesen. Dieser Schritt kann eingespart werden, wenn die Spalte in den UNIQUE-Index eingeschlossen wird. Da dieser Index automatisch durch das Erzeugen der UNIQUE-Einschränkung erstellt wurde, ist die Spalte nicht in den Index aufgenommen worden. Die automatische Index-Erstellung kann das natürlich nicht erledigen. Wir können dies jedoch leicht nachholen, indem die Einschränkung gelöscht und als Index neu erstellt wird:
Der Ausführungsplan enthält nun nur noch den Clusterd Index-Seek auf der Tabelle BestellPos (Abbildung 12.23).
Abbildung 12.23 Ausführungsplan mit abdeckendem Index
Wie immer schauen wir uns auch die E-/A-Statistik an, die sich ebenfalls noch einmal wenn auch nur geringfügig verbessert hat:
527
12 Optimierung von Abfragen Mit den präsentierten Daten ergibt sich diese Zusammenfassung: Totale Abfragekosten: 0,008 Logische E-/A-Operationen: 8 Insgesamt wurden die Kosten für diese Abfrage also gegenüber der ursprünglichen Version um den Faktor 8500 gesenkt. Die erforderlichen Leseoperationen sind in der Endversion ca. 11500 Mal kleiner.
12.7
Weitere Hinweise für Indizes Im vorangegangenen Abschnitt haben Sie gesehen, wie immens wichtig Indizes für performante Abfragen sind. An dieser Stelle sollen einige weitere Aspekte, Indizes betreffend, untersucht werden. Zunächst einmal enthalten Indizes redundante Information, die für eine logische Funktionalität der Datenbank nicht erforderlich ist. Alle Abfragen funktionieren im Prinzip auch ohne Indizes, können dann aber eben erheblich langsamer sein. Mit der Speicherung redundanter Information sind stets Probleme verbunden. Indizes bilden hier keine Ausnahme. Zunächst einmal benötigen Indizes auch Speicherplatz auf der Festplatte. Wenn Spalten verändert werden, die in Indizes enthalten sind, so müssen außer den eigentlichen Tabellendaten auch die entsprechenden Indizes verändert werden. Für Aktualisierungsoperationen sind Indizes also in der Regel eher hinderlich. Es gibt Ausnahmen, nämlich dann, wenn ein Index verwendet werden kann, um für eine Aktualisierungs- oder Löschoperation die Suche der Zeilen zu unterstützen. Dies kann zum Beispiel der Fall sein, wenn Sie eine Abfrage der Form:
mit einem existierenden Index auf der Spalte Nr ausführen. Das Aktualisieren von Index-Spalten kann letztlich dazu führen, dass der Indexbaum nicht mehr ausbalanciert ist, also für unterschiedliche Suchpfade sehr unterschiedliche Tiefen aufweist. Dies kann sogar so weit gehen, dass der Index vom Optimierer nicht mehr verwendet wird.
12.7.1 Index-Selektivität Die Selektivität eines Index ist ein Kriterium, das der Optimierer für die Verwendung eines Index in Erwägung zieht. Nehmen Sie als Beispiel unsere Tabelle BestellPos und den Index auf der Spalte LiefNr. In unserem ersten Beispiel des vorigen Abschnitts haben wir für diese Spalte einen Index erzeugt, der in der Beispielabfrage dann auch verwendet wurde. Der Index wurde deswegen hergenommen, weil der Optimierer entschieden hat, dass die Abfrage hiervon profitiert. Wenn die Filterbedingung in der WHERE-Klausel so formuliert wird, dass sehr viele Zeilen zurückgegeben werden, dann kann ein Tabellen-
528
12.7 Weitere Hinweise für Indizes Scan unter Umständen performanter sein als die Suche über den Indexbaum mit einem anschließenden Lookup. Schauen Sie sich hierzu diese Abfrage an:
Die Abfrage gibt insgesamt über 53000 Zeilen zurück. Obwohl ein Index für die Spalte LiefNr existiert, wird dieser nicht verwendet. Stattdessen wird ein Tabellen-Scan durchgeführt, wie Sie sich durch die Betrachtung des Ausführungsplanes überzeugen können. Ein Durchsuchen des Indexbaumes mit anschließendem Lookup wäre in diesem Fall einfach teurer gewesen als der Tabellen-Scan. Das gerade für die Index-Selektivität Gesagte gilt übrigens auch generell für die Anzahl von Zeilen in einer Tabelle. Stellen Sie sich diesen Extremfall vor: eine kleine Tabelle, die insgesamt in eine Datenseite passt. Diese Datenseite muss in jedem Fall gelesen werden, wenn Zeilen bzw. Spalten zurückgegeben werden sollen. Hier hilft ein Index also überhaupt nicht. Egal, was für einen Index Sie für diese Tabelle auch erzeugen, er wird in SELECT-Anweisungen immer ignoriert. Für Aktualisierungsoperationen allerdings gilt dies nicht! Der Index muss natürlich immer mit den Tabellendaten übereinstimmen. Also ist der Index nicht nur nutzlos, sondern sogar hinderlich.
12.7.2 Index-Fragmentierung Mit der Zeit kann ein Indexbaum entarten, wie bereits etwas weiter oben erläutert. Möglich ist auch, dass der Index im Laufe der Zeit fragmentiert wird, also auf der Festplatte nicht mehr zusammenhängend gespeichert wird. Dies kann natürlich immer dann, wenn die Indexdaten von der Festplatte gelesen werden, erheblichen Einfluss auf die Performanz haben. Daher sollten Sie Ihre Indizes in periodischen Abständen reorganisieren. Der Grad der Index-Fragmentierung kann durch die dynamische Verwaltungsfunktion abgefragt werden. Diese Funktion haben wir bereits etwas weiter oben in diesem Kapitel verwendet, um die Anzahl der Stufen in einem Index abzufragen. Die Funktion gibt auch eine Spalte zurück, die den Grad der Fragmentierung eines Index anzeigt: avg_fragmentation_in_Percent. Ist dieser Wert zu hoch, dann sollten Sie den Index reorganisieren. Es ist eine gute Idee, eine solche Reorganisation von Zeit zu Zeit durchzuführen. Hierzu existiert die Anweisung ALTER INDEX .. REORGANIZE, die so verwendet wird:
12.7.3 Fehlende Indizes Eine interessante Frage ist, wie man mitbekommt, dass durch Indizes eine Verbesserung der Abfrageleistung erzielt werden kann. In unseren obigen Beispielen haben wir dies manuell durch die Untersuchung von Ausführungsplänen erledigt.
529
12 Optimierung von Abfragen Der Abfrageoptimierer von SQL Server verfügt für diesen Zweck über ein fantastisches Feature: Er informiert im Ausführungsplan über fehlende Indizes und sogar über die erwartete Verbesserung der Abfrageleistung bei Verwendung eines entsprechenden Index. Wir wollen uns dies an einem Beispiel ansehen. Zunächst einmal sollen dafür die existierenden Indizes auf allen Tabellen der Datenbank AWPerformance gelöscht werden. Dies wird durch das folgende Skript erledigt:
Anschließend soll nun der XML-Ausführungsplan für eine Abfrage erzeugt werden. Die Informationen über fehlende Indizes sind nur im XML-Ausführungsplan enthalten. Im bislang meist verwendeten grafischen Ausführungsplan finden Sie diese Informationen nicht. Hier ist das Skript für dieses Experiment:
Der erzeugte Ausführungsplan enthält tatsächlich eine Sektion , wie das folgende Listing zeigt:
Die in dieser Sektion enthaltenen Informationen können leicht verwendet werden, um zu entscheiden, ob ein entsprechender Index erstellt werden sollte. Im präsentierten Beispiel kann diese Frage sicherlich mit einem klaren Ja beantwortet werden, da eine Verbesserung der Abfrageleistung um über 99% prognostiziert wird. Das entsprechende CREATE INDEX-Kommando sieht dann so aus:
Da SQL Server eine Ablaufhistorie über dynamische Verwaltungssichten bzw. -funktionen zur Verfügung stellt, sind Sie nicht einmal darauf angewiesen, für interessierende Abfra-
530
12.7 Weitere Hinweise für Indizes gen den XML-Ausführungsplan anzuzeigen, so wie oben geschehen. Es ist tatsächlich möglich, auch im Nachhinein abzufragen, in welchen Fällen der Optimierer Indizes vermisst hat. Dadurch können Sie periodisch überprüfen, ob sich das Hinzufügen von Indizes lohnen würde. Dies kann zum Beispiel durch die dynamische Verwaltungssicht geschehen, die detaillierte Informationen über fehlende Indizes zurückgibt:
Das Ergebnis sieht zum Beispiel so aus:
Weiter oben haben wir bereits gespeicherte Ausführungspläne verwendet, die im Prozedurcache gespeichert sind. Diese Pläne können über die dynamischen Verwaltungssicht abgefragt werden. Selbstverständlich enthalten diese Pläne auch die Informationen über fehlende Indizes, also entsprechende Sektionen. Mit den in Kapitel 8 erworbenen Kenntnissen über XQuery können wir die folgende Abfrage formulieren, um Abfragepläne zu erhalten, in denen fehlende Indizes beanstandet werden:
Diese Abfrage gibt als Ergebnis die -Sektionen zurück, enthält also Informationen über alle vom Optimierer vermissten Indizes.
12.7.4 Nicht verwendete Indizes Zum Schluss unserer Betrachtungen zur Optimierung durch Indizes soll nun noch eine weitere dynamische Verwaltungssicht erwähnt werden: . Diese Sicht gibt Auskunft über die Verwendung von Indizes im Rahmen von Such- und Aktualisierungsoperationen. Insbesondere können Sie diese Sicht verwenden, um herauszufinden, ob Indizes überhaupt verwendet werden. Nicht oder nur für Aktualisierungen verwendete Indizes sind natürlich nicht von Nutzen. Die folgende Abfrage liefert Daten zu allen Indizes der Datenbank AWPerformance, die seit dem letzten Start von SQL Server nicht verwendet wurden:
531
12 Optimierung von Abfragen
Seien Sie aber bitte vorsichtig bei der Auswertung des Ergebnisses. Immerhin ist es möglich, dass Indizes zurückgegeben werden, die sehr wohl Verwendung finden, die eben nur nicht seit dem letzten SQL Server-Start benötigt wurden. Außerdem filtert die obige Abfrage nicht diejenigen Indizes heraus, die lediglich für Aktualisierungen verwendet wurden. Auch diese sind ja nicht erforderlich. Probieren Sie es doch einmal aus, auch diese Indizes durch die Abfrage zurückzugeben. Hierzu müssen Sie die Spalte update der Sicht berücksichtigen.
12.8
Der Datenbankmodul Optimierungsratgeber Der Datenbankmodul Optimierungsratgeber (englisch: Database Tuning Advisor oder einfach DTA) ist ein Werkzeug zur Optimierung der physikalischen Struktur von Datenbanken. Dieser Ratgeber kann Vorschläge zur Partitionierung von Tabellen und Indizes, aber auch zum Löschen bzw. Anlegen oder Ändern bestimmter Indizes unterbreiten. Der DTA benötigt hierzu entsprechende Informationen, die in Form einer sogenannten Arbeitsauslastung zur Verfügung gestellt werden muss. Eine Arbeitsauslastung kann hier zum einen einfach ein SQL-Skript sein. Eine wesentlich bequemere Möglichkeit stellt allerdings die Verwendung einer Ablaufverfolgungsdatei des Profilers dar. Hierzu muss die Ablaufverfolgung allerdings bestimmte Ereignisse enthalten, die aber alle in der Vorlage Tuning enthalten sind. Unsere beiden weiter oben erzeugten Ablaufverfolgungen würden diese Voraussetzungen also erfüllen und können somit als Eingabe für den DTA verwendet werden. Wir wollen nun sehen, welche Vorschläge der DTA für eine der beiden Ablaufverfolgungen erstellt. Zunächst einmal wollen wir alle bisher erzeugten Indizes entfernen, damit der DTA auch tatsächlich Ratschläge unterbreiten kann. Lassen Sie bitte nochmals das bereits bekannte Skript zum Löschen aller Indizes auf der Datenbank AWPerformance laufen:
Jetzt kann der Optimierungsratgeber gestartet werden, zum Beispiel über das Menü Extras/Datenbankmodul-Optimierungsratgeber des Management Studios.
532
12.8 Der Datenbankmodul Optimierungsratgeber Nach dem Start wird sofort eine neue Sitzung angelegt. Geben Sie dieser Sitzung einen Namen, etwa AWPerformance-Optimierung. In der Box Arbeitsauslastung können Sie ein SQL-Skript oder auch eine Profiler-Ablaufverfolgung als Grundlage für die Optimierung festlegen. Wir wollen an dieser Stelle die zuvor mir der Tuning-Vorlage erzeugte Ablaufverfolgung des Profilers verwenden. Diese Ablaufverfolgung enthält alle für den DTA erforderlichen Ereignisse (Abbildung 12.24).
Abbildung 12.24 Der Datenbankmodul Optimierungsratgeber in Aktion
Im Punkt Datenbank für Auslastungsanalyse wird lediglich die Datenbank angegeben, mit der die Analyse beginnt. Wenn Sie in Ihrem Skript den Datenbankkontext durch USE verändern, so wirkt sich dies natürlich auch entsprechend auf den Optimierungsratgeber aus. In der unteren Liste können Sie dann für jede enthaltene Datenbank detailliert Tabellen und Sichten für die Optimierung auswählen. Auf der Seite Optimierungsoptionen werden weitergehende Optionen für die Optimierung konfiguriert (Abbildung 12.25). Hier können Sie zum Beispiel festlegen, ob bestehende Indexstrukturen in den ausgegebenen Empfehlungen nicht berücksichtigt werden sollen. Dies ist wichtig, wenn die Arbeitsauslastung nicht repräsentativ für Ihre Datenbank ist. Es kann ja sein, dass in Ihrer Arbeitsauslastung bestimmte Tabellen nicht verwendet werden. Der DTA würde empfehlen, Indizes für solche Tabellen zu entfernen, da diese Indizes ja nicht benutzt werden. Wählen Sie also die Option Alle vorhandenen physikalischen Entwurfsstrukturen beibehalten aus, wenn Sie die verhindern möchten.
533
12 Optimierung von Abfragen
Abbildung 12.25 Optimierungsoptionen des DTA
Nach einem Klick auf den Button Analyse starten in der Menüleiste beginnt die Analyse. Je nach Umfang der angegebenen Arbeitsauslastung kann dieser Prozess sehr lange dauern. Falls Sie die Analysezeit begrenzen möchten oder müssen, so ist dies ebenfalls durch die Einstellung der Analyse-Optionen möglich (siehe nochmals Abbildung 12.25). Nach Abschluss der Analyse erhalten Sie die vom DTA herausgefundenen Empfehlungen in der gleichnamigen Registerkarte (Abbildung 12.26). Sie haben dann die Möglichkeit, die einzelnen Empfehlungen genauestens zu untersuchen und zu entscheiden, ob sie entsprechende Änderungen an der Datenbank vornehmen. Diese Änderungen kann der DTA auch sofort selber durchführen. Hierzu wählen Sie einfach aus dem Menü Aktionen/Empfehlungen anwenden. Ein nützliches Feature ist auch die Möglichkeit, von den ausgesprochenen Empfehlungen einfach einige auszuschließen und dann die Analyse nochmals zu starten, also eine Art Was wäre wenn-Szenario. In unserem Beispiel prognostiziert der DTA eine Verbesserung der Abfrageleistung um 99% durch das Anlegen von zwei Indizes und das Aktualisieren von zwei weiteren Statistiken. Diese Empfehlungen könnten wir also ziemlich bedenkenlos anwenden.
534
12.9 Tipps zur Abfrageoptimierung
Abbildung 12.26 Ergebnis der Analyse
12.9
Tipps zur Abfrageoptimierung Zum Abschluss folgen noch einmal eine Zusammenfassung und Ergänzung der Ratschläge für eine Verbesserung der Leistung Ihrer Abfragen. SQL Server bietet mittlerweile großartige Unterstützung für die Abfrageoptimierung an. Verwenden Sie die vorhandenen Möglichkeiten und Werkzeuge unbedingt, aber verstehen Sie sie als Unterstützung für eine Entscheidungsfindung. Denken Sie bitte daran, dass das menschliche Gehirn durch Software unterstützt, aber nicht ersetzt werden kann. Ansonsten würde ich dieses Buch nicht schreiben, und Sie bräuchten es nicht zu lesen. Geben Sie in einer SELECT-Anweisung nur die benötigten Spalten zurück Die Verwendung von * zur Rückgabe aller Spalten ist eine bequeme Möglichkeit, die den Vorteil hat, dass bei Änderungen der Tabellenstruktur die Ausgabe automatisch angepasst wird. Sie erkaufen diesen Vorteil allerdings mit erheblichen Nachteilen: Die Benutzung von abdeckenden Indizes ist nicht möglich. Dadurch ist in der Regel immer ein zusätzlicher Lookup erforderlich, der unter Umständen eine entscheidende Verschlechterung des Laufzeitverhaltens zur Folge hat.
535
12 Optimierung von Abfragen Zur Laufzeit der Abfrage muss zunächst auch eine Abfrage der Metadaten erfolgen, welche die Spalten der Tabellen bzw. Sichten ermittelt. Dadurch wird die Leistung der Abfrage natürlich negativ beeinträchtigt. Dir Rückgabe nicht benötigter Daten kann die Netzwerklast enorm erhöhen. Nehmen Sie nur einmal an, dass die folgende Anweisung innerhalb einer gespeicherten Prozedur ausgeführt wird:
Wenn Sie der Tabelle Rechnung nun eine Spalte vom Typ VARCHAR(MAX) hinzufügen, in der jede Rechnung nochmals im PDF-Format abgelegt wird, so kann die obige Abfrage ganz automatisch um Größenordnungen langsamer sein. Probieren Sie verschiedene Varianten aus Experimentieren Sie! Bei komplexen Abfragen gibt es immer unterschiedliche Möglichkeiten, zum Ziel zu kommen. Probieren Sie verschiedene Möglichkeiten aus, und benutzen Sie hierzu auch die Ihnen zur Verfügung stehenden Ressourcen, wie zum Beispiel Ihre Kollegen oder News Groups. Sie haben in diesem Kapitel gesehen, wie Sie die Leistung von Abfragen messen können, und sind dadurch in der Lage, die unterschiedlichen Varianten einer Bewertung zu unterziehen. Nutzen Sie diese Möglichkeit zum Vergleich der gefundenen Lösungsvarianten. Dadurch werden Sie im Laufe der Zeit auch ein Gefühl dafür bekommen, wie der Abfrageoptimierer arbeitet, und werden dadurch in der Lage sein, kompakte und performante Abfragen zu entwerfen. Vermeiden Sie Hinweise zur Abfrageausführung Arbeiten Sie nicht gegen den Optimierer, sondern versuchen Sie, ihn zu verstehen und zu benutzen. Abfragehinweise sind in der Regel nicht erforderlich. Wenn Sie wissen, wie der Optimierer arbeitet, müssen Sie ihn nicht austricksen, sondern können Ihre Abfragen entsprechend gestalten. Verwenden Sie geeignete Indizes Überprüfen Sie Ihre Indizes dahingehend, ob sie tatsächlich auch verwendet werden, also ob sie erforderlich sind. Nicht benötigte Indizes belasten den Server bei Datenaktualisierungen. Indizes sind für folgende Spalten nützlich: Prädikate in einer WHERE-Klausel. Wie Sie gesehen haben, gilt dies allerdings nur, wenn der Index auch selektiv genug ist. Fremdschlüssel (FOREIGN KEY). Für Fremdschlüsselbeziehungen sollten Sie in der Regel einen Index erzeugen. Auch dies gilt allerdings nur dann, wenn dieser Index über eine ausreichende Selektivität verfügt. Abdeckende Indizes. Prüfen Sie, ob abdeckende Indizes Vorteile bringen. Hierbei leistet der DTA Hilfe.
536
12.9 Tipps zur Abfrageoptimierung Kurze Indizes. Verwenden Sie lieber viele Indizes auf wenige Spalten statt weniger Indizes mit vielen Spalten. Der Optimierer hat dadurch mehr Möglichkeiten. Verwenden Sie aktuelle Statistiken Gerade bei gespeicherten Prozeduren ist es nicht ausgeschlossen, dass der Plan für eine Prozedur auf veralteten Daten basiert, dass also die Statistiken nicht mehr aktuell sind. Aktuelle Statistiken sind essentiell für eine korrekte Arbeitsweise des Optimierers. Reorganisieren Sie von Zeit zu Zeit Ihre Indexstrukturen, oder verwenden Sie die Anweisung UPDATE STATISTICS bzw. die gespeicherte Systemprozedur zur periodischen Aktualisierung der Statistiken. Überprüfen Sie Ihr System regelmäßig Versuchen Sie, eine Performance Baseline zu erstellen, und testen Sie dann in regelmäßigen Abständen, wie der derzeitige Zustand von dieser Basis abweicht. Hierzu benötigen Sie eventuell spezielle T-SQL-Skripte oder auch Ablaufverfolgungen, die Sie von Zeit zu Zeit einfach messen. Für diese Art Messung ist eine Stoppuhr geeignet. Optimieren Sie die Datenbankstruktur Der beste Index nützt nichts, wenn Ihre Datenbank so aufgebaut ist, dass relevante Daten jeweils nur durch einen JOIN über acht bis zehn Tabellen zurückgegeben werden können. Sie benötigen also eine entsprechende Tabellenstruktur, damit Abfragen optimal erstellt werden können. Hierzu ist in der Regel ein ausgewogenes Verhältnis zwischen Normalisierung und Denormalisierung einer Datenbank erforderlich. Sie werden also nicht umhinkommen, sich auch mit der Thematik Datenbank-Design auseinanderzusetzen, die nicht Gegenstand dieses Buches ist. Verwenden Sie passende Datentypen in Tabellen Denken Sie daran, dass SQL Server Daten seitenweise liest. Je mehr Tabellenzeilen Sie in eine Daten- oder Indexseite aufnehmen können, umso schneller werden Ihre Abfragen sein, da weniger Lesevorgänge benötigt werden. Verwenden Sie also nach Möglichkeit immer den kleinsten passenden Datentyp, und seien Sie vorsichtig mit Zeichenketten fester Länge, also zum Beispiel NCHAR. Vermeiden Sie temporäre Datenbankobjekte Temporäre Tabellen oder Cursor verwenden alle die Systemdatenbank tempdb. Diese Datenbank müssen sich alle zu einer SQL Server-Instanz existierenden Verbindungen teilen. In der tempdb werden auch SQL Server-Objekte abgelegt, die zum Beispiel während einer Abfrage zwischengespeichert werden müssen. Benutzen Sie die tempdb so wenig wie möglich.
537
12 Optimierung von Abfragen Vermeiden Sie Cursor Dieser Tipp wurde bereits in Kapitel 7 genannt. Cursor benötigen eine Menge SQL ServerRessourcen und sind daher oftmals eine wesentliche Ursache für ein mangelhaftes Laufzeitverhalten. Wenn Sie mit der Optimierung beginnen, konzentrieren Sie sich bitte zunächst auf die Optimierung von Abfragen. Hier ist immer am meisten herauszuholen. Ich erlebe es häufig, dass ein Optimierungsansatz bei der Hardware, also beim Server beginnt. In der Regel ist ein solcher Ansatz aber lange nicht so vielversprechend wie die eigentliche Abfrageoptimierung. Dies sollte wirklich Ihr Hauptansatzpunkt sein.
12.10 Zusammenfassung In diesem Kapitel haben Sie erfahren, wie SQL Server Abfragen verarbeitet und wie der Abfrageoptimierer Ausführungspläne erstellt, um Abfragen auszuführen. Daran anschließend haben Sie die verschiedenen Möglichkeiten kennengelernt, welche SQL Server für die Unterstützung der Optimierung von Abfragen bietet. Hierbei haben wir uns im Wesentlichen auf die Verwendung von Indizes konzentriert. Sicherlich sind Indizes ein sehr wesentlicher Punkt bei der Optimierung. Vergessen Sie aber bitte nicht, dass Sie auch SQL-Anweisungen entwerfen sollten, die kompakt und schnell sind. Ein Index kann helfen. Dies ist durch die in den Beispielen herausgekommenen Ergebnisse eindrucksvoll und unzweifelhaft belegt worden. Schlecht entworfene Abfragen werden aber unter Umständen nicht von vorhandenen Indizes profitieren können. Ich hoffe, dass der in diesem Kapitel präsentierte Einblick in die Möglichkeiten zur Abfrageoptimierung Sie dazu ermuntert, tiefer in die Materie einzudringen, und auch, dass Sie an dieser Stelle mit dem dafür notwendigen Rüstzeug ausgestattet wurden.
538
13 Anwendungen mit den Notification Services erstellen Die SQL Server Notification Services unterstützen die Entwicklung und Bereitstellung von Anwendungen, die Nachrichten erstellen und versenden. Diese Nachrichten können bei Auftreten von bestimmten, zuvor definierten Ereignissen an eine Vielzahl von Benutzern und Geräten sogenannte Abonnenten versendet werden. Bereits 2002 wurden die Notification Services als Erweiterung für den SQL Server 2000 kostenlos zur Verfügung gestellt. Im SQL Server 2005 sind die Notification Services nun als eigenständige Komponente integriert. Mit den Notification Services können Sie Applikationen erstellen, die eine ereignisgesteuerte und asynchrone Verarbeitung von Daten erledigen.
13.1
Einführung in SQL Server Notification Services Notification Services ermöglichen das automatische Versenden von Benachrichtigungen an Abonnenten. Ein Abonnent also der Empfänger einer Benachrichtigung ist jemand, der Informationen über bestimmte Ereignisse erhalten möchte. Bei solchen Ereignissen handelt es sich zum Beispiel um spezielle Änderungen in Tabellendaten, bei deren Eintreten der Abonnent benachrichtigt werden möchte. Einsatzmöglichkeiten hierfür sind beispielsweise Abonnements für das Ereignis Der Kurs für Aktie X ist in der letzten Woche um 10% gestiegen., Aktie Y ist unter 50 gefallen. oder Das Fußballspiel zwischen Hansa Rostock und Bayern München wurde abgepfiffen und endete 1:0.. In einem solchen Fall kann der Abonnent mit einer generierten Nachricht informiert werden. Möglich ist auch die zyklische Information eines Abonnenten nach einem festgelegten Zeitplan. So kann zum Beispiel der Börsenkurs für Aktie X jeden Morgen um 09:00 Uhr an den PDA oder das Mobiltelefon eines Abonnenten gesendet werden. Natürlich ist es möglich, Applikationen dieser Art von Grund auf zu entwickeln, und möglicherweise haben Sie dies auch bereits mehrfach getan. Wozu dann also noch Notification Services, wenn es doch auch ohne geht? Nun, die Notification Services stellen ein Frame-
539
13 Anwendungen mit den Notification Services erstellen work zur Verfügung, mit dem solche Benachrichtigungsanwendungen erstellt werden können. Hat man dieses Framework einmal verinnerlicht, so vereinfacht sich die Entwicklung solcher ereignisbasierten Anwendungen sicherlich ganz enorm. Für die Erstellung kann dabei verwalteter Code, unter Verwendung der Notification Services Management Objects (NMO), oder eine spezielle XML-Beschreibungssprache zusammen mit T-SQLAnweisungen benutzt werden. Wir werden in diesem Kapitel eine Beispielanwendung entwickeln, die mittels XML definiert wird. Die zugehörige XML-Beschreibungssprache selber ist recht komplex, was den Einstieg in die Entwicklung von Notification ServicesAnwendungen ein wenig erschwert. Dieser Abschnitt über die SQL Server 2005 Notification Services wird keine umfassende Beschreibung der XML-Beschreibungssprache liefern (können), sondern anhand eines Beispiels die Verwendung wichtiger Elemente dieser Sprache erläutern. Nach einer Einführung in die grundlegende Architektur der Notification Services und die zu einer Notification Service-Anwendung gehörenden Elemente wird dann eine einfache Beispielanwendung erstellt, die deutlich machen soll, wie Notification Services-Anwendungen aufgebaut sind und wie diese implementiert werden.
13.2
Architektur Die grundlegende Arbeitsweise der Notification Services (NS) ist in Abbildung 13.1 grafisch dargestellt.
Abbildung 13.1 Übersicht Notification Services
Eine NS-Anwendung überwacht Ereignisse (zum Beispiel Änderungen an Tabellendaten), generiert Nachrichten und versendet diese an Abonnenten.
540
13.2 Architektur Zum Verständnis der NS-Arbeitsweise müssen wir uns zu Beginn über die in diesem Rahmen verwendete Terminologie verständigen: Ein Abonnement ist eine Interessenbekundung für das Auftreten eines Ereignisses und die entsprechende Registrierung hierfür. Ein Abonnent ist der Empfänger einer Nachricht. Damit ein Abonnent Benachrichtigungen empfängt, muss er ein entsprechendes Abonnement erstellen. Ein Ereignis ist eine Information, an der Abonnenten interessiert sein könnten. Eine Benachrichtigung ist eine Mitteilung, die an einen Abonnenten bei Auftreten eines für ihn interessanten Ereignisses für das er also ein Abonnement besitzt verschickt wird. Um eine Benachrichtigung versenden zu können, sind folgende Voraussetzungen zu erfüllen: 1. Zunächst einmal müssen Abonnenten angelegt werden. Dies sind die Personen oder Anwendungen, an die Ereignisbenachrichtigungen verschickt werden. 2. Ein Abonnent wird nur benachrichtigt, wenn er über wenigstens ein Abonnement verfügt. In diesem Abonnement spezifiziert der Abonnent, für welche auftretenden Ereignisse er wie benachrichtigt werden möchte. Das Wie bezieht sich hierbei auf das Gerät, an das die Benachrichtigung verschickt werden soll. Ein solches Gerät kann zum Beispiel ein PDA oder ein Mobiltelefon sein, möglich ist natürlich auch die Benachrichtigung via E-Mail. Ein Abonnement besteht genau genommen aus einer Kombination von: Abonnent (Wer soll benachrichtigt werden?) Gerät (Wie soll die Benachrichtigung erfolgen?) Ereignisdaten (Warum soll eine Nachricht generiert werden?) Lokalisationseinstellungen wie zum Beispiel Sprache (Noch einmal: Wie?) Auf diese Weise kann sich ein Abonnent für ein Ereignis mehrfach informieren lassen, also zum Beispiel sowohl via E-Mail als auch über SMS. Möglich ist damit auch die Benachrichtigung in verschiedenen Sprachen, was wohl üblicherweise dazu verwendet werden wird, den Abonnenten in seiner Sprache vom Eintreten eines bestimmten Ereignisses zu unterrichten. Der Ablauf einer Benachrichtigung verläuft dann so: 1. Die eintreffenden Ereignisse werden von NS mit den Abonnements verglichen. 2. Im Falle einer Übereinstimmung zwischen Ereignis und Abonnement werden die zum Abonnement gehörenden Nachrichten erstellt und die Abonnenten benachrichtigt. Der Vergleich von Ereignissen und Abonnements geschieht über ein Regelwerk, das der Entwickler der NS-Anwendung implementieren muss. Generell sammelt also eine NS-Anwendung Abonnenten, Ereignisse und Abonnements, generiert Benachrichtigungen und verteilt diese über definierte Protokolle an Geräte oder
541
13 Anwendungen mit den Notification Services erstellen Anwendungen. Abbildung 13.2 zeigt die Notification Services-Architektur nochmals, diesmal aber etwas detaillierter.
Abbildung 13.2 Notification Services-Architektur
Die Abbildung zeigt die drei wesentlichen Bestandteile von NS: Ereignisanbieter. Ereignisse werden zum NS-System über Ereignisanbieter hinzugefügt. Diese Ereignisanbieter überwachen System- oder Datenzustände auf Änderungen. SQL Server 2005 enthält bereits Ereignisanbieter für die Überwachung des Dateisystems und SQL Server-Ereignisse zur Überwachung von Datenbankänderungen in SQL Server- oder Analysis Services-Datenbanken. Das Hinzufügen von eigenen Ereignisanbietern ist ebenfalls möglich. Generator. Der Generator wertet die eintreffenden Ereignisse anhand der vom Anwendungsentwickler implementierten Logik aus. Falls ein Ereignis weiterverarbeitet werden soll, so wird dieses Ereignis in eine Tabelle der NS-Datenbank eingetragen. Verteiler. Der Verteiler überwacht die Ereignistabelle auf vom Generator hinzugefügte Ereignisse. Er formatiert diese Ereignisse und sendet sie unter Verwendung von Übermittlungsdiensten an die Abonnenten. Die Art der Formatierung wird vom Anwendungsentwickler spezifiziert. Das Senden der Benachrichtigungen kann hierbei über SQL Server-eigene oder über selbst entworfene Verteiler erfolgen. SQL Server ermöglicht bereits das Versenden über standardmäßig enthaltene Verteiler. Hierzu zählen die Verteilung via E-Mail (SMTP) durch das Übertragen von Dateien oder unter Benutzung des HTTP-Verteilers über HTML-Dateien bzw. andere HTTP-basierte Protokolle.
542
13.3 Erstellen von Notification Services-Anwendungen
13.3
Erstellen von Notification Services-Anwendungen In diesem Abschnitt werden zunächst die generellen Überlegungen und Schritte, die zur Erstellung von NS-Anwendungen erforderlich sind, erklärt. In Kapitel 13.4 werden wir dann eine NS-Beispielapplikation entwickeln und die hier aufgezählten Arbeitsschritte praktisch anwenden. Für die Erstellung einer NS-Anwendung sind im Wesentlichen vier Schritte erforderlich: 1. Definieren der Anwendung. In diesem Schritt überlegen Sie sich Ereignisse, Benachrichtigungen, Abonnements, Ereignisanbieter sowie Generator- und Verteilerkonfigurationen. Die Definition dieser Komponenten kann entweder über die Notification Services Management Objects (NMO) oder durch die Hinterlegung in zwei XML-Dateien erfolgen. In den meisten Fällen so auch in unserm Beispiel wird man sicherlich der XML-Datei-Variante den Vorzug geben. In diesem Fall erfolgt die Definition der Objekte durch die Erstellung dieser beiden Dateien: Instance Configuration File (ICF). In dieser Datei wird die Notification ServicesInstanz definiert. Eine NS-Instanz ist ein Host für eine oder mehrere NSAnwendungen. Die Instanz wird durch einen Windows-Dienst ausgeführt. Im ICF können Sie Parameter definieren sowie die gehosteten NS-Anwendungen spezifizieren. Die Erstellung einer NS-Instanz und der in ihr gehosteten NS-Anwendungen erfolgt einfach durch das Kompilieren des ICF. Ein ICF enthält letztlich auch Verweise auf das zu den gehosteten NS-Anwendungen gehörende: Application Definition File (ADF). Diese Datei enthält die eigentlichen Definitionen für Ereignisse, Abonnements, Benachrichtigungen, Ereignisanbieter, Generator und Verteiler und ist somit der Kern einer jeden NS-Anwendung. Beide XML-Dokumente müssen einem bestimmten XSD-Schema genügen; das Format ist also vorgeschrieben. Die Schemadateien ConfigurationFileSchema.xsd und ApplicationDefinitionFileSchema.xsd findet man im Programmverzeichnis des SQL Servers unter NotificationServices\version\XML Schemas, wobei version durch die jeweilige Version der NS ersetzt werden muss. Glücklicherweise ist es nicht erforderlich, diese beiden Dateien jedes Mal von Grund auf neu zu erstellen. In der OnlineDokumentation sind sowohl ICF- als auch ADF-Vorlagen enthalten, mit denen die Entwicklung eigener Konfigurationsdateien gestartet werden kann. Suchen Sie hierzu in der Online-Hilfe nach ICF template oder ADF template. 2. Kompilieren der Anwendung. Ist die Erstellung der ICF- und ADF-Dateien abgeschlossen, so kann die NS-Anwendung kompiliert werden. Hierzu kann man das Dienstprogramm nscontrol von der Kommandozeile verwenden oder die interaktive Schnittstelle im SQL Server Management Studio benutzen. Wir werden in unserem Beispiel ausschließlich die Erstellung über das Management Studio vornehmen. Durch die Kompilierung wird die Anwendung erstellt, und die erforderlichen Datenbanken werden angelegt. NS verwaltet die Daten in zwei Datenbanken, einer Instanz- und einer Anwendungsdatenbank. Die Instanzdatenbank enthält alle Informationen über Abonnenten und Geräte, die Anwendungsdatenbank verwaltet die Daten für die Abonne-
543
13 Anwendungen mit den Notification Services erstellen ments. Dadurch ist es möglich, Abonnenten mit mehreren NS-Anwendungen zu verbinden. 3. Anlegen von Abonnements. Eigentlich ist die Erstellung einer NS-Anwendung mit der Ausführung der ersten beiden Schritte abgeschlossen. Um jedoch einen praktischen Nutzen zu erzielen, müssen natürlich auch Abonnements existieren, an deren Abonnenten entsprechende Ereignisse verschickt werden. Benutzer, die Abonnements erstellen möchten, benötigen eine Schnittstelle für die Verwaltung dieser Abonnements. Typischerweise wird eine solche Schnittstelle eine Windows-Forms- oder Web-Anwendung sein, die unter Verwendung der im Namespace Microsoft.SqlServer.NotificationServices enthaltenen Klassen entwickelt wurde. 4. Hinzufügen eigener Komponenten (optional). Eigene Komponenten wie Ereignisanbieter, Formatierungsklassen oder Protokolle für die Verteilung von Nachrichten können bei Bedarf hinzugefügt werden. Dies ist immer dann erforderlich, wenn die entsprechenden Komponenten nicht zum Umfang von SQL Server 2005 gehören (siehe auch Abbildung 13.2). Die Basis einer NS-Anwendung ist die NS-Instanz. Eine solche Instanz ist ein WindowsDienst, der durch die Kompilierung der Anwendung erstellt, aber noch nicht gestartet wird. Eine NS-Instanz kann eine oder auch mehrere NS-Anwendungen beherbergen (hosten).
13.4
Eine Notification Services-Beispielanwendung Unsere Beispielanwendung soll über Termine von Veranstaltungen informieren. Ein Abonnent kann hierbei für eine Kombination von Ort und Künstler Interesse bekunden. Er wird dann eine Nachricht erhalten, sobald in die Datenbank der Veranstaltungsagentur ein entsprechender Veranstaltungstermin eingetragen wird.1 Abonnements sehen also zum Beispiel so aus: Benachrichtige mich über stattfindende Auftritte von Deep Purple in Berlin. Die Anwendung ist eine Windows-Applikation, die mit Visual Studio 2005 erzeugt wird. Erstellen Sie eine neue Windows-Anwendung KonzertTermine unter Verwendung der Projektvorlage C#/Windows. Das automatisch erstellte Formular werden wir später zur Verwaltung von Abonnements und für die Erzeugung von Ereignissen verwenden.
13.4.1 Instance Configuration File (ICF) Beginnen wollen wir mit der Erstellung des ICF. Fügen Sie hierzu dem Projekt über das Menü Projekt/Neues Element hinzufügen
eine XML-Datei hinzu, und benennen Sie diese KonzertTermineICF.xml In der SQL Server 2005-Online-Hilfe suchen Sie dann
1
544
In unserem Beispiel werden wir keine reale Datenbank einer Veranstaltungsagentur verwenden, sondern stattdessen nur eine einfache Simulation.
13.4 Eine Notification Services-Beispielanwendung nach ICF template und kopieren die Minimalvorlage für eine ICF-Datei in unsere leere Datei (Abbildung 13.3).
Abbildung 13.3 ICF-Minimalvorlage
KonzertTermineICF.xml soll die Definition der HAL9000-NSI_1-Instanz enthalten. Ändern Sie diese Datei hierfür wie in Listing 13.1 angegeben: Listing 13.1 Instance Configuration File (ICF)
545
13 Anwendungen mit den Notification Services erstellen
Die ICF-Datei enthält Definitionen für die folgenden Elemente: Parameter. Über werden Standardwerte für Parameter angegeben, die für die ICF-Datei gelten. Diese Parameter können auch mit Bezug auf Systemoder Umgebungsvariablen definiert werden, wie dies zum Beispiel mit dem Verweis auf für die Variable geschieht. Im Beispiel werden diese drei Parameter definiert: .
Die Instanz des Datenbankmoduls, die hier auf den Namen des Computers gesetzt wird. Bei einer benannten Instanz von SQL Server 2005 muss dieser Wert auf den Namen der Instanz angepasst werden. .
Der Name des lokalen Computers. Dieser Wert wird ebenfalls standardmäßig auf festgelegt. .
Der Pfad zur ICF-Datei KonzertTermineICF.xml.
Name der Instanz. Das Tag HAL9000-NSI_1.
546
enthält den Namen der NS-Instanz, hier
13.4 Eine Notification Services-Beispielanwendung Name des SQL Server-Systems. Der SQL Server, der die NS-Datenbanken hostet, wird über angegeben. Dieser Name wird hier durch den Wert der globalen Variablen bestimmt. Anwendungskonfiguration. Das -Element enthält die Definition aller NS-Anwendungen, die mit dieser Instanz verbunden sind. Es ist möglich, mehrere Anwendungen in einer Instanz auszuführen, im Beispiel verwenden wir nur eine Anwendung KonzertTermine. In diesem Abschnitt werden der Pfad der Anwendung und der Verweis auf die zur Anwendung gehörende ADF-Datei angegeben. Es erfolgt auch eine Definition der Parameter, die in der verbundenen ADF-Datei verwendet werden können. Verteilerkanäle. Hier erfolgt die Definition der Übermittlungskanäle. Im Beispiel verwenden wir nur einen Dateikanal, dadurch werden alle Ereignisse in die Datei Termine.htm geschrieben, die im Verzeichnis erstellt wird, das durch die Variable festgelegt wurde.
13.4.2 Application Definition File (ADF) Die eigentliche Definition der Anwendung erfolgt durch das ADF, auf das in der ICF verwiesen wird.
Abbildung 13.4 Minimalvorlage für ein ADF
547
13 Anwendungen mit den Notification Services erstellen Zum Erstellen des ADF starten wir ebenfalls mit der Minimalvorlage aus der OnlineDokumentation. Suchen Sie hierfür nach ADF template, und erstellen Sie ein neues XML-Dokument KonzertTermineADF.xml, in das Sie den Inhalt dieser Minimalvorlage einfügen (Abbildung 13.4). Im ADF werden die Definitionen für Ereignisklassen Benachrichtigungsklassen Abonnementklassen Ereignisanbieter Generatorkonfiguration Verteilerkonfiguration vorgenommen, die in den folgenden Abschnitten nacheinander besprochen werden. Ereignisklassen Zunächst einmal müssen die Ereignisse beschrieben werden, welche die Anwendung zur Generierung von Benachrichtigungen verwendet. In unserem Beispiel soll ein Ereignis erzeugt werden, sobald feststeht, dass ein Künstler zu einem bestimmten Termin an einem bestimmten Ort auftreten wird. Unser (einziges) Ereignis wird also durch Ort, Künstler und Datum bestimmt. Im ADF definieren Sie ein Ereignis durch die Spezifikation eines -Elementes. Fügen Sie hierzu den im folgenden Listing 13.2 gezeigten Code unter dem Kommentar für ein: Listing 13.2 Definition von Ereignisklassen
548
13.4 Eine Notification Services-Beispielanwendung Der XML-Code definiert eine Ereignisklasse mit den Feldern Artist, Ort und Termin. In unserer Windows-Anwendung werden wir später entsprechende Ereignisse erzeugen und an die NS-Applikation übermitteln. Die Ereignisklassendefinition wird bei der Erzeugung der NS-Anwendung zur Erstellung von Tabelle, Sichten und gespeicherten Prozeduren verwendet, mit denen die Verwaltung von Ereignissen erfolgen kann. Wir werden hierauf im Verlauf des Kapitels zurückkommen, sobald die Definition der NSAnwendung abgeschlossen ist und diese erzeugt wurde. Benachrichtigungsklassen Eine Benachrichtigungsklasse definiert Art und Aufbau der Nachricht, welche die Anwendung erstellt. Im ADF ist der Abschnitt für die Definition dieser Klassen vorgesehen. Ersetzen Sie in der Vorlage diesen Bereich durch den Code aus Listing 13.3: Listing 13.3 Benachrichtigungsklassen
549
13 Anwendungen mit den Notification Services erstellen
Zunächst wird der Name der Ereignisklasse auf TerminNachricht festgelegt. Durch die anschließenden Felddefinitionen wird der Aufbau der Benachrichtigungstabelle beschrieben. In diesem Fall enthält eine Nachricht dieselben Spalten wie das zuvor definierte Ereignis KonzertTermin: Artist, Ort, und Termin; dies ist aber nicht erforderlich. Eine Benachrichtigung muss nicht alle Ereignisdaten enthalten, kann andererseits aber zu den Ereignisdaten auch durchaus noch Elemente hinzufügen. Notification Services fügt zu den definierten Ereigniselementen noch die Spalten SubcriberID, DeviceName und SubscriberLocale hinzu, auf die wir weiter unten nochmals zurückkommen werden. Wenn die Nachricht von NS erstellt wurde, so wird diese mittels der im Bereich spezifizierten Formatierung aus dem NS-internen XML-Format in ein benutzerdefiniertes Format transformiert. In diesem Fall erfolgt die Überführung in ein HTMLFormat durch das einfache XSLT-Dokument TerminNachricht.xslt, das im folgenden Listing 13.4 gezeigt wird: Listing 13.4 XSL-Stylesheet zur Konvertierung des Ergebnisses
Durch dieses Stylesheet wird die Nachricht einfach in eine HTML-Tabelle eingetragen. Die anschließende Sektion definiert die Protokolle, die zur Nachrichtenübertragung zur Verfügung stehen. Die formatierte Nachricht kann durch eines der hier angegebenen Protokolle zum Abonnenten übertragen werden. Unser einfaches Beispiel verwendet lediglich das Dateiprotokoll, das in erster Linie für Tests und Prototyping gedacht
550
13.4 Eine Notification Services-Beispielanwendung ist. Zusammen mit der Definition in der Sektion des ICF erreichen wir durch diese Spezifikation eine Verteilung der Nachrichten in der Datei Termine.htm, die in dem Verzeichnis abgelegt wird, das durch den Parameter angegeben wurde (Standardwert im Beispiel ist: c:\sql2005\ns). Dies ist natürlich keine wirkliche Nachrichtenübermittlung, wie sie in realen Anwendungen benötigt wird. Für unser Beispiel ist es jedoch zunächst ausreichend, wenn wir beobachten können, wie die Benachrichtigungen aussehen und an wen diese Nachrichten verteilt werden. Hierfür ist das Dateiprotokoll eine sehr gute Wahl. Bitte erinnern Sie sich an das weiter oben Gesagte: Das Dateiprotokoll ist hauptsächlich während der Entwicklung der Anwendung, also zum Beispiel für Debugging-Zwecke, nützlich. Abonnementklassen Die Abonnementklasse definiert die Felder für die Abonnementdaten und eine Benachrichtigungsgenerierungsregel in Form von SQL-Anweisungen. Diese Anweisungen prüfen die Übereinstimmung von Ereignisdaten und Abonnementdaten. Führt diese Prüfung zu dem Ergebnis, dass eine Benachrichtigung versandt werden muss, wird ein Eintrag in der Benachrichtigungsklassensicht erzeugt (wir werden diese Sicht weiter unten noch verwenden, um anhängige Benachrichtigungen abzufragen). Ersetzen Sie den Abschnitt durch den Code aus Listing 13.5: Listing 13.5 Definition von Abonnementklassen
551
13 Anwendungen mit den Notification Services erstellen
Es wird eine Abonnementklasse mit dem Namen AboArtistOrt definiert. Die beiden benutzerdefinierten Elemente sind Artist und Ort. Ein Abonnement besteht also aus einer Kombination eines Künstlernamens und eines Auftrittsortes. Indem ein Abonnent ein solches Abonnement erstellt, legt er fest, dass er benachrichtigt werden möchte, sobald der angegebene Künstler an diesem Ort auftreten wird. Listing 13.5 zeigt auch die Definition der Felder und . Diese Felder können von Abonnenten verwendet werden, um ein Abonnement für ein bestimmtes Gerät oder Gebietsschema zu erstellen. Die Spalten wurden hier nur zu Demonstrationszwecken in das Dokument eingefügt, wir werden diese Spalten im Beispiel nicht sinnvoll verwenden. Das Feld SubsriberID wird von NS automatisch hinzugefügt. Es enthält die eindeutige Identifikation des Abonnenten. Interessant ist die Betrachtung der (einzigen) Ereignisregel, die durch eingeleitet wird. Diese Regel wird hier einfach KonzertRegel benannt und mit der zuvor erstellten Ereignisklasse KonzertTermin verknüpft. Die in der Sektion angegebene INSERT-Anweisung sorgt dafür, dass aus einem Ereignis immer dann eine Nachricht erzeugt wird, wenn sowohl der Künstler als auch der Auftrittsort dieses Künstlers im Ereignis mit dem Künstler und dem Ort des Abonnements übereinstimmen. Die INSERT-Anweisung hebt sich vom Rest des ADF insofern ab, als dass in unserem ADF bislang hier die einzige Stelle ist, an der Programmcode (SQL-Code) verwendet wird. Ansonsten wird ja im ADF über XML-Elemente nur beschrieben, was getan werden soll, wobei die Implementierung letztlich den NS überlassen wird. Im Bereich wird tatsächlich über SQLAnweisungen angegeben, wie Ereignisse mit Abonnements verknüpft werden. Die in der INSERT-Anweisung angegebenen Tabellen TerminNachricht, KonzertTermin, AboArtistOrt sind tatsächlich Sichten, die bei der Erstellung der NS-Anwendung entsprechend der Definitionen in den Sektionen , und des ADF erstellt werden. Schauen Sie sich hierzu den Code in den obigen Listings noch einmal an. Wir werden diese Sichten weiter unten verwenden, um die erzeugte NS-Anwendung zu untersuchen. Ereignisanbieter Der Ereignisanbieter sammelt eintreffende Ereignisse und übermittelt diese an die NSAnwendung. Im Beispiel verwenden wir den standardmäßig in NS enthaltenen SQLEreignisanbieter. Dieser Anbieter wird von NS den gehostet, d.h., er wird von NS gestartet
552
13.4 Eine Notification Services-Beispielanwendung und ausgeführt. Ereignisanbieter werden in der -Sektion beschrieben. Fügen den Code aus Listing 13.6 zur Definition des einzigen Ereignisanbieters ein: Listing 13.6 Ereignisanbieter
Der Ereignisanbieter wird KonzertSQLAnbieter benannt. Er verwendet den systemeigenen SQLProvider-Ereignisanbieter, der Name des Servers wird über die zuvor definierte Variable _NS-Host_ festgelegt. Über die anschließende -Sektion wird die Abfrage angegeben, die ausgeführt wird, um Ereignisse zu übermitteln. Im Beispiel wird der Inhalt der KonzertTermin-Sicht für die Ereignisklasse KonzertTermin abgefragt. Diese Ereignisklasse wurde in einer vorherigen Sektion bereits definiert (siehe Listing 13.2). Generator-Konfiguration Die -Sektion spezifiziert das SQL Server-System, das die Benachrichtigungen erzeugt. In diesem Fall wird einfach der lokale Server verwendet, der über den _NS-Host_Parameter in der ICF-Datei angegeben wurde: Listing 13.7 Generatorkonfiguration
Verteilerkonfiguration Unsere Anwendung hat nur einen Verteiler, der auf dem lokalen Server ausgeführt wird. Ändern Sie hierfür die -Sektion so ab, wie in Listing 13.8 gezeigt:
553
13 Anwendungen mit den Notification Services erstellen Listing 13.8 Verteiler-Konfiguration
Durch diese Konfiguration werden Benachrichtigungen durch den Verteiler auf dem lokalen Server verbreitet. Damit ist die Definition unserer simplen NS-Anwendung abgeschlossen. Natürlich besteht die Möglichkeit, eine NS-Anwendung durch eine Vielzahl weiterer Einstellungen zu konfigurieren und auf die jeweiligen Erfordernisse abzustimmen. Solche weiterführenden Möglichkeiten wurden in dieser Einführung in die Notification Services absichtlich ausgelassen, in der Online-Dokumentation finden Sie hierzu natürlich die entsprechenden Erläuterungen. Wir haben also eine NS-Anwendung definiert, die Benachrichtigungen versenden kann, sobald Auftrittsorte von Künstlern bekannt sind. Diese Anwendung kann nun erzeugt und ausgeführt werden. Anschließend werden wir die Anwendung veranlassen, Benachrichtigungen zu versenden. Dies alles ist Gegenstand der folgenden Abschnitte.
13.4.3 Erzeugen und Ausführen der NS-Anwendung Für das Erstellen der Anwendung unter Verwendung der gerade entwickelten Definitionsdateien sind diese vier Schritte erforderlich: Kompilieren Aktivieren Registrieren Starten Man kann hierzu entweder das Kommandozeilenprogramm nsconfig oder die interaktive Möglichkeit über das SQL Server Management Studio nutzen. In unserem Beispiel soll die Erzeugung der Instanz und der Anwendung interaktiv unter Verwendung des Management Studios erfolgen. Starten Sie hierzu das Management Studio, und öffnen Sie das Kontextmenü für die Notification Services. Wählen Sie dort Neue Notification ServicesInstanz
(Abbildung 13.5).
554
13.4 Eine Notification Services-Beispielanwendung
Abbildung 13.5 Anlegen einer neuen NS-Instanz
Im Dialog Neue Notification Services-Instanz geben Sie für die Konfigurationsdatei den Verweis zur erzeugten ICF-Datei an. Nach der Angabe dieser Datei werden die Parameter angezeigt, die hier auch noch geändert werden können (Abbildung 13.6).
Abbildung 13.6 Notification Services-Instanz erstellen
Wählen Sie auch die Option Instanz nach dem Erstellen aktivieren, dadurch wird die Aktivierung unmittelbar nach der Kompilierung vorgenommen. Nach dem Bestätigen mit OK wird die Kompilierung gestartet, der Fortschritt wird in einem neuen Dialog angezeigt.
555
13 Anwendungen mit den Notification Services erstellen Nach Abschluss der Erstellung sollten Sie eine Erfolgsmeldung angezeigt bekommen, so wie in Abbildung 13.7 zu sehen.
Abbildung 13.7 Kompilieren der NS-Anwendung
Durch die Kompilierung werden auf dem SQL Server zwei neue Datenbanken erzeugt: HAL9000-NSI_1NSMain Dies ist die Datenbank für die Notification Services-Instanz. Hier werden Daten verwaltet, die von allen NS-Anwendungen, die durch diese Instanz gehostet werden, gemeinsam benutzt werden können. HAL9000-NSI_1KonzertTermine Dies ist die Datenbank für unsere Anwendung. Hier werden zum Beispiel Ereignisse, Abonnements und Benachrichtigungen gespeichert. Wir werden in Kürze auf diese beiden Datenbanken zurückkommen und uns die enthaltenen Objekte ansehen. Zunächst einmal muss die erzeugte Notification Services-Instanz jedoch registriert und gestartet werden. Für die Registrierung wählen Sie über das Kontextmenü für die neu erstellte Anwendung Tasks/Registrieren
(Abbildung 13.8).
Abbildung 13.8 Registrieren einer NS-Instanz
556
13.4 Eine Notification Services-Beispielanwendung Eine NS-Instanz wird letztlich durch einen Windows-Dienst ausgeführt, der den Namen NS$InstanzName erhält, wobei InstanzName durch den Namen ersetzt wird, der in der Sektion des ICF angegebenen wurde (in unserem Beispiel HAL9000NSI_1). Durch das Registrieren der Instanz wird dieser Dienst konfiguriert. Im Fenster Instanz registrieren (Abbildung 13.9) wählen Sie die Option Windows-Dienst erstellen, und geben Sie Namen und Kennwort für die Ausführung des Dienstes an. Beachten Sie hierbei bitte, dass das angegebene Konto Zugriff auf die beiden erzeugten NSDatenbanken haben muss. Der Benutzer muss außerdem Schreibrechte auf das Verzeichnis haben, in dem die Benachrichtigungsdatei Termine.htm abgelegt wird. Dieses Verzeichnis wurde im ICF über die Variable _InstancePath_ angegeben.
Abbildung 13.9 Konfiguration der Registrierung einer NS-Instanz
Nachdem Sie ein gültiges Konto angegeben haben, bestätigen Sie das Eingabeformular mit OK. Anschließend sollten Sie eine Meldung über die erfolgreiche Registrierung erhalten, wie in Abbildung 13.10 gezeigt.
557
13 Anwendungen mit den Notification Services erstellen
Abbildung 13.10 Erfolgreiche Registrierung der NS-Instanz
Nach dem Registrieren kann die Instanz nun gestartet werden. Wählen Sie hierfür im Kontextmenü der Instanz Starten aus (siehe Abbildung 13.11).
Abbildung 13.11 Starten der Instanz
Wenn Sie sich nun in der Systemsteuerung Ihres Computers die Dienste ansehen, sollten Sie einen Dienst mit dem Namen NS$HAL9000-NSI_1 sehen, der sich im Zustand Gestartet befindet.
13.4.4 Überprüfen der erzeugten NS-Datenbanken und -Objekte In den beiden Datenbanken HAL9000-NSI_1NSMain (für die Instanz) und HAL9000NSI_1KonzertTermine (für die Anwendung) sind die Anwendungsklassen für Abonnenten, Ereignisse, Benachrichtigungen, Abonnements, Ereignisanbieter etc. in Form von Tabellen und Sichten enthalten. Besonders interessant sind die folgenden: Instanzdatenbank HAL9000-NSI_1NSMain Abonnenten. In der Instanzdatenbank HAL9000-NSI_1NSMain können Sie die existierenden Abonnenten über die Sicht NSSubscriberView abfragen:
558
13.4 Eine Notification Services-Beispielanwendung Anwendungsdatenbank HAL9000-NSI_1KonzertTermine Abonnements. Die Sicht AboArtistOrt enthält die Abonnementdaten, die zugrunde liegende Tabelle ist NSAboArtistOrtSubscriptions. Ereignisse. Über die Sicht KonzertTermin können die aktuell eingetroffenen Ereignisse abgefragt werden. Benachrichtigungen. TerminNachricht ist die Sicht, die Benachrichtigungen zurückgibt, die bereits erzeugt, aber noch nicht verteilt wurden. Ereignisanbieter. Für die Abfrage der hinterlegten Ereignisanbieter kann die Sicht NSProviders verwendet werden: Im Augenblick wird der größte Teil dieser Sichten allerdings noch keine Daten zurückgeben. Das ist natürlich nicht weiter verwunderlich, denn wir haben ja auch noch keinerlei Abonnenten, Abonnements oder Ereignisse angelegt. Dies soll nun über eine WindowsAnwendung erfolgen, deren Entwicklung Bestandteil des folgenden Abschnitts ist.
13.4.5 Erzeugen von Abonnements und Ereignissen In diesem Kapitel werden wir eine einfache Windows-Anwendung entwickeln, die unter Verwendung der Notification Services-Objektbibliothek des .NET Frameworks auf unsere Erzeugte NS-Instanz und -Anwendung zugreift. Diese Anwendung wird Abonnenten, Abonnements und Ereignisse erstellen und somit automatisch für die Generierung und Verteilung von Benachrichtigungen sorgen. Wir werden hierfür einen Windows-Client in C# erstellen. Die fertige Anwendung können Sie in Abbildung 13.12 sehen.
Abbildung 13.12 Windows-Anwendung zur Abonnement- und Ereignisverwaltung
559
13 Anwendungen mit den Notification Services erstellen Die Anwendung besteht aus drei Teilbereichen: Oben: Hinzufügen von Abonnenten. Mitte: Anzeigen und Hinzufügen von Abonnements. Unten: Hinzufügen von Ereignissen. Benennen Sie zunächst das im Projekt bereits existierende Formular in MainForm um, und fügen Sie dem Formular die folgenden Steuerelemente hinzu (Abbildung 13.13):
Abbildung 13.13 Steuerelemente des Hauptfensters
Konfigurieren des Projektes Unsere Anwendung benötigt Zugriff auf die SQL Server Notification Services-Objekte der .NET-Klassenbibliothek. Hierzu muss dem Projekt zunächst ein Verweis auf die entsprechende Assembly hinzugefügt werden. Klicken Sie hierzu mit der rechten Maustaste auf den Eintrag Verweise im Projektmappen-Explorer, und wählen Sie Verweis hinzufügen
(Abbildung 13.14).
Abbildung 13.14 Einen Verweis hinzufügen
560
13.4 Eine Notification Services-Beispielanwendung Im Dialog Verweis hinzufügen suchen Sie den Eintrag Microsoft. SqlServer.NotificationServices (Abbildung 13.15), wählen diesen aus und bestätigen den Dialog mit OK.
Abbildung 13.15 Verweis auf Microsoft.SqlServer.NotificationServices hinzufügen
Im Code des Formulars MainForm.cs können Sie nun noch für den bequemeren Zugriff auf die Objekte in dieser Assembly eine using-Direktive hinzufügen:
Initialisieren der Anwendung Der Code für die Klasse MainForm beginnt zunächst mit der Deklaration und Definition einer Reihe von Variablen, wie in Listing 13.9 gezeigt. Listing 13.9 Globale Variablen für die Klasse MainForm
561
13 Anwendungen mit den Notification Services erstellen
Zu Beginn erfolgt die Definition einiger Konstanten für die Namen von Instanz, Anwendung, Ereignisanbieter sowie Ereignis- und Abonnementklasse. Daran anschließend werden die beiden globalen Variablen und deklariert, die später mit Referenzen auf unsere NS-Instanz und NS-Anwendung initialisiert werden. Die beiden StringArrays und geben eine Liste von Künstlern und Orten vor, für die letztlich Abonnements und Ereignisse angelegt werden können. In einer realen Anwendung würde man natürlich auf die statische Definition solcher Listen verzichten und stattdessen die Daten hierfür aus den entsprechenden Tabellen einer Anwendungsdatenbank holen. Wie bereits weiter oben erwähnt, soll in unserem Beispiel lediglich eine Simulation der Datenbank einer Veranstaltungsagentur verwendet werden die String-Arrays und übernehmen genau diese Aufgabe. Listing 13.10 zeigt den Quellcode für das Load-Ereignis des (einzigen) Programmfensters. Hier wird zunächst versucht, die Variablen und zu initialisieren. Sollte dies fehlschlagen (zum Beispiel weil der Windows-Dienst für die Instanz nicht registriert wurde), so wird die Anwendung mit einer Fehlermeldung beendet. Ist die Initialisierung erfolgreich, so werden die Comboboxen für Künstler und Orte mit den Daten aus den String-Arrays gefüllt. Die beiden abschließenden Aufrufe der Funktionen und holen aus der Instanz- bzw. Anwendungsdatenbank die Informationen über bereits existierende Abonnenten und Abonnements und tragen diese Informationen in die zugehörigen Steuerelemente cbAbonnent und lbAbos (siehe Abbildung 13.12 und Abbildung 13.13) ein. Wir werden diese Funktionen weiter unten noch genauer besprechen. Listing 13.10 Quellcode für das Ereignis MainForm_Load
562
13.4 Eine Notification Services-Beispielanwendung
Abonnenten abfragen Das Abfragen der existierenden Abonnenten sowie das Eintragen dieser Abonnenten in die Combobox cbAbonnent wird innerhalb der Funktion vorgenommen, deren Quellcode in Listing 13.1 zu sehen ist. Listing 13.11 Informationen über existierende Abonnenten aus der Instanz holen
Die Abonnenten sind in der NS-Instanz gespeichert, ein Zugriff auf die NS-Anwendung ist hier nicht erforderlich. Der Aufruf von:
gibt eine Auflistung zurück, die alle Abonnenten der übergebenen Instanz enthält. Die Speicherung der Abonnenten in der Instanz ist natürlich deshalb sinnvoll, weil ein Abonnent dadurch für alle dieser Instanz zugeordneten NS-Anwendungen auch Abonnements erzeugen kann. Das im Code angelegte SubscriberEnumeration-Objekt für unsere NSInstanz wird dann zur Iteration über die existierenden Abonnenten verwendet. Die SubsciberID eines Subscriber-Objektes enthält in unserem Beispiel einfach den Namen des bonnenten, der hier in die Combobox cbAbonnent eingetragen wird.
563
13 Anwendungen mit den Notification Services erstellen Abonnenten hinzufügen Das Hinzufügen von Abonnenten erledigt die Funktion . Auch hier wird nur die NS-Instanz benötigt, da Abonnenten ja in der Instanzdatenbank gespeichert werden. Listing 13.12 zeigt den Quellcode: Listing 13.12 Einen Abonnenten hinzufügen
Die Funktion erzeugt zunächst einen neuen Abonnenten für die NS-Instanz und fügt diesen zur Instanz hinzu. Als ID des neuen Abonnenten wird die übergebene SubscriberID verwendet in unserem einfachen Beispiel ist dies der Name des Abonnenten. Das Hinzufügen erledigt der Aufruf von . Ein Abonnent kann mehrere Geräte angeben, an die Benachrichtigungen versandt werden sollen. Auf diese Art kann ein Abonnent für ein Ereignis mehrere Benachrichtigungen erhalten, etwa durch ein Abonnement für E-Mail und ein anderes für Mobiltelefon. Für jedes Gerät muss sowohl eine Adresse als auch ein Übertragungsprotokoll angegeben werden. Unser Beispiel legt zunächst ein neues GeräteObjekt an und verwendet als Geräte-Adresse eine fiktive E-Mail-Adresse, die den Namen (genauer die SubscriberID) des Abonnenten enthält. Als Geräte-Name wird hier stets nullDevice verwendet ein fiktives Gerät, das so nicht existiert. Unsere Nachrichtenübermittlung soll ja einfach dadurch erfolgen, dass Benachrichtigungs-daten in eine Datei eingetragen werden, und dafür benötigen wir kein Gerät. Da später für die Erstellung eines Abonnements aber stets ein Gerät angegeben werden muss, wird hier eben einfach ein Fantasiename verwendet. Wir verwenden als Übertragungskanal FileChannel und als Gerätetyp stets File. Diese Angaben korrespondieren mit den entsprechenden Sektionen in der IDF (hier wird der Übertragungskanal FileChannel über angegeben) und in auch der ADF (in der Sektion wird dort File als zur Verfügung stehendes Protokoll spezi-
564
13.4 Eine Notification Services-Beispielanwendung fiziert). Schließlich wird das für diesen Abonnenten konfigurierte Gerät zur Instanz hinzugefügt. Dies geschieht durch den Aufruf von . Abonnements abfragen Der Code zur Abfrage der existierenden Abonnements erfordert zwei ineinander geschachtelte Schleifen (Listing 13.13). Listing 13.13 Abfrage existierender Abonnements
In der äußeren Schleife wird über alle Abonnenten der Instanz iteriert, wie dies auch bereits in Listing 13.11 zu sehen war. Die Aufzählung dieser Abonnenten erhält man wieder durch den Aufruf von:
Die innere Schleife iteriert über alle Abonnements, die ein Abonnent für die Anwendung KonzertTermine sowie die Abonnement-Klasse AboArtistOrt besitzt. Diese Abonnements erhält man durch den Aufruf von:
Für jedes gefundene Abonnement wird dann ein String konstruiert, der die Information über den Abonnenten, den Künstler, den Ort sowie das Gerät, an das Benachrichtigungen verschickt werden sollen, enthält. Dieser String wird dann zur Listbox lbAbos hinzugefügt. (Wenn Sie sehen möchten, wie der String aufgebaut ist, schauen Sie sich bitte nochmals Abbildung 13.12 an.)
565
13 Anwendungen mit den Notification Services erstellen Abonnements hinzufügen Das Hinzufügen eines Abonnements erledigt die Funktion , deren Quellcode in Listing 13.14 zu sehen ist. Listing 13.14 Quellcode zum Hinzufügen eines Abonnements
Hier wird zunächst ein neues Abonnement-Objekt erzeugt:
Anschließend werden die Werte für das Gerät, die Lokalisierungseintellungen, den Abonnenten und die interessierenden Werte für Künstler und Ort festgelegt, bevor das Abonnement durch den Aufruf von hinzugefügt wird. Ereignisse hinzufügen Der Code zum Anlegen von Ereignissen ist in Listing 13.15 zu sehen. Listing 13.15 Code zum Hinzufügen von Ereignissen
566
13.4 Eine Notification Services-Beispielanwendung
Zuerst einmal wird ein neues Ereignis-Objekt für unsere Anwendung KonzertTermine und die Ereignisklasse KonzertTermin erzeugt. Dies geschieht durch den Aufruf von:
In den im Anschluss daran erzeugten können dann Ereignisse über hineingestellt werden, so wie dies mit der Programmzeile
geschieht. Erst der abschließende Aufruf von sorgt allerdings dafür, dass die im gesammelten Ereignisse tatsächlich in die Anwendungsdatenbank eingestellt werden. Auf diese Weise können im -Objekt zunächst die aufgetretenen Ereignisse gesammelt und dann als ein Batch zur NS-Anwendung übertragen werden. Zwischen dem Erzeugen eines neuen Ereignis-Objekts und dem Übertragen dieses Objekts in die -Liste werden noch die Ereignisdaten, also die Werte für den Künstler, den Auftrittsort und den Termin festgelegt. Ereignisse verdrahten Was nun noch fehlt, ist der Aufruf der gerade besprochenen Funktionen zum Anlegen von Abonnenten, Abonnements und Ereignissen. Die entsprechenden Funktionen , und sollen natürlich immer dann aufgerufen werden, wenn der zugehörige Button gedrückt wurde. Um dies zu erreichen, legen Sie bitte für die Klick-Ereignisse der Buttons bnAbonnent, bnAbbonement und bnEreignis den Code so an, wie in Listing 13.16 angegeben. Der Code für die Ereignis-Handler reicht den Aufruf einfach nur an die entsprechende Funktion durch, wobei allerdings noch eine Ausnahmebehandlung durchgeführt wird. Außerdem werden die Combobox cbAbonnent und die Listbox lbAbos aktualisiert, wenn Abonnenten oder Abonnements hinzugefügt wurden. Am Ende des Listings ist auch der Code für das Klick-Ereignis des Buttons bnExit zu sehen, dessen Ausführung einfach das Programm beendet.
567
13 Anwendungen mit den Notification Services erstellen Listing 13.16 Klick-Ereignisse für die vier verwendeten Buttons
568
13.4 Eine Notification Services-Beispielanwendung Anwendung starten Haben Sie den Code so wie in den obigen Abschnitten eingegeben, können Sie nun die Anwendung starten sowie Abonnenten, Abonnements und Ereignisse hinzufügen. Legen Sie zunächst einige Abonnenten an, indem Sie den Namen in die Textbox tbSubId eingeben und anschließend auf Hinzufügen klicken (Abbildung 13.16).
Abbildung 13.16 Abonnenten hinzufügen
Wenn Sie der Meinung sind, dass Sie genügend Abonnenten hinzugefügt haben, sehen Sie sich die existierenden Abonnenten in der Instanzdatenbank einmal an. Hierzu öffnen Sie im SQL Server Management Studio eine neue Abfrage, und geben Sie die folgende SelectAnweisung ein:
Das Ergebnis wird in etwa so aussehen wie in Abbildung 13.17 gezeigt (natürlich abhängig von den Abonnenten, die Sie hinzugefügt haben).
Abbildung 13.17 Liste der hinzugefügten Abonnenten
Anschließend erzeugen Sie für die angelegten Abonnenten Abonnements, indem Sie angeben, welcher Abonnent für welche Konzerte in welchem Ort Interesse bekundet. Hierzu wählen Sie im mittleren Bereich des Eingabeformulars jeweils einen Abonnenten, einen Künstler und einen Ort aus und klicken dann auf Hinzufügen. In Abbildung 13.18 ist zu sehen, dass der Abonnent Erik eine Benachrichtigung erhalten möchte, wenn Slade in Hamburg auftreten wird.
569
13 Anwendungen mit den Notification Services erstellen
Abbildung 13.18 Ein Abonnement hinzufügen
Die angelegten Abonnements können Sie überprüfen, indem Sie die Datenbank der Anwendung abfragen. Hierfür existiert die Sicht :
Das Ergebnis sollte in etwa so aussehen, wie in Abbildung 13.19 gezeigt natürlich auch hier wieder abhängig von den Abonnements, die Sie angelegt haben.
Abbildung 13.19 Angelegte Abonnements
Was nun noch fehlt, sind die Ereignisse, die bei Übereinstimmung mit Abonnements Benachrichtigungen auslösen. Erzeugen Sie hierfür ein beliebiges Ereignis, das zu einem zuvor angelegten Abonnement passt. Hierfür wählen Sie im unteren Teil des Eingabeformulars einen Künstler, einen Veranstaltungsort sowie einen Termin aus und klicken dann auf Hinzufügen (Abbildung 13.20).
Abbildung 13.20 Ein Ereignis hinzufügen
In der Anwendungsdatenbank existieren die beiden Sichten und , die beim Erstellen der Anwendung aus unseren Definitionen in der ADF erzeugt wurden. Über diese Sichten können eingestellte Ereignisse und anhängige Benachrichtigungen abgefragt werden. Wechseln Sie hierzu unmittelbar nach dem Hinzufügen des Ereignisses in das Management Studio, und starten Sie mehrfach die folgenden Abfragen:
570
13.4 Eine Notification Services-Beispielanwendung
Nach einer Weile sollte die erste Abfrage zunächst das bereitgestellte Ereignis zurückgeben. Sobald der Generator dieses Ereignis verarbeitet und daraus eine Benachrichtigung erzeugt hat, wird auch die zweite Abfrage eine Zeile zurückgeben, welche die Daten der Benachrichtigung anzeigt. Ist die Benachrichtigung erfolgt, werden beide Abfragen wieder keine Daten mehr liefern. Dafür sollte dann in Ihrem Anwendungsverzeichnis (im Beispiel: c:\sql2005\ns) die Datei Termine.htm existieren, welche die verteilten Benachrichtigungen enthält. Diese Datei sieht im Browser betrachtet in etwa so aus, wie in Abbildung 13.21 gezeigt.
Abbildung 13.21 Benachrichtigungen in der Datei Termine.htm
Auffallend ist, dass die Benachrichtigungen für alle Abonnenten in dieser Datei stehen. Die Unterscheidung, welche Nachricht für wen bestimmt ist, muss daher anhand der angegebenen SubscriberID erfolgen. Wie weiter oben erwähnt, ist das File-Protokoll ja haupt-
571
13 Anwendungen mit den Notification Services erstellen sächlich für Testumgebungen und Prototyping vorgesehen, und hierfür ist die Ausgabe aller Benachrichtigungen in nur eine Datei perfekt. Sie können dadurch an nur einer Stelle kontrollieren, wie Ihre Nachrichten verteilt und an wen diese versendet werden.
13.5
Zusammenfassung In diesem Kapitel wurde gezeigt, wie die SQL Server Notification Services für die ereignisgesteuerte Verarbeitung von Daten eingesetzt werden können. Aufgrund der recht komplexen Thematik konnte hier nur eine kurze Einführung gegeben werden das Ihnen vorliegende Buch ist ja ein Werk über die Datenbankentwicklung mit dem SQL Server 2005 insgesamt und nicht nur speziell über die Notification Services. Hierüber könnte man sicherlich ein eigenes Buch verfassen und tatsächlich ist dies auch bereits geschehen (siehe zum Beispiel [PATH06]). Möglicherweise ist es für Sie als Leser unbefriedigend, wenn eine so interessante Thematik nur einführend behandelt wird. Warum sie dann nicht einfach auslassen und sich mehr auf andere Schwerpunkte konzentrieren, mögen Sie fragen. Nach meiner Auffassung gehören die Notification Services einfach in ein Buch über die Datenbankentwicklung mit dem SQL Server 2005. Datenbank- und Anwendungsentwicklern eröffnen sich durch den Einsatz der NS fantastische und faszinierende neue Möglichkeiten für die Erstellung ereignisgesteuerter Anwendungen. Ich hoffe, dass Sie mir nach der Lektüre dieses Kapitels in dem Punkt zustimmen. In diesem Kapitel wurde gezeigt, wie man NS-Instanzen und -Anwendungen konfiguriert und erstellt. Sie haben gesehen, wie man im Instance Configuration File (ICF) und im Application Definition File (ADF) Ereignis- und Benachrichtigungsklassen definiert sowie Abonnements und Regeln für die Auslösung von Ereignissen und die entsprechende Verteilung von Benachrichtigungen spezifiziert. Im Anschluss daran haben Sie eine WindowsAnwendung entwickelt, die das Hinzufügen von Abonnenten, Abonnements und Ereignissen ermöglicht. Schließlich konnten Sie die erzeugten und verteilten Benachrichtigungen in einer HTML-Datei beobachten.
572
Literatur [AZEV06]
Azevedo, P., Brosius, G., Dehnert, S.: Business Intelligence und Reporting mit Microsoft SQL Server 2005. 1. Auflage. Microsoft Press 2006
[BAUD06]
Bauder, I.: Microsoft SQL Server 2005 für Administratoren. 1. Auflage. Hanser Verlag 2006
[BEN-06]
Ben-Gan, I.: Inside Microsoft SQL Server 2005 T-SQL Querying. 1. Auflage. Microsoft Press 2006
[CELK00]
Celko, J.: SQL FOR SMARTIES. 2. Auflage. Morgan Kaufmann 2000
[LEHN04]
Lehner, W., Schöning, H.: XQuery. Grundlagen und fortgeschrittene Methoden. 1. Auflage. dpunkt Verlag 2004
[PATH06]
Pather, S.: Microsoft SQL Server 2005 Notification Services. 1. Auflage. Sams 2006
[SCHU06]
Schulz, M., Knuth, J., Pruß, V.: Microsoft SQL Server 2005 Reporting Services. Das Praxisbuch. 1. Auflage. Microsoft Press 2006
[SCHR06]
Schrödl, H.: Business Intelligence mit Microsoft SQL Server 2005. 1. Auflage. Hanser Verlag 2006
[STEI03]
Steiner, R.: Grundkurs Relationale Datenbanken. 5. Auflage. Vieweg 2003
573
Register @ @@DATEFIRST 129 @@ERROR 150, 152, 169 @@ROWCOUNT 152 @@SERVERNAME 129 @@SPID 129 @@TRANCOUNT 210, 211 @@VERSION 130
A Abfrageoptimierer 307, 489, 497, 498, 504, 530, 536, 538 Abfrageoptimierung Tipps 535 Abgeleitete Tabellen 192 Ablaufverfolgung 53, 221, 223, 511-513, 515-518, 520, 532, 533 Abonnement 552, 564 Abonnementklassen 551 Abonnent 564 Notification Services Abonnent 539, 541, 544, 552, 563-566, 568, 569 ABS 133 552 ADF 547-549 AdventureWorks 18, 153
Datenbankdiagramm 6 Aggregatfunktionen 179, 180, 182, 190 Aktivitätsmonitor 214, 215, 216 ALL 78 Allgemeine Tabellenausdrücke 193 ALTER COLUMN 94, 169 ALTER DATABASE 82-84, 164, 241, 255 ROLLBACK IMMEDIATE 84 TRUSTWORTHY 441 ALTER LOGIN 263 ALTER SCHEMA 86 ALTER TABLE 93, 94, 96, 98-102, 105, 168, 378, 379 Spalten ändern 94 Spalten hinzufügen 94 Spalten löschen 94 ALTER VIEW 156 Analysis Services 13, 14, 18 AND 78 Anweisungen Trennen von 69 Anweisungsberechtigungen 266 ANY 78 Application Definition File 547 APPLY 203, 204, 386 Arguments 553
575
Register Assemblys 424-428, 441, 464, 467, 468 208 Ausdrücke 76 Ausführungspläne 307,505, 508-510, 531, 538 Anzahl von Zeilen 507 HASH MATCH-Operator 508 Index-Seek 526 Nested Loops 526 Tabellen-Scan 508 Unterstrukturkosten 507 XML-Format 508 AUTHENTICATION 472 Authentifizierungsmodus 259 Autocommit 211
B BACKUP 246, 248, 249, 251 DATABASE 246 BEGIN 293 CATCH 169 TRANSACTION 210-213 TRY 169 Benachrichtigung 551, 564 Benachrichtigungsklassen 549 Benutzer 21 Benutzerdefinierte Funktionen 20, 320 Skalarwertfunktionen 320 Tabellenwertfunktionen 323 Benutzerdefinierter Datentyp 20 Benutzerzuordnung 263 Berechnete Spalten 102, 148 Berechtigungen auf Objektebene 268 Berechtigungshierarchie 267 BETWEEN 78, 101, 117 Beziehungen 104, 108, 109 Block 293, 294 boolesche Algebra 160 BREAK 295 Buffer cache hit ratio 489, 490 Buffer cache hit ratio base 490 Business Intelligence Development Studio 14
576
C Cache-Trefferquote 489 CASE 149, 167, 194, 199, 200, 202 CAST 75, 143 CEILING 133 CHAR 136 CHARINDEX 137 CHECK 100, 101, 105, 107, 124, 158, 162 Checkpoint 232 CLR 423 CLR-Integration Aktivierung 425 Debugging 462 Kontextverbindung zum SQL Server 438 Methoden benutzerdefinierter Aggregate 458 Methoden benutzerdefinierter Typen 447 Serialisierung benutzerdefinierter Typen 455 Sicherheit 463 Signieren einer Assembly 464 Skalarwertfunktionen 442 Spaltenerzeugungsfunktion 444 SqlTrigger Direktive 460 Tabellenwertfunktionen 443 Tabular Data Streams 436 TriggerContext 461 und externe Ressourcen 439 COALESCE 167, 168 Common Language Runtime 423 Common Table Expressions siehe Allgemeine Tabellenausdrücke 193 COMPUTE 182, 183, 184 Consistency 208 CONSTRAINT 97, 99, 100, 105, 106 CONTINUE 295 CONVERT 75, 142, 143 CREATE ASSEMBLY 426, 465 PERMISSION_SET 427 create contract 405 CREATE DATABASE 79, 80, 81, 82, 164 CREATE ENDPOINT 471, 474, 475, 476, 484
Register CREATE EVENT NOTIFICATION 418 CREATE INDEX 498, 500, 501, 530 CREATE LOGIN 260 create message type 405 CREATE PROCEDURE 307 create queue 405 CREATE SCHEMA 85, 86, 91 AUTHORIZATION 86 create service 405 CREATE SYNONYM 89 CREATE TABLE 90-92, 96-104, 110, 159 CREATE USER 420 CREATE VIEW 69, 154, 155, 193 CROSS JOIN 173 CTE siehe Allgemeine Tabellenausdrücke 193 CUBE 185, 186 Cursor 297, 537, 538 @@FETCH_STATUS 303 CLOSE 303 DEALLOCATE 303 Durchlaufen 300 DYNAMIC 300 FAST_FORWARD 300 FETCH 301, 302 Hinweise 306 KEYSET 300 OPEN 301 OPTIMISTIC 300 READ_ONLY 300 STATIC 300 UPDATE 305
D Das Business Intelligence Development Studio 51 Data Control Language 68 Data Definition Language 68 Data Manipulation Language 68 Data Query Language 68 DATABASEPROPERTY 130 DATABASEPROPERTYEX 130 DataSet 472
DATEADD 145 DATEDIFF 145 DATENAME 145 Datenbankbenutzer 263 Datenbankdiagramme 108 Datenbank-E-Mail konfigurieren 9, 10, 11, 16, 411 Datenbankmodul Optimierungsratgeber 532, 533 Arbeitsauslastung 532 Datenbankmodul-Optimierungsratgeber 58 Datenbankrollen 265 db_owner 265 public 265 Datencache 489, 490, 492, 496, 502-504 Datentypen 71 Binäre 74 Datum und Zeit 74 Numerische 71 sonstige 74 Zeichenketten 72 DATEPART 146 DAY 146 DB_ID 131 DB_NAME 131 DBCC 242, 247, 502 DBMS 9, 10, 11 dbo 84, 85, 87, 88, 89 Deadlock Graph 221, 222 Deadlock-Monitor 220, 221, 222 Deadlock-Opfer 220, 222 Deadlocks Siehe Sperren DECLARE 287 DEFAULT 101, 102, 104, 107, 110, 128, 146 DELETE 125 WHERE 125 DELETED 202 DeliveryChannels 551 DENSE_RANK 186, 188, 189 DENY 269 550, 552 Dialoge 399, 402
577
Register Dienste 399 DIFFERENCE 137 Dirty Reads 224, 225, 229, 332 DISTINCT 122, 179, 180, 191 Dreiwertige Logik 160 DROP DATABASE 84 DROP LOGIN 261 DROP SCHEMA 87 DROP SYNONYM 89, 90 DROP TABLE 107 DROP USER 420 DTA siehe Datenbankmodul Optimierungsratgeber 58, 532 Durability 208, 209, 234 Dynamic Management Views siehe Dynamische Verwaltungssichten Dynamische Verwaltungssichten 283, 284 Dynamisches SQL 292
E END 293 END CATCH 169 END TRY 169 Endpunkt 471, 472 Ereignis 550, 552, 564 Ereignisanbieter 552, 553 ERROR_NUMBER() 222 ERRORLOG 272 ETL 13 EventRule 552 EXEC siehe EXECUTE 123 EXECUTE 123 EXISTS 78, 118, 119 EXP 134 Explizite Transaktionen 213, 235 EXTERNAL ACCESS ASSEMLBY 464
F Fehlerbehandlung 169 FLOOR 134 fn_trace_gettable 516 FOR XML 339 AUTO 342
578
EXPLICIT 344, 366 Optionen 355 PATH 352, 358, 373 RAW 341 Unterabfragen mit XML PATH 362 FOREIGN KEY 105, 106, 107, 124, 126 Fremdschlüssel 105, 106, 107, 126, 153
G Gebietsschema 552 Generator 553 Gespeicherte Prozeduren 20, 306 Ausgabeparameter 313 Parameter 311 Rückgabewerte 314 GETDATE 146 GO 287 GOTO 223 GRANT 269 grant connect on endpoint 478, 480 GROUP BY 162, 179-182, 190, 191, 192
H HAVING 160, 181, 182 Heap 491, 492, 494, 495 HOST_NAME 127 HTTP 472 HTTP.sys 473 HTTP-Endpunkt 470 HTTP-Namensraum 473
I ICF 547, 553, 555 IDENT_CURRENT 127 IDENTITY 97, 98, 127, 159 IF 293 Implizite Transaktionen 211 IN 78, 118 Index 20 Index Allocation Map 491 Indizes 491-502, 524-538 Abdeckende 497 Anzahl der Indexstufen 499
Register Automatische Erzeugung 499 Blattseite 492 Fehlende 529 Fragmentierung von 529 Füllfaktor 500 Gruppierte 492 Hinweise für die Erstellung von 528 Indexbaum 492 Löschen 501 Nicht gruppierte 494, 495 Nicht verwendete 531 Selektivität von 528 INFORMATION_SCHEMA 277 Initiator 405 INNER JOIN 173 INSERT 110 INSERTED 202 Instanz 544, 545, 554, 555, 563 Integration Services 13, 14 Intermediate Language 423 ISDATE 150 ISNULL 117, 162, 167, 168, 169 ISNUMERIC 151 Isolation 208, 209, 227
J JOIN 172-178, 180, 182, 192, 193, 198, 204 Ausführungsreihenfolge 176 Just-In-Time-Compiler 423
K Kataloginformationen 277 Kommentare 70 Komponenten von SQL Server 9 Konfigurations-Manager 24 Konversation 405
L LEFT 137, 175, 176, 177, 178, 180 Leistungsindikatoren 517, 519 LEN 138 LIKE 78, 119, 137, 138, 188 Lock Escalation 214, 217
LOG 134 Log Sequence Number 232 LOG10 134 LOWER 138 LSN siehe Log Sequence Number 232 LTRIM 138
M Management Studio 554 Microsoft.SqlServer.NotificationServices 561 Microsoft.SqlServer.Server 430 SqlContext 430 .SqlProcedure 435 SqlDataRecord 430 SqlPipe 430 SqlTriggerContext 430 model 79 MONTH 147 msdb 297, 315
N Nachrichten 399-402, 405 Nachrichtentyp 401, 405 Namensauflösung 87 Namensraum 88 NEWID 151 NO_TRUNCATE 251 NOT 77, 78, 99, 101, 106, 107, 119, 157, 159, 160, 162 Notification Services 9, 12, 15, 18, 544, 550, 554, 555 Abonnement 541, 559, 561, 565, 566, 568, 570 Abonnementklassen 548, 551 Application Definition File 543, 572 Benachrichtigung 539, 541, 550, 569, 571 Benachrichtigungsklassen 548, 549, 572 Ereignis 539, 541, 542, 548, 551, 561, 562, 566-568, 570-572 Ereignisanbieter 542-544, 548, 553, 558, 559, 562
579
Register Ereignisklassen 548 Generator 542, 543, 553, 571 Generatorkonfiguration 548 Instance Configuration File 543, 544, 545, 572 Verteiler 542, 543, 554 Verteilerkonfiguration 548 Notification Services Anwendung Aktivieren 554 Kompilieren 554 Registrieren 554 Starten 554 Notification Services Management Objects 540, 543 NotificationClasses 549 nsconfig 554 NTILE 186, 189, 190 Null Besonderheiten von 159 Hinweise für die Verwendung 166
O Oberflächenkonfiguration 25 OBJECT_NAME 132 Objektberechtigungen 266 Objekt-Explorer 30 Filter 31 ON DELETE 106, 107, 125, 126 ON UPDATE 106, 124 OPENROWSET 318 OPENXML 339, 367, 369, 370, 371, 373, 374, 379, 387, 388, 395 flags 368 WITH-Klausel 368 xpath 368 Operatoren Arithmetische 76 Bitweise 76 Logische 77 Rangfolge 78 Vergleichs- 77 Zeichenketten- 76 Zuweisungsoperator 78
580
OR 78 OUTER JOIN 175 OUTPUT 202, 203, 291, 313, 314, 326 OVER 186, 187, 188, 190, 191
P PAGLOCK 230 PARTITION BY 188, 190 PATINDEX 138, 139 Phantom Reads 224, 332 PIVOT 198, 200-202 POWER 134 Primärschlüssel 95- 99, 103, 105, 107, 168 PRIMARY KEY 96, 97, 98, 107, 168 PRINT 122, 152 Providers 553 Prozedurcache 489, 502, 504, 505, 509, 531 ProzessID 215
R RAND 135 RANK 186, 188, 189 Read ahead 489 READ COMMITTED 225, 226, 228, 229, 230, 231 READ UNCOMMITTED 224, 225, 229, 231 Registrierte Server 30 Rekursive Abfragen 196 Anker-Element 198 Relationen 66 Relationenmodell 66 REPEATABLE READ 226, 227, 229, 230, 231 REPLACE 139 REPLICATE 139 Replikation 12, 15 Reporting Services 12, 13, 14, 16, 17 RESTORE LOG 254 RETAINDAYS 248 REVERSE 140 REVOKE 269 RIGHT 140, 176, 182
Register Rolle 21 Rollforward 234 ROLLUP 184, 185 ROUND 135 ROW_NUMBER 186-188 ROWLOCK 230 RTRIM 140
S sa 263 Schemas 20, 84-90, 205 Seiteneffekte 442 SELECT 111 Aliasnamen für Spalten 112 Aliasnamen für Tabellen 113 DISTINCT 122 EXCEPT 171 INTERSECT 171 Projektion 115 Selektion 115 Sortieren 113 UNION 170 UNION ALL 170 WHERE 116 SELECT INTO 158 SENT BY 405 Serialisierung 209 SERIALIZABLE 228, 229, 231 Server-eigene Authentifizierung 28 SERVERPROPERTY 127 Serverrollen 261 sysadmin 262 Service Broker 11, 12, 271, 397, 400, 401, 402, 417 Aktivierung 417 Architektur 399 Dialog 399 END CONVERSATION 410 Komponenten 401 Konversation 399 Nachrichtenaustausch 406 SEND 410 SENT BY ANY 405
SENT BY INITIATOR 406 SENT BY TARGET 405 Transaktionssicherheit 400 Service Pack 2 254 serviceorientierte Architekturen 397 Dienste 398 Konversationen 398 Nachrichten 398 Verträge 398 Warteschlangen 398 SESSION_USER 128 SET 288 SET DATEFIRST 129 SET SHOWPLAN_XML 508 SET STATISTICS IO 503, 521 SET STATISTICS TIME 504 Sicherungsmedium 239, 244 Sicht 19 Sichten 154 Aktualisierung von Daten 156 WITH CHECK OPTION 157 SID 261 Skripterstellung 34 SNAPSHOT 227-229, 235, 236 SOA siehe serviceorientierte Architekturen 398 SOAP 469-473, 479, 480, 486 SOAP-Endpunkt 469 SOME 78 SOUNDEX 140, 141 sp_change_users_login 261 sp_configure 280, 283, 286, 292, 315-319, 333, 426 sp_delete_http_namespace_reservation 475 sp_executesql 292, 293 sp_help 281, 286 sp_helpdb 123 sp_procoption 314 sp_reserve_http_namespace 475, 477 sp_send_dbmail 297, 415, 416, 418 sp_spaceused 281, 282 sp_trace_create 514, 515 sp_trace_setevent 514, 515
581
Register sp_trace_setstatus 514-516 sp_who 282 SPACE 141 Speicherverwaltung 488 Block 488 Extent siehe Block 488 Seite 488 Sperren 207, 211, 213-219, 222, 228-230, 235, 236 Aktualisierungs 217 Beabsichtigte 217 Deadlocks 219 Exklusive 217 Gemeinsame 217 Katalog 218 Kompatibilität 219 Sperrhinweise 219 Sperrhinweise 219, 224, 229-232, 235 SQL Server Agent 14, 15, 17, 242, 249 SQL Server Management Studio 14, 16, 27 Abfrageeditor 38 Abfrageoptionen 46 Ausführungspläne 44 Berichte 37 Datenbankprojekte 47 Projektmappen-Explorer 38 Vorlagen 43 SQL Server Profiler 52, 276, 511, 513, 518 Ereignisse 56 serverseitige Ablaufverfolgung 513 Spaltenfilter 56 Vorlagen 53 SQL Server-Editionen 15 SQL Server-eigene Authentifizierung 257 SQL Server-Protokolle 272 sql:column 381 sql:variable 382 Sqlcmd 61, 69 SqlCommand 438, 439, 461 SqlConnection 438, 439, 461 SqlContext.Pipe.Send 436 SqlDataReader 430, 438, 439 SQLProvider 553
582
SQRT 136 SQUARE 136 Standardschema 85, 87, 89, 90 Stapel 69, 70, 123 Stapeltrennzeichen 69, 70 Statistiken 489, 507, 526, 534, 537 STR 144 STUFF 141 Subscriber 563 SubscriberEnumeration 563 SubscriberID 550, 571 SubscriberLocale 550, 552 SubscriptionClasses 551 SUBSTRING 141 Synonyme 89, 144 sys Schema 279 sys.assemblies 428 sys.dm_db_index_physical_stats 493, 494, 499, 501, 529 sys.dm_db_missing_index_details 531 sys.dm_exec_cached_plans 531 sys.dm_exec_query_plan 510, 531 sys.dm_exec_query_stats 510 sys.dm_exec_requests 285 sys.dm_exec_sessions 285 sys.dm_exec_sql_text 510 sys.dm_io_virtual_file_stats 284, 285 sys.dm_os_performance_counters 223, 284, 489, 490, 517 sys.dm_tran_locks 214, 216, 218 sys.events 420 sys.master_files 285 sys.soap_endpoints 472 System.Data.SqlTypes 431 System.Net.CredentialCache.Default Credentials 485 SYSTEM_USER 128 Systemdatenbanken 11, 16, 17, 21 master 16 model 16 msdb 17, 252 sichern 251 tempdb 17
Register wiederherstellen 255 Systemfunktionen 286 Systemkataloge 11 Systemobjekte 82, 85 Systemprozeduren 279, 280, 283, 286 Systemsichten 277, 279, 283, 286
T Tabellendesigner 91 TABLESAMPLE 121 TABLOCK 230 TABLOCKX 230, 231 tempdb 109, 227, 236, 298, 300, 301, 306, 315 Temporäre Tabellen 109 TOP 120, 121 Transaktionen 10, 207, 208 Isolationsstufe 219 Transaktionsprotokoll 232-234, 238, 240, 248, 251 Abschneiden 251 Transaktionsprotokollsicherung 248 Trigger 20, 325 aktivieren 330 DDL 333 deaktivieren 330 DML 326 Hinweise zur Verwendung 332 INSTEAD OF 331 nested triggers 333 Rekursion 332 sp_settriggerorder 330 TRUNCATE TABLE 126 TRUSTWORTHY 441 Typ-Konvertierung 75
UPDATE 123 WHERE 123 UPPER 142
V Variablen 287, 288, 289, 290, 291, 292, 295, 297, 298, 300, 302, 314, 318, 323, 324, 338 TABLE 291 Wertzuweisung 289 Verteiler 553 Verträge 401 Visual Studio 472 Visual Studio 2005 423 Datenbankprojekte 432 VORDEFINIERT\Administratoren 262 Vordefinierte Benutzer 264 dbo 264 guest 264
W WAITFOR 152, 315 WAITFOR RECEIVE 415 Warteschlangen 397, 399, 400-402, 417 Web Services 469, 470, 471, 473, 478, 486 WEBMETHOD 472 WHILE 294 Wiederherstellungsmodell 240, 241, 242, 254 Windows-Systemmonitor 276, 517 Windows-Anmeldung 257 Windows-Authentifizierung 27 Winkelfunktionen 132, 133 WITH 84, 157, 158, 184, 185, 193, 194 Write Ahead-Protokoll 232 WSDL 469, 472, 473, 478-483
U UDDI 469 UNIQUE 98, 99, 107, 162, 166, 498, 500, 525-527 UNPIVOT 198, 201, 202
583
Register
X XML-Datentyp 372 exist 382 Getypte XML-Daten 376 modify 388, 410 nodes 384 query 379 Ungetypte XML-Daten 375 value 383 XML-Indizes 391 Löschen 395 Primäre 392
584
Sekundäre 394 xp_cmdshell 282, 283, 318, 319 XQuery 368, 372, 379-384, 388-391, 394, 395 XSD Schema 401
Y YEAR 147
Z Zeilenidentität 95, 98