Jürgen Bayer SQL/92, SQL:1999 und TSQL (SQL Server 2000) Erzeugen von Datenbanken, Abfragen und Verändern von Daten Inhaltsverzeichnis 1 Einführung 1 1.1 Der Inhalt dieses Artikels 1 1.2 Was ist SQL? 1 2 Grundlegendes 6 2.1 Datentypen 6 2.2 Literale 3 11 Die Data Definition Language 13 3.1 Anlegen einer Datenbank 13 3.2 Löschen eines Schemas bzw. einer Datenbank 17 3.3 Modifizieren eines Schemas bzw. einer Datenbank 18 3.4 Domänen 19 3.5 Anlegen von Tabellen 20 3.6 Tabellen modifizieren 27 3.6.1 Hinzufügen von Spalten 27 3.6.2 Löschen von Spalten 28 3.6.3 Ändern von Spalten 28 3.6.4 Einschränkungen anfügen 29 3.6.5 Einschränkungen löschen 29 3.6.6 Einschränkungen im SQL Server deaktivieren und aktivieren 30 3.7 Tabellen löschen 30 3.8 Indizes erstellen und löschen 31 3.8.1 Indizes erstellen 31 3.8.2 Löschen eines Index 32 4 Die Data Manipulation Language 33 4.1 Anfügen von Datensätzen 33 4.2 Löschen von Datensätzen 36 4.3 Ändern von Datensätzen 38 4.4 Abfragen von Datensätzen 40 4.4.1 Einführung 40 4.4.2 Die Restriktion 41 4.4.3 Die Projektion 41 4.4.4 Die AS-Klausel 42 4.4.5 Einschränken der Ausgabe 43 4.4.6 Arithmetische Ausdrücke 48 4.4.7 Sortieren 49 4.4.8 Gruppieren 49 4.4.9 Funktionen 56 4.4.10 ALL, DISTINCT und TOP 66 4.4.11 Unterabfragen (Subquerys) 67 4.4.12 Die Operatoren ANY, ALL und EXISTS 68 4.4.13 Joins (Verbundausdrücke) 70 5 Sichten 86 6 Die Data Control Language 89 6.1 Einführung 89 6.2 Erteilen und Verweigern von Zugriffsrechten 90 7 7.1 8 Anhang Literaturverzeichnis Index 91 91 92 Dieser Artikel behandelt die für die Abfrage und die Manipulation von Datenbanken wichtigen Aspekte von SQL/92, dem zurzeit in den meisten Datenbanksystemen verwendeten SQLStandard, einige wichtige Neuerungen von SQL:1999 und das SQL des Microsoft SQL Servers 2000 (TSQL). Kapitel 1 beschreibt zunächst, was SQL überhaupt ist und gibt einen Überblick über die im Artikel verwendete Beispiel-Datenbank und über die verwendeten Konventionen. Die weiteren Kapitel beschreiben die einzelnen Teile von SQL: Kapitel 2 erläutert einige grundlegende Dinge wie die in SQL/92, SQL:1999 und TSQL verwendeten Datentypen und Literale. Kapitel 3 behandelt die SQL-Anweisungen zum Erstellen und Modifizieren von Datenbanken und Tabellen. Kapitel 4 beschreibt die SQL-Anweisungen zum Abfragen, Anfügen, Aktualisieren und Löschen von Daten. Das Kapitel 5 zeigt schließlich, wie Sie Views (Sichten) erzeugen und verwenden. SQL (Structured Query Language) wird von den meisten Datenbanksystemen unterstützt und ist eine vom ANSI1-Komitee genormte Sprache zur Abfrage und Modifikation von Datenbanken. Der Grundstein von SQL wurde 1974 bei IBM mit der Entwicklung der DatenbankAbfragesprache SEQUEL (Structured English Query Language) gelegt. Der erste genormte SQL-Standard wurde 1986 vom ANSI-Kommitee ratifiziert und wird offiziell als SQL/86 bezeichnet. 1987 wurde der ANSI-Standard dann auch als internationaler Standard von der International Organization for Standards (ISO) akzeptiert. 1989 wurde SQL um das Merkmal Integrity Enhancement Feature2 (IEF) erweitert und damit zu SQL/89. SQL/92, der derzeit von den meisten Datenbanksystemen unterstützte Standard, wurde 1992 als stark erweiterte Version von SQL/89 ratifiziert. 1995 wurde SQL/92 um das Call Level Interface (CLI) erweitert und stark geändert und korrigiert. SQL/92 wurde zu Anfang auch SQL/2 genannt, ist aber im Allgemeinen nur als SQL/92 bekannt. Der zurzeit neueste Standard ist SQL:1999. SQL:1999 (auch bekannt als SQL-99) erweitert SQL/92 hauptsächlichlich um die Möglichkeit, benutzerdefinierte Datentypen zu speichern und unterstützt damit objektorientierte Datenbanksysteme. SQL:1999 wird zurzeit von sehr wenigen Datenbanksystemen wie beispielsweise Oracle 8 unterstützt. Im Dokument dbs.unileipzig.de/en/lokal/standards.pdf finden Sie eine Beschreibung der Unterschiede zu SQL/92. Der SQL-Standard, der vom ANSI-Komitee verfasst wurde, steht leider im Internet nicht frei zur Verfügung. Sie können die Standard-Dokumente käuflich erwerben (webstore.ansi.org/ansidocstore/find.asp), die Preise sind allerdings so hoch, dass Sie wohl besser ein gutes Buch kaufen sollten. Sehr zu empfehlen ist der Klassiker »SQL – 1 ANSI = American National Standards Institute 2 Was immer das auch ist ... Einführung 1 Der Standard« von Chris J. Date und Hugh Darwen. Bei Ocelot finden Sie die Beschreibung der wichtigen SQL-Features: www.ocelot.ca/dbms.htm. ! " Da ANSI-SQL nur eine Empfehlung darstellt, verwenden viele Datenbanksysteme eine von SQL/92 mehr oder weniger abweichende SQL-Syntax. Die grundsätzlichen Elemente sind zwar fast immer dieselben, Unterschiede sind jedoch zumindest in der Verwendung von Wildcards und bei der Angabe von Datumswerten zu beobachten. So verwendet SQL/92 z. B. die Wildcards »%« für beliebige Zeichen und »_« für ein einzelnes beliebiges Zeichen und AccessSQL statt dessen die Wildcards »*« und »?«. Client/Server-Datenbanksysteme, wie z. B. der Microsoft SQL Server oder Oracle erweitern SQL/92 meist noch um spezielle Befehle, die eine Programmierung mit SQL über einfache Programmstrukturen (wie z. B. Abfragen und Schleifen) ermöglichen. Dieses erweiterte SQL wird oft in Stored Procedures und Triggern verwendet. Dieser Artikel beschreibt die grundsätzliche Anwendung von SQL/92 und geht speziell auf die SQL-Syntax des Microsoft SQL Server ein. Einige Bestandteile des SQL/92-Standards, wie Module, Cursors und Dynamic SQL werden allerdings nicht besprochen. #$ Das englischsprachige Datenbanksystem Ocelot behauptet von sich, ein Datenbanksystem zu sein, das den SQL/92-Standard vollständig und SQL:1999 in Teilen unterstützt. Das Schöne daran ist, dass Ocelot nichts kostet und aus dem Internet heruntergeladen werden kann. Sie finden Ocelot auf der Website www.ocelot.ca/dbms.htm. Ein Nachteil von Ocelot ist allerdings, dass die Schnittstelle zur Datenbank aus klassischen (ODBC-)DLLs besteht und Sie entweder die ODBC-Schnittstelle zum Zugriff auf Ocelot-Datenbanken oder die DLLFunktionen direkt verwenden müssen, um mit dieser Datenbank zu arbeiten. % & ' SQL wird üblicherweise in drei Bereiche unterteilt: • Die Data Definition Language (DDL) ermöglicht das Anlegen und Modifizieren von Datenbankobjekten. • Die Data Manipulation Language (DML) ermöglicht das Abfragen, Hinzufügen, Ändern und Löschen von Daten. • Mit der Data Control Language (DCL) können Sie administrative Aufgaben, wie z. B. das Anlegen von Benutzerkonten und die Vergabe von Benutzerrechten vornehmen. ( ) * + Eine SQL-Anweisung beginnt immer mit einem Schlüsselwort, das dem Typ der Abfrage entspricht (z. B. SELECT, INSERT oder UPDATE) und wird mit einem Semikolon abgeschlossen (das jedoch in vielen Datenbanksystemen auch weggelassen werden kann). Zusätzlich können noch sogenannte „Klauseln“ hinzugefügt werden, die z. B. das Ergebnis einschränken, gruppieren oder sortieren. Einführung 2 , , - Den Beispielen liegen die folgenden Basistabellen zugrunde, die in der beim SQL Server 2000 beiliegenden Beispieldatenbank Northwind gespeichert sind (Abbildung 2). Abbildung 1: Die Beispieltabellen der Northwind-Datenbank mit den Beziehungen zwischen den Tabellen Diese Abbildung wurde mit Microsoft Access erzeugt und spiegelt die Beziehungen zwischen den Tabellen sehr gut wieder. Eine 1 in einer Beziehung steht dafür, dass diese Tabelle die Mastertabelle ist. Eine liegende 8 steht dafür, dass es sich um eine 1:N-Beziehung handelt. ./ 0 / Dieser Artikel verwendet die folgenden (größtenteils auch in anderen Umgebungen verwendeten) typographischen Konventionen: Beispiele werden in diesem Artikel schattiert dargestellt: SELECT CustomerID, CompanyName FROM Customers ORDER BY CompanyName DESC; Syntaxbeschreibungen werden ebenfalls schattiert: SELECT Datenfeldliste FROM Tabelle(n) [WHERE Bedingung(en)] [GROUP BY Datenfeld(er) [HAVING Bedingung(en)]] [ORDER BY Datenfeld(er) [{ASC | DESC}]]; Kursive Wörter in Syntaxbeschreibungen sind Platzhalter für von Ihnen anzugebende Informationen. Im obigen Beispiel müssen Sie für das Argument Datenfeld(er) z. B. alle Felder eintragen, die Sie im Ergebnis der Abfrage ausgeben wollen. Die in eckigen Klammern stehenden Elemente sind optional. Diese Elemente können, müssen Sie jedoch nicht angeben. Die eckigen Klammern werden allerdings nicht mit angegeben. Einführung 3 Wenn irgendwo in Syntaxbeschreibungen der Begriff »Liste« vorkommt, ist immer eine kommabegrenzte Liste einzelner Werte gemeint. Die Datenfeldliste aus dem Beispiel oben könnte z. B. folgendermaßen aussehen: CustomerID, CompanyName, ContactName Die oben beschriebene SELECT-Anweisung können Sie demnach also z. B. folgendermaßen aufrufen: SELECT CustomerID, CompanyName FROM Customers; SELECT CustomerID, CompanyName FROM Customers WHERE City = 'Berlin'; SELECT CustomerID, CompanyName FROM Customers WHERE City = 'Berlin' ORDER BY CompanyName; SELECT CustomerID, CompanyName FROM Customers ORDER BY CompanyName; Geschweifte Klammern mit vertikalen Trennlinien ({ASC | DESC}) bedeuten, dass Sie die Wahl zwischen zwei oder mehr Elementen haben. Ist eines der Elemente unterstrichen dargestellt, ist dieses Element die Voreinstellung, die zum Tragen kommt, wenn Sie keines der Elemente angeben. Sie müssen eines der Elemente wählen, es sei denn, alle Elemente stehen außerdem in eckigen Klammern. Bei der Abfrage von Daten können Sie z. B. aufsteigend oder absteigend sortieren: [ORDER BY Datenfeld(er) [{ASC | DESC}]] Beispiele: SELECT CustomerID, CompanyName FROM Customers ORDER BY CompanyName; SELECT CustomerID, CompanyName FROM Customers ORDER BY CompanyName ASC; SELECT CustomerID, CompanyName FROM Customers ORDER BY CompanyName DESC; Da ASC im verwendeten Beispiel unterstrichen dargestellt ist, können Sie davon ausgehen, dass SQL die aufsteigende Sortierung verwendet, wenn Sie weder ASC noch DESC angeben. Weitere Konventionen stellt Tabelle 1.1 dar. Konvention Beschreibung SELECT, FROM Im normalen Text werden sprachspezifische Schlüsselwörter in der Schriftart Courier dargestellt. sql.doc Dateinamen werden kursiv dargestellt. DATEI / DRUCKEN Menübefehle werden in Kapitälchen bezeichnet. Ein Schrägstrich trennt die zu aktivierenden Menüs voneinander und vom Befehl. Tabelle 1.1: Weitere Konventionen in diesem Artikel Einführung 4 Hinweise, die Sie beachten sollten, werden wie in diesem Beispiel schattiert dargestellt. Besondere „Warnungs“-Hinweise weisen auf wichtige Informationen hin, die Sie auch jeden Fall beachten sollten. Häufig werden damit Fallen gekennzeichnet, in die Sie tappen können, wenn Sie die entsprechende Warnung nicht kennen. Tipps werden dargestellt wie in diesem Beispiel. Einführung 5 ( !#$ Spätestens bei der Erzeugung von Tabellen werden Sie in SQL mit Datentypen konfrontiert. SQL/92 unterstützt die in Tabelle 2.1 aufgeführten Datentypen. Datentyp Beschreibung CHARACTER(n) oder CHAR(n) Zeichenkette fester Länge mit genau n Zeichen, wobei n größer als 0 sein muss. In CHAR-Spalten, die keine NULL-Werte erlauben, werden Zeichenketten nach rechts mit Leerzeichen aufgefüllt, die u. U. bei der Bearbeitung abgeschnitten werden müssen. Werden Zeichenketten eingegeben, die größer sind als der Maximalwert, so schneidet das Datenbanksystem die Zeichen nach rechts einfach ab. Der SQL Server erlaubt maximal 8000 Zeichen in einem CHAR-Feld. CHARACTER VARYING(n) oder VARCHAR(n) Zeichenkette variabler Länge mit bis zu n Zeichen, wobei n größer als 0 sein muss. Bei den meisten Datenbanksystemen ist VARCHAR(n) dem Datentyp CHAR(n) vorzuziehen, da variable Datentypen immer nur die Daten physikalisch speichern, die auch im Datenfeld abgelegt sind. Somit können Sie ein Datenfeld ruhig größer gestalten als notwendig, um für später genügend Spielraum zu besitzen. Der SQL Server erlaubt maximal 8000 Zeichen in einem VARCHAR-Feld. BIT(n) Binäre Daten fester Länge. Der SQL Server kennt den Datentyp BIT nicht als Datentyp für binäre Daten, sondern als Datentyp für boolsche Daten (0 und 1). Statt BIT(n) verwendet der SQL Server BINARY(n). BIT VARYING(n) Binäre Daten variabler Länge. Der SQL Server kennt den Datentyp BIT VARYING nicht. Statt BIT VARYING(n) verwendet der SQL Server varbinary(n). DECIMAL(p, q) oder DEC(n) speichert Dezimalzahlen mit festen Dezimalstellen und Vorzeichen mit p Ziffern insgesamt und mindestens q Ziffern rechts vom Komma. Die tatsächliche Anzahl der Stellen rechts vom Komma hängt von der Implementierung dieses Datentyps im Datenbanksystem ab. DECIMAL zählt wie NUMERIC zu den Festkommazahlen. Beim SQL Server können Sie für p maximal 38 angeben. q muss größer 0 und kleiner/gleich p sein. Der Wertebereich von DECIMAL liegt beim SQL Server zwischen -1038 +1 und 1038 - 1. NUMERIC(p, q) entspricht funktional dem Typ DECIMAL. SQL/92 sieht vor, dass die Genauigkeit bei NUMERIC(p, q) genau p Stellen beträgt, bei DECIMAL(p, q) allerdings mindestens p Stellen. Beim SQL Server bestehen keine Unterschiede zwischen NUMERIC und DECIMAL. INTEGER oder INT Vorzeichenbehaftete ganze Zahl. Die Größe hängt von der Implementierung dieses Datentyps im Datenbanksystem ab. Bei 32 BitBetriebssystemen beträgt die Größe in der Regel 32 Bit. Der SQL Server verwendet 32 Bit und speichert damit Zahlen zwischen -231 (-2.147.483.648) und 231-1 (2.147.483.647). Grundlegendes 6 SMALLINT Vorzeichenbehaftete ganze Zahl. Die Größe hängt von der Implementierung dieses Datentyps im Datenbanksystem ab. Bei 32 BitBetriebssystemen beträgt die Größe in der Regel 16 Bit. Der SQL Server verwendet 16 Bit und speichert damit Zahlen zwischen -32768 und 32767. FLOAT(p) FLOAT ist eine Gleitkommazahl mit insgesamt p Ziffern. Die Anzahl der Stellen rechts vom Komma hängt von der Implementierung dieses Datentyps im Datenbanksystem und von der Anzahl Ziffern vor dem Komm ab. Beim SQL Server können Sie für p einen Wert zwischen 1 und 53 angeben. Bei 1-24 werden sieben Dezimalstellen verwaltet, bei 25-53 dagegen 15. Der SQL Server erlaubt in einem FLOAT-Feld (je nach p-Wert) maximal Zahlen im Bereich von –1,79 * 10308 bis 1,79 * 10308. Tabelle 2.1: Die Datentypen von SQL/92 SQL/92 kennt noch einige Abkürzungen für Datentypen, die in Tabelle 2.2 dargestellt werden. Abkürzung Beschreibung CHARACTER oder CHAR CHAR ist eine Abkürzung für CHAR(1). NUMERIC(p) steht für NUMERIC(p, 0). DECIMAL(p) steht für DECIMAL(p, 0) . FLOAT FLOAT ist eine Abkürzung für FLOAT(p), wobei p durch das Datenbanksystem definiert wird. Der SQL Server verwendet für p den Wert 24. Damit sind im SQL Server Zahlen im Bereich von -3,40 * 1038 bis 3,40 * 1038 möglich. REAL REAL ist eine Abkürzung für FLOAT(p), wobei p durch das Datenbanksystem definiert wird. Der SQL Server verwendet den Wert 24 für p. Damit sind Zahlen zwischen -3,40 * 1038 und 3,40 * 1038 möglich. DOUBLE PRECISION oder DOUBLE DOUBLE ist eine Abkürzung für FLOAT(p), wobei p durch das Datenbanksystem definiert wird. Beim SQL Server steht DOUBLE für FLOAT. Tabelle 2.2: Die Abkürzungen für Datentypen bei SQL/92 Grundlegendes 7 1 $$$ SQL:1999 fügt den SQL/92-Datentypen vier weitere hinzu (Tabelle 2.3). Datentyp Bedeutung LARGE OBJECT oder LOB Dieser Datentyp kann entweder Zeichenketten (CHAR), CLOBs (Character Large Objects) oder BLOBs (Binary Large Objects) speichern. CLOBs erlauben sehr große Zeichenkettendaten, BLOBs sehr große binäre Daten. LOBs weisen gegenüber normalen Feldern einige Einschränkungen auf. So können Sie diese Felder z. B. nicht als Primärschlüssel, in UNIQUEEinschränkungen, in GROUP BY- oder ORDER BY-Klauseln verwenden. BOOLEAN Dieser boolesche Datentyp kann in SQL-Anweisungen direkt mit den Konstanten true, false und unknown verglichen werden. Datentyp ARRAY[n] Dieser Datentyp erlaubt die Deklaration eines Arrays in einer Spalte: CREATE TABLE Demo ( Weekdays VARCHAR(10) ARRAY[7]) ROW(Datentypliste) Über Row können Sie eine Spalte unterstrukturieren. So können Sie z. B. in einer Kunden-Tabelle eine Spalte Name erzeugen, die aus den Unterspalten FirstName und LastName besteht: CREATE TABLE Customer ( CustomerId INTEGER, Name ROW ( FirstName VARCHAR(255), LastName VARCHAR(255)), Street VARCHAR(255), City VARCHAR(255)); Tabelle 2.3: Die zusätzlichen Datentypen von SQL:1999 " 2 !- SQL/92 unterstützt Datums- und Uhrzeitangaben leider in einer sehr komplexen Form, weswegen ich diese Datentypen separat und nur grundlegend beschreibe. Die meisten Datenbanksysteme reduzieren die Komplexität auf einen oder zwei einfacher anzuwendende Datentypen. Datentyp Beschreibung DATE DATE speichert in SQL/92 eine Folge von Zahlen, die das Jahr, den Monat und den Tag eines Datums speichern. Das Jahr wird vierstellig im Bereich von 0000 bis 9999 angegeben, der Monat zweistellig im Bereich 01 bis 12 und der Tag ebenfalls zweistellig im Bereich 01 bis 31. TIME[(n)] TIME speichert in SQL/92 eine Folge von Zahlen, die eine Zeit angibt. Die [WITH TIMEZONE] Stunde wird zweistellig im Bereich 00 bis 23 angegeben, die Minute ebenfalls zweistellig im Bereich 00 bis 59. Die Sekunde kann als Dezimalzahl angegeben werden. Die optionale Angabe n bezieht sich auf die maximale Anzahl Ziffern rechts vom Komma für die Sekunde. Geben Sie die Option WITH TIMEZONE an, wird die Zeit nicht als Ortszeit gespeichert, sondern als sogenannte UTCZeit (eine allgemeine Vergleichszeit, die an allen Orten der Welt den gleichen Wert besitzt). Damit sind Zeitvergleiche auch an verschiedenen Orten der Welt möglich. Die Umwandlung nach UTC übernimmt das Datenbanksystem beim Anfügen und Ändern von Datensätzen in der Regel automatisch. TIMESTAMP[(n)] Ein TIMESTAMP-Wert (Zeitstempel) speichert ein Datum mit Zeitangabe. Tabelle 2.4: Die Abkürzungen für Datentypen bei SQL/92 Grundlegendes 8 Daneben können SQL/92-Datenbanken noch Datums- und Zeitintervalle speichern. Ein Intervall ist eine Angabe wie z. B. ein Monat oder 90 Tage oder 1 Jahr, 2 Monate. Ich spreche Intervalle hier nur grundlegend an, weil ich denke, dass diese von den meisten Datenbanksystemen nicht unterstützt werden. Sie deklarieren ein Intervall folgendermaßen: INTERVAL Anfang [TO Ende] Anfang bezeichnet, welchen Datumsbereich Sie als erste Angabe im Intervall verwenden wollen (YEAR, MONTH, DAY, HOUR oder SECOND). Ende bezeichnet die zweite optionale Intervallangabe. Wenn Sie z. B. Monate speichern wollen, geben Sie INTERVALL MONTH an. Wenn Sie Jahre und Monate speichern wollen, geben Sie INTERVALL YEAR TO MONTH an. Da Datums- und Zeitdatentypen in den meisten Datenbanksystemen sowieso anders implementiert sind, verzichte ich auf eine tiefergehende Beschreibung. 0 Der SQL Server definiert zusätzlich zu den SQL/92-Datentypen die in Tabelle 2.5 dargestellten. Datentyp Beschreibung nchar[(n)] Der (SQL/92-)Datentyp char speichert ASCII-Zeichen. In einem solchen Feld können Sie beim SQL Server maximal 8000 Zeichen speichern. nchar wird wie char verwendet, speichert jedoch Unicode-Zeichen. Das Unicode zwei Byte pro Zeichen verwaltet, können Sie in einem nchar-Feld maximal 4000 Zeichen verwalten. Auf Betriebssystemen, die Texte standardmäßig als Unicode speichern (Windows NT, 2000, XP) führt die Verwendung von nchar gegenüber char zu einer Performance-Erhöhung, da dann keine Konvertierung erfolgt. nvarchar[(n)] nvarchar ermöglicht wir varchar die dynamische Speicherung von Zeichen, speichert jedoch (wie nchar) Unicode-Zeichen. datetime Dieser Datentyp speichert Datumswerte zwischen dem 1.1.1753 und dem 31.12.9999. Wenn bei der Eingabe die Zeit weggelassen wird, wird der Wert 00:00:00 verwendet, wird das Datum weggelassen wird der 01.01.1900 eingetragen. smalldatetime Speichert Datumswerte zwischen dem 01.01.1900 und dem 06.06.2079 mit einer Genauigkeit von einer Minute. Wenn Sie erreichen wollen, dass Ihre Software noch nach dem 06.06.2079 korrekt läuft, verwenden Sie diesen Datentyp nicht (denken Sie an das Jahr 2000-Problem). bigint speichert ganze Zahlen zwischen -263 (-9.223.372.036.854.775.808) und 263-1 (9.223.372.036.854.775.807). tinyint speichert ganze Zahlen zwischen 0 und 255. money speichert Dezimalzahlen mit vier festen Dezimalstellen zwischen 922.337.203.685.477,5808 und +922.337.203.685.477,5807. Wird für Währungen verwendet, da diese mit vier Dezimalstellen gerechnet werden. Berechnungen mit festen Dezimalstellen sind schneller als Berechnungen mit dynamischen Dezimalstellen. smallmoney speichert Währungsbeträge zwischen -214.748,3648 und +214.748,3647 Grundlegendes 9 text speichert dynamische ASCII-Zeichenketten bis zu 2.147.483.647 Zeichen. ntext ntext speichert wie text dynamische Zeichenketten, allerdings als Unicode-Zeichen. Deswegen können auch nur 1.073.741.823 Zeichen gespeichert werden. image image speichert Bitdaten (normalerweise Bilddaten) bis 2.147.483.647 Byte Größe. image wird häufig aber auch für die Speicherung von binären Objekten, wie z. B. OLE-Objekten verwendet. binary Dieser Datentyp speichert binäre Daten mit einer maximalen Länge von 8000 Byte. varbinary varbinary speichert wie binary Bitmasken. Der Server benötigt jedoch nur soviel Speicherplatz, wie die tatsächlich eingegebenen Daten und nicht wie bei binary immer die eingestellte Größe. bit Bit speichert nur 0 oder 1 und ermöglicht damit boolesche Felder. Sie können normale Integerwerte in einem solchen Feld speichern, Werte ungleich 0 werden jedoch als 1 gespeichert. Bit benötigt ein Byte Speicherplatz. timestamp Ein timestamp-Feld wird bei jedem Hinzufügen oder Ändern eines Datensatzes automatisch mit einem über die gesamte Tabelle eindeutigen Wert (der aus der Systemzeit generiert wird) belegt. Solche Felder benötigen Sie immer dann (und eigentlich auch nur dann), wenn Tabellen in mehreren Datenbanken repliziert werden sollen. Der SQL Server erkennt beim Synchronisieren der einzelnen Datenbanken am timestamp-Feld, ob ein Datensatz verändert wurde. Die Speicherung entspricht der eines varbinary(8)-Feldes. Sie können nur ein timestamp-Feld pro Tabelle generieren. uniqueidentifier Dieser Datentyp dient der Speicherung einer GUID (Global Unique Identifier). Eine GUID ist ein 16-Bit Hexadezimalwert, der über die gesamte Welt eindeutig ist. In TSQL kann eine GUID mit der NEWID-Funktion erzeugt werden. GUID’s werden häufig zur Benennung und Identifikation von Objekten verwendet. sysname sysname ist ein benutzerdefinierter Datentyp, der als nvarchar(128) definiert ist. Nullwerte werden nicht erlaubt. Dieser Datentyp wird vom SQL Server in Systemtabellen verwendet. cursor verweist auf einen Cursor. Dieser Datentyp wird eigentlich nur innerhalb von Stored Procedures eingesetzt, um einen Cursor zu verwalten, über den eine Tabelle oder Abfrage sequenziell durchlaufen werden soll. table Dieser Datentyp wird hauptsächlich innerhalb von Stored Procedures verwendet, um das Ergebnis einer Abfrage temporär zwischenzuspeichern (ähnlich einem Recordset bei ADO oder einem ResultSet bei JDBC). sql_variant Dieser Datentyp ermöglicht das Speichern verschiedener Datentypen in einem Feld, ähnlich dem Datentyp Variant in Visual Basic. Tabelle 2.5: Die zusätzlichen Datentypen des SQL Servers Grundlegendes 10 #$ Wenn Sie Werte für Spalten in SQL-Anweisungen eingeben, müssen Sie für diese Werte die korrekten Literale einsetzen. Deswegen folgt hier eine kurze Beschreibung der am meisten verwendeten Literale. Datentyp Schreibweise Zeichenketten Zeichenketten werden üblicherweise in einfachen Anführungszeichen eingeschlossen: ' Dies ist eine Zeichenkette' . Kommt das einfache Anführungszeichen innerhalb der eigentlichen Zeichenkette vor, wird dieses üblicherweise verdoppelt: ' That' ' s how it is' . Besonders dann, wenn Sie die Eingabe einer Zeichenkette, die Sie in SQL-Anweisungen verwenden, dem Anwender Ihrer Applikation überlassen, müssen Sie darauf achten, dass eingegebene einfache Anführungszeichen verdoppelt werden. Bitketten Bitketten werden eigentlich selten in SQL-Anweisungen eingesetzt. Für den Fall, dass Sie dies einmal benötigen: Bitketten werden als Duale Zahl in einfachen Anführungszeichen geschrieben und ein B vorangestellt: B' 11001001' . Wenn Sie die Zahl hexadezimal angeben wollen, stellen Sie ein X voran: X' C19A' . Genau numerische Werte Genau numerische Werte werden als Dezimalzahl mit oder ohne Vorzeichen geschrieben. Als Dezimaltrennzeichen gilt der Punkt: 1.234. Annähernd genau numerische Werte Annähernd genau numerische Werte werden in wissenschaftlicher Notation geschrieben: xEy bedeutet x * 10y. Datumswerte Datumswerte werden in der Form DATE ' yyyy-mm-dd'angegeben. Das Schlüsselwort DATE scheint bei SQL/92 zwingend notwendig zu sein. Zeitwerte Zeitwerte werden in der Form TIME ' hh:mm:ss[.Dezimalwert]' angegeben. Das Schlüsselwort TIME scheint zwingend notwendig zu sein. Zeitstempel Zeitstempel werden ähnlich wie Datums- und Zeitwerte angegeben: TIMESTAMP ' yyyy-mm-dd hh:mm:ss[.Dezimalwert]' . Tabelle 2.6: Die Literale von SQL/92 Grundlegendes 11 0 Die Literale des SQL Server basieren auf SQL/92 und sind um Literale für die zusätzlichen Datentypen erweitert (Tabelle 2.7). Datentyp Schreibweise Datumswerte Für Datumsangaben können Sie unter vier verschiedenen Varianten wählen: ' Monat_ausgeschrieben Tag, Jahr' (Beispiel: ' April 15, 1998' ), ' Tag Monat_ausgeschrieben Tag, Jahr' (Beispiel: ' 15 April, 1998' ), ' JahrMonatTag'(Beispiel: ' 19980415' ) oder ' Monat/Tag/Jahr'(Beispiel: ' 04/15/98' ). Die Variante ' Monat/Tag/Jahr'ist sprachspezifisch (hier für die englische Version). In einer deutschen Version des SQL Servers funktioniert diese Variante nicht (dafür gibt es dort eine deutsche Variante). Wenn Sie Datumswerte angeben müssen, verwenden Sie idealerweise die Variante ' JahrMonatTag' , weil Sie diese in allen Sprachen einsetzen können. Zeitwerte werden in der Form ' hh:mm:ss'(Beispiel: ' 12:59:10'oder (für Amerikaner) ' hh:mm {AM | PM}'(Beispiel: ' 12:59 PM' ) angegeben. bit Beim Datentyp bit geben Sie eine 0 für False oder eine 1 für True an. Tabelle 2.7: Die zusätzlichen Literale beim SQL Server !- 0/ Der Datentyp eines Literals leitet sich aus der Form ab, in der es geschrieben ist (Tabelle 2.8). Literal Datentyp Zeichenketten CHAR(n) Bitketten BIT(n) Genau numerische Werte NUMERIC(p, q), wobei p die Anzahl der Ziffern vor dem Annähernd genau numerische Werte in der wisschenschaftlichen Schreibweise (z. B. 0.5E2) FLOAT(p), wobei p die Anzahl der Stellen ist, die dem E im Literal Dezimaltrennzeichen und q die Anzahl der Stellen hinter dem Dezimaltrennzeichen ist. voranstehen. Tabelle 2.8: Datentypen von Literalen Grundlegendes 12 3 / 3 Die meisten mir bekannten Datenbanksysteme verwenden das Konzept der Datenbanken. SQL/92 jedoch kennt keine Datenbanken, sondern nur Kataloge und Schemata. Ein Katalog ist eine Sammlung aus Schemata. Ein Schema ist in SQL/92 eine Sammlung aus Domänen3, Basistabellen, Sichten4, Einschränkungen5, Rechten, Zeichenmengen, Zeichenordnungen und Zeichenübersetzungen, also der Objekte, die üblicherweise auch in einer Datenbank gespeichert werden. Ein Vergleich des Schema-Konzepts mit dem Datenbankkonzept ist recht schwierig. Eine Datenbank enthält dieselben Objekte wie ein Schema, ist jedoch unabhängig von anderen Datenbanken innerhalb des Datenbanksystems. Ein Schema gehört dagegen immer zu einem Katalog, der auch mehrere Schemata enthalten kann. Bei mehreren Schemata innerhalb eines Katalogs können Sie nicht davon ausgehen, dass die einzelnen Schemata voneinander unabhängig sind. Einige Datenbanksysteme, wie z. B. Oracle, basieren ausschließlich auf dem Schemakonzept, andere, wie z. B. Access und dBASE, basieren ausschließlich auf dem Datenbankkonzept. Einige Datenbanksysteme, wie z. B. der SQL Server, unterstützen beide Konzepte (wobei beim SQL Server das Schemakonzept eigentlich nicht verwendet wird und Schemata in Datenbanken angelegt werden). #$ SQL/92 enthält keine Anweisung zur Erzeugung von Katalogen und überläßt die Implementierung dieser Anweisungen den Datenbanksystemen. Ein Schema erzeugen Sie mit der CREATE SCHEMA-Anweisung: CREATE SCHEMA {Schema-Name | AUTHORIZATION Benutzername} [DEFAULT CHARACTER SET Zeichenmenge] [Schema-Element-Liste] Viele Datenbanksysteme (wie Oracle und der SQL Server) assoziieren ein Schema mit einem im Datenbanksystem gespeicherten Benutzer. Der über AUTHORIZATION Benutzername angegebene Benutzer wird zum Besitzer der in diesem Schema angelegten Objekte. SQL/92 erlaubt daneben auch die Erzeugung von Schemata, die keinem Benutzer zugeordnet sind. Die Schema-Element-Liste setzt sich zusammen aus einer oder mehreren Anweisungen zur Erzeugung von Domänen, Basistabellen und den anderen Schema-Elementen und der Gewährung von Rechten auf diesen Objekten. 3 Domänen sind auch als „benutzerdefinierte Datentypen“ oder „Wertebereiche“ bekannt. Mit einer Domäne können Sie z.B. den Datentyp char(25) als „address“ bezeichnen und bei Ihrer Tabellenerzeugung statt char(25) immer den neuen Datentyp address verwenden. 4 Eine Sicht (engl. View) ist eine gespeicherte Abfrage über eine oder mehrere Basistabellen, die eine spezielle Sicht auf die Daten der Tabellen ermöglicht. 5 Einschränkungen (engl. Constraints) definieren Primär- und Fremdschlüssel und überprüfen die eingegebenen Daten in einer Tabelle. Die Data Definition Language 13 Beispiel: Erzeugen eines Schemas mit zwei Tabellen für den Benutzer sa beim SQL Server: CREATE SCHEMA AUTHORIZATION sa CREATE TABLE Customers ( CustomerID int NOT NULL, CompanyName nvarchar(80) NOT NULL, PostCode nvarchar(10) NOT NULL, City nvarchar(80) NOT NULL, Street nvarchar(80) NOT NULL ) CREATE TABLE Products ( ProductID int NOT NULL, ProductName nvarchar(80) NOT NULL, Price money NOT NULL) Der SQL Server erzeugt ein Schema innerhalb einer Datenbank. Das SchemaKonzept dient beim SQL Server lediglich zur Erzeugung von Tabellen und Sichten und Rechten darauf in einem Schritt und wird in der Praxis eigentlich nicht verwendet. Für Datenbanksysteme, die nach dem Datenbankkonzept arbeiten, wird an Stelle von CREATE SCHEMA die CREATE DATABASE-Anweisung verwendet. Die Syntax dieser Anweisung unterscheidet sich erheblich bei den verschiedenen Datenbanksystemen. In der einfachsten Form können Sie die folgende Syntax verwenden: CREATE DATABASE Datenbankname; Der Datenbankname richtet sich nach den Konventionen, die im verwendeten Datenbanksystem gültig ist. Je nach Datenbanksystem müssen (oder können) Sie beim Anlegen einer Datenbank weitere Argumente übergeben. 0 Beim SQL Server können Sie beim Anlegen einer Datenbank u. a. steuern, in welchen Dateien die Datenbank angelegt wird: CREATE DATABASE Datenbankname [ON [Dateispezifikation [,...n]] [,Dateigruppe [,...n]] ] [LOG ON Dateispezifikation [,...n]] [COLLATE Sortierungsname] [FOR LOAD | FOR ATTACH] Das Argument Dateispezifikation bezieht sich auf die folgenden Angaben, die pro Datei notwendig sind: [PRIMARY] ( NAME = Logischer_Dateiname [, FILENAME = 'Dateiname'] [, SIZE = Größe] [, MAXSIZE = {Maximale_Größe | UNLIMITED}] [, FILEGROWTH = Größen_Inkrement]) Wenn Sie mehrere Dateien für eine Datenbank anlegen (was z. B. in Systemen mit mehreren Festplatten Sinn macht, um die Auslastung der Platten zu verteilen), müssen Sie eine Datei über PRIMARY als primäre Datei angeben. Die primäre Datei enthält die Systemtabellen der Datenbank. Wenn Sie PRIMARY nicht angeben, wird die erste Datei zur primären Datei. Die Data Definition Language 14 Das Argument Dateigruppe bezieht sich auf Angaben, die für Dateigruppen notwendig sind: FILEGROUP Dateigruppen_Name Dateispezifikation [,...n] Dateigruppen werden weiter unten erläutert. Übersichtlicher wird die Syntax unten in den Beispielen. Mit ON legen Sie fest, in welcher Datei bzw. in welchen Dateien die Datenbank angelegt wird. Der SQL Server ermöglicht die Anlage von Datenbanken in mehreren Dateien, von denen eine die primäre Datei ist. Die Anlage in mehreren Dateien kann Performancevorteile bringen, wenn die einzelnen Dateien auf verschiedenen Festplatten angelegt werden und besitzt Vorteile bei sehr großen Datenbanken bei einem Plattencrash. In einem solchen Fall können Sie mehrere Dateien auch auf mehreren kleineren Festplatten aus einem Backup restaurieren und benötigen nicht unbedingt eine große Festplatte mit ausreichend viel Platz. Im Notfall, wenn die Datenbank schnell wieder laufen muss, haben Sie vielleicht keine große Festplatte, aber (evtl. aus den Client-Computern) mehrere kleinere zur Verfügung. Mit dem Argument FILEGROUP können Sie mehrere Dateien in einzelnen Dateigruppen anlegen. Mit Dateigruppen können Sie die Performance erhöhen, indem Sie festlegen, in welchen Dateigruppen (die in der Regel auf verschiedenen Festplatten angelegt werden), die Systemtabellen und in welchen die verschiedenen Tabellen und Indizes angelegt werden (beim Erstellen von Tabellen und Indizes können Sie festlegen, in welcher Dateigruppe diese angelegt werden). Tabellen und Indizes, auf die häufig zugegriffen wird, können Sie z. B. in einer Dateigruppe anlegen, deren Dateien auf einer sehr schnellen Festplatte gespeichert sind. Dateigruppen können zudem Sinn machen, wenn nur Teile der Datenbank über ein Backup gesichert werden sollen (ein Backup spezieller Dateigruppen). Der SQL Server verwaltet die in der CREATE DATABASE-Anweisung oben angegebenen Dateien in der primären, namenlosen Dateigruppe. In dieser Dateigruppe werden die Systemtabellen der Datenbank angelegt. Per Voreinstellung ist die primäre Dateigruppe auch die Default-Dateigruppe, in der neue Tabellen und Indizes angelegt werden, wenn bei deren Anlage derselben keine Dateigruppe angegeben wird. Sie können mit der ALTER DATABASE-Anweisung eine andere Dateigruppe zur DefaultDateigruppe machen. Das Argument LOG ON steuert, in welchen Dateien das Transaktionsprotokoll6 der Datenbank angelegt wird. Über COLLATE können Sie die Standardsortierung der Datenbank angeben, wenn Sie eine andere als die Standardsortierung des SQL Servers verwenden wollen (hauptsächlich für internationale Datenbanken). Sie können hier einen Windows- oder SQL-Sortierungsnamen angeben. Die entsprechenden Namen sind in der TSQL-Referenz beschrieben. Die entsprechenden Seiten finden Sie im Inhaltsverzeichnis über INSTALLIEREN VON SQL SERVER / SORTIERUNGSOPTIONEN FÜR INTERNATIONALE UNTERSTÜTZUNG. FOR LOAD existiert nur noch aus Kompatibilitätsgründen zum SQL Server 6.5. Mit FOR ATTACH können Sie eine Datenbank aus bereits vorhandenen Dateien aufbauen. Dieses Argument benötigen Sie immer dann, wenn Sie Datenbanken mit mehr als 16 Dateien aufbauen wollen, da CREATE DATABASE in einem Schritt nur maximal 16 Dateien zulässt. Für jede Datei müssen sie den physikalischen Dateinamen im Argument FILENAME und den logischen Dateinamen im Argument NAME angeben. Der logische Dateiname muss nicht angegeben werden, wenn Sie eine Datenbank mit FOR ATTACH aus bereits vorhandenen Dateien zusammensetzen. Dieser Dateiname wird in SQL-Anweisungen angegeben, wenn diese sich auf die Datei beziehen (wie z. B. ALTER DATABASE). Der logische Dateiname muss innerhalb der Datenbank eindeutig sein. Das Argument SIZE steuert die Anfangsgröße der Datei. Wird SIZE nicht angegeben, verwendet der SQL Server für die primäre Datei die Größe der model-Datenbank7 und für alle 6 Das Transaktionsprotokoll wird im Artikel »SQL Server 2000« erläutert. 7 Die model-Datenbank dient beim SQL Server als Vorlage für neu angelegte Datenbanken Die Data Definition Language 15 weiteren Dateien eine Größe von 1 MB. MAXSIZE bestimmt, wie groß die Datei automatisch wachsen kann. Geben Sie dieses Argument nicht an, oder geben Sie hier UNLIMITED an, kann die Datei bis zu einer (theoretisch) unendlichen Größe wachsen. Geben Sie den Suffix KB oder MB an, um die maximale Dateigröße festzulegen. Das Argument FILEGROWTH bestimmt, wie die Datei vergrößert wird. Sie können hier einen Wert in KB, MB oder in Prozent angeben. Wenn Sie den Wert 0 angeben, wird die Datei nicht automatisch vergrößert. Normalerweise legen Sie eine SQL Server-Datenbank mit einer Anfangsgröße von 1 MB pro Datei an und ermöglichen das automatische Wachsen in %-Schritten (z. B. 15%). Dadurch wird gewährleistet, dass die Datenbank nach einer anfänglichen Initialisierungsphase im späteren Betrieb nicht allzu häufig vergrößert werden muss. Das automatische Wachsen der Dateien verhindert massive Probleme, die entstehen, wenn der freie Platz in der Datenbank bzw. im Transaktionsprotokoll zu klein ist. Das folgende Beispiel erzeugt eine einfache Datenbank mit einer Datei für die Datenbank und einer Datei für das Transaktionsprotokoll: CREATE DATABASE Bestellungen ON PRIMARY ( NAME = Bestellungen_Data, FILENAME = 'c:\Bestellungen_Data.mdf', SIZE = 1MB, MAXSIZE = UNLIMITED, FILEGROWTH = 15% ) LOG ON ( NAME = Bestellungen_Log, FILENAME = 'c:\Bestellungen_Log.ldf', SIZE = 1MB, MAXSIZE = UNLIMITED, FILEGROWTH = 15% ) Das nächste Beispiel legt diese Datenbank mit mehreren Dateien für die Datenbank und einer Datei für das Transaktionsprotokoll an: CREATE DATABASE Bestellungen ON PRIMARY ( NAME = Bestellungen_Data_1, FILENAME = 'c:\Bestellungen_Data_1.mdf', SIZE = 1MB, MAXSIZE = UNLIMITED, FILEGROWTH = 15% ), ( NAME = Bestellungen_Data_2, FILENAME = 'c:\Bestellungen_Data_2.mdf', SIZE = 1MB, MAXSIZE = UNLIMITED, FILEGROWTH = 15% ) LOG ON ( NAME = Bestellungen_Log, FILENAME = 'c:\Bestellungen_Log.ldf', SIZE = 1MB, MAXSIZE = UNLIMITED, FILEGROWTH = 15% ) Die Data Definition Language 16 Das letzte Beispiel legt die Datenbank mit mehreren in Dateigruppen organisierten Dateien für die Datenbank und einer Datei für das Transaktionsprotokoll an: CREATE DATABASE Bestellungen ON PRIMARY ( NAME = Bestellungen_Data_1, FILENAME = 'c:\Bestellungen_Data_1.mdf', SIZE = 1MB, MAXSIZE = UNLIMITED, FILEGROWTH = 15% ), FILEGROUP Bestellungen_Gruppe_1 ( NAME = Bestellungen_Data_2, FILENAME = 'c:\Bestellungen_Data_2.mdf', SIZE = 1MB, MAXSIZE = UNLIMITED, FILEGROWTH = 15% ), ( NAME = Bestellungen_Data_3, FILENAME = 'c:\Bestellungen_Data_3.mdf', SIZE = 1MB, MAXSIZE = UNLIMITED, FILEGROWTH = 15% ), FILEGROUP Bestellungen_Gruppe_2 ( NAME = Bestellungen_Data_4, FILENAME = 'c:\Bestellungen_Data_4.mdf', SIZE = 1MB, MAXSIZE = UNLIMITED, FILEGROWTH = 15% ), ( NAME = Bestellungen_Data_5, FILENAME = 'c:\Bestellungen_Data_5.mdf', SIZE = 1MB, MAXSIZE = UNLIMITED, FILEGROWTH = 15% ) LOG ON ( NAME = Bestellungen_Log, FILENAME = 'c:\Bestellungen_Log.ldf', SIZE = 1MB, MAXSIZE = UNLIMITED, FILEGROWTH = 15% ) 3 4* * " + #$ In SQL/92 löschen Sie ein Schema über DROP SCHEMA Name {RESTRICT | CASCADE}; Geben Sie RESTRICT an, können Sie das Schema nur dann löschen, wenn dieses keine Objekte enthält. Mit CASCADE werden auch alle enthaltenen Objekte automatisch mit gelöscht. Der SQL Server kennt keine DROP SCHEMA-Anweisung. Eine Datenbank können Sie über DROP DATABASE Datenbankname; löschen. Beachten Sie, dass die in Daten in den enthaltenen Objekten nach dem Löschen für immer verloren sind! Die Data Definition Language 17 0 Der SQL Server erweitert die DROP DATABASE-Anweisung lediglich darin, dass mehrere Datenbanken gelöscht werden können: DROP DATABASE Datenbankname [,...n] 3 3 &/ * " + #$ Das Modifizieren eines Schemas ist in SQL/92 scheinbar nicht möglich und wohl auch nicht notwendig. Der Begriff „Modifizieren“ steht übrigens nicht für das Löschen, Ändern und Anfügen von Schema-Objekten (was über die entsprechenden SQL-Anweisungen wie z. B. CREATE TABLE möglich ist), sondern für die Veränderung der Einstellungen des Schemas. 0 Eine Datenbank über ALTER DATABASE modifizieren, d.h., die Größe und verschiedene Einstellungen ändern. Die Syntax der ALTER DATABASE-Anweisung variiert sehr stark zwischen den verschiedenen Datenbanksystemen und wird hier nur für den SQL Server beschrieben: ALTER DATABASE Datenbankname { ADD FILE Dateispezifikation [,...n] [TO FILEGROUP Dateigruppen_Name] | ADD LOG FILE Dateispezifikation [,...n] | REMOVE FILE Logischer_Dateiname | ADD FILEGROUP Dateigruppen_Name | REMOVE FILEGROUP Dateigruppen_Name | MODIFY FILE Dateispezifikation | MODIFY NAME = Neuer_Datenbankname | MODIFY FILEGROUP Dateigruppen_Name {Dateigruppen_Eigenschaft | NAME = Neuer_Name} | SET Optionsspezifikation [,...n] [WITH Terminierung] | COLLATE Sortierungsname } Das Argument Dateispezifikation bezieht sich auf die folgenden Angaben, die pro Datei notwendig sind: (NAME = Logischer_Dateiname [, NEWNAME = Neuer_logischer_Dateiname] [, FILENAME = 'Dateiname'] [, SIZE = Größe] [, MAXSIZE = {Maximalgröße | UNLIMITED}] [, FILEGROWTH = Größen_Increment] ) Das Argument Dateigruppe bezieht sich auf Angaben, die für Dateigruppen notwendig sind: FILEGROUP Dateigruppen_Name Dateispezifikation [,...n] Das Argument Optionsspezifikation spezifiziert die Optionen der Datenbank. Da dieses Argument sehr komplex ist und wahrscheinlich eher selten genutzt wird (da diese Optionen übersichtlicher im Enterprise Manager des SQL Servers definiert werden können), verzichte ich hier auf eine nähere Beschreibung. Die Data Definition Language 18 Mit ADD FILE können Sie die Datenbank um weitere Dateien erweitern und diese Dateien direkt vorhandenen oder neuen Dateigruppen zuordnen. Das folgende Beispiel fügt der Datenbank Bestellungen eine weitere Datei hinzu: ALTER DATABASE Bestellungen ADD FILE ( NAME = Bestellungen_Data_3, FILENAME = 'c:\Bestellungen_Data_3.mdf', SIZE = 1MB, MAXSIZE = UNLIMITED, FILEGROWTH = 15% ); Mit ADD LOG FILE können Sie neue Dateien für das Transaktionsprotokoll hinzufügen. REMOVE FILE löscht eine Datei aus der Datenbank. So können Sie eine Datei z. B. aus einer Datenbank entfernen: ALTER DATABASE Bestellungen REMOVE FILE Bestellungen_Data_3; Mit ADD FILEGROUP können Sie neue Dateigruppen hinzufügen, mit REMOVE FILEGROUP existierende löschen. Das Argument MODIFY FILE ermöglicht die Einstellungen (SIZE, MAXSIZE oder FILEGROWTH) einer Datei zu verändern. Zur Veränderung müssen Sie den logischen Dateinamen angeben. Sie können jeweils nur eine Einstellung verändern. Das folgende Beispiel setzt die automatische Vergrößerung der Datei Bestellungen_Data_2 auf 20%: ALTER DATABASE Bestellungen MODIFY FILE ( NAME = Bestellungen_Data_2, FILEGROWTH = 20% ); Wenn Sie die Größe verändern wollen (was nur notwendig ist, wenn die Datei nicht automatisch vergrößert wird), können Sie nur einen Wert einstellen, der größer ist als die aktuelle Größe. Mit MODIFY FILEGROUP können Sie die Optionen einer Dateigruppe einstellen. Mit dem Argument READONLY schützen Sie die Dateigruppe vor Änderungen. Das Argument READWRITE hebt diesen Schreibschutz wieder auf. Mit dem Argument DEFAULT wird Sie die Dateigruppe zur Default-Dateigruppe. In der Default-Dateigruppe werden neue Tabellen und Indizes angelegt, wenn beim Anlegen derselben keine spezielle Dateigruppe angegeben wird. Ist keine spezielle Dateigruppe als Default-Dateigruppe definiert, verwendet der SQL Server die primäre Dateigruppe als Default-Dateigruppe. 35 /") #$ Laut Date und Darven (SQL - Der Standard) stellen Domänen „... einen absolut fundamentalen Bestandteil des theoretischen Relationenmodells dar ...“. SQL/92 unterstützt Domänen jedoch nur extrem schwach in Form der Spezifikation von Datentypen für Spalten. So können Sie z. B. mit CREATE DOMAIN ADDRESS varchar(80); einen Datentyp definieren, den Sie beim Erstellen und Modifizieren von Tabellen verwenden können: CREATE TABLE Customers( CustomerID int, CompanyName ADDRESS, City ADDRESS); Die Data Definition Language 19 0 Der SQL Server unterstützt keine Domänen. Benutzerdefinierte Datentypen werden beim SQL Server über die Stored Procedure sp_addtype angelegt: sp_addtype ADDRESS, 'varchar(80)' Beim Anlegen solcher Datentypen können Sie zudem festlegen, ob die Spalte später NULLWerte erlaubt oder nicht: sp_addtype ADDRESS, 'varchar(80)', 'NOT NULL' Löschen können Sie Benutzerdefinierte Datentypen im SQL Server über die Stored Procedure sp_droptype. 36 0/ 7 #$ Das Anlegen von Tabellen geschieht über die CREATE TABLE-Anweisung. SQL/92 definiert dazu die folgende Syntax: CREATE TABLE Tabellenname ( {Spaltendefinition | Tabelleneinschränkung} [,...n] ); Der Tabellenname richtet sich nach der Namengebung im verwendeten Datenbanksystem. Die Spaltendefinition definiert jeweils eine Spalte und kann mehrfach angegeben werden. Die Spaltendefinition gliedert sich auf in: Spaltenname {Spaltentyp | Domäne} [DEFAULT {Defaultwert | Funktion | NULL}] [Spalteneinschränkung] Für jede Spalte geben Sie den Namen und den Datentyp oder die Domäne dieser Spalte an. Optional können Sie einen Defaultwert angeben, der in diese Spalte für neue Datensätze eingetragen wird. Voreinstellung hier ist der Wert NULL. Sie können jedoch auch jedes, zum Datentyp der Spalte passende Literal oder eine Funktion angeben, die diesen Datentyp zurückgibt. Beispiel: Erzeugen einer einfachen Kundentabelle: CREATE TABLE Customers ( CustomerID int DEFAULT 0, CompanyName varchar(80), PostCode int DEFAULT 47475, City varchar(80) DEFAULT 'Kamp-Lintfort', Street varchar(80) ); Die Data Definition Language 20 * ) - Zunächst eine kurze Begriffsklärung: Eine „Einschränkung“ (engl. Constraint) definiert gewisse Einschränkungen der für eine Tabellenspalte möglichen Werte. Diese Werte sind zwar zunächst über den Datentyp der Spalte eingeschränkt, können über Constraints jedoch noch weiter eingeschränkt werden. Zu den üblichen Einschränkungen gehören die folgenden: Einschränkung Bedeutung PrimärschlüsselEinschränkungen (Primary Key Constraints) Ein Primärschlüssel definiert eine oder mehrere Spalten einer Tabelle als primärem Zugriffsschlüssel. Dieser ermöglicht einen eindeutigen Zugriff auf die Datensätze der Tabelle. Primärschlüssel werden auch verwendet, um Beziehungen zwischen Tabellen zu definieren. In der Mastertabelle ist dazu ein Primärschlüssel definiert, der mit einem Fremdschlüssel in einer Detailtabelle in Beziehung steht. Primärschlüssel werden häufig nur über eine Spalte definiert, können jedoch auch aus mehreren Spalten bestehen. Ein Primärschlüssel ist immer eindeutig indiziert. Damit können die Werte im Primärschlüssel nur einmal in der Tabelle vorkommen. Fremdschlüssel (Foreign Key Constraint) Ein Fremdschlüssel definiert die Beziehung zwischen einer Mastertabelle und einer Detailtabelle. Der Fremdschlüssel wird dabei immer in der Detailtabelle angelegt. EindeutigkeitsEinschränkung (Unique Constraint) Eine Eindeutigkeits-Einschränkung sorgt dafür, dass in die entsprechende Spalte keine Werte mehrfach eingegeben werden (ohne dass diese Spalte zum Primärschlüssel wird). Dasselbe können Sie in den meisten Datenbanksystemen auch erreichen, indem Sie einen eindeutigen Index über diese Spalten definieren (siehe bei »Indizes erstellen und löschen« in Abschnitt 3.8). Beim SQL Server wird eine Eindeutigkeits-Einschränkung sowieso eindeutig indiziert, weshalb ich hier keinen Unterschied zu einem Index erkennen kann. SQL/92 kennt jedoch keine Indizes, womit Eindeutigkeits-Einschränkungen hier eine Berechtigung besitzen. Prüfeinschränkungen (Check Constraints) Mit einer Prüfeinschränkung sorgen Sie dafür, dass in einer Spalte nur Werte eingegeben werden können, die einer anzugebenden Prüfbedingung entsprechen. In einer Rabatt-Spalte können Sie mit einer Prüfbedingung z. B. dafür sorgen, dass nur Zahlen zwischen 0 und 100 eingegeben werden können. Tabelle 3.1: Die üblichen Einschränkungen für Spalten Einschränkungen für Spalten beziehen sich immer nur auf genau eine Spalte. Die weiter unten beschriebenen Einschränkungen für Tabellen können sich dagegen auf mehr als eine Spalte beziehen. Das in der Spaltendefinition optionale Argument Spalteneinschränkung bezieht sich auf das Erzeugen von Einschränkungen für diese Spalte. Die genaue SQL/92-Syntax dazu konnte ich bisher nicht ermitteln. Die wahrscheinlichste Syntax für Spalteneinschränkungen ist: [CONSTRAINT [Name]] { [PRIMARY KEY] | [UNIQUE] | [FOREIGN KEY REFERENCES Mastertabelle [(Spaltenliste)]] | [CHECK (Prüfbedingung)] } Die Data Definition Language 21 Das folgende Beispiel erzeugt eine Kunden- und eine Bestellungen-Tabelle mit je einem Primärschlüssel, einem Fremdschlüssel auf der Bestellungen-Tabelle auf der Spalte CustomerID, einer Eindeutigkeits-Einschränkung auf dem Feld CompanyName, und einer Prüfeinschränkung auf der Spalte PostCode: CREATE TABLE Customers ( CustomerID int DEFAULT 0 CONSTRAINT PK_Customers PRIMARY KEY, CompanyName varchar(80) CONSTRAINT UQ_Customers_CompanyName UNIQUE, PostCode int DEFAULT 47475 CONSTRAINT CK_Customers_Postcode CHECK(PostCode >= 1000 AND PostCode <= 99999), City varchar(80) DEFAULT 'Kamp-Lintfort', Street varchar(80) ) CREATE TABLE Orders ( OrderID int DEFAULT 0 CONSTRAINT PK_Orders PRIMARY KEY, CustomerID int CONSTRAINT FK_Orders_Customers FOREIGN KEY REFERENCES Customers(CustomerID), OrderDate char(10) ) Wie Sie sehen, verwende ich in den Beispielen das Schlüsselwort CONSTRAINT damit ich den Einschränkungen einen Namen geben kann. Wenn Sie den Namen selbst angeben (und die Namensgebung damit nicht dem Datenbanksystem überlassen) können Sie die Einschränkung später ohne Probleme löschen, aufheben oder verändern. * ) 7 Im Gegensatz zu Spalteneinschränkungen beziehen sich Einschränkungen für die Tabelle auf die gesamte Tabelle, können also mehrere Spalten referenzieren. Wenn Sie z. B. einen Primärschlüssel aufbauen wollen, der über mehr als eine Spalte geht, können Sie diesen nur über eine Tabelleneinschränkung erzeugen. Das optionale Argument Tabelleneinschränkung gliedert sich dazu auf in: { [CONSTRAINT Name PRIMARY KEY (Spaltenliste)] | [CONSTRAINT Name FOREIGN KEY (Spaltenliste) REFERENCES Mastertabelle(Spaltenliste)] | [CONSTRAINT Name CHECK(Prüfbedingung)] } Die Data Definition Language 22 Das bei den Spalteneinschränkungen beschriebene Beispiel sieht mit Tabelleneinschränkungen so aus: CREATE TABLE Customers ( CustomerID int DEFAULT 0, CompanyName varchar(80), PostCode int DEFAULT 47475, City varchar(80) DEFAULT 'Kamp-Lintfort', Street varchar(80), CONSTRAINT PK_Customers PRIMARY KEY (CustomerID), CONSTRAINT UK_Customers UNIQUE (CompanyName), CONSTRAINT CK_Customers_Postcode CHECK (PostCode > 1000 AND PostCode <= 99999) ) CREATE TABLE Orders ( OrderID int DEFAULT 0, CustomerID int, OrderDate char(10), CONSTRAINT PK_Orders PRIMARY KEY (OrderID), CONSTRAINT FK_Orders_Customers FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID) ) 0/ 7 " 0 Die Syntax der CREATE TABLE-Anweisung basiert beim SQL Server auf der SQL/92-Syntax, ist jedoch erweitert: CREATE TABLE [{Datenbankname.[Besitzer]. | Besitzer.}]Tabellenname( { Spaltendefinition | Spaltenname AS Ausdruck_für_eine_berechnete_Spalte | Tabelleneinschränkung } [,...n]) [ON {Dateigruppe | DEFAULT} ] [TEXTIMAGE_ON {Dateigruppe | DEFAULT} ] Im Unterschied zu SQL/92 kann hier bei der Erzeugung der Besitzer der Tabelle angegeben werden. Der Besitzer einer Tabelle besitzt immer alle Rechte auf dieser Tabelle. Per Voreinstellung ist immer der Benutzer, der die Tabelle anlegt, der Besitzer. Ein weiterer Unterschied ist, dass statt einer Spaltendefinition auch eine Berechnung für eine berechnete Spalte angegeben werden kann. Das folgende Beispiel erzeugt eine Tabelle für Bestelldetails mit einer berechneten Spalte für die Detailsumme: CREATE TABLE Order_Details ( ID int, OrderID int, ProductID int, Quantity int, UnitPrice money, DetailSum AS Quantity * UnitPrice ) Um diesen Beispiel zu testen, können Sie die folgenden SQL-Anweisungen verwenden: INSERT INTO ORDER_DETAILS (ID, OrderID, ProductID, Quantity, UnitPrice) VALUES (1,1000,110,10,1.35) SELECT * FROM Order_Details Die Data Definition Language 23 Das Argument Spaltendefinition bezieht sich auf die folgenden Angaben: Spaltenname Datentyp [COLLATE Sortierungsname] [ { [DEFAULT Default_Literal] | [IDENTITY [(Startwert, Schrittweite) [NOT FOR REPLICATION]] } ] [ROWGUIDCOL] [Spalteneinschränkung] Im Unterschied zu SQL/92 kann die Spalte beim SQL Server zu einer Identity-Spalte (Identity Column) werden. Eine Identity-Spalte ist eine Spalte, die einen numerischen Datentyp besitzen muss und vom Server bei jedem neuen Datensatz um einen einzustellenden Wert inkrementiert wird. Sie können pro Tabelle nur eine Identity-Spalte definieren und nur solche Spalten verwenden, die einen numerischen Datentyp besitzen, keine Nullwerte zulassen und keinen Defaultwert besitzen. Die Identity-Spalte kann mit der Spalte für den Primärschlüssel identisch sein. Der Option Startwert spezifiziert den Anfangswert für neue Datensätze. Schrittweite definiert den Wert mit dem für jeden neuen Datensatz inkrementiert wird. Die Voreinstellung für Startwert und Schrittweite ist jeweils 1. Das folgende Beispiel erzeugt eine Tabelle Order_Details mit einer Identity-Spalte, deren Wert bei 1000 beginnt und die mit 1 addiert wird: CREATE TABLE Order_Details ( ID int IDENTITY(1000,1), OrderID int, ProductID int, Quantity int, UnitPrice money, DetailSum AS Quantity * UnitPrice ) Die Option ROWGUIDCOL besagt, dass die Spalte eine Spalte ist, die einen GUID8-Wert speichern soll, der einen Datensatz ein-eindeutig identifizieren soll. GUID-Werte sind hexadezimale Werte (wie z. B. der Wert BA2DE025-8467-11D3-9E43-00400548C419), die per „intelligentem“ Zufallsgenerator mit Hilfe des aktuellen Datums, der Zeit, der ID der Netzwerkkarte und aktuellen Registerinhalten erzeugt werden und die über die gesamte Welt (bzw. über das gesamte „Universum“) eindeutig sind. GUID’s werden üblicherweise zur Identifizierung gespeicherter Objekte verwendet. Mit GUID‘s können Sie auch viele Probleme umgehen, die bei Identitätsspalten auftreten können, wenn mehrere Benutzer gleichzeitig neuen Datensätze anlegen. Der SQL Server nutzt GUID-Spalten außerdem bei Replikationen. Eine GUID-Spalte muss den Datentyp uniqueidentifier besitzen und sollte als Defaultwert die Funktion NEWID() zugewiesen bekommen, die einen neuen GUID-Wert erzeugt: CREATE TABLE Objects ( GUID uniqueidentifier ROWGUIDCOL DEFAULT NEWID(), ObjectName varchar(80) ) Testen können Sie diese Tabelle mit den folgenden Anweisungen: INSERT INTO Objects (ObjectName) VALUES ('ObjectA') INSERT INTO Objects (ObjectName) VALUES ('ObjectB') SELECT * FROM Objects 8 Global Unique Identifier Die Data Definition Language 24 - * ) Das in der Spaltendefinition verwendete Argument Spalteneinschränkung definiert wie bei SQL/92 eine Einschränkung für ein Datenfeld mit den folgenden Angaben: [CONSTRAINT Einschränkungsname] { [{NULL | NOT NULL}] | [{PRIMARY KEY | UNIQUE} [{CLUSTERED | NONCLUSTERED}] [WITH FILLFACTOR = Füllfaktor] [ON {Dateigruppe | DEFAULT}]] | [FOREIGN KEY REFERENCES Mastertabelle [(Spaltenliste)] [ON DELETE {CASCADE | NO ACTION}] [ON UPDATE {CASCADE | NO ACTION}] [NOT FOR REPLICATION]] | [CHECK [NOT FOR REPLICATION] (Prüfbedingung)] } Zusätzlich zu SQL/92 können Sie hier mit der Option NOT NULL festlegen, dass die Spalte keine NULL-Werte erlaubt. Der Wert NULL steht für „Nichts“ und ist nicht zu verwechseln mit der Zahl 0. Eine leere Spalte speichert immer einen NULL-Wert (Spalten mit ZeichenkettenDatentypen können daneben auch eine leere Zeichenkette speichern, die jedoch kein NULL-Wert ist). 8 " * " 4* + Ab der Version 2000 erlaubt der SQL Server (endlich) Fremdschlüssel, die eine Löschoder/und Aktualisierungsweitergabe besitzen. Normale Fremdschlüssel erlauben das Löschen von Master-Datensätzen und das Verändern des Primärschlüssels dieser Datensätze nicht, wenn eine Detailtabelle den Primärschlüssel referenziert. Damit sichern Datenbanksysteme ab, dass die über Fremdschlüssel aufgebauten Beziehungen nicht in einen inkonsistenten Zustand versetzt werden können (was ja der eigentliche Sinn eines Fremdschlüssels ist). Wenn Sie für eine Beziehung beim SQL Server nun über ON DELETE CASCADE die Löschweitergabe einschalten, können Sie Master-Datensätze löschen, die in Detailtabellen referenziert werden. In diesem Fall löscht der SQL Server allerdings automatisch und ohne weitere Meldungen gleich auch alle Detaildatensätze. Die Löschweitergabe sollten Sie nur sparsam und in Sonderfällen einsetzen. Wenn Sie zwischen einer Kunden- und einer Bestellungen-Tabelle eine Beziehung mit Löschweitergabe erstellen und einen Kunden löschen, wird kein Fehler generiert, auch wenn die Kunden-Nummer in mehreren Bestellungen verwendet wird. Der SQL Server löscht dann einfach auch alle Bestellungen, was in der Praxis zu enormen Fehlern führt. So sind Umsatzauswertungen in diesem Fall z. B. nicht mehr korrekt, da die Bestellungen der gelöschten Kunden fehlen. Eine Löschweitergabe wäre aber z. B. zwischen einer Bestellungen- und der BestelldetailsTabelle sinnvoll: Wird eine Bestellung gelöscht, ist es sinnvoll, gleich auch die Bestelldetails zu löschen. Die Aktualisierungsweitergabe, die Sie mit ON UPDATE CASCADE einschalten, ermöglicht, dass der Primärschlüsselwert einer Mastertabelle geändert werden kann, auch wenn dieser bereits in Detailtabellen verwendet wird. Der SQL Server gibt die Aktualisierung einfach an alle Detailtabellen weiter. Wenn Sie z. B. ermöglichen wollen, dass eine Kunden-Nummer geändert werden kann, müssen Sie alle Fremdschlüsseln, die die Kunden-Nummer referenzieren, mit Aktualisierungsweitergabe definieren. Die Data Definition Language 25 Das folgende Beispiel erzeugt je eine Tabelle Bestellungen und Bestelldetails und setzt diese Tabellen über das Feld Bestell_Nr in Beziehung. Die Beziehung wird so definiert, dass die Aktualisierungs- und die Löschweitergabe eingeschaltet ist: CREATE TABLE Bestellungen ( Bestell_Nr int PRIMARY KEY NOT NULL, Datum datetime DEFAULT GETDATE() NOT NULL, Kunden_Nr int NOT NULL) CREATE TABLE Bestelldetails ( ID int IDENTITY PRIMARY KEY NOT NULL, Bestell_Nr int NOT NULL CONSTRAINT FK_Bestellungen FOREIGN KEY REFERENCES Bestellungen(Bestell_Nr) ON DELETE CASCADE ON UPDATE CASCADE, Artikel_Nr int NOT NULL, Anzahl int NOT NULL DEFAULT 0) Sie können nun z. B. eine Bestellung mit zwei Bestelldetails hinzufügen: INSERT INTO Bestellungen (Bestell_Nr, Kunden_Nr) VALUES(1, 1001) INSERT INTO Bestelldetails (Bestell_Nr, Artikel_Nr, Anzahl) VALUES(1, 11588, 100) INSERT INTO Bestelldetails (Bestell_Nr, Artikel_Nr, Anzahl) VALUES(1, 18941, 50) Da die Aktualisierungsweitergabe eingeschaltet ist, können Sie die Bestellnummer nachträglich ändern (was ansonsten zu einem Fehler führen würde): UPDATE Bestellungen SET Bestell_Nr = 200200001 WHERE Bestell_Nr = 1 Wenn Sie nun die Bestelldetails abfragen: SELECT * FROM Bestelldetails erkennen Sie, dass der SQL Server die Bestellnummer in diesen auf den neuen Wert gesetzt hat. Da auch die Löschweitergabe eingeschaltet ist, können Sie die Bestellung auch löschen: DELETE FROM BEstellungen WHERE Bestell_Nr = 200200001 Der SQL Server meldet, dass ein Datensatz gelöscht wurde und verschweigt damit, dass die zwei Bestelldetail-Datensätze im Hintergrund ebenfalls gelöscht wurden. Wenn Sie die Bestelldetails nun noch einmal abfragen, stellen Sie fest, dass die Detaildatensätze im Hintergrund vom SQL Server gelöscht wurden. * ) " 7 Das Argument Tabelleneinschränkung definiert wie bei SQL/92 eine Einschränkung für die gesamte Tabelle und ist ebenfalls erweitert: [CONSTRAINT Einschränkungsname] { {PRIMARY KEY | UNIQUE} [{CLUSTERED | NONCLUSTERED}] (Spaltenliste) [WITH FILLFACTOR = Füllfaktor] [ON {Dateigruppe | DEFAULT}] | FOREIGN KEY (Spaltenliste) REFERENCES Mastertabelle [(Spaltenliste)] [ON DELETE { CASCADE | NO ACTION }] [ON UPDATE { CASCADE | NO ACTION }] [NOT FOR REPLICATION] | CHECK [NOT FOR REPLICATION](Prüfbedingung) } Die Data Definition Language 26 Beim Erzeugen von Primärschlüsseln können Sie mit der Option CLUSTERED einen gruppierten Index erzeugen. Mit der Option WITH FILLFACTOR können Sie den Füllfaktor einstellen. Gruppierte Indizes und den Füllfaktor erläutere ich im Artikel »SQL Server 2000«. Mit der Option ON Dateigruppe können Sie den Primärschlüssel in einer bestimmten Dateigruppe anlegen. 39 7 "/ Bei bestehenden Tabellen können Sie über die ALTER TABLE-Anweisung • Spalten hinzufügen, löschen und ändern, • Einschränkungen hinzufügen und löschen. Die einzelnen Varianten werden der Übersicht wegen im Folgenden einzeln erläutert, können jedoch (in logisch korrekter Folge) auch in einer ALTER TABLE-Anweisung stehen. 39 : 0/ - #$ Sie fügen Spalten in SQL/92 mit ALTER TABLE Tabellenname ADD [COLUMN] Spaltendefinition [,...n] hinzu. Das folgende Beispiel fügt der Customers-Tabelle die Spalte Discount hinzu: ALTER TABLE Customers ADD COLUMN Discount smallint DEFAULT 0 0 Der SQL Server kann mit dem Schlüsselwort COLUMN nichts anfangen, ermöglicht jedoch zudem das Anfügen von berechneten Spalten: ALTER TABLE Tabellenname ADD { Spaltendefinition | Spaltenname AS Ausdruck für eine berechnete Spalte } [,...n] Die Data Definition Language 27 39 4* 0/ - #$ Spalten löschen Sie in SQL/92 mit ALTER TABLE Tabellenname DROP COLUMN Spaltenname {RESTRICT | CASCADE} [,...n] Mit der Option RESTRICT können Sie eine Spalte nur dann löschen, wenn keine Einschränkung auf dieser Spalte definiert ist. Mit der Option CASCADE werden alle Einschränkungen automatisch mit gelöscht. Beispiel: Löschen der Spalte Discount aus der Customers-Tabelle: ALTER TABLE Customers DROP COLUMN Discount CASCADE; 0 Der SQL Server kennt die Optionen RESTRICT und CASCADE nicht und verhält sich, als ob RESTRICT angegeben ist. 393 ; 0/ - #$ SQL/92 erlaubt beim Ändern einer Spalte lediglich das Löschen und Neudefinieren des Defaultwertes: ALTER TABLE Tabellenname ALTER [COLUMN] Spaltenname {SET {Defaultwert | Funktion | NULL} | DROP DEFAULT} [,...n] 0 Der SQL Server ermöglicht dagegen die Änderung des Datentyps, der Sortierfolge und der NULL-Option und das Anfügen bzw. Löschen der ROWGUID-Einstellung: ALTER TABLE Tabellenname ALTER COLUMN Spaltenname { Neuer Datentyp [(Genauigkeit[, Dezimalstellen])] [COLLATE Sortierungsname] [NULL | NOT NULL] | {ADD | DROP} ROWGUIDCOL } [,...n] Der Defaultwert einer Spalte wird über das Ändern der entsprechenden DefaultEinschränkung realisiert. Das folgende Beispiel ändert den Datentyp der Discount-Spalte der Customers-Tabelle: ALTER TABLE Customers ALTER COLUMN Discount tinyint NOT NULL Die Data Definition Language 28 Das Ändern von Spalten funktioniert beim SQL Server mit SQL nur dann, wenn keine Einschränkungen auf dieser Spalte definiert sind. Der Enterprise Manager ist in der Lage, auch solche Spalten ohne Probleme zu ändern. 395 * ) #$ In SQL/92 fügen Sie eine Einschränkung folgendermaßen hinzu: ALTER TABLE Tabellenname ADD { [CONSTRAINT Name PRIMARY KEY (Spaltenliste)] | [CONSTRAINT Name FOREIGN KEY (Spaltenliste) REFERENCES Mastertabelle(Spaltenliste)] | [CONSTRAINT Name CHECK(Prüfbedingung)] } [,...n] 0 Beim SQL Server verwenden Sie die folgende Syntax: ALTER TABLE Tabellenname [{WITH CHECK | WITH NOCHECK}] ADD Tabelleneinschränkung [,...n] Einschränkungen bauen sich dabei genauso auf wie bei der Erzeugung von Tabellen. Die WITH NOCHECK-Option bewirkt, dass die Einschränkung zunächst deaktiviert ist. Solche Einschränkungen können Sie später über ALTER TABLE Tabellenname CHECK Einschränkungsname aktivieren. Das folgende Beispiel fügt der Customers-Tabelle eine Prüfeinschränkung an: ALTER TABLE Customers ADD CONSTRAINT CK_Discount CHECK (Discount >= 0 AND Discount <= 100) 396 * ) 4* Das Löschen einer Einschränkung geschieht in SQL/92 und beim SQL Server über ALTER TABLE Tabellenname DROP CONSTRAINT Einschränkungsname [, ...n] Beispiel: Löschen der zuvor erzeugten Prüfeinschränkung auf der Customers-Tabelle: ALTER TABLE Customers DROP CONSTRAINT DF_Discount Die Data Definition Language 29 399 * ) 0 " 0 0 Beim SQL Server können Sie Einschränkungen deaktivieren, was häufig eine große Hilfe ist, wenn Sie Daten importieren müssen, die nicht den Regeln der Einschränkung entsprechen. Sie deaktivieren eine Einschränkung über ALTER TABLE Tabellenname NOCHECK CONSTRAINT Einschränkungsname Das Aktivieren geschieht über die CHECK-Option: ALTER TABLE Tabellenname CHECK CONSTRAINT Einschränkungsname Beispiel: Deaktivieren der Einschränkung auf der Discount-Spalte, um Datensätze mit ungültigem Rabatt importieren zu können (wird hier nur simuliert), importieren dieser Datensätze und wiedereinschalten der Einschränkung: ALTER TABLE Customers NOCHECK CONSTRAINT DF_Discount INSERT INTO Customers VALUES (1001, 'Panzerknacker AG', 12345, 'Entenhausen', 'Entenweg 1', 101) ALTER TABLE Customers CHECK CONSTRAINT DF_Discount INSERT INTO Customers VALUES (1002, 'Trillians Galaxy Shop, 99999, 'Galaxy', 'No Street 0', 101) Die erste INSERT-Anweisung erzeugt keinen Fehler, weil die Einschränkung deaktiviert ist. Die zweite INSERT-Anweisung erzeugt dagegen einen Fehler und wird nicht ausgeführt. 3< 7 4* #$ Das Löschen einer Tabelle geschieht in SQL/92 recht einfach über DROP TABLE Tabellenname {RESTRICT | CASCADE} Geben Sie RESTRICT an, wird DROP TABLE scheitern, wenn die Tabelle in einer Sicht verwendet wird oder wenn die Tabelle von Fremdschlüsseln auf anderen Tabellen referenziert wird. Wenn Sie CASCADE angeben, werden alle abhängigen Sichten und Fremdschlüssel automatisch mit gelöscht. 0 Der SQL Server kennt die Optionen RESTRICT und CASCADE nicht und arbeitet immer, als wenn Sie RESTRICT angeben. Die Data Definition Language 30 3= 4* 3= Indizes beschleunigen Abfragen, wenn die im Index enthaltenen Felder in WHERE-, ORDER BY-, oder GROUP BY-Klauseln oder als Fremdschlüssel in Joins verwendet werden oder wenn mit anderen Techniken (z. B. mit der Seek-Methode in ADO) in diesen Spalten gesucht wird. Ein Index wird in der Regel in einem balancierten Baum gespeichert. Der SQL Server speichert für normale (nicht gruppierte) Indizes für jeden Datensatz ein Blatt im Baum mit den Schlüsselwerten und einem Verweis auf den Datensatz. Die Suche in einem balancierten Baum ist wesentlich schneller als die sequentielle Suche in den Datensätzen, die ohne Index notwendig wäre. Die zur Erzeugung von Indizes in vielen SQL-Büchern beschriebene CREATE INDEXAnweisung scheint gar kein Bestandteil von SQL/92 zu sein (jedenfalls ist weder diese Anweisung noch die Verwendung des Begriffs „Index“ im Standard-Werk »SQL - Der Standard« zu finden. Im Internet ist unter www.se.postgressql.org/docs/postgres/sql-createindex.htm (Beschreibung des SQL-Dialekts von Postgres) nachzulesen, dass SQL/92 keine CREATE INDEX-Anweisung kennt). " Die allgemeine Form der CREATE INDEX-Anweisung ist (nach dem Buch »The practical SQL Handbook«): CREATE [UNIQUE] INDEX Indexname ON Tabellenname (Spaltenliste) Damit können Sie einen mehrdeutigen oder eindeutigen Index auf einer oder mehreren Spalten erzeugen. Ein eindeutiger Index lässt in den Spalten des Index nur eindeutige Werte zu. Ein eindeutiger Index auf der Spalte CompanyName z. B. lässt nicht zu, dass in zwei Datensätzen derselbe Firmenname gespeichert wird. Wird ein Index über mehrere Spalten definiert, bezieht sich die Eindeutigkeit auf die Summe der Spalten. Das folgende Beispiel erzeugt einen Index auf den Spalten CompanyName und City der Customers-Tabelle CREATE UNIQUE INDEX IX_Customers_CompanyName_City ON Customers (CompanyName, City) Dieses Beispiel bewirkt, dass die Spalten CompanyName und City zusammen in einem eindeutigen Index definiert sind. Nun kann in dieser Tabelle ein Kunde „Panzerknacker AG“ in „Entenhausen“ und ein Kunde „Panzerknacker AG“ in „Berlin“ gespeichert werden, aber keine zwei Kunden „Panzerknacker AG“ in „Entenhausen“. Für binäre Spalten (dazu gehören auch Memo-Spalten, die große Texte speichern können) kann in der Regel kein Index vergeben werden. Die Data Definition Language 31 0 Der SQL Server kennt eine erweiterte Syntaxvariante zur Erzeugung von Indizes: CREATE [UNIQUE] [{CLUSTERED | NONCLUSTERED}] INDEX Indexname ON Tabellenname (Spaltenliste) [WITH [PAD_INDEX] [[,] FILLFACTOR = Füllfaktor] [[,] IGNORE_DUP_KEY] [[,] DROP_EXISTING] [[,] STATISTICS_NORECOMPUTE] [[,] SORT_IN_TEMPDB] ] [ON Dateigruppe] Die Optionen CLUSTERED und NONCLUSTERED, der Füllfaktor und die Option IGNORE_DUP_KEY sind im Artikel » SQL Server 2000« beschrieben. Mit der Option DROP_EXISTING können Sie einen bereits vorhandenen Index gleichen Namens überschreiben (was sonst nicht möglich ist). Mit ON Dateigruppe können Sie einen Index in einer bestimmten Dateigruppe erzeugen. 3= 4* > Mit DROP INDEX Tabellenname.Indexname können Sie einen Index löschen. Die Data Definition Language 32 5 & - / Die Data Manipulation Language besteht aus den folgenden Teilbereichen: • Anfügen und Löschen von Datensätzen • Ändern von Datensätzen • Abfragen von Datensätzen Das Anfügen, Löschen und Ändern von Datensätzen wird zuerst erläutert, weil dies die logische Reihenfolge bei der Erstellung von Datenbanken darstellt. Ich würde eigentlich lieber zuerst das Abfragen von Datensätzen behandeln, weil die in Abfragen oft verwendete WHERE-Klausel und andere Features, wie z. B. Joins auch beim Anfügen, Löschen und Ändern von Datensätzen verwendet werden, aber ich will die logische Reihenfolge nicht durcheinanderbringen (schließlich muss man ja nach dem Erstellen einer Datenbank diese zunächst füllen, damit Abfragen überhaupt möglich sind). " " 7 , - Die Beispiele zum Anfügen, Ändern und Löschen von Daten verändern natürlich die Datenbank. Wenn Sie solche Beispiele nur testen wollen ohne die Daten tatsächlich zu verändern, können Sie diese einfach in eine Transaktion einschließen. Sie starten eine Transaktion mit BEGIN TRANSACTION und verwerfen alle Änderungen mit ROLLBACK TRANSACTION. Wenn Sie die Anweisungen also in diese Anweisungen einschließen, werden die Änderungen nicht wirklich vorgenommen. Zu Überprüfung, ob die Änderungen an der Datenbank ausgeführt würden (wenn Sie keine Transaktion verwenden oder die Transaktion mit COMMIT TRANSACTION abschließen), können Sie vor dem Verwerfen der Transaktion einfach eine SELECT-Anweisung einfügen. Das folgende Beispiel aktualisiert Artikelpreise in einer Transaktion, die am Ende verworfen wird: BEGIN TRANSACTION UPDATE Products SET UnitPrice = UnitPrice * 1.1 WHERE SupplierID = 1 SELECT * FROM Products ROLLBACK TRANSACTION Die SELECT-Anweisung, die innerhalb der Transaktion ausgeführt wird, zeigt die geänderten Daten. Nach dem Rollback besitzen die Daten dann aber wieder die alten Werte. 5 0/ ) #$ Datensätze werden in SQL/92 mit der INSERT-Anweisung angefügt. Mit diese Anweisung können Sie einzelne Datensätze anfügen, indem Sie die Werte der Spalten für den Datensatz angeben, oder Sie können alle über eine SELECT-Anweisung ausgewählten Datensätze anderer Tabellen an eine Tabelle oder eine Sicht9 anfügen. 9 Eine Sicht (engl. View) ist eine in der Datenbank gespeicherte Abfrage, die eine bestimmte, über das SELECT-Statement definierte Sicht auf die Basistabelle(n) bietet. Über eine Sicht können Sie komplexe Abfragen vor Benutzern verbergen und Benutzern spezifische Rechte auf den Basisdaten vergeben. Datensätze können Sie nicht nur an Tabellen, sondern auch an Sichten anfügen. Sichten sind weiter unten näher beschrieben. Die Data Manipulation Language 33 Mit INSERT INTO {Tabelle | Sicht} [(Spaltenliste)] VALUES (Wertliste) fügen Sie einen einzelnen Datensatz an eine Tabelle oder eine Sicht an. Wenn alle Spalten gefüllt werden, kann die Spaltenliste entfallen. Die Werte der Spalten werden dann der Reihe nach in der Wertliste eingetragen. Wenn Sie nicht alle Spalten füllen wollen, können Sie zum einen die Spaltenliste weglassen und an Stelle der auszulassenden Spalte den Wert NULL oder die Option DEFAULT (für den Defaultwert der Spalte) einsetzen, oder Sie geben die zu füllenden Spalten in der Spaltenliste an. Das folgende Beispiel fügt einen Datensatz an die Customers-Tabelle unter Angabe aller Spaltenwerte an: INSERT INTO Customers VALUES (1001, 'Panzerknacker AG', 12345, 'Entenhausen', 'Entenweg 1') Soll im Beispiel die Spalte PostCode ausgelassen werden, können Sie das DEFAULTSchlüsselwort verwenden um die Spalte mit dem Defaultwert der Tabelle zu füllen: INSERT INTO Customers VALUES (1002, 'Trillians Galaxy Shop', DEFAULT, 'Galaxy', 'No Street 0') Besonders dann, wenn Sie nicht wissen, in welcher Reihenfolge die Spalten definiert sind, sollten Sie die die Variante mit Angabe der Spaltennamen vorziehen, da Sie hier die Spalten in beliebiger Reihenfolge angeben können: INSERT INTO Customers (CustomerID, CompanyName, City, Street) VALUES (1002, 'Trillians Galaxy Shop', 'Galaxy', 'No Street 0') Mit INSERT INTO {Tabelle | Sicht} [(Spaltenliste)] SELECT-Anweisung fügen Sie alle in der SELECT-Anweisung ausgewählten Datensätze an eine Tabelle an. Die in der SELECT-Anweisung ausgewählten Spalten müssen mit den Spalten der Tabelle bzw. den in der Spaltenliste angegebenen Spalten im Datentyp identisch bzw. konvertierbar sein. Der Name der Spalten in der SELECT-Anweisung spielt allerdings keine Rolle. Die SELECT-Anweisung ist in Abschnitt 4.4 ausführlich beschrieben und wird hier nur in einfacher Form dargestellt. Beachten Sie, dass Sie auch sehr komplexe SELECT-Anweisungen (wie z. B. solche mit Gruppierungen und Joins) verwenden können, um Datensätze anzufügen. Die folgende Anweisung fügt alle Lieferanten (Suppliers), die aus den USA stammen, an die Kundentabelle an: INSERT INTO Customers SELECT SupplierID, CompanyName, ContactName, ContactTitle, Address, City, Region, PostalCode, Country, Phone, Fax FROM Suppliers WHERE Country = 'USA' Die explizite Angabe der Spalten aus der Suppliers-Tabelle ist hier notwendig, da diese zusätzlich noch die Spalte Homepage enthält. Die folgende Anweisung INSERT INTO Customers SELECT * FROM Suppliers WHERE Country = 'USA' scheitert beim SQL Server mit dem Fehler Server: Nachr.-Nr. 213, Schweregrad 16, Status 5, Zeile 1 INSERT-Fehler: Spaltenname oder Anzahl der übergebenen Werte entspricht nicht der Tabellendefinition. Die Data Manipulation Language 34 Der Stern (*) in der SELECT-Anweisung steht für „Alle Spalten“. Das folgende, etwas komplexere Beispiel schreibt die Jahresumsätze aller Kunden in eine neue Tabelle. Das Beispiel setzt bereits Gruppierungen und Joins ein, die erst im Abschnitt 4.4.13 erläutert werden: CREATE TABLE Sales ( Year int, CustomerID varchar(10), Sales float) INSERT INTO Sales SELECT YEAR(OrderDate), CustomerID, SUM(UnitPrice * Quantity) FROM [Order Details] INNER JOIN Orders ON [Order Details].OrderID = Orders.OrderID GROUP BY YEAR(OrderDate), CustomerID ORDER BY YEAR(OrderDate), CustomerID Dieses (SQL Server-) Beispiel erzeugt eine Tabelle Sales, die mit den Summen der Umsätze jedes Kunden für je ein Jahr gefüllt wird. Die Summen werden über eine Gruppierung errechnet. Die Umsätze sind in der Tabelle Order Details gespeichert, die Kunden-ID in der Tabelle Orders, weswegen ein Join über diese beiden Tabelle notwendig ist. Der SQL Server erlaubt (leider) Bezeichner mit Leerzeichen (die in den meisten Datenbanksystemen nicht erlaubt sind). Wenn solche Bezeichner in SQLAnweisungen verwendet werden, müssen diese von eckigen Klammern umschlossen werden (die hier ausnahmsweise nicht für optionale Elemente stehen). 0 Die INSERT-Anweisung beim SQL Server ist sehr komplex und kann an dieser Stelle nicht ausführlich erläutert werden. Zunächst die (wie so oft verwirrende) Syntax: INSERT [INTO] { Tabelle [WITH (Tabellenhinweise [...n])] | Sicht | Datensatzgruppen_Funktion } { [(Spaltenliste)] { VALUES ({DEFAULT | NULL | Ausdruck } [,...n]) | Abgeleitete_Tabelle | SELECT-Anweisung } | DEFAULT VALUES } Neben Tabellen und Sichten können Sie beim SQL Server Datensätze noch in Tabellen anfügen, die in entfernten Datenbanken gespeichert sind. Dazu geben Sie im Argument Datensatzgruppen_Funktion eine TSQL10-Funktion an, die eine Datensatzgruppe zurückgibt. Sie können dazu die Funktionen OPENQUERY verwenden, die eine Abfrage auf einem verknüpften entfernten Server ausführt, oder die Funktion OPENROWSET, die eine Abfrage auf einer beliebigen OLE-DB11-Datenquelle ausführt. Besonders die Variante mit der OPENROWSETFunktion ist interessant, wenn Sie Daten in andere Datenbanken exportieren müssen (was allerdings auch mit dem Enterprise Manager möglich ist). Dazu sollten Sie sich allerdings ein wenig mit OLE-DB auskennen. An dieser Stelle muss ein (funktionierendes!) Beispiel 10 TSQL (Transact SQL) ist die erweiterte SQL-Syntax des SQL Servers 11 OLE DB ist der aktuelle Microsoft Standard zum Zugriff auf Datenbanken. Der SQL Server selbst basiert auf OLE DB. Die Data Manipulation Language 35 ausreichen: In der Tabelle Orders der Northwind-Datenbank eines anderen SQL Servers soll ein Datensatz angefügt werden: INSERT INTO OPENROWSET('SQLOLEDB', 'Arthur';'sa';'', 'SELECT * FROM Northwind.dbo.Orders') (CustomerID, EmployeeID, OrderDate) VALUES ('ALFKI', 1, '19991231') Dieses Beispiel verwendet den OLE DB-Provider SQLOLEDB, der den Zugriff auf SQL ServerDatenbanken realisiert und fügt an die Tabelle Orders der Datenbank Northwind im SQL Server Arthur einen Datensatz an. Als Benutzer wird sa ohne Passwort angegeben. Das Argument Tabellenhinweise gliedert sich auf in: { INDEX(index_val [,...n]) | FASTFIRSTROW | HOLDLOCK | PAGLOCK | READCOMMITTED | REPEATABLEREAD | ROWLOCK | SERIALIZABLE | TABLOCK | TABLOCKX | UPDLOCK } Diese Optionen sind recht gut in der TSQL-Hilfe bei der FROM-Klausel beschrieben und betreffen den Isolations-Level bei Transaktionen, das Sperren von Datensätzen etc. Wenn Sie statt der Spaltenliste das Argument DEFAULT VALUES angeben, wird der neue Datensatz mit den Defaultwerten für die einzelnen Spalten gefüllt. 5 4* 0/ ) #$ In SQL/92 ist das Löschen von Datensätzen recht einfach: DELETE FROM {Tabelle | Sicht} [WHERE Bedingung] Geben Sie keine Bedingung an, werden alle Datensätze gelöscht. Wenn Sie nur bestimmte Datensätze löschen wollen, verwenden Sie eine WHERE-Klausel, die die zu löschenden Datensätze einschränkt. Die WHERE-Klausel wird in Abschnitt 4.4.5 ausführlich beschrieben und hier nur an einem einfachen Beispiel erläutert: Die folgende Anweisung löscht alle Kunden aus Berlin: DELETE FROM Customers WHERE City = 'Berlin' Wenn die Datenbank referentielle Integrität unterstützt, können keine Datensätze aus Mastertabellen gelöscht werden, deren Schlüsselwerte in Detailtabellen vorkommen. Das obige Beispiel schlägt z. B. fehl, weil Kunden aus Berlin in der Tabelle Orders vorkommen. Die WHERE-Klausel kann (natürlich) auch Unterabfragen enthalten, wie das folgende Beispiel zeigt, das alle Bestelldetails der Bestellungen von 1996 löscht: DELETE FROM [Order Details] WHERE OrderID IN (SELECT OrderID FROM Orders WHERE YEAR(OrderDate) = 1996) Wenn Sie z. B. Datensätze aus zwei über mehrere Datenfelder verknüpften Tabellen löschen wollen, können Sie keine einfache DELETE-Anweisung einsetzen. Sind die Tabellen nur über ein Datenfeld miteinander verknüpft, ist das Löschen der Datensätze aus der Detailtabelle und der Mastertabelle recht einfach. Die Data Manipulation Language 36 In der Northwind-Datenbank würden Sie alle Bestellungen des Kunden mit dem Kunden-Code "ALFKI" z. B. folgendermaßen löschen: DELETE FROM [Order Details] WHERE OrderID IN (SELECT OrderID FROM Orders WHERE CustomerID = "ALFKI") DELETE FROM Orders WHERE CustomerID = "ALFKI" 0 Der SQL Server erweitert die DELETE-Anweisung um einige komplexe Features. Ich will die komplexe Syntax hier nicht beschreiben und verweise dazu auf die TSQL-Hilfe. Interessant ist, dass Sie beim SQL Server auch Abfragen einsetzen können, die mehrere Tabellen über Joins miteinander in Beziehung setzen. Da Sie jedoch immer nur aus einer Tabelle löschen können, verwenden Sie eine erweiterte Syntaxvariante: DELETE {Tabelle1 | Sicht1} [FROM {Tabelle1 | Sicht1} {INNER | LEFT [OUTER] | RIGHT [OUTER] | FULL [OUTER]} JOIN {Tabelle2 | Sicht2} ON {Tabelle1 | Sicht1}.Schlüsselspalte = {Tabelle2 | Sicht2}.Schlüsselspalte] Joins werden in Abschnitt 4.4.13 ausführlich erläutert. Mit dieser Syntax löschen Sie aus Tabelle1 alle Datensätze, die der Bedingung entsprechen. Das Besondere dabei ist die Verwendung eines Joins, damit Sie auch Spalten in der Bedingung aufnehmen können, die in anderen verknüpften Tabellen gespeichert sind. Diese Variante wird zwar selten eingesetzt, ist aber oft die einzige Möglichkeit, Datensätze aus verknüpften Tabellen zu löschen. Wären die Tabellen Order Details und Orders z. B. über mehr als ein Datenfeld miteinander verknüpft, wäre die Abfrage mit dem IN-Operator nicht mehr möglich12. 12 Der theoretische Ansatz, alle Bestellungen des Kunden mit dem Kunden-Code "ALFKI" aus dem unteren Beispiel über DELETE FROM Bestelldetails WHERE (Lieferschein_Nr IN (SELECT Lieferschein_Nr FROM Bestellungen WHERE Kunden_Nr = "ALFKI")) AND (Lieferschein_Jahr IN (SELECT Lieferschein_Jahr FROM Bestellungen WHERE Kunden_Nr = "ALFKI")) zu löschen, würde dazu führen, dass auch Bestellungen anderer Kunden gelöscht werden, nämlich die, bei denen eine Lieferschein-Nummer-/Lieferschein-Jahr-Kombination aus den beiden Listen zufällig zutrifft. Wenn z.B. für den Kunden "ALFKI" zwei Bestellungen mit der Kombination 1000-98 und 1001-99 gespeichert sind und für den Kunden "ANATR" sind zwei Bestellungen mit der Kombination 1001-98 und 1002-99 gespeichert, wird die Bestellung 1001-98 dieses Kunden gelöscht, weil die LieferscheinNummer und das Lieferschein-Jahr in den über die SELECT-Statements erzeugten Listen vorkommen. Die Data Manipulation Language 37 Eine, der Northwind-Datenbank ähnliche Datenbank könnte z. B. aufgebaut sein wie in Abbildung 2). Abbildung 2: Bestelldatenbank mit Referenz über zwei Datenfelder Hier müssen Sie die erweiterte Syntax der DELETE-Anweisung mit einem JOIN verwenden, wenn Sie alle Bestellungen des Jahres 1996 löschen wollen: Beispiel: Löschen der Bestelldetails des Jahres 1996: DELETE Bestelldetails FROM Bestelldetails INNER JOIN Bestellungen ON (Bestelldetails.Lieferschein_Nr = Bestellungen.Lieferschein_Nr AND Bestelldetails.Lieferschein_Jahr = Bestellungen.Lieferschein_Jahr) WHERE YEAR(Bestellungen.Rechnungsdatum) = 1996 Beispiel: Löschen der Bestellungen des Jahres 1996: DELETE FROM Bestellungen WHERE YEAR(Bestellungen.Rechnungsdatum) = 1996 53 ; 0/ ) #$ Die Werte einzelner Spalten können Sie in SQL/92 mit der UPDATE-Anweisung ändern: UPDATE {Tabelle | Sicht} SET Spalte = {Ausdruck | NULL | DEFAULT} [,...n] [WHERE Bedingung] Die zu ändernden Datensätze werden wie beim Löschen über eine WHERE-Klausel eingeschränkt. Der Ausdruck für den neuen Wert muss lediglich einen für die Spalte gültigen Datentyp enthalten und kann Konstanten, Berechnungen und Funktionen enthalten. Beispiel: Erhöhen der Artikelpreise der Artikel, die vom Lieferanten mit der ID 1 geliefert werden, um 10%: UPDATE Products SET UnitPrice = UnitPrice * 1.1 WHERE SupplierID = 1 Sie können in einem Schritt gleich mehrere Spalten ändern. Beispiel: Ändern des Lieferanten von 1 auf 2 mit gleichzeitiger Erhöhung der Artikelpreise um 10%: UPDATE Products SET SupplierID = 2, UnitPrice = UnitPrice * 1.1 WHERE SupplierID = 1 Etwas schwieriger ist das Aktualisieren von Spalten nach den Daten aus einer anderen Tabelle. Wenn z. B. alle Artikelpreise der Tabelle Order Details auf den aktuellen Stand gebracht Die Data Manipulation Language 38 werden sollen (z. B. nach einer Änderung der Artikelpreise), können Sie eine Unterabfrage verwenden: UPDATE [Order Details] SET UnitPrice = (SELECT UnitPrice FROM Products WHERE Products.ProductID = [Order Details].ProductID) Die Unterabfrage wird für jeden Datensatz der Order Details-Tabelle ausgeführt und ergibt genau einen Wert, nämlich den aktuellen Einzelpreis des Artikels. Anmerkung: In der Praxis können Sie solche Abfragen natürlich nur für Bestellungen ausführen, für die noch keine Rechnung gedruckt wurde. Die Tabelle Orders sollte dazu eine zusätzliche Spalte Bill_Printed oder ähnlich besitzen, die Sie in die Abfrage einbeziehen können. Die Abfrage oben würde dann noch komplexer werden. Dazu ein ähnlich gelagertes Beispiel: Anpassen nur der Einzelpreise der Bestellungen des Kunden ALFKI: UPDATE [Order Details] SET UnitPrice = (SELECT UnitPrice FROM Products WHERE Products.ProductID = [Order Details].ProductID) WHERE [Order Details].OrderID IN (SELECT OrderID FROM Orders WHERE CustomerID = 'ALFKI') 0 Die UPDATE-Anweisung des SQL Servers basiert auf der SQL/92-Syntax und ist wieder um einiges komplexer und wird an dieser Stelle nur vereinfacht beschrieben. Interessant ist, dass Sie wie beim Löschen auch Joins und die Funktionen OPENQUERY und OPENROWSET verwenden können: UPDATE {Tabelle1 | Sicht1 | Datensatzgruppen_Funktion} SET Spalte = {Ausdruck | NULL | DEFAULT} [,...n] [FROM {Tabelle1 | Sicht1} {INNER | LEFT [OUTER] | RIGHT [OUTER] | FULL [OUTER]} JOIN {Tabelle2 | Sicht2} ON {Tabelle1 | Sicht1}.Schlüsselspalte = {Tabelle2 | Sicht2}.Schlüsselspalte] [WHERE Bedingung] Das folgende Beispiel aktualisiert die Spalte Notes der Mitarbeiter (Employees), die 2002 eine Bestellung aufgenommen haben: UPDATE Employees SET Notes = 'Hat 2002 Bestellungen erfasst' FROM Employees INNER JOIN Orders ON Employees.EmployeeID = Orders.EmployeeID WHERE Year(Orders.OrderDate) = 2002 Wie so oft können Sie dieses Beispiel auch anders programmieren. So können Sie statt dem Join z. B. eine Unterabfrage verwenden: UPDATE Employees SET Notes = 'Hat 2002 Bestellungen erfasst' WHERE EmployeeID IN (SELECT DISTINCT EmployeeID FROM Orders WHERE Year(OrderDate) = 2002) Welche dieser beiden Varianten schneller ausgeführt wird, müssen Sie im Einzelfall testen. Im Normalfall müsste die Join-Variante schneller ausgeführt werden. Beachten Sie die Möglichkeit, Daten aus anderen Datenbanken über die OPENQUERY-Funktion ändern zu können. Ein ähnliches Beispiel dazu finden Sie beim Hinzufügen von Datensätzen. Die Data Manipulation Language 39 55 0/ ) 55 Ist die Datenbank einmal erzeugt und gefüllt, stellt das Abfragen von Datensätzen die wohl wichtigste Aufgabe dar. Die für diese „Auswahlabfragen“ verwendete SELECT-Anweisung ist wohl die komplizierteste Anweisung in SQL. Mit einer Auswahlabfrage können Sie in der Regel fast jede, auch noch so komplizierte Fragestellung lösen, Sie müssen lediglich wissen, wie . Eine Auswahlabfrage ergibt immer eine temporäre Tabelle mit Datensätzen, deren Spalten aus einzelnen Datensätzen einer oder mehrerer Basistabellen oder Sichten zusammengesetzt sind. Diese Ergebnistabelle können Sie in der Regel genauso bearbeiten, wie eine Basistabelle. Beachten Sie, dass Auswahlabfragen in der Regel nicht in einem Abfragetool, wie dem Query Analyzer des SQL Server, geschrieben werden, sondern im Programmcode einer ClientAnwendung. Schreiben Sie z. B. eine Visual Basic-Anwendung, die auf die Tabellen eines SQL Servers zurückgreifen soll, erzeugen Sie über ADO13 ein sogenanntes Recordset (eine „Datensatzgruppe“) und übergeben beim Öffnen des Recordsets eine SELECT-Anweisung. Das Recordset enthält nach dem Öffnen alle Datensätze, die die SELECT-Anweisung zurückgibt. Normalerweise können Sie mit dem Ergebnis solcher Abfragen in Client-Anwendungen alles machen, was die im Client verwendete Datenzugriffstechnologie (bei Visual Basic ist das ADO) erlaubt. Einige Datenzugriffstechnologien, wie z. B. die von Borland Delphi verwendete BDE14 lassen allerdings nicht zu, dass die Ergebnisse von Abfragen, die auf mehreren Basistabellen basieren, editiert werden können (die Abfrage ist dann keine „Life“-Datenmenge). Mit einer Auswahlabfrage können Sie nicht nur Spalten aus einer oder mehreren Tabellen auswählen und darstellen, sondern auch Spalten berechnen und mit Hilfe von Funktionen Werte, wie z. B. eine Summe berechnen. Das Ganze wird Ihnen weiter unten ausführlich erläutert. Auswahlabfragen werden grundsätzlich unterteilt in Restriktionen, Projektionen und Verknüpfungen. #$ In SQL/92 besitzt die SELECT-Anweisung die folgende Form: SELECT [{ALL | DISTINCT}] Spaltenliste [INTO Neue_Tabelle] FROM Tabellenliste [WHERE Bedingungsausdruck] [[GROUP BY Spaltenliste] [HAVING Bedingungsausdruck]] [ORDER BY Spaltenliste]; Die Reihenfolge der Klauseln muss eingehalten werden, auch wenn optionale Teile weggelassen werden. Die einzelnen Klauseln werden im Folgenden ausführlich der Reihe nach erläutert. 0 Der SQL Server erweitert die SELECT-Anweisung in der Grundform fast gar nicht. Lediglich die einzelnen Klauseln sind (teilweise massiv) erweitert. Die zusätzlichen Features werden in der TSQL-Hilfe zur SELECT-Anweisung beschrieben. 13 Die ActiveX Data Objects (ADO) basieren auf OLE DB und stellen ein einfaches Objektmodell zum Zugriff auf verschiedene Datenbanken dar. 14 BDE = Borland Database Engine Die Data Manipulation Language 40 55 / Die Restriktion (auch „Auswahl“, „Selektion“ oder „Einschränkung“) ergibt eine Teilmenge von Zeilen einer Tabelle. Im einfachsten Fall verwenden Sie eine Anweisung der folgenden Form: SELECT * FROM Tabelle; Der Asterisk (*) steht für „Alle Datenfelder“. Das Beispiel: SELECT * FROM Categories; ergibt also alle Datensätze der Basistabelle in derselben Reihenfolge, wie diese in der Basistabelle gespeichert sind und mit allen Datenfeldern: CategoryID CategoryName Description Picture 1 Beverages Soft drinks, coffees, teas, beers, and ales 2 Condiments Sweet and savory sauces, relishes, spreads, and seasonings 3 Confections Desserts, candies, and sweet breads 4 Dairy Products Cheeses 5 Grains/Cereals Breads, crackers, pasta, and cereal 6 Meat/Poultry Prepared meats 7 Produce Dried fruit and bean curd 8 Seafood Seaweed and fish 553 ? /@ / Die Projektion ergibt ebenfalls eine Teilmenge von Zeilen einer Tabelle, wobei jedoch nur bestimmte Spalten oder Ergebnisse von Berechnungen bzw. Funktionen mit in das Ergebnis übernommen werden. Im einfachsten Fall geben Sie die benötigten Spalten statt dem Asterisk an, wobei die einzelnen Spalten durch Kommata voneinander getrennt werden: SELECT Spalte1, Spalte2, Spalte3 [,...n] FROM Tabelle; Beispiel: SELECT CategoryName, Description FROM Categories; Ergebnis: CategoryName Description Beverages Soft drinks, coffees, teas, beers, and ales Condiments Sweet and savory sauces, relishes, spreads, and seasonings Confections Desserts, candies, and sweet breads Dairy Products Cheeses Grains/Cereals Breads, crackers, pasta, and cereal Meat/Poultry Prepared meats Produce Dried fruit and bean curd Seafood Seaweed and fish Sie können in der Spaltenliste der Projektion auch eine Funktion oder eine Berechnung einsetzen. Näheres dazu finden Sie unter Funktionen in Abschnitt 4.4.9. Die Data Manipulation Language 41 555 . Wenn Sie den Namen eines Datenfeldes im Ergebnis einer Projektion ändern wollen, oder das Ergebnis einer Berechnung mit einem Spaltennamen versehen wollen, verwenden Sie die ASKlausel. Beispiel: Umbenennen der Spalten ShipperID und CompanyName: SELECT ShipperID AS ID, CompanyName AS Name, Phone FROM Shippers; Ergebnis: ID Name Phone 1 Speedy Express (503) 555-9831 2 United Package (503) 555-3199 3 Federal Shipping (503) 555-9931 Beispiel: Benennen einer berechneten Spalte: SELECT (Quantity * UnitPrice) AS Sum FROM [Order Details] Ergebnis: Sum 168.00 98.00 174.00 167.40 ... : + + A " 0/ 7 + Manche Datenbanksysteme wie Access oder der SQL Server erlauben Feld- und Tabellennamen, die normalerweise nicht verwendbare Zeichen, wie z. B. das Leerzeichen oder Bindestriche enthalten. Ist dies der Fall, müssen Sie den Feld- oder Tabellennamen in spezielle Zeichen einbetten. In Access und beim SQL Server ist dies z. B. die eckige Klammer. Beispiele: SELECT * FROM Orders; SELECT * FROM [Order Details]; Die Data Manipulation Language 42 556 * ) Sie können die Ergebniszeilen einer Auswahlabfrage einschränken, indem Sie in der WHEREKlausel eine Bedingung angeben, die bei der Abfrage der Basistabellen auf diese angewendet wird. Eine einfache SELECT-Anweisung mit WHERE-Klausel hat die folgende Form: SELECT Spaltenliste FROM Tabelle WHERE Bedingung; Beispiel: SELECT CompanyName FROM Customers WHERE City = 'Madrid'; Ergebnis: CompanyName Bólido Comidas preparadas FISSA Fabrica Inter. Salchichas S.A. Romero y tomillo Die WHERE-Klausel ist grundsätzlich die erste Klausel in der SELECT-Anweisung wenn noch andere Klauseln verwendet werden (z. B. ORDER BY zum Sortieren). Gültige Operatoren für die WHERE-Klausel sind: =, <, >, <=, >=, AND, OR, NOT, IS, IN, LIKE, MATCHES, BETWEEN Einzelne Bedingungen können Sie mit AND und OR verknüpfen. Das folgende Beispiel fragt alle Produkte ab, die der Kategorie 2 angehören und deren Einzelpreis >= 25 ist: SELECT ProductName, UnitPrice FROM Products WHERE CategoryID = 2 AND UnitPrice >= 25; Zur besseren Übersicht und um Fehler zu vermeiden, können Sie die Bedingungen auch klammern: SELECT ProductName, UnitPrice FROM Products WHERE (CategoryID = 2) AND (UnitPrice >= 25); Das Klammern ist besonders wichtig, wenn Sie AND, OR und/oder NOT gleichzeitig verwenden, da bei einer Abfrage ohne Klammerung die Priorität dieser Operatoren die Auswertung bestimmt (NOT wird vor AND ausgewertet und AND vor OR). Mit der Priorität hab ich selbst oft so meine Probleme. Wenn Sie z. B. alle Produkte ermitteln wollen, die zur Kategorie 2 oder 3 gehören und einen Preis >= 25 haben, können Sie die Abfrage nicht folgendermaßen definieren: SELECT ProductName, CategoryID, UnitPrice FROM Products WHERE CategoryID = 2 OR CategoryID = 3 AND UnitPrice >= 25 Das Ergebnis ist falsch, da AND vor OR ausgewertet wird, was der folgenden Klammerung entspricht: SELECT ProductName, CategoryID, UnitPrice FROM Products WHERE (CategoryID = 2) OR ((CategoryID = 3) AND (UnitPrice >= 25)); Die Data Manipulation Language 43 Das falsche Ergebnis: ProductName CategoryID UnitPrice Aniseed Syrup 2 10.00 Chef Anton' s Cajun Seasoning 2 22.00 Chef Anton' s Gumbo Mix 2 21.35 Grandma' s Boysenberry Spread 2 25.00 Northwoods Cranberry Sauce 2 40.00 Genen Shouyu 2 15.50 Sir Rodney' s Marmalade 3 81.00 Gumbär Gummibärchen 3 31.23 Schoggi Schokolade 3 43.90 Gula Malacca 2 19.45 Sirop d' érable 2 28.50 Tarte au sucre 3 49.30 Vegie-spread 2 43.90 Louisiana Fiery Hot Pepper Sauce 2 21.05 Louisiana Hot Spiced Okra 2 17.00 Original Frankfurter grüne Soße 2 13.00 Sie müssen hier klammern um ein korrektes Ergebnis zu erzielen: SELECT ProductName, CategoryID, UnitPrice FROM Products WHERE (CategoryID = 2 OR CategoryID = 3) AND (UnitPrice >= 25); Das korrekte Ergebnis: ProductName CategoryID UnitPrice Grandma' s Boysenberry Spread 2 25.00 Northwoods Cranberry Sauce 2 40.00 Sir Rodney' s Marmalade 3 81.00 Gumbär Gummibärchen 3 31.23 Schoggi Schokolade 3 43.90 Sirop d' érable 2 28.50 Tarte au sucre 3 49.30 Vegie-spread 2 43.90 Die Data Manipulation Language 44 "" !- Bei der Abfrage werden Zeichenketten in einfache Anführungszeichen eingeschlossen. Zahlwerte werden direkt angegeben: SELECT CompanyName FROM Customers WHERE City = 'Madrid'; SELECT ProductName FROM Products WHERE SupplierID = 2; Sie können Klammern verwenden, um die Priorität der Operatoren zu verschieben. Die Reihenfolge der logischen Operatoren ist: NOT wird vor AND; AND wird vor OR ausgewertet. Sie müssen bei der Verwendung von Dezimalzahlen die Ländereinstellung des Systems beachten, auf dem die Datenbank installiert ist. Bei Desktop-Datenbanken ist dies in der Regel die Einstellung in der Systemsteuerung des Client-Rechners (der ja bei Desktop-Datenbanken die Abfrage ausführt). Bei Client/Server-Datenbanken wird die Ländereinstellung oft im Datenbank-Server unabhängig von der Systemeinstellung vorgenommen. Ist beim SQL Server z. B. die englische Sprache eingestellt, müssen Sie den Punkt als Dezimaltrennzeichen verwenden: Select ProductName FROM Products WHERE UnitPrice = 21.35; Datumswerte führen immer zu Problemen. Die Syntax, wie Datumswerte angegeben werden, unterscheidet sich erheblich zwischen den einzelnen Datenbanksystemen. Bei Access müssen Sie z. B., unabhängig von der Spracheinstellung, ein Datum immer in Hashs (#) eingebettet in der englischen Syntax (mm/dd/yyyy) angeben. Beim SQL Server können Sie ein Datum sprachunabhängig idealerweise als String in der Form ' yyyymmdd'angeben. Das folgende Beispiel zeigt eine Datumsabfrage in Microsoft Access (alle Bestellungen vom 11.07.1996): SELECT * FROM Orders WHERE OrderDate = #07/11/96#; Eine Datumsabfrage im SQL Server (alle Bestellungen vom 11.07.1996) sieht etwas anders aus: SELECT * FROM Orders WHERE OrderDate = '19960711'; Bei Datumsabfragen müssen Sie immer beachten, dass eine Datumsspalte in den meisten Datenbanksystemen auch eine Zeit speichern kann. Wenn Sie nach dem 01.07.1996 suchen, meinen Sie eigentlich 01.07.1996 00:00:00. Wenn Datensätze zwar den 01.07.1996 im Datumsfeld gespeichert haben, dies jedoch mit einer Zeitangabe, werden Sie diese Datensätze nicht unbedingt finden, wenn Sie mit einer einfachen Vergleichsbedingung suchen. Um das Problem zu lösen, können Sie den Datumswert einfach in einen Vergleich der folgenden Form einschließen: (Datumsspalte >= Erster_Suchtag) AND (Datumsspalte < Letzter_Suchtag + 1) Das folgende SQL-Server-Beispiel ermittelt alle Bestellungen vom 11.07.1996: SELECT * FROM Orders WHERE (OrderDate >= '19960711') AND (OrderDate < '19960712'); Alternativ können Sie die SQL/92-Funktionen DAY, MONTH und YEAR verwenden, um einen Datumsbereich zu filtern: SELECT * FROM Orders WHERE (DAY(OrderDate) = 7) AND (MONTH(OrderDate) = 11) AND (YEAR(OrderDate) = 1996); Die Data Manipulation Language 45 & * Wenn eine gesuchte Zeichenkette nicht in der genauen Schreibweise bekannt ist, können Sie diese Zeichenkette mit Wildcards suchen. SQL/92 kennt dazu den LIKE-Operator: WHERE Ausdruck LIKE 'Muster' Im Muster können Sie die Wildcards % und _ verwenden. Das Prozentzeichen steht für „Kein oder mehrere beliebige Zeichen“, der Unterstrich steht für „genau ein beliebiges Zeichen“. Das folgende Beispiel ermitttelt alle Kunden, deren Name ‚tru‘ enthält: SELECT * FROM Customers WHERE CompanyName LIKE '%tru%'; Der SQL Server erweitert die Wildcards des LIKE-Operators um eine in eckigen Klammern eingesetzte Zeichenliste. Diese Zeichenliste funktioniert ähnlich dem Unterstrich-Wildcard, mit dem Unterschied, dass Sie die gültigen Zeichen in der Form abcdef oder als Liste in der Form af angeben. Beispiel: Suchen aller Kunden, deren Name mit ‚A‘ beginnt, dann ein ‚n‘ oder ‚r‘ folgt und beliebig weitergeht: SELECT * FROM Customers WHERE CompanyName LIKE 'A[nr]%'; Wenn Sie Zeichen vom Suchergebnis ausschließen wollen, stellen Sie ein ^ vor dieses Zeichen. Beispiel: Suchen aller Kunden, deren Name mit ‚A‘ beginnt und dann nicht mit einem ‚n‘ weitergeht: SELECT * FROM Customers WHERE CompanyName LIKE 'A[^n]%'; Sie können den LIKE-Operator auch mit Nicht-Textfeldern verwenden, wenn Sie diese explizit in einen Textdatentyp umwandeln. Die meisten Datenbanken bieten dazu spezielle Funktionen. Das folgende SQL-Server –Beispiel ermittelt alle Produkte mit einem Preis, der mit .35 endet SELECT * FROM Products WHERE CONVERT(varChar, UnitPrice) LIKE '%.35'; & * Wenn Sie einen Wert in einer festgelegten Menge suchen, können Sie den IN-Operator verwenden: WHERE Datenfeld IN (Wertliste) Die folgende Anweisung sucht alle Kunden, die in Berlin oder Mannheim ansässig sind: SELECT CustomerID, CompanyName, City FROM Customers WHERE City IN ('Berlin', 'Mannheim'); Ergebnis: CustomerID CompanyName City ALFKI Alfreds Futterkiste Berlin BLAUS Blauer See Delikatessen Mannheim Die Wertliste kann auch über eine Unterabfrage (siehe Abschnitt 4.4.11) ermittelt werden. Die Unterabfrage darf dabei nur eine Spalte ergeben. Das folgende Beispiel ermittelt alle Kunden, die im Jahr 1997 bestellt haben: SELECT CompanyName FROM Customers WHERE CustomerID IN (SELECT CustomerID FROM Orders WHERE YEAR(OrderDate) = 1997) Die Data Manipulation Language 46 , * * Wenn Sie nach einem Wert innerhalb eines bestimmten Bereichs suchen, können Sie den BETWEEN-Operator verwenden: WHERE Ausdruck BETWEEN Wert1 AND Wert2 So können Sie z. B. alle Produkte ermitteln, deren Einzelpreis zwischen 12 und 15 liegt (inkl. der 12 und der 15!): SELECT ProductID, ProductName, UnitPrice FROM Products WHERE UnitPrice BETWEEN 12 AND 15; Ergebnis: ProductID ProductName UnitPrice 25 NuNuCa Nuß-Nougat-Creme 14.00 31 Gorgonzola Telino 12.50 34 Sasquatch Ale 14.00 42 Singaporean Hokkien Fried Mee 14.00 46 Spegesild 12.00 48 Chocolade 12.75 58 Escargots de Bourgogne 13.25 67 Laughing Lumberjack Lager 14.00 68 Scottish Longbreads 12.50 70 Outback Lager 15.00 73 Röd Kaviar 15.00 77 Original Frankfurter grüne Soße 13.00 - * Wenn Sie Datensätze mit leeren Spalten finden wollen, so müssen Sie, da in leeren Spalten der Wert NULL abgelegt ist, mit NULL vergleichen. Sie können für den Vergleich jedoch nicht den Vergleichsoperator (=) verwenden! Um NULL-Werte zu finden, verwenden Sie den ISOperator: WHERE Ausdruck IS [NOT] NULL Wenn Sie in einem Textfeld suchen, kann es sein, dass in diesem Feld eine leere Zeichenkette gespeichert ist. Eine leere Zeichenkette ist ungleich NULL! Sie müssen also in Textfeldern u. U. auch mit einer leeren Zeichenkette vergleichen. So können Sie z. B. herausfinden, bei welchen Kunden keine Region gespeichert ist: SELECT CustomerID, CompanyName, Region FROM Customers WHERE (Region IS NULL) OR (Region = '') Die Data Manipulation Language 47 Ergebnis: CustomerID CompanyName Region ALFKI Alfreds Futterkiste ANATR Ana Trujillo Emparedados y helados ANTON Antonio Moreno Taquería AROUT Around the Horn BERGS Berglunds snabbköp BLAUS Blauer See Delikatessen ... ... - ... 8 0 Der SQL Server ermöglicht über die Funktionen OPENQUERY und OPENROWSET auch in externen Datenbanken zu suchen. Diese beiden Funktionen habe ich bereits grundsätzlich bei Anfügen von Datensätzen in Abschnitt 4.1 beschrieben. Beispiel: Suchen aller Autoren aus der Datenbank pubs im SQL Server Arthur: SELECT a.* FROM OPENROWSET('SQLOLEDB','Arthur';'sa';'', 'SELECT * FROM pubs.dbo.authors ORDER BY au_lname, au_fname') AS a 559 " * * In Projektionen können Sie die Operatoren +, -, *, / und () verwenden um mit Spalten zu rechnen. Die Anwendung dieser Operatoren unterscheidet sich nicht von der allgemeinen mathematischen Anwendung. Das folgende Beispiel berechnet Zwischensummen für eine Teilbestellung: SELECT OrderID, Quantity * UnitPrice AS Amount FROM [Order Details] WHERE OrderID = 10248 Ergebnis: OrderID Amount 10248 168.00 10248 98.00 10248 174.00 Die Data Manipulation Language 48 55< / Das Ergebnis einer SELECT-Abfrage können Sie mit der ORDER BY-Klausel sortieren: ORDER BY Spalte [{ASC|DESC}] [,...n] Wenn Sie die Option ASC oder nichts angeben wird diese Spalte aufsteigend sortiert, bei DESC wird absteigend sortiert. Wenn Sie eine Untersortierung erreichen wollen, geben Sie die weiteren Spalten durch Kommata getrennt an. Das folgende Beispiel ergibt eine nach Lieferant und Artikel-Nr sortierte Artikel-Tabelle: SELECT SupplierID , ProductName FROM Products ORDER BY SupplierID, ProductName Ergebnis: SupplierID ProductName 1 Aniseed Syrup 1 Chai 1 Chang 2 Chef Anton' s Cajun Seasoning 2 Chef Anton' s Gumbo Mix 2 Louisiana Fiery Hot Pepper Sauce 2 Louisiana Hot Spiced Okra 3 Grandma' s Boysenberry Spread 3 Northwoods Cranberry Sauce 3 Uncle Bob' s Organic Dried Pears ... ... 55= ( -- Das Gruppieren ist ein wichtiges Hilfsmittel zur Erleichterung von Berechnungen über gespeicherte Daten. Mit einer Gruppierung können Sie z. B. die Summe aller Umsätze der einzelnen Kunden in einem bestimmten Zeitraum ermitteln. Sie gruppieren Datensätze mit der GROUP BY-Klausel: GROUP BY Spaltenliste [HAVING Bedingung] Die GROUP BY Klausel führt dazu, dass für alle Datensätze, die in Bezug auf die gruppierten Spalten den gleichen Inhalt aufweisen, nur noch ein einziger Datensatz angezeigt wird. GROUP BY wird verwendet, wenn für nach bestimmten Datenfeldern gruppenweise zusammengefasste Datensätze Berechnungen durchgeführt werden sollen oder Sie einfach einen Überblick über Datensätze mit gleichen Feldwerten erhalten wollen. Beispielsweise wird hiermit in der Customers-Tabelle die Anzahl der Kunden ermittelt, die in den einzelnen Orten ansässig sind. Da die GROUP BY Klausel eine Gruppe von Datensätzen zu einem einzigen Datensatz zusammenfaßt, können Sie nur die Spalten, nach denen Sie gruppieren, in der Projektion direkt angeben. Alle weiteren Spalten können Sie nur als Argument einer Aggregatfunktion (siehe weiter unten) einsetzen. Die Funktion bezieht sich dann immer auf die Datensätze, die in der Gruppe zusammengefasst werden. Die Data Manipulation Language 49 Das folgende Beispiel ermittelt alle in der Artikeltabelle gespeicherten Lieferanten (unabhängig davon, ob der Lieferant auch in der Lieferanten-Tabelle existiert) mit der Anzahl der Artikel, die der jeweilige Lieferant liefert: SELECT SupplierID, COUNT(*) AS Quantity FROM Products GROUP BY SupplierID; Ergebnis: SupplierID Quantity 1 3 2 4 3 3 4 3 ... ... * ) ( -- Mit der HAVING-Klausel können Sie die Ausgabe der gruppierten Datensätze einschränken: HAVING Bedingung HAVING legt Bedingungen fest, die auf eine Gruppe anzuwenden sind. Die Bedingung wird genauso formuliert, wie bei der WHERE-Klausel. Das folgende Beispiel ermittelt alle Lieferanten, die mindestens fünf Artikel liefern: SELECT SupplierID, COUNT(*) AS Quantity FROM Products GROUP BY SupplierID HAVING COUNT(*) >= 5 Ergebnis: SupplierID Quantity 7 5 12 5 HAVING wird erst angewendet, nachdem die Datensätze gruppiert wurden. Wenn Sie die Datensätze vor Ausführung der Gruppierung einschränken wollen, wenden Sie die WHERE-Klausel an. Stellen Sie sich den Vorgang so vor: Das Datenbanksystem schreibt zuerst alle Datensätze, die der WHERE-Klausel entsprechen, in eine temporäre Ergebnistabelle. Auf diese Tabelle wird danach erst die Gruppierung angewendet und eine weitere temporäre Ergebnistabelle erzeugt. Auf diese wird schließlich die HAVING-Klausel und die eventuell noch vorhandene ORDER BY-Klausel angewendet. Wenn Sie Datensätze nicht nach einem Ergebnis der Gruppierung (z. B. einer Summe oder einer Anzahl) einschränken wollen, sondern nach ganz normalen Datenfeldern, sollten Sie immer die WHERE-Klausel einsetzen. Da das Gruppieren wesentlich mehr Zeit in Anspruch nimmt als das Auswählen bestimmter Datensätze, ist eine gruppierte SQL-Abfrage, die Daten über WHERE filtert, wesentlich schneller als eine, die die bereits gruppierten Daten mit HAVING filtert. Die Data Manipulation Language 50 Das folgende Beispiel ermittelt die Anzahl der Produkte der Lieferanten, die Produkte der Kategorie 1 liefern: SELECT SupplierID, COUNT(*) AS Quantity FROM Products WHERE CategoryID = 1 GROUP BY SupplierID Ergebnis: SupplierID Quantity 1 2 7 1 10 1 12 1 16 3 18 2 20 1 23 1 Alternativ könnten Sie auch mit HAVING filtern, wenn Sie zusätzlich nach CategoryID gruppieren: SELECT SupplierID, COUNT(*) AS Quantity FROM Products GROUP BY SupplierID, CategoryID HAVING CategoryID = 1 Die erste Variante mit der WHERE-Klausel besitzt jedoch Vorteile in Bezug auf die Ausführungsgeschwindigkeit, da das Gruppieren immer wesentlich länger dauert als eine WHERE-Abfrage. Bei Abfragen über mehrere verknüpfte Tabellen ist die Anwendung der WHERE-Klausel oft auch aus logischer Sicht notwendig. - ( BC? ,D B- / " 0 Der SQL Server kennt die Optionen CUBE und ROLLUP, die bei der GROUP BY-Klausel angewendet werden können: GROUP BY Feldnamen WITH CUBE GROUP BY Feldnamen WITH ROLLUP Mit CUBE und ROLLUP können Sie Zwischen- und Endergebnisse für berechnete Spalten ermitteln. Beide Optionen fügen der Ergebnisliste Datensätze an, die Summenwerte enthalten. Diese zusätzlichen Datensätze enthalten in den Spalten, für die die Summe berechnet wurde, den Wert NULL. Das folgende Beispiel berechnet die Anzahl der Artikel in den einzelnen Kategorien: SELECT CategoryId, COUNT(*) AS Quantity FROM Products GROUP BY CategoryID WITH ROLLUP Die Data Manipulation Language 51 Ergebnis: SupplierId Quantity 1 12 2 12 3 13 4 10 5 7 6 6 7 5 8 12 NULL 77 Wenn Sie die Aggregatfunktion GROUPING verwenden, können Sie besser erkennen, welche Datensätze von CUBE oder ROLLUP angefügt wurden. GROUPING(Datenfeld) AS Spaltenname ergibt eine zusätzliche Spalte, die den Wert 1 enthält, wenn ein Datensatz eine Summe für das Datenfeld enthält und 0, wenn nicht: SELECT SupplierID, CategoryID, COUNT(*) AS Quantity, GROUPING(CategoryID) AS IsSumRecord FROM Products GROUP BY SupplierID, CategoryID WITH ROLLUP Ergebnis: SupplierID CategoryID Quantity IsSumRecord 1 1 2 0 1 2 1 0 1 NULL 3 1 2 2 4 0 2 NULL 4 1 ... ... ... ... NULL NULL 77 1 Der Unterschied zwischen CUBE und ROLLUP ist, dass ROLLUP nur die Summen der Gruppenfelder in hierarchischer Form ergibt. CUBE bildet zusätzlich Summen für alle anderen Kombinationen von Feldern. Beispiel: Ermitteln der Bestellsummen für einzelne Städte, Kunden und Kategorien: SELECT Customers.City, Customers.CompanyName, Products.CategoryID, Sum([Order Details].[UnitPrice]*[Order Details].[Quantity]) AS Amount FROM Orders, [Order Details], Products, Customers WHERE Orders.OrderID = [Order Details].OrderID AND Orders.CustomerID = Customers.CustomerID AND [Order Details].ProductID = Products.ProductID GROUP BY Customers.City, Customers.CompanyName, Products.CategoryID WITH ROLLUP Die Data Manipulation Language 52 Ergebnis (Erläuterungen hinzugefügt): City CompanyName CategoryID Amount Aachen Drachenblut Delikatessen 1 247.20 Aachen Drachenblut Delikatessen 3 374.76 Aachen Drachenblut Delikatessen 4 2270.00 Aachen Drachenblut Delikatessen 6 656.00 Aachen Drachenblut Delikatessen 8 215.25 Aachen Drachenblut Delikatessen NULL 3763.21 Summe für Drachenblut Delikatessen Aachen NULL NULL 3763.21 Summe für Aachen Albuquerque Rattlesnake Grocery Canyon 1 19334.90 Albuquerque Rattlesnake Grocery Canyon 2 1690.00 Albuquerque Rattlesnake Grocery Canyon 3 11369.70 Albuquerque Rattlesnake Grocery Canyon 4 8056.40 Albuquerque Rattlesnake Grocery Canyon 5 4833.30 Albuquerque Rattlesnake Grocery Canyon 6 3830.80 Albuquerque Rattlesnake Grocery Canyon 7 2213.25 Albuquerque Rattlesnake Grocery Canyon 8 917.55 Albuquerque Rattlesnake Grocery Canyon NULL Albuquerque NULL NULL ... ... ... Erläuterung 52245.90 Summe für Rattlesnake Canyon Grocery 52245.90 Summe für Albuquerque Buenos Aires Cactus Comidas para 1 llevar 1091.00 Buenos Aires Cactus Comidas para 3 llevar 75.00 Buenos Aires Cactus Comidas para 4 llevar 37.50 Buenos Aires Cactus Comidas para 7 llevar 364.80 Die Data Manipulation Language 53 Buenos Aires Cactus Comidas para 8 llevar Buenos Aires Cactus Comidas para NULL llevar 246.50 1814.80 Summe für Cactus Comidas para llevar Buenos Aires Océano Atlántico Ltda. 1 180.00 Buenos Aires Océano Atlántico Ltda. 2 622.00 Buenos Aires Océano Atlántico Ltda. 3 1311.00 Buenos Aires Océano Atlántico Ltda. 4 914.00 Buenos Aires Océano Atlántico Ltda. 7 403.20 Buenos Aires Océano Atlántico Ltda. 8 30.00 Buenos Aires Océano Ltda. Atlántico NULL 3460.20 Summe für Atlántico Ltda. Océano Buenos Aires Rancho grande 1 527.00 Buenos Aires Rancho grande 2 285.00 Buenos Aires Rancho grande 3 749.10 Buenos Aires Rancho grande 4 192.00 Buenos Aires Rancho grande 5 390.00 Buenos Aires Rancho grande 7 371.00 Buenos Aires Rancho grande 8 330.00 Buenos Aires Rancho grande NULL 2844.10 Summe für Rancho grande Buenos Aires NULL NULL 8119.10 Summe für Buenos Aires ... ... ... NULL NULL NULL 1354458.59 Gesamtsumme ROLLUP ermittelt lediglich die Summen für die Gruppenhierarchie. In diesem Beispiel ermittelt ROLLUP die folgenden Summen: • Summe für eine Stadt-Firma-Kombination • Summe für eine Stadt • Gesamtsumme CUBE ermittelt zusätzlich alle weiteren Summen für alle möglichen Spalten-Kombinationen. In diesem Beispiel ermittelt CUBE die folgenden Summen: • Summe für eine Stadt-Firma-Kombination • Summe für eine Stadta • Gesamtsumme • Summe für eine Firma-Kategorie-Kombination • Summe für eine Firma • Summe für eine Stadt-Kategorie-Kombination • Summe für eine Kategorie Die Data Manipulation Language 54 Zusätzlich zu der Ausgabe, die oben mit ROLLUP erreicht wurde, ermittelt CUBE die folgenden Datensätze (Auszug): City CompanyName CategoryID Amount NULL Alfreds Futterkiste 1 648.00 Summe für Alfreds Futterkiste, Kategorie 1 NULL Alfreds Futterkiste 2 1364.00 Summe für Alfreds Futterkiste, Kategorie 2 NULL Alfreds Futterkiste 4 1255.00 Summe für Alfreds Futterkiste, Kategorie 4 NULL Alfreds Futterkiste 7 775.20 Summe für Alfreds Futterkiste, Kategorie 7 NULL Alfreds Futterkiste 8 554.00 Summe für Alfreds Futterkiste, Kategorie 8 NULL Alfreds Futterkiste NULL NULL Ana Trujillo Emparedados y 1 helados ... ... NULL Ana Trujillo Emparedados y 8 helados NULL Ana Trujillo Emparedados y NULL helados ... ... ... Aachen NULL 1 Albuquerque NULL 1 Århus NULL 1 2436.00 Summe für Århus, Kategorie 1 Århus NULL 1 1485.70 Summe für Århus, Kategorie 1 Barcelona NULL 1 Barquisimeto NULL 1 ... ... ... NULL NULL 1 Albuquerque NULL 2 Anchorage NULL 2 Århus NULL 2 ... ... ... ... Erläuterung 4596.20 Summe für Alfreds Futterkiste 60.00 Summe für Ana Trujillo ..., Kategorie 1 ... ... 60.00 Summe für Ana Trujillo ..., Kategorie 8 1402.95 Summe für Ana Trujillo ... 247.20 Summe für Aachen, Kategorie 1 19334.90 Summe für Kategorie 1 90.00 Summe für Kategorie 1 669.00 Summe für Kategorie 1 Albuquerque, Barcelona, Barquisimeto, .. 286526.95 Summe für Kategorie 1 1690.00 Summe für Kategorie 2 720.15 Summe für Kategorie 2 Albuquerque, Anchorage, 3521.40 Summe für Århus, Kategorie 2 ... Die Data Manipulation Language 55 55$ 8 / Funktionen helfen Ihnen, Datensätze auszuwerten. Unterschieden werden Funktionen in: • Aggregatfunktionen (Funktionen, die einen Wert über mehrere Datensätze ermitteln) und • skalare Funktionen (Funktionen, die einen Wert für jeden Datensatz einzeln ermitteln). / Aggregatfunktionen werden, sofern die SQL-Anweisung keine GROUP BY-Klausel enthält, auf die gesamte Tabelle angewendet. Sie erhalten dann nur einen einzigen Ergebnisdatensatz. In diesem Fall darf keine Spalte in die Anweisung übernommen werden, die nicht Argument einer Funktion ist. Das Beispiel: SELECT SupplierID, MIN(UnitPrice) AS MinUnitPrice FROM Products; funktioniert nicht, da das Feld SupplierID nicht in eine Funktion eingeschlossen ist! Das Beispiel: SELECT MIN(UnitPrice) AS MinUnitPrice FROM Products; ergibt einen Datensatz, der den kleinsten Nettobetrag aller Datensätze der Tabelle beinhaltet: MinUnitPrice 2.50 Wenn Sie mit der GROUP BY-Klausel nach bestimmten Datenfeldern gruppieren, können Sie die gruppierten und alle anderen Spalten als Argumente von Funktionen einsetzen. Funktionen werden dann auf die Gruppen angewendet. Das folgende Beispiel ermittelt den minimalen und maximalen Preises für Artikel jedes Lieferanten: SELECT SupplierID, MIN(UnitPrice) AS MinUnitPrice, MAX(UnitPrice) AS MaxUnitPrice FROM Products GROUP BY SupplierID; Ergebnis: SupplierID MinUnitPrice MaxUnitPrice 1 10.00 19.00 2 17.00 22.00 3 25.00 40.00 4 10.00 97.00 5 21.00 38.00 6 6.00 23.25 ... ... ... Aggregatfunktionen können Sie nicht direkt in eine WHERE-Klausel einbinden. Die Data Manipulation Language 56 Das Beispiel: SELECT * FROM Products WHERE UnitPrice < AVG(UnitPrice); zur Ermittlung aller Artikel, deren Nettobetrag unterhalb des mittleren Nettobetrags liegt, funktioniert so nicht (der SQL Server z. B. meldet den Fehler „Ein Aggregat darf nicht in der WHERE-Klausel auftreten - es sei denn, es befindet sich in einer Unterabfrage, die in einer HAVING-Klausel oder einer Auswahlliste enthalten ist, und die Spalte, die aggregiert wird, ist ein Außenverweis“)! Sie müssen sich dann mit einer komplexen Abfrage behelfen: SELECT * FROM Products WHERE UnitPrice < (SELECT AVG(UnitPrice) FROM Products); / #$ SQL/92 stellt die in Tabelle 4.1 dargestellten Aggregatfunktionen zur Verfügung. Funktion Beschreibung AVG([DISTINCT] Ausdruck) ermittelt den Durchschnittswert. COUNT( {* | [DISTINCT] COUNT ermittelt die Anzahl der Datensätze innerhalb einer MAX(Ausdruck) ermittelt den größten Wert. MIN(Ausdruck) ermittelt den kleinsten Wert. SUM([DISTINCT] Ausdruck) ermittelt die Summe der Werte in der angegebenen Spalte. Ausdruck}) Gruppe. Normalerweise verwenden Sie zur Ermittlung der Anzahl COUNT(*), das angeblich schneller ausgeführt wird als Count(Datenfeld). Wenn Sie die Anzahl der unterschiedlichen Werte eines Feldes berechnen wollen, verwenden Sie Count(DISTINCT Datenfeld). Tabelle 4.1: Die Aggregatfunktionen von SQL/92 Die Data Manipulation Language 57 0 Der SQL Server kennt einige weitere Aggregatfunktionen (Tabelle 4.2). Funktion Beschreibung CHECKSUM_AGG(Ausdruck) Diese Funktion (Checksum Aggregate) ermittelt die aggregierte Prüfsumme der Werte in einer Gruppe. Mit Hilfe der Funktion BINARY_CHECKSUM, die die Checksumme einzelner Felder ermittelt, können Sie z. B. ermitteln, ob eine Tabelle gegenüber einer vorherigen Checksummenabfrage geändert wurdeDazu fragen Sie die aggregierte Checksumme aller Felder ab: SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*)) FROM Products Sie können auch ermitteln, ob einzelne Felder geändert wurden, indem Sie einfach die Feldnamen angeben: SELECT CHECKSUM_AGG(BINARY_CHECKSUM(UnitPrice)) FROM Products Das Ergebnis ist eine Integer-Zahl. Diese können Sie mit einer zuvor ermittelten Checksumme vergleichen um Änderungen zu erkennen. COUNT_BIG(Ausdruck) ermittelt wie COUNT die Anzahl von Datensätzen bzw. Nicht-NullFeldern in einer Gruppe, gibt aber einen bigint-Wert zurück. GROUPING(Feldname) Name GROUPING wird zusammen mit der GROUP BY-Option CUBE oder ROLLUP verwendet um zu ermitteln, ob eine Zeile von CUBE oder ROLLUP hinzugefügt wurde (siehe in Abschnitt 4.4.8 bei Spezielle GROUP BY-Optionen beim SQL Server). STDEV(Ausdruck) berechnet die statistische Standardabweichung für einen Ausdruck. Der Ausdruck kann auch ein Feldname sein. STDEVP(Ausdruck) berechnet die statistische Standardabweichung für die Gesamtheit aller Werte für einen Ausdruck. VAR(Ausdruck) berechnet die statistische Varianz für einen Ausdruck. VARP(Ausdruck) berechnet die statistische Varianz für die Gesamtheit aller Werte für einen Ausdruck. Tabelle 4.2: Die weiteren Aggregatfunktionen des SQL Servers 7 A'7 Durch die Angabe von DISTINCT werden alle mehrfach vorkommenden Werte für die Ausgabe oder Berechnung eliminiert. Wenn in der Tabelle mehrere Datensätze mit identischen Werten in der mit DISTINCT angegebenen Spalte existieren, wird nur jeweils der erste Datensatz in das Ergebnis übernommen bzw. innerhalb einer Funktion nur der erste Datensatz zur Berechnung verwendet. Wenn das angegebene Feld indiziert ist, wird der erste Datensatz aus dem Index, sonst der erste Satz aus der Tabelle verwendet. Die Data Manipulation Language 58 8 / #$ SQL/92 stellt deutlich mehr skalare Funktionen zur Verfügung als Aggregatfunktionen. Die in Tabelle 4.3 beschriebenen skalaren Funktionen können Sie beliebig in Ausgabespalten und WHERE-Bedingungen einsetzen. Die wichtigen Funktionen sind hervorgehoben dargestellt. Funktion Beschreibung BIT_LENGTH({Zeichenkette | gibt die Länge einer angegebenen Zeichenkette oder Bitkette in Bits zurück. Der SQL Server kennt kann nichts damit anfangen. Verwenden Sie hier stattdessen die LEN-Funktion. CASE CASE gibt abhängig von einer der mehreren Bedingungen einen skalaren Wert zurück. CASE wird aufgrund der Komplexität unten Bitkette}) separat beschrieben. CAST(Ausdruck AS {Datentyp | Domäne} wandelt den Datentyp eines Ausdrucks in einen anderen Datentyp um. Die Umwandlung funktioniert nur dann, wenn der Datentyp konvertierbar ist. Das Beispiel: SELECT CAST('123' AS INT) funktioniert, das Beispiel SELECT CAST('abc' AS INT) ergibt einen Fehler. CHAR_LENGTH({Zeichenkette | CHAR_LENGTH gibt die Länge in Zeichen zurück, wenn Sie eine CONVERT(Zeichenkette USING CONVERT wandelt eine angegebene Zeichenkette in einen anderen CURRENT_DATE gibt das aktuelle Datum zurück. Der SQL Server kennt diese Funktion nicht.. CURRENT_TIME gibt die aktuelle Zeit zurück. Der SQL Server kennt diese Funktion nicht. CURRENT_TIMESTAMP gibt das aktuelle Datum inkl. der aktuellen Zeit zurück. CURRENT_USER gibt den Login-Namen des aktuellen Benutzers zurück. Anmerkungen zu CURRENT_USER und SESSION_USER finden Sie unten. Sie können auch die Kurzform USER verwenden. EXTRACT(Feld FROM gibt eine Zahl zurück, die einen Teil des angegebenen Datums darstellt. Als Feld können Sie die folgenden Schlüsselwörter einsetzen: YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, TIMEZONE_HOUR oder TIMEZONE_MINUTE. Der SQL Server kennt diese Funktion nicht. LOWER(Zeichenkette) gibt die Zeichenkette in Kleinschrift zurück. Bitkette}) Umwandlung) Datumsausdruck) Zeichenkette übergeben und die Länge in Oktetten15, wenn Sie eine Bitkette übergeben (aber wer will schon Oktette ermitteln ...). Zeichencode um. Als Umwandlung können Sie z. B. EBCDIC_TO_ASCII oder ASCII_TO_EBCDIC angeben, um zwischen ASCII und EBCDIC (IBM-Zeichencode) zu konvertieren. Achtung: Beim SQL Server hat CONVERT eine andere Funktion, hier wandelt CONVERT Datentyen in andere Datentypen um. 15 Ein Oktett ist die Länge einer Bitkette dividiert durch 8, wobei der eventuelle Rest der Division ignoriert wird. Die Data Manipulation Language 59 OCTET_LENGTH(Bitkette) ermittelt die Länge der Bitkette in Oktetten (siehe bei CHAR_LENGTH). POSITION(Suchzeichenkette IN gibt die Position der Suchzeichenkette in der Zeichenkette zurück. Zeichenkette) Der SQL Server kennt diese Funktion nicht. Verwenden Sie hier die Funktion CHARINDEX. SESSION_USER gibt den Login-Namen des aktuellen Benutzers der aktuellen Sitzung zurück. Anmerkungen zu CURRENT_USER und SESSION_USER finden Sie unten. SUBSTRING(Zeichenkette FROM gibt einen Teil einer Zeichenkette zurück, wobei Start die Position des ersten Zeichens definiert und Anzahl die Anzahl der Zeichen, Start FOR Anzahl) die ab Start zurückgegeben werden. Der SQL Server verwendet eine andere Syntax (siehe weiter unten). SYSTEM_USER gibt den Login-Namen des am Betriebssystem (nicht am SQL Server!) angemeldeten Benutzers zurück. TRANSLATE(Zeichenkette USING Zeichenübersetzung) gibt eine übersetzte Zeichenkette zurück, wobei eine vordefinierte Zeichenübersetzung verwendet wird. Mit TRANSLATE(' A,B,C,D'USING PUNCTATION_TO_SPACES) wandeln Sie z. B. die Zeichenkette ' A,B,C,D'um in ' A B C D' . Der SQL Server kennt das Schlüsselwort TRANSLATE nicht. TRIM(Art FROM Zeichenkette) gibt eine Zeichenkette ohne vorangestellte und/oder angehangene Füllzeichen (Leerzeichen etc.) zurück. Mit Art geben Sie an, welche Füllzeichen entfernt werden sollen. Sie können hier LEADING, TRAINLING oder BOTH angeben. Der SQL Server kennt TRIM nicht. Verwenden Sie hier stattdessen LTRIM. und RTRIM. UPPER(Zeichenkette) gibt die Zeichenkette in Großschrift zurück. USER USER ist eine Kurzform von CURRENT_USER. Tabelle 4.3: Skalare Funktionen bei SQL/92 " 'C A7EC BAEC Der Unterschied zwischen CURRENT_USER und SESSION_USER ist recht schwierig zu verstehen (sogar Soukup und Delaney behaupten auf Seite 504, dass CURRENT_USER und SESSION_USER identische Ergebnisse liefern!). Wenn ein Benutzer sich in ein Datenbanksystem einloggt, wird immer eine Sitzung aufgebaut. SESSION_USER gibt demnach zunächst den Login-Namen des aktuell eingeloggten Benutzers zurück. Mit der Anweisung SET SESSION AUTHORIZATION kann die Sitzungs-Authentisierungs-ID allerdings auch nachträglich geändert werden (was ich allerdings nie machen würde). Die CURRENT_USER-Funktion gibt in SQL/92 zunächst die Authentisierungs-ID des Moduls (?) zurück, in dem die SQL-Anweisung gespeichert ist, die die CURRENT_USER-Funktion enthält (vgl. dazu Date, Darven S. 250). Was der Begriff „Modul“ in SQL/92 bedeutet, konnte ich bisher nicht herausfinden. In einem Test mit dem SQL Server gab CURRENT_USER auch dann den Login-Namen des aktuell eingeloggten Benutzers zurück, wenn dieser eine Stored Procedure aufgerufen hatte, die die CURRENT_USER-Funktion enthielt und die im Besitz eines anderen Benutzers war. Die Data Manipulation Language 60 ' CASE ist eine mächtige Funktion, mit der Sie einen Wert abhängig von einer oder mehreren Bedingungen zurückgeben können. CASE besitzt zwei Varianten: CASE Zu_Prüfender_Ausdruck WHEN Prüfausdruck THEN Ergebnisausdruck [...n] [ELSE Else_Ergebnisausdruck] END Bei dieser Variante wird der zu prüfende Ausdruck mit den Prüfausdrücken verglichen. Wenn ein Vergleich wahr wird, wird der in Ergebnisausdruck zurückgegeben. Sie können mehrere WHEN-Blöcke einbauen. Das folgende Beispiel wertet den aktuellen Wochentag aus: SELECT CASE DATEPART(dw, GETDATE()) WHEN 5 THEN 'Noch einen Tag bis zum Wochenende :-)' WHEN 6 THEN 'Yeah, Wochenende' WHEN 7 THEN 'Nur noch einen Tag Wochenende :-(' WHEN 1 THEN 'UUH, Montag :-((' ELSE 'Waiting for weekend ...' END Bei der zweiten Variante können Sie unterschiedliche Bedingungsausdrücke verwenden: CASE WHEN Bedingungsausduck THEN Ergebnisausdruck [...n] [ELSE Else_Ergebnisausdruck] END So können Sie den aktuellen Wochentags auch auswerten: SELECT CASE WHEN (DATEPART(dw, GETDATE()) = 6) OR (DATEPART(dw, GETDATE()) = 7) THEN 'Yeah, Wochenende' WHEN (DATEPART(dw, GETDATE()) = 1) THEN 'UUH, Montag :-((' ELSE 'Waiting for weekend' END 8 / Die folgenden Funktionen sind zwar in „SQL - Der Standard“ nicht als SQL/92-Funktionen beschrieben, stehen in vielen Datenbanksystemen jedoch zur Verfügung: Funktion Beschreibung DATE() gibt das aktuelle Systemdatum zurück. DAY(Datumsausdruck) ermittelt den Tag aus einem Datumsausdruck als Zahl. MONTH(Datumsausdruck) ermittelt den Monat aus einem Datumsausdruck als Zahl. YEAR(Datumsausdruck) ermittelt das Jahr aus einem Datumsausdruck als Zahl. WEEKDAY(Datumsausdruck) ermittelt den Tag aus einem Datumsausdruck als Zahl: 1 = Sonntag, 2 = Montag ...7 = Samstag (amerikanisches Format). Tabelle 4.4: Weitere skalare Funktionen bei Standard-SQL Beispiel: Ermitteln aller Bestellungen des Jahres 1996: SELECT * FROM Orders WHERE YEAR(OrderDate) = 1996; Die Data Manipulation Language 61 0 Um die Übersicht nicht unter der Vielzahl der Funktionen leiden zu lassen, beschreibe ich hier die wichtigsten der skalaren Funktionen des SQL Servers inklusive den in SQL/92 bereits definierten. Datumsfunktionen Funktion Beschreibung DATEADD(Datumsteil, Anzahl, ergibt ein Datum, bei dem eine in Anzahl übergebene Anzahl von in Datumsteil definierten Intervallen addiert ist. Die Intervalle werden als String übergeben und sind weiter unten beschrieben. DATEDIFF(Datumsteil, berechnet die Differenz zwischen zwei Datumswerten in Intervallen, die in Datumsteil übergeben werden. DATENAME(Datumsteil, Datumsausdruck) ergibt einen Text, der das in Datumsteil übergebene Intervall des Datums bezeichnet. DATEPART(Datumsteil, ergibt einen Integerwert für ein bestimmtes Intervall eines Datums. DAY(Datumsausdruck) ermittelt den Tag aus einem Datumsausdruck als Zahl. GETDATE() ergibt das aktuelle Systemdatum. GETUTCDATE() ermittelt das aktuelle Datum als UTC-Datumswert (Universal Time Coordinate oder Greenwich Mean Time). MONTH(Datumsausdruck) ermittelt den Monat aus einem Datumsausdruck als Zahl. WEEKDAY(Datumsausdruck) ermittelt den Tag aus einem Datumsausdruck als Zahl: 1 = Sonntag, 2 = Montag ...7 = Samstag (amerikanisches Format). YEAR(Datumsausdruck) ermittelt das Jahr aus einem Datumsausdruck als Zahl. Datumsausdruck) Startdatum, Enddatum) Datumsausdruck) Tabelle 4.5: Die Datumsfunktionen des SQL Servers Intervallangaben der Datumsfunktionen Intervall Beschreibung yy, yyyy Jahr qq, q Quartal mm, m Monat dy, y Tag des Jahres dd, d Tag wk, ww Woche hh Stunde ss, s Sekunde ms Millisekunde Tabelle 4.6: Intervallangaben der Datumsfunktionen des SQL Servers Die Data Manipulation Language 62 Mathematische Funktionen Funktion Bedeutung ABS(Numerischer_Ausdruck) berechnet den Absolutwert. ACOS(Float_Ausdruck) berechnet den Arkuskosinus (den Winkel im Bogenmaß, dessen Kosinus der Wert des übergebenen Float-Ausdrucks ist). ASIN(Float_Ausdruck) berechnet den Arkussinus (den Winkel im Bogenmaß, dessen Sinus der Wert des übergebenen Float-Ausdrucks ist). ATAN(Float_Ausdruck) berechnet den Arkustangens (den Winkel im Bogenmaß, dessen Tangens der Wert des übergebenen Float-Ausdrucks ist). ATN2(Float_Ausdruck, berechnet den Winkel im Bogenmaß, dessen Tangens zwischen den beiden angegebenen Float-Ausdrücken liegt. CEILING(Numerischer_Ausdruck) gibt den kleinsten Integerwert zurück, der größer oder gleich dem übergebenen Dezimalwert ist. COS(Float_Ausdruck) berechnet den Cosinus. COT(Float_Ausdruck) gibt den Kotangens des in Bogenmaß übergebenen Winkels zurück. DEGREES(Numerischer_Ausdruck) berechnet einen in Radians übergebenen Winkel in Grad. EXP(Float_Ausdruck) berechnet den Exponentialwert. FLOOR(Numerischer_Ausdruck) gibt den größten Integerwert zurück, der kleiner oder gleich dem übergebenen Dezimalwert ist. LOG(Float_Ausdruck) berechnet den natürlichen Logarithmus. LOG10(Float_Ausdruck) berechnet den Logarithmus zur Basis 10. PI() gibt den Wert für PI zurück. POWER(Numerischer_Ausdruck, y) potenziert den übergebenen numerischen Ausdruck mit der in y übergebenen Potenz . POWER(2, 3) entspricht also z. B. 23. RADIANS(Numerischer_Ausdruck) berechnet einen in Grad übergebenen Winkel in Radians. RAND([Startwert]) gibt einen dezimalen Zufallswert zwischen 0 und 1 zurück. Startwert spezifiziert einen optionalen Integer-Startwert. ROUND(Numerischer_Ausdruck, rundet einen Wert auf die in Länge übergebene Anzahl Dezimalstellen. Ist Funktion nicht oder mit 0 angegeben, wird gerundet, ist Funktion mit 1 angegeben, wird abgeschnitten. SIGN(Numerischer_Ausdruck) gibt das Vorzeichen einer Zahl codiert als Zahl zurück: +1 = positives Vorzeichen, 0 = kein Vorzeichen, -1 = negatives Vorzeichen. SIN(Float_Ausdruck) berechnet den Sinus. SQUARE(Float_Ausdruck) berechnet das Quadrat eines Ausdrucks. SQRT(Float_Ausdruck) berechnet die Quadratwurzel eines Ausdrucks. TAN(Float_Ausdruck) berechnet den Tangens. Float_Ausdruck) Länge [, Funktion]) Tabelle 4.7: Die mathematischen Funktionen des SQL Servers Die Data Manipulation Language 63 Stringfunktionen Funktion Bedeutung ASCII(Zeichenkette) gibt den ASCII-Wert des äußerst linken Zeichens im übergebenen String zurück. CHAR(Zeichencode) gibt einen ASCII-String mit einem Zeichen zurück, das dem übergebenen Zeichencode entspricht. CHARINDEX(Ausdruck1, gibt die Position des ersten Auftretens von Ausdruck1 in Ausdruck2. In Start_Position können Sie angeben, ab welchem Zeichen gesucht werden soll DIFFERENCE(Zeichenkette1, ergibt den SOUNDEX-Unterschied zwischen zwei Strings. 0 steht für den kleinstmöglichen, 4 für den größtmöglichen Unterschied. LEFT(Zeichenkette, Anzahl) ergibt die in Anzahl definierten linken Zeichen des übergebenen Strings. LEN(Zeichenkette) ergibt die Länge des Strings in Zeichen. LOWER(Zeichenkette) wandelt einen String in Kleinschreibung um. LTRIM(Zeichenkette) gibt einen String ohne führende Leerzeichen zurück. NCHAR(Zeichencode) gibt einen Unicode-String mit einem Zeichen zurück, das dem übergebenen Zeichencode entspricht. PATINDEX(' %Muster%' , ergibt den Index des ersten Vorkommens eines Stringmusters in einem String. Das Stringmuster wird mit Wildcards angegeben. REPLACE('Zeichenkette1', gibt einen String zurück, bei dem alle Vorkommen von Zeichenkette2 in Zeichenkette1 durch Zeichenkette3 ersetzt ist QUOTENAME(Zeichenkette Diese Funktion konvertiert eine Zeichenkette in eine mit SQL-ServerFeldtrennzeichen ausgerüstete Zeichenkette, sodass diese als Objektname verwendet werden kann. Als Trennzeichenn können Sie einfache Apostrophe, Anführungszeichen und eine linke oder rechte eckige Klammer angeben. Wenn Sie kein Zeichen angeben, verwendet der SQL Server die Standardzeichen für Bezeichner-Begrenzungen ([, ]). Damit können Sie Objektnamen, die Zeichen außerhalb von Buchstaben, Zahlen und dem Unterstrich beinhalten, recht einfach in für SQL-Anweisungen gültige Bezeichner umwandeln. QUOTENAME('Kunden-Nr') ergibt z. B. [Kunden-Nr] REPLICATE(Zeichenkette, Integer_Ausdruck) gibt einen String zurück, der den in Zeichenkettenausdruck übergebenen String so oft enthält, wie in Integer_Ausdruck angegeben. REVERSE(Zeichenkette) dreht einen String um. Ausdruck2 [, Start_Position]) Zeichenkette2) Ausdruck) 'Zeichenkette2', 'Zeichenkette3') [, ' Trennzeichen' ]) RIGHT(Zeichenkette, Anzahl) ergibt die in Anzahl übergebene Anzahl rechter Zeichen des übergebenen Strings. RTRIM(Zeichenkette) Gibt einen String ohne rechte Leerzeichen zurück. SOUNDEX(Zeichenkette) Gibt einen vier-Zeichen SOUNDEX-Code zurück. SPACE(Anzahl) Gibt eine bestimmte Anzahl Leerzeichen zurück STR(Float_Ausdruck [, konvertiert einen Dezimalwert in einen String. Länge [, Dezimalstellen]]) Die Data Manipulation Language 64 STUFF(Zeichenkette1, Start, Länge, Zeichenkette2) Diese Funktion löscht die in Länge angegebene Anzahl Zeichen ab der in Start angegebenen Position aus der ersten übergebenen Zeichenkette und fügt dort die zweite übergebene Zeichenkette ein. SUBSTRING(Ausdruck, Start, gibt einen Teistring zurück. Länge) UNICODE(Zeichenkette) gibt einen Integerwert zurück, der den Unicode-Wert des übergebenen Zeichens definiert. UPPER(Zeichenkette) wandelt eine Zeichenkette in Großschreibung um. Tabelle 4.8: Die Stringfunktionen des SQL Servers Systemfunktionen (Auszug) Funktion Bedeutung APP_NAME() gibt den Anwendungsnamen zurück, sofern von der aktuellen Sitzung ein solcher festgelegt wurde. CASE Zu_Prüfender_Ausdruck WHEN Prüfausdruck THEN CASE ist eine mächtige Funktion, mit der Sie einen Wert abhängig von einer oder mehreren Bedingungen zurückgeben können. CASE Ergebnisausdruck [...n] [ELSE Else_Ergebnisausdruck ] wird in Abschnitt 4.4.9 näher beschrieben. END CASE Diese zweite Form von CASE wird ebenfalls in Abschnitt 4.4.9 WHEN Bedingungsausduck THEN näher beschrieben. Ergebnisausdruck [...n] [ELSE Else_Ergebnisausdruck ] END COALESCE(Ausdruck1 [, Ausdruck2] [...]) gibt den ersten der übergebenen Ausdrücke zurück, der nicht NULL ist. COALESCE(Ausdruck1, Ausdruck2) entspricht: CASE WHEN (Ausdruck1 IS NOT NULL) THEN Ausdruck1 WHEN (Ausdruck2 IS NOT NULL) THEN Ausdruck2 ELSE NULL CURRENT_USER() gibt den Login-Namen des Benutzers zurück, der diese Funktion aufgerufen hat. Entspricht USER_NAME(). DATALENGTH (Ausdruck) gibt die Anzahl Bytes zurück, die zur Speicherung eines Ausdrucks verwendet werden. ISDATE(Ausdruck) gibt 1 zurück wenn der übergebene Stringausdruck ein gültiges Datum ist und 0, wenn nicht. ISNULL(Prüfausdruck, gibt den in Ersatz_Ausdruck übergebenen Wert zurück wenn der in Prüfausdruck übergebene Ausdruck NULL ergibt. ISNUMERIC(Ausdruck) gibt 1 zurück wenn der übergebene Stringausdruck numerisch ist und 0, wenn nicht. NEWID() erzeugt einen weltweit eindeutigen GUID-Wert. Ersatz_Ausdruck) Tabelle 4.9: Die wichtigsten Systemfunktionen des SQL Servers Die Data Manipulation Language 65 Konvertierungsfunktionen Funktion Bedeutung CAST(Ausdruck AS Datentyp) gibt den in Ausdruck übergebenen Ausdruck mit dem in Datentyp übergebenen Datentyp zurück. CONVERT (Datentyp [(Länge)], CONVERT ist im Wesentlichen identisch mit CAST. Ausdruck [, Stil]) Tabelle 4.10: Die Konvertierungsfunktionen des SQL Servers 55 F % 7 A'7 7B? #$ In SQL/92 können Sie bei der SELECT-Anweisung mit DISTINCT angeben, dass doppelte Ergebnisdatensätze nicht in das Ergebnis übernommen werden: SELECT {ALL | DISTINCT} FROM Tabelle ... Geben Sie DISTINCT an, muss jeder Ergebnisdatensatz in den abgefragten Spalten eindeutig sein. Mit Hilfe von DISTINCT können Sie Redundanz in der Ausgabe vermeiden. Die Voreinstellung ALL bewirkt, dass immer alle Datensätze im Ergebnis erscheinen. Das folgende Beispiel ermittelt alle Lieferanten aus der Artikeltabelle ohne Redundanzen: SELECT DISTINCT SupplierID FROM Products; Ergebnis: SupplierID 1 2 3 4 5 6 7 ... Die Data Manipulation Language 66 0 Wie viele andere Datenbanksysteme auch erweitert der SQL Server die SELECT-Optionen um die TOP-Option: SELECT {ALL | DISTINCT | TOP n [PERCENT]} FROM Tabelle ... Mit TOP n erhalten Sie die ersten n Datensätze aus dem Ergebnis. Die Abfrage sollte dazu nach den Feldern sortiert sein, die im Ergebnis oben stehen sollen. Sie können für n ganze Zahlen oder Prozentwerte angeben. So können Sie z. B. die zehn teuersten Artikel ermitteln: SELECT TOP 10 ProductName, UnitPrice FROM Products ORDER BY UnitPrice DESC Beachten Sie, dass die Abfrage absteigend sortiert ist, sodass die teuersten Artikel im Ergebnis oben stehen. Die folgende Abfrage ergibt 20 Prozent der teuersten Artikel: SELECT TOP 20 PERCENT ProductName, UnitPrice FROM Products ORDER BY UnitPrice DESC Sie können TOP 100 PERCENT verwenden, um Sichten (Views, siehe Kapitel 5), die normalerweise nicht sortiert werden können, dennoch zu sortieren: CREATE VIEW Products_with_Category AS SELECT TOP 100 PERCENT ProductId, ProductName, UnitPrice, CategoryName FROM Products INNER JOIN Categories ON Products.CategoryId = Categories.CategoryId ORDER BY ProductName 55 C G H !I SQL/92 ermöglicht, dass Sie innerhalb eines Bedingungsausdrucks weitere SELECTAnweisungen einsetzen, die Sie in Klammern setzen. Diese „Subquerys“ werden bei der Ausführung der SQL-Anweisung von innen nach außen gehend zuerst ausgewertet. Das Ergebnis der Unterabfrage können Sie in der Projektion oder als Operand in einer Bedingung verwenden. Damit können die Daten einer Tabelle abhängig von den Daten einer anderen Tabelle ausgegeben werden. Bevor ich Unterabfragen weiter ausführe möchte ich anmerken, dass die meisten Unterabfragen durch Joins wesentlich eleganter und auch performanter gelöst werden können. Joins werden in Abschnitt 4.4.13 beschrieben. Das folgende Beispiel ermittelt alle Lieferanten, die Produkte liefern: SELECT * FROM Suppliers WHERE SupplierID IN (SELECT SupplierID FROM Products); In diesem Beispiel werden nur alle Lieferanten ausgegeben, die auch in der Artikeltabelle mit Ihrer Id-Nummer gespeichert sind. Alle Lieferanten, die zurzeit nichts liefern, werden somit ausgeschlossen. Die folgende Anweisung ermittelt die Artikel, deren Preis dem größten Preis entspricht: SELECT * FROM Products WHERE UnitPrice IN (SELECT MAX(UnitPrice) FROM Products); In der Mengenklammer muss die Projektion auf eine Spalte reduziert werden, deren Datentyp zum Datentyp der Vergleichsspalte passen muss. Da eine Unterabfrage normalerweise, wie andere Abfragen auch, mehrere Datensätze (mit jeweils einem Datenfeld) ergibt, müssen Sie den IN-Operator verwenden, um festzustellen, ob das Suchfeld im Ergebnis der Unterabfrage vorkommt. Vergleichsoperatoren können nur dann Die Data Manipulation Language 67 verwendet werden, wenn die Unterabfrage definitiv nur einen Datensatz liefert, oder wenn einer der Zusatzoperatoren ANY und ALL mit verwendet wird (siehe weiter unten). Die SELECT-Anweisung kann beliebig tief geschachtelt werden. Eine Unterabfrage kann optional auch eine WHERE-Klausel beinhalten. In der WHERE-Klausel können Sie sich auf Spalten aus der übergeordneten Abfrage beziehen. Die Unterabfrage wird (aus logischer Sicht) für jeden Datensatz der in der übergeordneten Abfrage verwendeten Tabellen aufgerufen. Das folgende Beispiel ermittelt alle Bestelldetails der Bestellungen des Jahres 1996: SELECT * FROM [Order Details] WHERE OrderID IN (SELECT OrderID FROM Orders WHERE YEAR(OrderDate) = 1996) Die Spalte OrderDate bezieht sich in diesem Beispiel auf die übergeordnete Tabelle Orders. 55 B- / AD% J 7 Die Operatoren ANY, ALL und EXISTS werden im Zusammenhang mit einem Vergleichsoperator und Wertlisten verwendet (die üblicherweise mit Unterabfragen ermittelt werden): Ausdruck Vergleichsoperator [ANY | ALL | EXISTS] (Wertliste) Die Operatoren ANY und ALL werden in der Praxis kaum eingesetzt weil Abfragen mit diesen Operatoren meist (oder vielleicht immer?) durch andere Abfragen ersetzt werden können. In den Beispielen zu ANY und ALL sind solche Abfragen dargestellt. AD Mit dem ANY-Operator (Any = engl.: Irgendein) wird die Bedingung bereits wahr, wenn mindestens einer der von der Unterabfrage gelieferten Werte dem Vergleich entspricht. Mit ANY ermitteln Sie Datensätze die in einer Spalte einen Wert besitzen, der bezogen auf die kleiner/gleich als der größte oder größer/gleich als der kleinste Wert der Wertliste ist oder Sie ermitteln Datensätze mit einem Spaltenwert, der in der Menge der in der Wertliste enthaltenen Werte vorkommt. Beispiele: Bedingung Werte, die die Bedingung erfüllen Bemerkungen > ANY(1, 2, 3) alle Werte größer als 1 ermittelt, ob ein Wert größer als der kleinste Wert der Wertliste ist. < ANY(1, 2, 3) alle Werte kleiner als 3 ermittelt, ob ein Wert kleiner als der größte Wert der Wertliste ist. = ANY(1, 2, 3) 1 oder 2 oder 3 ist identisch mit IN (1, 2, 3). Tabelle 4.11: Beispiele für den ANY-Operator Das folgende Beispiel ermittelt alle Produkte mit einem Preis kleiner als der größte der vom Lieferanten mit der ID 1 gelieferten Artikel (der größte Einzelpreis des Lieferanten 1 ist 19,00): SELECT ProductName, UnitPrice FROM Products WHERE UnitPrice < ANY (SELECT UnitPrice FROM Products WHERE SupplierID = 1) ORDER BY UnitPrice Die Data Manipulation Language 68 Ergebnis: ProductName UnitPrice Geitost 2.5000 Guaraná Fantástica 4.5000 Konbu 6.0000 Filo Mix 7.0000 Tourtière 7.4500 Rhönbräu Klosterbier 7.7500 ... ... Chai 18.0000 Steeleye Stout 18.0000 Chartreuse verte 18.0000 Lakkalikööri 18.0000 Boston Crab Meat 18.4000 Der ANY-Operator kann häufig auch umgangen werden. Das vorstehende Beispiel kann z. B. auch anders gelöst werden: SELECT ProductName, UnitPrice FROM Products WHERE UnitPrice < (SELECT MIN(UnitPrice) FROM Products WHERE SupplierID = 1) ORDER BY UnitPrice Das folgende Beispiel ermittelt alle Bestelldetails der Bestellungen des Jahres 1996: SELECT * FROM [Order Details] WHERE OrderID = ANY (SELECT OrderID FROM Orders WHERE YEAR(OrderDate) = 1996) Der Vergleich mit = ANY ist redundant, da auch der IN-Operator dazu verwendet werden kann: SELECT * FROM [Order Details] WHERE OrderID IN (SELECT OrderID FROM Orders WHERE YEAR(OrderDate) = 1996) Mit dem ALL-Operator wird die Bedingung nur dann war, wenn alle von der Unterabfrage gelieferten Werte dem Vergleich entsprechen. Hiermit können Sie Datensätze ermitteln, die in einer Spalte einen kleineren oder größeren Wert besitzen, als die Werte, die in der Unterabfrage ermittelt werden. Die Bedingung > ALL (Wertliste) bedeutet: »Größer als alle Werte, die in der Wertliste angegeben sind.« Entsprechend können Sie die Operatoren <, <= und >= einsetzen. Der Operator = hat beim ALL-Operator wenig Sinn (damit ein Wert gleich allen Werten einer Liste ist, darf die Liste nur aus einem Wert bestehen). Die Data Manipulation Language 69 Tabelle 4.12 zeigt einige Beispiele für den All-Operator. Bedingung Werte, die erfüllen die Bedingung Bemerkungen > ALL(1, 2, 3) alle Werte größer als 3 ermittelt, ob ein Wert größer als der größte Wert der Wertliste ist. < ALL(1, 2, 3) alle Werte kleiner als 1 ermittelt, ob ein Wert kleiner als der kleinste Wert der Wertliste ist Tabelle 4.12: Beispiele für den ALL-Operator Das folgende Beispiel ermittelt alle Artikel, deren Preis größer ist als der größte Preis der Artikel, die vom Lieferanten mit der ID 1 geliefert werden: SELECT ProductName, UnitPrice, SupplierID FROM Products WHERE UnitPrice > ALL (SELECT UnitPrice FROM Products WHERE SupplierID = 1); Der ALL-Operator kann häufig auch umgangen werden. Das vorstehende Beispiel kann z. B. auch anders gelöst werden: SELECT ProductName, UnitPrice, SupplierID FROM Products WHERE UnitPrice > (SELECT MAX(UnitPrice) FROM Products WHERE SupplierID = 1); J 7 Der EXISTS-Operator überprüft, ob die Wertliste Werte enthält. Dazu verwenden Sie die folgende Syntax: WHERE [NOT] EXISTS (Wertliste) EXISTS lässt die Bedingung wahr werden, wenn mindestens ein Wert (bzw. mit NOT kein Wert) in der Wertliste gefunden wird. Wenn Sie für die Wertliste eine Unterabfrage einsetzen, können Sie an Stelle einer Spalte auch den Wert Null einsetzen, was die Ausführung der Abfrage erheblich beschleunigt. Die folgende Anweisung ermittelt alle Lieferanten, die mindestens einen Artikel liefern: SELECT * FROM Suppliers WHERE EXISTS (SELECT NULL FROM Products WHERE Products.SupplierID = Suppliers.SupplierID) Dieses Beispiel ist sehr uneffektiv. Sie sollten idealerweise beide Tabellen über einen Inner Join miteinander verknüpfen. Ein Inner Join ergibt automatisch nur alle Datensätze, deren Schlüsselfelder in den betroffenen Tabellen vorkommen und wird wesentlich schneller ausgeführt. 55 3 K/ GL * I Joins verbinden mehrere Tabellen miteinander und ergeben eine virtuelle Tabelle, die aus den in der Projektion angegebenen Spalten der beteiligten Basistabellen zusammengesetzt werden. Die zu verbindenden Tabellen müssen für alle Joins ausser dem Cross-Join, dazu über mindestens eine Spalte verfügen, über die die Verknüpfung aus logischer Sicht realisiert ist (viele Datenbanksysteme erlauben auch die „physikalische“ Verknüpfung von Tabellen über die sogenannte Referentielle Integrität). Die Data Manipulation Language 70 Verbundausdrücke können in zwei Varianten geschrieben werden: Die ältere Variante verwendet keine speziellen Schlüsselwörter, sondern einen einfachen Vergleich der Schlüsselspalten in einer WHERE-Klausel. Das folgende Beispiel ermittelt alle Bestellungen mit den Spalten OrderID, OrderDate (aus Orders) und CompanyName (aus Customers): SELECT OrderID, OrderDate, CompanyName FROM Orders, Customers WHERE Orders.CustomerID = Customers.CustomerID Ergebnis: OrderID OrderDate CompanyName 10248 1996-07-04 00:00:00.000 Vins et alcools Chevalier 10249 1996-07-05 00:00:00.000 Toms Spezialitäten 10250 1996-07-08 00:00:00.000 Hanari Carnes 10251 1996-07-08 00:00:00.000 Victuailles en stock 10252 1996-07-09 00:00:00.000 Suprêmes délices 10253 1996-07-10 00:00:00.000 Hanari Carnes ... ... ... Diese ältere Variante wird von vielen Datenbanksystemen unterstützt und ist recht intuitiv, besonders dann, wenn mehrere Tabellen miteinander verbunden werden sollen. Die neuere Variante verwendet das Schlüsselwort JOIN. Das folgende Beispiel ermittelt alle Bestellungen mit den Spalten OrderID, OrderDate (aus Orders) und CompanyName (aus Customers): SELECT OrderID, OrderDate, CompanyName FROM Orders INNER JOIN Customers ON Orders.CustomerID = Customers.CustomerID SQL/92 beschreibt bereits die JOIN-Variante. Schon aus diesem Grunde sollten Sie sich damit auseinandersetzen. Ein anderer wichtiger Grund für die neue Variante ist die Tatsache, dass z. B. der SQL Server (und wahrscheinlich auch andere Datenbanksysteme) bei der Verwendung der alten Variante bei der Verwendung von Outer-Joins in manchen Fällen ein falsches Ergebnis liefert. Hierzu ein Zitat: The old SQL Server *= and =* operators are prone ("sind anfällig für”, Anm. des Verfassers) to ambiguities (“Mehrdeutigkeiten”, Anm. des Verfassers), especially when three or more tables, views or subqueries are involved. Often the results anren’t what you’d expect, even though you might be able to explain them. (Soukup, Delaney. Inside SQL Server 7.0, S. 314) Wenn schon die Autoren von Inside SQL Server 7.0 so etwas schreiben, sollten wir, so denke ich, die Finger von der alten Variante lassen. Ich selbst habe schon einmal Stunden nach dem Fehler gesucht und diesen dann in der Verwendung der alten Join-Syntax lokalisiert. : + - " Wenn Sie Spalten aus mehreren Tabellen in die Projektion eintragen, müssen Sie den Tabellennamen mit einem Punkt getrennt vor die Spalte setzen, wenn ein Spaltenname in mehreren Tabellen vorkommt. Das Voranstellen des Tabellennamens schadet jedoch auch nicht, wenn die Spaltennamen eindeutig sind. Die Data Manipulation Language 71 Beispiel: SELECT Products.ProductName, Suppliers.CompanyName FROM Products INNER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID; Wenn aus einer der auszugebenden Tabellen alle Spalten ausgegeben werden sollen, muss vor dem Asterisk explizit der Tabellenname angegeben werden: Tabelle.* Beispiel: SELECT Products.*, Suppliers.CompanyName FROM Products INNER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID; Wenn nur der Asterisk angegeben ist, werden automatisch alle Spalten aller beteiligten Tabellen ausgegeben. : + ! > * Die Syntaxbeschreibung bezieht sich der Übersicht wegen zunächst immer auf den Verbund von nur zwei Tabellen. Weiter unten wird Ihnen jedoch auch der Verbund von mehr als zwei Tabellen erläutert. " ! > L * #$ Die allgemeine Syntax der Verbundausdrücke besitzt in SQL/92 die folgende Form: SELECT Spaltenliste FROM Tabellenreferenz1 [NATURAL] [Verbundtyp] JOIN Tabellenreferenz2 [{ON Bedingungsausdruck | USING (Spaltenliste)}] Verbundtyp kann sein: CROSS, INNER, LEFT [OUTER], RIGHT [OUTER], FULL [OUTER] oder UNION. Die einzelnen Verbundtypen werden unten näher beschrieben. Die Verbundbedingung können Sie entweder in einem Bedingungsausdruck angeben (wie in den Beispielen oben) oder, sofern die Verbundspalten gleich benannt sind, mit der USING-Klausel. In SQL/92 können Sie die Abfrage der Produkte mit den Lieferantennamen (siehe oben) also auch folgendermaßen schreiben: SELECT Products.ProductName, Suppliers.CompanyName FROM Products INNER JOIN Suppliers USING(SupplierID); Wird nur das Schlüsselwort JOIN angegeben, wird ein INNER JOIN verwendet. Die Tabellenreferenz kann eine Basistabelle, eine Sicht, eine abgeleitete Tabelle oder wieder eine über einen Verbundausdruck ermittelte virtuelle Tabelle sein (dazu mehr weiter unten unter Die Data Manipulation Language 72 Joins über mehr als zwei Tabellen in Abschnitt 4.4.13). Wenn Sie das Schlüsselwort NATURAL oder UNION verwenden, dürfen Sie keinen Bedingungsausdruck und keine USING-Klausel angeben. Ein NATURAL JOIN verbindet zwei Tabellen über gleichnamige Schlüsselspalten, d.h., alle Spalten in den in der Abfrage enthaltenen Tabellen, die denselben Namen besitzen, werden miteinander in Beziehung gesetzt. Das Beispiel oben könnte also auch so geschrieben werden: SELECT Products.ProductName, Suppliers.CompanyName FROM Products NATURAL INNER JOIN Suppliers; Die Data Manipulation Language 73 " ! > L * " 0 Der SQL Server kennt die Schlüsselwörter USING und NATURAL nicht und erweitert die Verbundausdrücke um einige einstellbare Optionen: SELECT Spaltenliste FROM Tabellenreferenz1 [Verbundtyp] [Join_Option] JOIN Tabellenreferenz2 [ON Bedingungsausdruck] Als Verbundtyp ist beim SQL Server UNION nicht erlaubt (solche Abfragen werden anders geschrieben, siehe weiter unten). Möglich sind also: CROSS, INNER, LEFT [OUTER], RIGHT [OUTER] oder FULL [OUTER]. Die Tabellenreferenz kann eine Basistabelle, eine Sicht, eine abgeleitete Tabelle oder wieder eine über einen Verbundausdruck ermittelte virtuelle Tabelle sein (dazu mehr weiter unten unter Die Data Manipulation Language 74 Joins über mehr als zwei Tabellen in Abschnitt 4.4.13). In der Join-Option können Sie die interne Ausführung des Verbundes für die Query-Engine des SQL Servers beeinflussen um Abfragen evtl. optimieren zu können. Da diese Aufgabe intern bereits der „Query Optimizer“ vornimmt, wird dafür wenig Bedarf sein. Für eine Beschreibung der Join-Optionen verweise ich auf die TSQL-Hilfe. '/ K/ G. 0 I Der Begriff „Cross Join“ ist lediglich eine anderen Bezeichnung für das Erweiterte Karthesische Produkt, das allerdings meist nur als „Karthesisches Produkt“ bezeichnet wird. Das Karthesische Produkt „multipliziert“ jeden Datensatz einer Tabelle mit jedem Datensatz der anderen Tabelle. In der alten Form schreiben Sie dazu: SELECT Spaltenliste FROM Tabellenreferenz1, Tabellenreferenz2 In der neuen Form verwenden Sie das Schlüsselwort CROSS JOIN: SELECT Spaltenliste FROM Tabellenreferenz1 CROSS JOIN Tabellenreferenz2 Am besten lässt sich das Karthesische Produkt am Beispiel einer Tabelle mit Vornamen und einer Tabelle mit Nachnamen erläutern: Wenn Sie alle möglichen Kombinationen von Vor- und Nachnamen erhalten wollen, schreiben Sie z. B.: SELECT FirstNames.FirstName, LastNames.LastName FROM FirstNames CROSS JOIN LastNames Um das Beispiel mit der Northwind-Datenbank umsetzen zu können, wollen wir alle Firmennamen aus der Kunden-Tabelle mit allen Produktnamen verbinden: SELECT CompanyName, ProductName FROM Customers CROSS JOIN Products Das Karthesische Produkt wird übrigens häufig nur zufällig und ungewollt erzeugt, nämlich dann, wenn bei Verwendung der alten Join-Variante der Vergleich der Schlüsselspalten vergessen wird. K/ Der Inner Join ergibt alle Datensätze, bei denen der Schlüsselwert gleich ist. Er ergibt keine Datensätze der Mastertabelle, deren Schlüsselwert nicht in der Detailtabelle vorkommt und keine Datensätze der Detailtabelle, deren Schlüsselwert nicht in der Mastertabelle vorkommt (was bei Verwendung der Referentiellen Integrität nicht vorkommt!). Besonderes Augenmerk muss auf die Möglichkeit gesetzt werden, dass Schlüsselspalten evtl. auch den Wert NULL enthalten können. Solche Datensätze werden beim Inner Join ebenfalls nicht in das Ergebnis übernommen. Die Data Manipulation Language 75 Der Inner Join wir nach der alten Syntax folgendermaßen geschrieben: SELECT Spaltenliste FROM Tabellenreferenz1, Tabellenreferenz2 WHERE Tabellenreferenz1.Schlüsselspalte {= | < | <= | > | >= | <> } Tabellenreferenz2.Schlüsselspalte [AND Tabellenreferenz1.Schlüsselspalte2 {= | < | <= | > | >= | <> } Tabellenreferenz2.Schlüsselspalte2 [...n]] Nach der neuen Syntax schreiben Sie: SELECT Spaltenliste FROM Tabellenreferenz1 [INNER] JOIN Tabellenreferenz2 ON Tabellenreferenz1.Schlüsselspalte {= | < | <= | > | >= | <> } Tabellenreferenz2.Schlüsselspalte [AND Tabellenreferenz1.Schlüsselspalte2 {= | < | <= | > | >= | <> } Tabellenreferenz2.Schlüsselspalte2 [...n]] Die Vergleiche mit anderen Operatoren als = sind für spezielle Anwendungszwecke gedacht. In den meisten Fällen vergleichen Sie die Schlüsselspalten mit =. Das Schlüsselwort INNER ist optional, Sie können dies also auch weglassen. Beispiel: Ermitteln der Produktnamen und der Namen der Lieferanten für das jeweilige Produkt: SELECT Products.ProductName, Suppliers.CompanyName FROM Products INNER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID; Ergebnis: ProductName CompanyName Chai Exotic Liquids Chang Exotic Liquids Aniseed Syrup Exotic Liquids Chef Anton' s Cajun Seasoning New Orleans Cajun Delights Chef Anton' s Gumbo Mix New Orleans Cajun Delights Grandma' s Boysenberry Spread Grandma Kelly' s Homestead ... ... Ein Inner Join, der alle Spalten aller beteiligten Tabellen ausgibt und der den Vergleich mit = verwendet, wird übrigens auch als „Equijoin“ bezeichnet, wenn nur bestimmte Spalten ausgegeben werden, wird der Join auch als „Natürlicher Join“ bezeichnet. 7 @ / Wenn Sie die Schlüsselfelder der Tabellen mit einem Vergleichsoperator vergleichen, so erhalten Sie einen Thetajoin. Da Sie auch den Gleichheitsoperator einsetzen können, ist jeder Equijoin oder natürliche Join auch ein Thetajoin. Der Umgang mit Joins, die andere Operatoren verwenden ist ein recht „tricky“ und hilft Ihnen in seltenen Fällen zu einem Ergebnis, dass Sie sonst (evtl. über eine Unterabfrage?) vielleicht nicht erhalten würden. Bei der Anwendung von anderen Operatoren als dem = beziehen Sie sich übrigens in der Regel nicht auf die Schlüsselspalten, sondern auf andere Spalten. Wenn Sie z. B. die folgende Syntax verwenden: SELECT Spaltenliste FROM Tabellenreferenz1 [INNER] JOIN Tabellenreferenz2 ON Tabellenreferenz1.SpalteX <> Tabellenreferenz2.SpalteX erhalten Sie ein Karthesisches Produkt aller Datensätze aus Tabelle1 und Tabelle2, die sich im Wert in der SpalteX unterscheiden. Die Data Manipulation Language 76 Schreiben Sie stattdessen: SELECT Spaltenliste FROM Tabellenreferenz1 [INNER] JOIN Tabellenreferenz2 ON Tabellenreferenz1.Schlüsselspalte = Tabellenreferenz2.Schlüsselspalte AND Tabellenreferenz1.SpalteX <> Tabellenreferenz2.SpalteX , so heben Sie das Karthesische Produkt auf und geben nur Datensätze aus, die miteinander in Beziehung stehen und sich im Wert der SpalteX unterscheiden. Im Prinzip gleicht diese Syntax der folgenden, häufig zu sehenden intuitiveren Lösung: SELECT Spaltenliste FROM Tabellenreferenz1 [INNER] JOIN Tabellenreferenz2 ON Tabellenreferenz1.Schlüsselspalte = Tabellenreferenz2.Schlüsselspalte WHERE Tabellenreferenz1.SpalteX <> Tabellenreferenz2.SpalteX Diese stellt jedoch eine Mischung aus der alten und der neuen Syntaxvariante dar und sollte deswegen nicht verwendet werden. Beispiel: Ermitteln der Bestelldetails, deren Einzelpreis ein anderer ist als der im verbundenen Produkt gespeicherte: SELECT [Order Details].OrderID, [Order Details].ProductID, [Order Details].UnitPrice, Products.UnitPrice FROM [Order Details] INNER JOIN Products ON [Order Details].ProductID = Products.ProductID AND [Order Details].UnitPrice <> Products.UnitPrice K/ B K/ Nun zu der Besonderheit des Inner Join im Vergleich zum Outer Join: Der Inner Join nimmt, wie bereits ober beschrieben, nur Datensätze in das Ergebnis auf, bei denen die Werte der Verbundspalten gleich sind. Nehmen Sie zwei Tabellen an, die folgendermaßen aussehen: Vornamen ID Vorname 1 Zaphod 2 Arthur 4 Tricia NULL Nobody Nachnamen ID Nachname 1 Beeblebrox 2 Dent 3 Duck NULL Unknown Die Data Manipulation Language 77 Diese Tabellen können Sie folgendermaßen erzeugen und füllen: CREATE CREATE INSERT INSERT INSERT INSERT INSERT INSERT INSERT INSERT TABLE Vornamen (ID int, Vorname Varchar(50)) TABLE Nachnamen (ID int, Nachname Varchar(50)) INTO Vornamen VALUES (1, 'Zaphod') INTO Vornamen VALUES (2, 'Arthur') INTO Vornamen VALUES (4, 'Tricia') INTO Vornamen VALUES (NULL, 'Nobody') INTO Nachnamen VALUES (1, 'Beeblebrox') INTO Nachnamen VALUES (2, 'Dent') INTO Nachnamen VALUES (3, 'Duck') INTO Nachnamen VALUES (NULL, 'Unknown') Eine Abfrage wie die folgende: SELECT Vornamen.Vorname, Nachnamen.Nachname FROM Vornamen INNER JOIN Nachnamen ON Vornamen.ID = Nachnamen.ID würde das folgende Ergebnis liefern: Vorname Nachname Zaphod Beeblebrox Arthur Dent Die Datensätze mit der ID 4 aus Vornamen und der ID 3 und 5 aus Nachnamen werden nicht in das Ergebnis übernommen, da die Werte der Verbundspalten nicht in der jeweils anderen Tabelle gefunden werden. Außerdem werden die Datensätze mit dem NULL-Wert in Vornamen und Nachnamen ebenfalls nicht in das Ergebnis übernommen! In der Praxis werden Sie selten solche Abfragen über zwei gleichwertige Tabellen ausführen. Meistens wird eine der Tabellen eine Detailtabelle sein (z. B. die Products-Tabelle) und die andere stellt als Mastertabelle (z. B. die Suppliers-Tabelle) Datensätze zur Verfügung, deren Schlüsselwerte in der Detailtabelle gespeichert sind. Probleme mit Werten in den Verbundspalten, die in der jeweils anderen Tabelle nicht vorkommen, werden in der Praxis in der Regel nicht auftreten, weil die Beziehung zwischen den Tabellen im Datenbanksystem mit der sogenannten Referentiellen Integrität gesichert ist. Auf NULL-Werte, die in der Verbundspalte gespeichert sein können, müssen Sie jedoch achten! Wir wollen nun einmal die Products-Tabelle ein wenig ändern: Der Lieferant mit der ID 1 ist in Konkurs gegangen und wird in der Products-Tabelle mit dem Wert NULL überschrieben, da noch kein neuer Lieferant für diese Produkte verfügbar ist: UPDATE Products SET SupplierID = NULL WHERE SupplierID = 1 Wenn Sie nun die bereits oben verwendete Abfrage ausführen: SELECT Products.ProductName, Suppliers.CompanyName FROM Products INNER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID; ist das Ergebnis nicht das, welches eigentlich erwartet war: ProductName CompanyName Chef Anton' s Cajun Seasoning New Orleans Cajun Delights Chef Anton' s Gumbo Mix New Orleans Cajun Delights Grandma' s Boysenberry Spread Grandma Kelly' s Homestead Uncle Bob' s Organic Dried Pears Grandma Kelly' s Homestead Northwoods Cranberry Sauce Grandma Kelly' s Homestead ... ... Die Data Manipulation Language 78 Die Produkte „Chai“, „Chang“ und „Aniseed Syrup“ fehlen im Ergebnis, da bei diesen Produkten nun in der Spalte SupplierID der Wert NULL eingetragen ist. Dieses Problem können Sie mit einem Outer Join lösen. Ein Outer Join existiert in drei Varianten: Left Outer Join, Right Outer Join und Full Outer Join. Der Left Outer Join ergibt alle Datensätze der im Verbundausdruck linken Tabelle, auch wenn in der rechten Tabelle keine Datensätze mit einem passenden Wert in der Verbundspalte gefunden werden. Der Right Outer Join ergibt entsprechend alle Datensätze der im Verbundausdruck rechten Tabelle. Der Full Outer Join ergibt alle Datensätze beider Tabellen, auch wenn in beiden Tabellen keine Datensätze mit einem jeweils passenden Wert in der Verbundspalte gefunden werden. Die folgenden Beispiele demonstrieren die verschiedenen Outer Joins an Hand der Vornamenund Nachnamen-Tabelle (die Tabelle, deren Datensätze komplett ausgegeben werden, ist jeweils fett formatiert): Left Outer Join SELECT Vornamen.Vorname, Nachnamen.Nachname FROM Vornamen LEFT OUTER JOIN Nachnamen ON Vornamen.ID = Nachnamen.ID Ergebnis: Vorname Nachname Zaphod Beeblebrox Arthur Dent Tricia NULL Nobody NULL Right Outer Join SELECT Vornamen.Vorname, Nachnamen.Nachname FROM Vornamen RIGHT OUTER JOIN Nachnamen ON Vornamen.ID = Nachnamen.ID Ergebnis: Vorname Nachname Zaphod Beeblebrox Arthur Dent NULL Duck NULL Unknown Full Outer Join SELECT Vornamen.Vorname, Nachnamen.Nachname FROM Vornamen FULL OUTER JOIN Nachnamen ON Vornamen.ID = Nachnamen.ID Die Data Manipulation Language 79 Ergebnis: Vorname Nachname Zaphod Beeblebrox Arthur Dent NULL Duck NULL Unknown Tricia NULL Nobody NULL Outer Joins sind sehr wichtig, wenn Sie unbedingt alle Datensätze einer bestimmten Tabelle in der Abfrage berücksichtigen müssen, die Ihren Bedingungen entsprechen. Bei Umsatzabfragen z. B. führt die Verwendung eines Inner Joins in der Regel zu Fehlern, wenn die Detailtabelle Datensätze enthält, deren Verknüpfungsfelder leer sind oder in der Mastertabelle nicht gefunden werden. Das folgende (komplizierte) Beispiel soll die Umsätze aller Artikel gruppiert nach Lieferant ermitteln. Mit einem Inner Join erhalten Sie ein falsches Ergebnis, da die Artikel, die zuvor vom Lieferant 1 geliefert wurden, nun in der Spalte SupplierID den Wert NULL gespeichert haben: SELECT Suppliers.CompanyName, SUM([Order Details].Quantity * [Order Details].UnitPrice) AS Sales FROM ([Order Details] INNER JOIN Products ON [Order Details].ProductID = Products.ProductID) INNER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID GROUP BY Suppliers.CompanyName ORDER BY Suppliers.CompanyName Dieses Beispiel verwendet bereits Joins über drei Tabellen, die weiter unten näher beschrieben werden. Im Prinzip ist die Auflösung der Joins einfach: Der Join zwischen Order Details und Products ergibt eine virtuelle Ergebnistabelle, die als linke Tabellenreferenz für den Join mit der Suppliers-Tabelle verwendet wird. Ergebnis: CompanyName Sales Aux joyeux ecclésiastiques 163135.0000 Bigfoot Breweries 23776.8000 Cooperativa de Quesos ' Las Cabras' 26768.8000 Escargots Nouveaux 6664.7500 Forêts d' érables 66266.7000 Formaggi Fortini s.r.l. 51082.5000 Gai pâturage 126582.0000 ... ... Die Data Manipulation Language 80 Die Umsätze der Artikel mit SupplierID NULL fehlen! Die Lösung für dieses Problem ist die Verwendung eines Outer Joins, der alle Datensätze der Products-Tabelle mit einbezieht: SELECT Suppliers.CompanyName, SUM([Order Details].Quantity * [Order Details].UnitPrice) AS Sales FROM ([Order Details] INNER JOIN Products ON [Order Details].ProductID = Products.ProductID) LEFT OUTER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID GROUP BY Suppliers.CompanyName ORDER BY Suppliers.CompanyName Ergebnis: CompanyName Sales NULL 35916.8000 Aux joyeux ecclésiastiques 163135.0000 Bigfoot Breweries 23776.8000 Cooperativa de Quesos ' Las Cabras' 26768.8000 Escargots Nouveaux 6664.7500 Forêts d' érables 66266.7000 Formaggi Fortini s.r.l. 51082.5000 Gai pâturage 126582.0000 G' day, Mate 69636.6000 ... ... 8/ " In der alten Form werden Outer Joins mit den Vergleichsoperatoren *= (Left Outer Join) und =* (Right Outer Join) geschrieben. Ob ein Full Outer Join (evtl. *=*) möglich ist konnte ich nicht herausfinden. Beachten Sie die Hinweise in Abschnitt 4.4.13 bevor Sie die alte Syntax verwenden. , 0/ K/ Wie bei normalen Abfragen können Sie das Ergebnis aller Joins über die WHERE-Klausel einschränken, gruppieren und sortieren. Beispiel: SELECT ProductName, CompanyName AS Supplier FROM Products INNER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID WHERE Products.CategoryID = 1; Die Data Manipulation Language 81 7 A'7 Mit SELECT DISTINCT wählen Sie Daten aus mehrfach auftretenden Datensätzen verknüpfter Tabellen nur einmal aus. Die Suppliers-Tabelle enthält z. B. keine mehrfach vorkommenden Einträge für die Spalte SupplierID, im Gegensatz zur Products-Tabelle, in der mehrfache Datensätze mit gleichen Werten in der Spalte SupplierID vorkommen können. Die folgende SQL-Anweisung zeigt, wie Sie mit DISTINCT eine Liste der Lieferanten erstellen können, die mindestens einen Artikel liefern: SELECT DISTINCT Suppliers.CompanyName AS Supplier FROM Products INNER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID; Ergebnis: Supplier Aux joyeux ecclésiastiques Bigfoot Breweries Cooperativa de Quesos ' Las Cabras' Escargots Nouveaux Exotic Liquids Forêts d' érables ... Ohne DISTINCT würde die folgende Ausgabe resultieren: Supplier Aux joyeux ecclésiastiques Aux joyeux ecclésiastiques Bigfoot Breweries Bigfoot Breweries Bigfoot Breweries ... Wenn Sie Abfragen in Access mit DISTINCT verwenden wollen, werden Sie einen Unterschied zu Standard SQL feststellen. Access verwendet unverständlicherweise DISTINCTROW an Stelle von DISTINCT, wenn mehrere Tabellen miteinander verknüpft werden. Bei Access wird DISTINCTROW ignoriert, wenn die Abfrage nur eine Tabelle umfasst, oder wenn Sie Felder von allen abgefragten Tabellen ausgeben. In einer Abfrage über eine einzige Tabelle müssen Sie stattdessen DISTINCT verwenden. Die Data Manipulation Language 82 K/ " + 7 Joins über mehr als zwei Tabellen sind nicht einfach zu schreiben und in den meisten SQLBüchern auch nicht beschrieben. Im Prinzip ist die Auflösung von Joins über mehr als zwei Tabelle jedoch recht einfach: Die grundsätzliche Syntax eines Verbundausdrucks bezieht sich ja auf Tabellenreferenzen: Tabellenreferenz1 [NATURAL] [Verbundtyp] JOIN Tabellenreferenz2 Eine Tabellenreferenz kann nun auch das Ergebnis eines Joins sein. Der Übersicht halber schreiben Sie diesen Join besser in Klammern. Beispiel: Mit den Daten der Tabellen Products, Suppliers und Categories (Abbildung 3) soll eine Liste erstellt werden, die den Artikelnamen, den Lieferanten- und den und Kategoriename auflistet. Abbildung 3: Auszug aus der Northwind-Datenbank Der Join dazu sieht folgendermaßen aus: SELECT Suppliers.CompanyName AS Supplier, Products.ProductName, Categories.CategoryName AS Category FROM Categories INNER JOIN (Products INNER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID) ON Categories.CategoryID = Products.CategoryID ORDER BY Suppliers.CompanyName, Products.ProductName; Die Categories-Tabelle referenziert hier das Ergebnis des Joins zwischen Products und Suppliers. Der Join zwischen Products und Suppliers wird natürlich zuerst ausgeführt. Sie müssen also beachten, dass die Categories-Tabelle nur die Datensätze referenzieren kann, die auch im Ergebnis des inneren Joins erscheinen. In diesem Beispiel werden z. B. nicht alle Produkte ausgegeben, da bei einigen in der Spalte SupplierID der Wert Null gespeichert ist. Die Data Manipulation Language 83 Ergebnis: Supplier Productname Category Aux joyeux ecclésiastiques Chartreuse verte Beverages Aux joyeux ecclésiastiques Côte de Blaye Beverages Bigfoot Breweries Laughing Lumberjack Lager Beverages Bigfoot Breweries Sasquatch Ale Beverages Bigfoot Breweries Steeleye Stout Beverages Cooperativa de Quesos ' Las Cabras' Queso Cabrales Dairy Products ... ... ... Die Auflösung des Problems, dass nicht alle Produkte erscheinen, ist natürlich wieder ein Outer Join: SELECT Suppliers.CompanyName AS Supplier, Products.ProductName, Categories.CategoryName AS Category FROM Categories LEFT OUTER JOIN (Products LEFT OUTER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID) ON Categories.CategoryID = Products.CategoryID ORDER BY Suppliers.CompanyName, Products.ProductName; Der linke Left Outer Join ist aus logischer Sicht notwendig. Weiter unten finden Sie Erläuterungen dazu. Ergebnis: Supplier Productname Category NULL Aniseed Syrup Condiments NULL Chai Beverages NULL Chang Beverages Aux joyeux ecclésiastiques Chartreuse verte Beverages Aux joyeux ecclésiastiques Côte de Blaye Beverages Bigfoot Breweries Laughing Lumberjack Lager Beverages ... ... ... Outer Joins in Abfragen über mehr als zwei Tabellen haben so ihre Tücken. In manchen Fällen lässt das Datenbanksystem die Kombination von Inner und Outer Joins oder die Kombination von Left und Right Outer Joins nicht zu oder, was viel schlimmer ist, die Kombination von Inner und Outer Joins führt zu einem falschen Ergebnis. Beispiel: Nun sollen nicht alle Produkte, sondern alle Lieferanten ausgegeben werden (auch Lieferanten, die zurzeit gar keine Produkte liefern). Der erste Ansatz wäre vielleicht der folgende: SELECT Suppliers.CompanyName AS Supplier, Products.ProductName, Categories.CategoryName AS Category FROM Categories INNER JOIN (Products RIGHT OUTER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID) ON Categories.CategoryID = Products.CategoryID ORDER BY Suppliers.CompanyName, Products.ProductName; Der SQL Server 7 (!) führt die Abfrage mit falschem Ergebnis aus (der Lieferant „Exotic Liquids“ fehlt). Der SQL Server 2000 hat allerdings keine Probleme mit der Abfrage. Wenn Sie Die Data Manipulation Language 84 diese Abfrage mal mit einer Access-Datenbank ausführen, meldet die Jet-Engine korrekterweise den Fehler »Die Abfrage konnte nicht ausgeführt werden, da sie mehrdeutige Inklusionsverknüpfungen enthält«. Sie können keine Outer Joins mit Inner Joins kombinieren und davon ausgehen, dass das Ergebnis korrekt ist. Dieses Verhalten ist recht logisch. Der innere Join ergibt alle Lieferanten, d.h., auch die, die keine Produkte liefern. Die virtuelle Spalte CategoryID dieser Datensätze enthält dann den Wert NULL. Ist der äußere Join ein Inner Join, werden diese Datensätze natürlich nicht in das Endergebnis übernommen. Die Lösung des Problems ist die korrekte Verwendung von Outer Joins: SELECT Suppliers.CompanyName AS Supplier, Products.ProductName, Categories.CategoryName AS Category FROM Categories RIGHT OUTER JOIN (Products RIGHT OUTER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID) ON Categories.CategoryID = Products.CategoryID ORDER BY Suppliers.CompanyName, Products.ProductName; Die korrekte Verwendung der Left und Right Outer Joins ist hier sehr schwierig. Gehen Sie dabei von innen nach außen vor: Ist der geklammerte Join ein Outer Join, muss der andere Join ebenfalls ein Outer Join sein, der alle Datensätze des geklammerten Joins berücksichtigt. Sie können sich die Arbeit zur Erstellung eines Joins oft erheblich erleichtern, indem Sie das Abfrage-Tool von Access verwenden. In eine Access-Datenbank können Sie zur Erstellung von Abfragen die benötigten Tabellen über ODBC einbinden (bei Access 2000 auch über ADO). Der SQL Server Enterprise Manager besitzt ebenfalls ein grafisch orientiertes Tool zur Abfragegenerierung, das Sie allerdings nur verwenden können, wenn Sie eine neue Sicht erzeugen. Die Data Manipulation Language 85 6 * Sichten (Views) sind gespeicherte Abfragen (die oft auch als virtuelle Tabellen bezeichnet werden), die sich aus einzelnen ausgewählten Tabellenspalten aus einer oder mehreren Basistabellen zusammensetzen. Sichten werden erstellt, um dem Benutzer eine bestimmte Auswahl von Spalten (auch aus verschiedenen Tabellen) innerhalb einer (virtuellen) Tabelle zur Verfügung zu stellen. Der Benutzer kann mit dieser Sicht arbeiten wie mit einer Basistabelle. Änderungen in den Spalten der Sicht werden immer auch in die Spalten der Basistabellen übertragen (sofern die Sicht aktualisierbar ist). Genauso sind Änderungen der Basistabellen natürlich in der Sicht sichtbar. Über eine Sicht können Sie • komplexe Abfragen vor Clients verbergen. So können Sie zum Beispiel eine komplexe Abfrage in einer Sicht speichern. Ein Client führt nur einen SELECT, UPDATE, INSERT oder DELETE auf dieser Sicht aus und weiß nichts von der Komplexität der zugrundeliegenden Abfrage. • Benutzern Zugriffsrechte auf Tabellen oder Spalten geben, auf die diese normalerweise keine Rechte besitzen. • die Sicherheit komplexer verwalten, als dies mit Zugriffsrechten allein möglich ist (Beispiel dazu siehe unten). • die Performance erhöhen, da eine Sicht in der Datenbank bereits kompiliert ist. #$ Eine Sicht erzeugen Sie in SQL/92 über: CREATE VIEW Viewname [(Spaltenliste)] AS Select_Anweisung [WITH [CASCADED | LOCAL] CHECK OPTION] In der Abfrage geben Sie eine SELECT-Anweisung an. Basiert die Abfrage auf mehr als einer Tabelle, können Sie die Spaltennamen für Spalten, deren Name mehrfach vorkommen, in der optionalen Spaltenliste anpassen (einfacher wäre allerdings, die Spalten mit der AS-Klausel in der Abfrage umzubenennen). Die CHECK OPTION wird weiter unten erläutert. Beispiel: CREATE VIEW Products_With_Category_Name AS SELECT Products.ProductID, Products.ProductName, Categories.CategoryName FROM Products INNER JOIN Categories ON Products.CategoryID = Categories.CategoryID 0 Die Syntax des SQL Servers unterscheidet sich ein wenig von der SQL/92-Syntax: CREATE VIEW Viewname [(Spaltenliste)] [WITH ENCRYPTION] AS SELECT_Anweisung [WITH CHECK OPTION] Die Check-Option ist hier einfacher aufgebaut als bei SQL/92. Erläuterungen dazu finden Sie weiter unten. Die Option WITH ENCRYPTION sorgt dafür, dass die Sicht in der Systemtabelle, die für die Speicherung von Sichten verwendet wird, verschlüsselt gespeichert wird. Sichten 86 / * Ein häufiges Problem bei Sichten ist, dass diese normalerweise nicht innerhalb der SELECTAnweisung sortiert werden können (ORDER BY ist in SQL/92 nicht zulässig). Sie können die Sicht natürlich bei der Abfrage sortieren: SELECT Feldliste FROM Sicht ORDER BY Feldliste Der SQL Server 2000 ermöglicht aber das direkte Sortieren, allerdings nur dann, wenn die Option TOP mit angegeben ist. Um eine Sicht zu sortieren, geben Sie also einfach TOP 100 PERCENT an: CREATE VIEW Products_With_Category_Name AS SELECT TOP 100 PERCENT Products.ProductID, Products.ProductName, Categories.CategoryName FROM Products INNER JOIN Categories ON Products.CategoryID = Categories.CategoryID ORDER BY Products.ProductName Dabei sollten Sie beachten, dass die Abfrage der Daten aufgrund der TOP-Option u. U. länger dauert als ohne diese Option, was ich allerdings bisher nicht nachweisen konnte. L + 0/ * Sichten werden verwendet wie Tabellen. Sie können Auswahlabfragen, aber auch Aktualisierungs-, Anfüge- und Löschabfragen auf der Sicht ausführen. Voraussetzung dafür ist allerdings, dass die der Sicht zugrundeliegende Abfrage das Aktualisieren, Anfügen bzw. Löschen zulässt. Das folgende Beispiel erzeugt eine Sicht, die alle Produkte der Kategorie 1 auflistet: CREATE VIEW Products_Of_Cat1 AS SELECT * FROM Products WHERE CategoryID = 1 Eine Auswahlabfrage würde dann z. B. folgendermaßen aussehen: SELECT * FROM Products_Of_Cat1 WHERE SupplierID = 16 Eine Aktualisierungsabfrage würde z. B. so aussehen: Update Products_Of_Cat1 SET UnitPrice = UnitPrice * 1.2 WHERE SupplierID = 16 Bei der Abfrage müssen Sie bedenken, dass die Sicht die Datensätze eventuell bereits einschränkt. Die Beispiel-Sicht zeigt z. B. grundsätzlich nur die Produkte der Kategorie 1. Eine Abfrage auf die Sicht berücksichtigt dann natürlich nur diese Produkte. ' * B- / Normalerweise erlaubt eine Sicht die enthaltenen Datensätze so zu ändern, dass diese aus der Sicht entfernt werden und neue Datensätze anzufügen, die nicht in der Sicht erscheinen. Beispiel: Ändern der Kategorie der Produkte, die vom Lieferant 16 geliefert werden: UPDATE Products_Of_Cat1 SET CategoryID = 2 WHERE SupplierID = 16 Beispiel: Einfügen eines neuen Produkts der Kategorie 2: INSERT INTO Products_Of_Cat1 VALUES ('Test', 16, 2, NULL, 11.25, 0, 0, 10, 0) Sichten 87 Dieses nicht intuitive Verhalten der Sicht können Sie mit der Check-Option unterbinden: CREATE VIEW Products_Of_Cat1 AS SELECT * FROM Products WHERE CategoryID = 1 WITH CHECK OPTION Eine Abfrage, die die enthaltenen Datensätze so ändert, dass diese aus der Sicht entfernt würden oder neue Datensätze anfügt, die nicht in der Sicht erscheinen würden, schlägt nun mit einer Fehlermeldung fehl. In SQL/92 können Sie bei der Check-Option noch den Zusatz LOCAL oder CASCADED angeben. Mit der Option LOCAL wird die Bedingung der Sicht nur gegen die Sicht selbst geprüft. Mit der Option CASCADED werden auch alle Bedingungen von Sichten mit geprüft, auf denen die Sicht basiert. Da Sie wahrscheinlich selten Sichten definieren, die auf Sichten basieren und die meisten Datenbanksysteme diese Optionen gar nicht kennen, ist das Verständnis dieser Optionen nicht allzu wichtig. Sichten 88 9 '/ / 9 Jedes Objekt in einer Datenbank hat normalerweise einen Eigentümer (Owner). Der Eigentümer eines Objekts hat normalerweise alle Rechte auf diesem Objekt. Zusätzlich können allen anderen Benutzern Zugriffsrechte auf diesen Objekten vergeben werden. Daneben werden Benutzern noch allgemeine Rechte, wie z. B. das Recht Tabellen anzulegen, erteilt. Die Zugriffsrechte werden in SQL/92 folgendermaßen definiert: + * Recht Bedeutung CREATE DATABASE Recht, Datenbanken zu erzeugen CREATE TABLE Recht, Tabellen zu erzeugen CREATE VIEW Recht, Sichten zu erzeugen Tabelle 6.1: Die Anweisungs-Rechte von SQL/92 * B @ Recht Bedeutung USAGE Recht zur Nutzung einer spezifischen Domäne SELECT Recht zur Abfrage einer spezifischen Tabelle INSERT(Spalte) Recht zum Einfügen in eine bestimmte Spalte einer spezifischen Tabelle INSERT Recht zum Einfügen in alle Spalten einer spezifischen Tabelle UPDATE(Spalte) Recht zum Aktualisieren einer bestimmten Spalte einer spezifischen Tabelle UPDATE Recht zum Aktualisieren aller Spalten einer spezifischen Tabelle DELETE Recht zum Löschen aus einer spezifischen Tabelle REFERENCES(Spalte) Recht zur Referenzierung einer bestimmten Spalte einer spezifischen Tabelle REFERENCES Recht zur Referenzierung aller Spalten einer spezifischen Tabelle Tabelle 6.2: Rechte für einzelne Objekte bei SQL/92 Die Data Control Language 89 9 L + 2 0/ * Anweisungs- und Objekt-Rechte werden mit der GRANT-Anweisung vergeben: GRANT {ALL | Anweisungsliste} TO Benutzerliste GRANT Rechteliste ON Objekt TO Benutzerliste [WITH GRANT OPTION] Beispiel: Erteilen des SELECT- und UPDATE-Rechts auf der Tabelle Orders für den Benutzer Zaphod: GRANT SELECT, UPDATE ON Orders TO Zaphod Die Option WITH GRANT OPTION bewirkt, dass der Benutzer das Recht erhält, selbst Rechte auf diesem Objekt zu vergeben. Zunächst dürfen immer nur der Objekteigentümer und in der Regel auch der Administrator der Datenbank Rechte vergeben. Rechte können mit der REVOKE-Anweisung wieder entfernt werden: REVOKE [GRANT OPTION FOR] Rechteliste ON Objekt FROM Benutzerliste {RESTRICT | CASCADE} Die Data Control Language 90 < < 0 * Bowman, Judith S., Emerson, Sandra L., Darnovsky Marcy: The practical SQL Handbook. Using Structured Query Language. Addison-Wesley Developers Press. 1997. Date, Chris J, Darwen Hugh: SQL-Der Standard. SQL/92 mit den Erweiterungen CLI und PSM. Addison Wesley München. 1999. Anmerkung: Standardwerk, aber kompliziert geschrieben und der Autor verweist sehr häufig für Erklärungen auf andere Kapitel. Soukup, Ron, Delaney, Kalen: Inside Microsoft SQL Server 7.0. Microsoft Press Redmond, Washington. 1999. Delaney, Kalen: Inside Microsoft SQL Server 2000. Microsoft Press Redmond, Washington. 2001. Anhang 91 = > ABS-Funktion 63 COS-Funktion 63 ACOS-Funktion 63 COT-Funktion 63 Aggregatfunktionen 56 COUNT_BIG-Funktion 58 Aktualisierungsweitergabe 25 COUNT-Funktion 57 ALL 66 CREATE DATABASE-Anweisung 14 ALL-Operator 69 CREATE SCHEMA-Anweisung 13 ALTER DATABASE-Anweisung 18 CREATE TABLE-Anweisung (SQL Server) 23 ANSI 1 CREATE TABLE-Anweisung. 20 ANSI-SQL 2 CROSS JOIN 75 ANY-Operator 68 CURRENT_DATE-Funktion 59 Arithmetische Ausdrücke 48 CURRENT_TIME-Funktion 59 ARRAY-Datentyp 8 CURRENT_TIMESTAMP-Funktion 59 ASCII-Funktion 64 CURRENT_USER-Funktion 59, 60, 65 ASIN-Funktion 63 cursor-Datentyp 10 AS-Klausel 42 Data Control Language 2, 89 ATAN2-Funktion 63 Data Definition Language 2 ATAN-Funktion 63 Data Manipulation Language 2, 33 AVG-Funktion 57 DATALENGTH-Funktion 65 Bedingungen 43 DATEADD-Funktion 62 Benutzerdefinierte Datentypen 20 DATE-Datentyp 8 Bereichssuche 47 DATEDIFF-Funktion 62 BETWEEN-Operator 47 DATE-Funktion 61 bigint-Datentyp 9 DATENAME-Funktion 62 binary-Datentyp 10 Datenbanken BIT VARYING-Datentyp 6 Anlegen 13 BIT_LENGTH-Funktion 59 Dateien hinzufügen 19 bit-Datentyp 10 Dateien löschen 19 BIT-Datentyp 6 Dateigruppen 15 BOOLEAN-Datentyp 8 Dateigruppen hinzufügen und löschen 19 CASE-Funktion 59, 61 Einstellungen verändern 19 CAST-Funktion 59 Löschen 17 CEILING-Funktion 63 Modifizieren 18 CHAR_LENGTH-Funktion 59 Datensätze CHARACTER VARYING-Datentyp 6 Abfragen 40 CHARACTER-Datentyp 6 Ändern 38 CHAR-Datentyp 6 Anfügen 33 CHARINDEX-Funktion 64 Anfügen mit Werten aus anderen Tabellen 34 Check-Option 87 Löschen 36 CHECKSUM_AGG-Funktion 58 Datentypen 6 COALESCE-Funktion 65 ARRAY 8 Constraints Siehe Einschränkungen bei Abfragen 45 CONVERT-Funktion 59 bigint 9 Index 92 binary 10 Datums- und Zeitdatentypen 8 bit 10 DAY-Funktion 61, 62 BIT 6 DCL 2, 89 BIT VARYING 6 DDL 2 BOOLEAN 8 DEC-Datentyp 6 CHAR 6 DECIMAL-Datentyp 6 CHARACTER 6 DEGREES-Funktion 63 CHARACTER VARYING 6 DELETE-Anweisung 36 cursor 10 DIFFERENCE-Funktion 64 DATE 8 DISTINCT 66, 82 datetime 9 DML 2, 33 Datums- und Zeitdatentypen 8 Domänen 19 DEC 6 DOUBLE PRECISION-Datentyp 7 DECIMAL 6 DOUBLE-Datentyp 7 DOUBLE 7 DROP DATABASE-Anweisung 17 DOUBLE PRECISION 7 DROP SCHEMA-Anweisung 17 FLOAT 7 DROP TABLE-Anweisung 30 image 10 Eigentümer 89 INT 6 Einschränkungen 21 INTEGER 6 beim SQL Server 25 LARGE OBJECT 8 Deaktivieren und aktivieren 30 LOB 8 für die Tabelle 22 money 9 für Spalten 21 nchar 9 Hinzufügen 29 ntext 10 Löschen 29 NUMERIC 6 Equijoin 76 nvarchar 9 EXISTS-Operator 70 ROW 8 EXP-Funktion 63 smalldatetime 9 FLOAT-Datentyp 7 SMALLINT 7 FLOOR-Funktion 63 smallmoney 9 Full Outer Join 79 sql_variant 10 Funktionen 56 sysname 10 GETDATE-Funktion 62 table 10 GETUTCDATE-Funktion 62 text 10 GROUP BY-Klausel 49 TIME 8 GROUPING-Funktion 58 timestamp 10 Gruppieren 49 TIMESTAMP 8 GUID-Werte 24 tinyint 9 HAVING-Klausel 50 uniqueidentifier 10 image-Datentyp 10 varbinary 10 Index VARCHAR 6 DATEPART-Funktion 62 datetime-Datentyp 9 Erstellen 31 Löschen 32 Indizes 31 Index 2 INNER JOIN 75 ON UPDATE CASCADE 25 IN-Operator 46 Operatoren 43 INSERT-Anweisung 33 ORDER BY-Klausel 49 INT-Datentyp 6 Outer Join 77, 79, 84 INTEGER-Datentyp 6 Korrekte Verwendung 85 ISDATE-Funktion 65 Owner 89 ISNULL-Funktion 65 PATINDEX-Funktion 64 ISNUMERIC-Funktion 65 PI-Funktion 63 IS-Operator 47 POSITION-Funktion 60 Joins 70 POWER-Funktion 63 Karthesisches Produkt 75 Projektion 41 Kataloge 13 QUOTENAME-Funktion 64 Konventionen 3 RADIANS-Funktion 63 LARGE OBJECT-Datentyp 8 RAND-Funktion 63 Left Outer Join 79 REPLACE-Funktion 64 LEFT-Funktion 64 REPLICATE-Funktion 64 LEN-Funktion 64 Restriktion 41 LIKE-Operator 46 REVERSE-Funktion 64 Literale 11 Right Outer Join 79 Datentypen 12 RIGHT-Funktion 64 LOB-Datentyp 8 ROUND-Funktion 63 LOG10-Funktion 63 ROW-Datentyp 8 LOG-Funktion 63 RTRIM-Funktion 64 Löschweitergabe 25 Schemata 13 LOWER-Funktion 59, 64 SELECT-Anweisung 40 LTRIM-Funktion 64 SEQUEL 1 MAX-Funktion 57 SESSION_USER-Funktion 60 Mengensuche 46 Sichten 86 MIN-Funktion 57 Check-Option 87 money-Datentyp 9 SIGN-Funktion 63 MONTH-Funktion 61, 62 SIN-Funktion 63 Mustersuche 46 Skalare Funktionen 59 Natürlicher Join 76 smalldatetime-Datentyp 9 nchar-Datentyp 9 SMALLINT-Datentyp 7 NCHAR-Funktion 64 smallmoney-Datentyp 9 NEWID-Funktion 65 Sortieren 49 NOT NULL 25 SOUNDEX-Funktion 64 ntext-Datentyp 10 SPACE-Funktion 64 NULL-Werte 25 Spalten NUMERIC-Datentyp 6 Ändern 28 nvarchar-Datentyp 9 Hinzufügen 27 Ocelot 2 Löschen 28 OCTET_LENGTH-Funktion 60 ON DELETE CASCADE 25 Spalteneinschränkungen 21 SQL Server 25 Index 3 SQL/86 1 Thetajoin 76 SQL/89 1 TIME-Datentyp 8 SQL/92 1 timestamp-Datentyp 10 sql_variant-Datentyp 10 TIMESTAMP-Datentyp 8 SQRT-Funktion 63 tinyint-Datentyp 9 SQUARE-Funktion 63 TOP 67 STDEV-Funktion 58 TRANSLATE-Funktion 60 STDEVP-Funktion 58 TRIM-Funktion 60 STR-Funktion 64 UNICODE-Funktion 65 STUFF 65 uniqueidentifier-Datentyp 10 STUFF-Funktion 65 Unterabfragen 67 Subquerys 67 UPDATE-Anweisung 38 SUBSTRING-Funktion 60, 65 UPPER-Funktion 60, 65 SUM-Funktion 57 USER-Funktion 60 sysname-Datentyp 10 USING-Klausel 72 SYSTEM_USER-Funktion 60 varbinary-Datentyp 10 Tabelle VARCHAR-Datentyp 6 Löschen 30 Tabellen Anlegen 20 Tabelleneinschränkungen 22 SQL Server 26 VAR-Funktion 58 VARP-Funktion 58 Verbundausdrücke 70 Views 86 WEEKDAY-Funktion 61, 62 table-Datentyp 10 WHERE-Klausel 43 TAN-Funktion 63 YEAR-Funktion 61, 62 text-Datentyp 10 Index 4