Heute ist die Structured Query Language das Standardmittel zur Manipulation und Abfrage von Daten in relationalen Datenbanken, wenn auch mit proprietären Erweiterungen unter den Produkten. Die Einfachheit und Allgegenwärtigkeit von SQL hat sogar dazu geführt, dass die Entwickler vieler „NoSQL“- oder nicht-relationaler Datenspeicher, wie z. B. Hadoop, Teilmengen von SQL übernommen haben oder ihre eigenen SQL-ähnlichen Abfragesprachen entwickelt haben.
Aber SQL war nicht immer die „universelle“ Sprache für relationale Datenbanken. Von Anfang an (ca. 1980) hatte SQL gewisse Nachteile. Viele Forscher und Entwickler, mich eingeschlossen, dachten damals, dass der Overhead von SQL es davon abhalten würde, jemals in einer Produktionsdatenbank eingesetzt zu werden.
Klarerweise lagen wir falsch. Aber viele sind immer noch der Meinung, dass trotz der Einfachheit und Zugänglichkeit von SQL der Preis, der durch die Laufzeitleistung gefordert wird, oft zu hoch ist.
SQL-Geschichte
Bevor es SQL gab, hatten Datenbanken enge, navigierbare Programmierschnittstellen und wurden typischerweise um ein Netzwerkschema herum entworfen, das CODASYL-Datenmodell. CODASYL (Committee on Data Systems Languages) war ein Konsortium, das für die Programmiersprache COBOL (ab 1959) und die Erweiterungen der Datenbanksprache (ab 10 Jahre später) verantwortlich war.
Wenn Sie gegen eine CODASYL-Datenbank programmierten, navigierten Sie zu Datensätzen über Sets, die Eins-zu-Viel-Beziehungen ausdrücken. Ältere hierarchische Datenbanken erlauben nur, dass ein Datensatz zu einem Set gehört. Netzwerkdatenbanken erlauben es, dass ein Datensatz zu mehreren Sets gehört.
Angenommen, Sie wollten eine Liste der Studenten erstellen, die in CS 101 eingeschrieben sind. Zuerst würden Sie "CS 101"
im Courses
nach dem Namen suchen, diesen als Eigentümer oder Elternteil des Enrollees
-Sets festlegen, das erste Mitglied (ffm
) des Enrollees
-Sets finden, das ein Student
-Datensatz ist, und es auflisten. Dann würden Sie in eine Schleife gehen: Nächstes Mitglied (fnm
) suchen und auflisten. Wenn fnm
fehlschlug, verließ man die Schleife.
Das mag dem Datenbankprogrammierer wie eine Menge Routinearbeit vorkommen, aber zur Ausführungszeit war es sehr effizient. Experten wie Michael Stonebraker von der University of California in Berkeley und Ingres wiesen darauf hin, dass eine solche Abfrage in einer CODASYL-Datenbank wie IDMS etwa die Hälfte der CPU-Zeit und weniger als die Hälfte des Speichers benötigt als die gleiche Abfrage in einer relationalen Datenbank mit SQL.
Zum Vergleich: Die äquivalente SQL-Abfrage, um alle Studenten des Kurses „CS 101“ zu ermitteln, wäre etwas wie
SELECT student.name FROM courses, enrollees, students WHERE course.name ="CS 101"
Diese Syntax impliziert einen relationalen inneren Join (eigentlich zwei), wie ich weiter unten erklären werde, und lässt einige wichtige Details aus, wie z. B. die für die Joins verwendeten Felder.
Relationale Datenbanken und SQL
Warum sollte man eine Verbesserung der Ausführungsgeschwindigkeit und des Speicherverbrauchs um den Faktor zwei aufgeben? Es gab zwei wichtige Gründe: einfache Entwicklung und Portabilität. Ich dachte, dass beides 1980 im Vergleich zu Leistung und Speicherbedarf keine große Rolle spielte, aber als die Computerhardware besser und billiger wurde, hörten die Leute auf, sich um Ausführungsgeschwindigkeit und Speicher zu kümmern und machten sich mehr Sorgen um die Entwicklungskosten.
Mit anderen Worten: Moore’s Law tötete CODASYL-Datenbanken zugunsten von relationalen Datenbanken. Tatsächlich war die Verbesserung der Entwicklungszeit signifikant, aber die SQL-Portabilität erwies sich als Wunschtraum.
Woher kamen das relationale Modell und SQL? E.F. „Ted“ Codd war ein Informatiker am IBM San Jose Research Laboratory, der in den 1960er Jahren die Theorie des relationalen Modells ausarbeitete und 1970 veröffentlichte. IBM zögerte mit der Implementierung einer relationalen Datenbank, um die Einnahmen seiner CODASYL-Datenbank IMS/DB zu schützen. Als IBM schließlich sein System R-Projekt startete, war das Entwicklungsteam (Don Chamberlin und Ray Boyce) nicht unter Codd, und sie ignorierten Codds 1971 veröffentlichtes Papier zur relationalen Sprache Alpha, um ihre eigene Sprache, SEQUEL (Structured English Query Language), zu entwerfen. 1979, noch bevor IBM sein Produkt auf den Markt gebracht hatte, integrierte Larry Ellison die Sprache in seine Oracle-Datenbank (wobei er IBMs SEQUEL-Veröffentlichungen aus der Zeit vor der Markteinführung als Spezifikation verwendete). Aus SEQUEL wurde bald SQL, um eine internationale Markenrechtsverletzung zu vermeiden.
Die „Töne für SQL“ (wie Michael Stonebraker es ausdrückte) kamen nicht nur von Oracle und IBM, sondern auch von Kunden. Es war nicht einfach, CODASYL-Datenbankdesigner und -Programmierer einzustellen oder auszubilden, also sah SEQUEL (und SQL) viel attraktiver aus. SQL war in den späten 1980er Jahren so attraktiv, dass viele Datenbankhersteller im Wesentlichen einen SQL-Abfrageprozessor auf ihre CODASYL-Datenbanken klammerten, zum großen Missfallen von Codd, der der Meinung war, dass relationale Datenbanken von Grund auf neu entwickelt werden mussten, um relational zu sein.
Eine reine relationale Datenbank, wie sie von Codd entworfen wurde, ist auf Tupeln aufgebaut, die in Relationen gruppiert sind, die der Prädikatenlogik erster Ordnung entsprechen. Relationale Datenbanken der realen Welt haben Tabellen, die Felder, Einschränkungen und Auslöser enthalten, und die Tabellen sind durch Fremdschlüssel miteinander verbunden. Ein SQL-Abfrageprozessor und ein Abfrageoptimierer wandeln die SQL-Deklaration in einen Abfrageplan um, der von der Datenbank-Engine ausgeführt wird.
SQL enthält eine Untersprache für die Definition von Schemata, die Datendefinitionssprache (DDL), sowie eine Untersprache für die Modifikation von Daten, die Datenmanipulationssprache (DML). Beide haben ihre Wurzeln in frühen CODASYL-Spezifikationen. Die dritte Untersprache in SQL deklariert Abfragen durch die SELECT
-Anweisung und relationale Joins.
SQL SELECT-Anweisung
Die SELECT
-Anweisung teilt dem Abfrageoptimierer mit, welche Daten er zurückgeben soll, in welchen Tabellen er suchen soll, welche Relationen er verfolgen soll und welche Reihenfolge er den zurückgegebenen Daten auferlegen soll. Der Abfrageoptimierer muss selbst herausfinden, welche Indizes zu verwenden sind, um Brute-Force-Tabellenscans zu vermeiden und eine gute Abfrageleistung zu erzielen, es sei denn, die jeweilige Datenbank unterstützt Index-Hinweise.
Ein Teil der Kunst des relationalen Datenbankdesigns hängt von der vernünftigen Verwendung von Indizes ab. Wenn Sie einen Index für eine häufige Abfrage weglassen, kann die gesamte Datenbank bei starker Leselast langsamer werden. Wenn Sie zu viele Indizes haben, kann die gesamte Datenbank bei hohen Schreib- und Aktualisierungslasten langsamer werden.
Eine weitere wichtige Kunst ist die Wahl eines guten, eindeutigen Primärschlüssels für jede Tabelle. Sie müssen nicht nur die Auswirkungen des Primärschlüssels auf gewöhnliche Abfragen berücksichtigen, sondern auch, wie er sich in Joins verhält, wenn er als Fremdschlüssel in einer anderen Tabelle auftaucht, und wie er sich auf die Lokalität der Daten auswirkt.
Im fortgeschrittenen Fall von Datenbanktabellen, die in Abhängigkeit vom Wert des Primärschlüssels auf verschiedene Volumes aufgeteilt werden, dem sogenannten horizontalen Sharding, müssen Sie auch berücksichtigen, wie der Primärschlüssel das Sharding beeinflusst. Tipp: Sie wollen, dass die Tabelle gleichmäßig über die Volumes verteilt ist, was darauf hindeutet, dass Sie keine Datumsstempel oder fortlaufende ganze Zahlen als Primärschlüssel verwenden wollen.
Die Diskussion der SELECT
-Anweisung mag einfach beginnen, kann aber schnell verwirrend werden. Bedenken Sie:
SELECT * FROM Customers;
Einfach, oder? Es fragt nach allen Feldern und allen Zeilen der Customers
-Tabelle. Nehmen wir aber an, die Tabelle Customers
hat hundert Millionen Zeilen und hundert Felder, und eines der Felder ist ein großes Textfeld für Kommentare. Wie lange wird es dauern, all diese Daten über eine Netzwerkverbindung mit 10 Megabit pro Sekunde herunterzuladen, wenn jede Zeile durchschnittlich 1 Kilobyte Daten enthält?
Vielleicht sollten Sie die Menge, die Sie über die Leitung schicken, reduzieren. Überlegen Sie:
SELECT TOP 100 companyName, lastSaleDate, lastSaleAmount, totalSalesAmount FROM Customers
WHERE state ="Ohio" AND city ="Cleveland"
ORDER BY lastSaleDate DESCENDING;
Nun werden Sie viel weniger Daten abrufen. Sie haben die Datenbank gebeten, Ihnen nur vier Felder zu geben, nur die Firmen in Cleveland zu berücksichtigen und Ihnen nur die 100 Firmen mit den jüngsten Umsätzen zu geben. Um das am effizientesten auf dem Datenbankserver zu erledigen, muss jedoch braucht die Tabelle Customers
einen Index auf state+city
für die WHERE
Klausel und einen Index auf lastSaleDate
für die ORDER BY
und TOP 100
Klauseln.
Das TOP 100
ist übrigens für SQL Server und SQL Azure gültig, aber nicht für MySQL oder Oracle. In MySQL würden Sie LIMIT 100
nach der WHERE
-Klausel verwenden. In Oracle würden Sie eine Bindung an ROWNUM
als Teil der WHERE
-Klausel verwenden, d.h. WHERE... AND ROWNUM <=100
. Leider gehen die ANSI/ISO-SQL-Standards (und davon gibt es bisher neun, die von 1986 bis 2016 reichen) nur so weit, dass jede Datenbank ihre eigenen proprietären Klauseln und Funktionen einführt.
SQL-Joins
Bislang habe ich die SELECT
-Syntax für einzelne Tabellen beschrieben. Bevor ich JOIN
-Klauseln erklären kann, müssen Sie Fremdschlüssel und Beziehungen zwischen Tabellen verstehen. Ich erkläre das anhand von Beispielen in DDL, unter Verwendung der SQL Server-Syntax.
Die Kurzversion ist recht einfach. Jede Tabelle, die Sie in Relationen verwenden wollen, sollte eine Primärschlüssel-Beschränkung haben; dies kann entweder ein einzelnes Feld oder eine Kombination von Feldern sein, die durch einen Ausdruck definiert werden. Beispiel:
CREATE TABLE Persons (
PersonID int NOT NULL PRIMARY KEY,
PersonName char(80),
...
Jede Tabelle, die sich auf Persons
beziehen muss, sollte ein Feld haben, das dem Persons
Primärschlüssel entspricht, und um die relationale Integrität zu wahren, sollte dieses Feld eine Fremdschlüssel-Beschränkung haben. Beispiel:
CREATE TABLE Orders (
OrderID int NOT NULL PRIMARY KEY,
...
PersonID int FOREIGN KEY REFERENCES Persons(PersonID)
);
Es gibt längere Versionen beider Anweisungen, die das Schlüsselwort CONSTRAINT
verwenden, mit dem Sie die Einschränkung benennen können. Das ist das, was die meisten Datenbank-Design-Tools erzeugen.
Primärschlüssel sind immer indiziert und eindeutig (die Feldwerte können nicht dupliziert werden). Andere Felder können optional indiziert werden. Es ist oft sinnvoll, Indizes für Fremdschlüsselfelder und für Felder, die in WHERE
und ORDER BY
-Klauseln vorkommen, zu erstellen, allerdings nicht immer, wegen des potenziellen Overheads bei Schreibvorgängen und Aktualisierungen.
Wie würden Sie eine Abfrage schreiben, die alle Bestellungen von John Doe zurückgibt?
SELECT PersonName, OrderID FROM Persons
INNER JOIN Orders ON Persons.PersonID = Orders.PersonID
WHERE PersonName ="John Doe";
In der Tat gibt es vier Arten von JOIN
INNER
OUTER
LEFT
, und RIGHT
. Das INNER JOIN
ist der Standard (Sie können das Wort INNER
weglassen), und es ist derjenige, der nur Zeilen enthält, die übereinstimmende Werte in beiden Tabellen enthalten. Wenn Sie Personen auflisten wollen, ob sie Bestellungen haben oder nicht, würden Sie zum Beispiel ein LEFT JOIN
verwenden:
SELECT PersonName, OrderID FROM Persons
LEFT JOIN Orders ON Persons.PersonID = Orders.PersonID
ORDER BY PersonName;
Wenn Sie anfangen, Abfragen zu machen, die mehr als zwei Tabellen verbinden, die Ausdrücke verwenden oder die Datentypen erzwingen, kann die Syntax anfangs etwas haarig werden. Glücklicherweise gibt es Datenbankentwicklungswerkzeuge, die korrekte SQL-Abfragen für Sie generieren können, oft durch Ziehen und Ablegen von Tabellen und Feldern aus dem Schemadiagramm in ein Abfragediagramm.
SQL Stored Procedures
Manchmal bringt Sie die deklarative Natur der SELECT
-Anweisung nicht ans Ziel. Die meisten Datenbanken haben eine Funktion, die Stored Procedures genannt wird; leider ist dies ein Bereich, in dem fast alle Datenbanken proprietäre Erweiterungen der ANSI/ISO SQL-Standards verwenden.
In SQL Server war der ursprüngliche Dialekt für Stored Procedures (oder Stored Procs) Transact-SQL, auch T-SQL genannt; in Oracle war es PL-SQL. Beide Datenbanken haben weitere Sprachen für Stored Procedures hinzugefügt, wie z. B. C#, Java und R. Eine einfache T-SQL Stored Procedure ist vielleicht nur eine parametrisierte Version einer SELECT
-Anweisung. Ihre Vorteile sind einfache Handhabung und Effizienz. Stored Procedures werden optimiert, wenn sie gespeichert werden, nicht jedes Mal, wenn sie ausgeführt werden.
Eine kompliziertere T-SQL Stored Procedure kann mehrere SQL-Anweisungen, Eingabe- und Ausgabeparameter, lokale Variablen, BEGIN...END
Blöcke, IF...THEN...ELSE
Bedingungen, Cursor (zeilenweise Verarbeitung eines Sets), Ausdrücke, temporäre Tabellen und eine ganze Reihe anderer prozeduraler Syntax verwenden. Wenn die Stored-Procedure-Sprache C#, Java oder R ist, werden Sie natürlich die Funktionen und Syntax dieser prozeduralen Sprachen verwenden. Mit anderen Worten, trotz der Tatsache, dass die Motivation für SQL darin bestand, standardisierte deklarative Abfragen zu verwenden, sieht man in der realen Welt eine Menge datenbankspezifischer prozeduraler Serverprogrammierung.
Das bringt uns nicht ganz zurück in die schlechten alten Zeiten der CODASYL-Datenbankprogrammierung (obwohl Cursor dem schon sehr nahe kommen), aber es geht zurück von der Idee, dass SQL-Anweisungen standardisiert sein sollten und dass Performance-Bedenken dem Datenbank-Abfrageoptimierer überlassen werden sollten. Am Ende ist eine Verdoppelung der Leistung oft zu viel, um sie auf dem Tisch liegen zu lassen.
SQL lernen
Die unten aufgeführten Seiten können Ihnen helfen, SQL zu lernen oder die Eigenheiten verschiedener SQL-Dialekte zu entdecken.
- Codecademy. SQL lernen. Kostenlos und interaktiv. Pro-Upgrade gegen Gebühr erhältlich.
- Khan Academy. Einführung in SQL: Abfragen und Verwalten von Daten. Kostenloses Video-Tutorial.
- SoloLearn. SQL-Grundlagen. MySQL-orientiert. Kostenlos.
- SQL Problems and Solutions und SQL Exercises. Interaktives Lehrbuch und Übungen. Kostenlos.
- SQLZoo. Ein interaktives SQL-Tutorial, entwickelt und gepflegt von der Edinburgh Napier University. Unterstützt Microsoft SQL Server, Oracle Database, MySQL, IBM DB2, und PostgreSQL. Kostenlos.
- Tutorialspoint. Lernen Sie SQL. Nur Text, nicht interaktiv. Kostenlos.
- Udacity. Intro to Relational Databases. Verwendet Python und erfordert einige Python-Kenntnisse. Kostenlos.
- Udemy. Kostenlose Kurse: Einführung in Datenbanken und SQL-Abfragen, MySQL-Datenbank für Anfänger, Microsoft SQL für Anfänger, Hands-on SQL für Anfänger (
SELECT
FROM
undWHERE
) und Sachin Quickly Learns (SQL). - Vertabelo Academy. SQL-Grundlagen, Operieren mit Daten in SQL, Erstellen von Tabellen in SQL und acht weitere interaktive SQL-Kurse. Einige Kurse können kostenlos getestet werden, danach kann eine Gebühr anfallen. Es gibt sieben weitere Kurse für Microsoft SQL Server. Die Seite bietet auch ein grafisches Datenbank-Design-Tool für PostgreSQL, MySQL, Oracle Database, SQL Server, SQLite und IBM DB2.
- W3Schools. SQL Tutorial. Kostenlos und ohne Registrierung verfügbar.
Datenbankdokumentation: