Verwendung: Verwaltung großer Datenmengen
Beispiele:
Grundlegende Operationen für Datenbankeinträge:
SQL:
INSERT INTO product (name, category, price)
VALUES ('IPhone', 'electronics', 699);
MongoDB shell:
db.products.insertOne({
name: 'IPhone',
category: 'electronics',
price: 699,
});
SQL:
SELECT * FROM product
WHERE category = 'electronics';
MongoDB shell:
db.products.find({ category: 'electronics' });
SQL:
SELECT name, category FROM product
WHERE category = 'electronics';
MongoDB shell:
db.products
.find({ category: 'electronics' })
.project({ name: 1, price: 1 });
SQL:
UPDATE product
SET category = 'phones'
WHERE name = 'IPhone';
MongoDB shell:
db.products.updateOne(
{ name: 'IPhone' },
{ $set: { category: 'phones' } }
);
SQL:
DELETE FROM product
WHERE name = 'IPhone';
MongoDB shell:
db.products.deleteOne({ name: 'IPhone' });
Erstellen / Ändern / Abfragen von Daten in einem Online Playground
SQL (Structured Query Language): entwickelt in den 1970ern, viele verschiedene Varianten
MongoDB: 2009 veröffentlicht
SQL: Einträge werden in Tabellen mit vordefinierten Feldnamen und Feldtypen gespeichert (Datenbankschema)
MongoDB: Einträge (Dokumente) in einer Collection können beliebige Felder haben; optional mit einem Schema validierbar
SQL: standardisierte Sprache (theoretisch), unabhängig von der verwendeten Programmiersprache
MongoDB: direkte Bindings für Programmiersprachen
siehe: https://docs.mongodb.com/manual/reference/bson-types/
Dokumente bekommen automatisch ein eindeutiges _id
-Feld:
entry = {
_id: ObjectId('5e715e1b31315b0be066db84'),
name: 'Argentina',
continent: 'South America',
};
MongoDB basiert auf dem BSON Dateiformat. Dieses ähnelt JSON, ist aber ein binäres Format und lässt sich effizienter lesen und schreiben.
Der Export bzw Import geschieht mittels der Programme mongodump
und mongorestore
Atlas: gehostete MongoDB-Datenbanken von den Entwicklern von MongoDB (Login via Google möglich)
versuche:
MongoDB Shell = einfaches Befehlszeileninterface für MongoDB
online ausprobieren:
https://docs.mongodb.com/manual/tutorial/getting-started/
eine Untermenge der MongoDB shell in reinem JavaScript verwenden (ohne Installation von MongoDB):
wichtige Befehle:
.insertOne
.insertMany
.find
.findOne
.updateOne
.replaceOne
.deleteOne
.deleteMany
Erstellen eines Eintrags:
db.countries.insertOne({
name: 'Argentina',
continent: 'South America',
});
Erstellen mehrerer Einträge:
db.countries.insertMany([
{ name: 'Finland', continent: 'Europe' },
{ name: 'Greece', continent: 'Europe' },
]);
Auslesen aller Elemente:
db.countries.find();
Auslesen bestimmter Elemente:
db.countries.find({ continent: 'Europe' });
Auslesen eines einzelnen Eintrags mittels findOne
:
db.countries.findOne({ name: 'Greece' });
Abändern eines Dokuments - Setzen des Eintrags "population":
db.countries.updateOne(
{ name: 'Argentina' },
{ $set: { population: 44 } }
);
Ersetzen eines Dokuments:
db.countries.replaceOne(
{ name: 'Brazil' },
{ name: 'Brazil', population: 210 }
);
Löschen eines Dokuments:
db.countries.deleteOne({ name: 'Finland' });
Löschen aller Einträge:
db.countries.deleteMany({});
Erstellen und Ändern einer Kontaktdatenbank
db.todos.find({ completed: false }).count();
$text
$regex
$gt
, $gte
, $lt
, $lte
$in
db.products.find({ name: { $text: 'fairphone' } });
db.products.find({
category: 'phone',
price: { $lt: 300 },
});
db.products.find({
category: { $in: ['laptop', 'tablet'] },
price: { $lt: 400 },
});
siehe: https://docs.mongodb.com/manual/reference/operator/query/
Abfragen von bestimmten Feldern:
db.products
.find({ category: 'phone' })
.project({ name: 1, price: 1 });
liefert nur name und price (und _id) aller Einträge
SQL = Structured Query Language
Standardisierte Abfragesprache für tabellarische Datenbanken
Standardisiert von ANSI und ISO - allerdings weichen Implementierungen oft vom Standard ab
Die beste Unterstützung für standardisiertes SQL bietet wohl PostgreSQL
Alte Entwürfe des Standards (kostenlos):
open source:
proprietär:
https://db-fiddle.com (PostgreSQL, MySQL, SQLite)
https://www.w3schools.com/sql/trysql.asp?filename=trysql_select_all (SQLite)
Desktop-Anwendung:
https://sqlitebrowser.org/ (SQLite)
SQL Statements werden mit ;
beendet
Kommentare sind auf zwei Arten möglich:
/* mehrzeiliger
Kommentar */
-- einzeiliger Kommentar
SQL ist größtenteils case-insensitive
Konvention: Keywords groß geschrieben, Rest meist klein und durch Unterstriche getrennt
Beispiel:
SELECT first_name, last_name, tel FROM person;
Tabellen- und Spaltennamen werden von SQL in Großschreibweise konvertiert, z.B. first_name
→ FIRST_NAME
, person
→ PERSON
Ausnahme: In PostgreSQL werden Namen in Kleinschreibweise konvertiert
Sollen Namen in exakter Schreibweise übernommen werden, müssen sie in Anführungszeichen gesetzt werden. Dies ist eher unüblich.
SELECT "First_Name", "Last_Name", "Tel" FROM "Person";
Ausnahme: In MySQL würden hier Backticks (`) statt Anführungszeichen verwendet werden; hier kann über den Modus ANSI_QUOTES
ein Standard-kompatibles Verhalten erreicht werden
Befehl: CREATE TABLE
CREATE TABLE person(
name VARCHAR(50),
tel VARCHAR(20)
);
ISO / ANSI SQL Standard (Auswahl):
BOOLEAN
INT
/ INTEGER
, SMALLINT
, BIGINT
NUMERIC
/ DECIMAL
REAL
, DOUBLE PRECISION
VARCHAR(n)
VARBINARY(n)
DATE
, TIME
, TIMESTAMP
INSERT INTO person (name, tel)
VALUES ('John Smith', '012345');
Kurzschreibweise:
INSERT INTO person
VALUES ('John Smith', '012345');
Daten aller Personen auslesen
SELECT name, tel FROM person;
oder
SELECT * FROM person;
SELECT tel
FROM person
WHERE name = 'John Smith';
SELECT tel
FROM person
WHERE name LIKE '% Smith'
AND tel LIKE '+49%';
UPDATE person
SET name = 'John Miller'
WHERE name = 'John Smith';
DELETE FROM person
WHERE name = 'John Miller';
Erstellen und Abändern einer Kontaktdatenbank
Validierung mittels JSON schema, z.B.:
const elementSchema = {
bsonType: 'object',
required: [
'atomic_number',
'symbol',
'name',
'atomic_mass',
],
properties: {
atomic_number: {
bsonType: 'int',
minimum: 1,
},
symbol: {
bsonType: 'string',
},
name: {
bsonType: 'string',
},
atomic_mass: {
bsonType: 'double',
},
},
};
db.createCollection('elements', {
validator: { $jsonSchema: elementSchema },
});
ISO / ANSI SQL Standard (Auswahl):
BOOLEAN
INT
/ INTEGER
, SMALLINT
, BIGINT
REAL
, DOUBLE PRECISION
VARCHAR(n)
VARBINARY(n)
DATE
, TIME
, TIMESTAMP
Werden durch die Ausdrücke TRUE
und FALSE
repräsentiert
Abweichungen vom Standard:
BOOLEAN
→ BIT
0
und 1
SMALLINT
(üblicherweise 16 Bit)INT
/ INTEGER
(üblicherweise 32 Bit)BIGINT
(üblicherweise 64 Bit)NUMERIC
/ DECIMAL
(Dezimalzahlen mit variabler Genauigkeit)REAL
(üblicherweise 32 Bit)DOUBLE PRECISION
(üblicherweise 64 Bit)Abweichungen vom Standard:
REAL
→ FLOAT
Dezimalzahl mit 10 Stellen vor und 2 Stellen nach dem Komma:
DECIMAL(12, 2)
MySQL unterscheidet z.B. zwischen:
SMALLINT
(-32768 bis 32767)UNSIGNED SMALLINT
(0 bis 65535)Dies ist nicht Teil des SQL Standards
VARCHAR(10)
: Text der Maximallänge 10Üblicherweise wird Unicode unterstützt
Bei SQL Server sollte für Unicodeunterstütztung NVARCHAR
verwendet werden (benötigt doppelt so viel Speicherplatz)
bei Oracle nennt sich der entsprechende Datentyp VARCHAR2
Die Maximallänge hat keine Auswirkung auf den Speicherbedarf auf der Festplatte; allerdings kann sie den RAM-Verbrauch beim Lesen und Schreiben beeinflussen
Text wird immer mit einfachen Anführungszeichen begrenzt.
INSERT INTO test VALUES ('Hello');
Escapen von einfachen Anführungszeichen durch Verdoppelung:
INSERT INTO test VALUES ('Let''s go');
VARBINARY(n)
: Bytesequenz mit Maximallänge n
Abweichungen vom Standard:
BYTEA
BLOB
, aber VARBINARY
wird akzeptiertTypen:
DATE
: DatumTIME
: UhrzeitTIMESTAMP
: Datum und UhrzeitBeispiele:
'2013-02-14'
(empfohlenes Format)'13:02:17'
, '13:02:17.232'
'2013-02-14 13:02:17'
, '2013-02-14T13:02:17'
Abweichungen vom Standard:
SQLite
unterstütztTIMESTAMP
→ DATETIME
Einschränkungen:
TIMESTAMP
auf Jahre zwischen 1970 und 2038 beschränkt - eine bessere Alternative ist DATETIME
Einträge:
CREATE TABLE element(
atomic_number INT,
symbol VARCHAR(2),
name VARCHAR(20),
atomic_mass REAL
);
Einschränkungen von Spalten:
NOT NULL
UNIQUE
PRIMARY KEY
FOREIGN KEY
)Eintrag darf nicht leer gelassen werden
CREATE TABLE element(
atomic_number INT NOT NULL,
symbol VARCHAR(3) NOT NULL,
name VARCHAR(20) NOT NULL,
atomic_mass REAL NOT NULL
);
Jeder Eintrag in einer Spalte muss einzigartig sein
CREATE TABLE element(
atomic_number INT NOT NULL UNIQUE,
symbol VARCHAR(2) NOT NULL UNIQUE,
name VARCHAR(20) NOT NULL UNIQUE,
atomic_mass REAL NOT NULL
);
Ermöglicht eindeutige Identifizierung einer Zeile in einer Tabelle
Ein sprechender Schlüssel ist nur in besonderen Fällen einsetzbar, ein Surrogatschlüssel ist immer möglich
Ein Primary Key ist automatisch unique und not null.
Sprechender Schlüssel:
CREATE TABLE element(
atomic_number INT PRIMARY KEY,
symbol VARCHAR(2) NOT NULL UNIQUE,
name VARCHAR(20) NOT NULL UNIQUE,
atomic_mass REAL NOT NULL
);
Surrogatschlüssel:
CREATE TABLE element(
id INT PRIMARY KEY,
atomic_numer INT,
symbol VARCHAR(2) NOT NULL UNIQUE,
name VARCHAR(20) NOT NULL UNIQUE,
atomic_mass REAL NOT NULL
);
Automatisches Erstellen eines numerischen Primary Keys beginnend bei 1:
Standard SQL (implementiert in PostgreSQL, Oracle):
CREATE TABLE element(
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY;
...
);
Nicht-standardisierte Varianten:
AUTO_INCREMENT
AUTOINCREMENT
SERIAL
In SQLite wird immer automatisch ein numerischer eindeutiger Schlüssel unter dem Namen rowid
angelegt.
Generell: geordnete Listen können viel schneller durchsucht werden als ungeordnete (binäre Suche)
Beispiel: im Telefonbuch kann man schnell nach dem Nachnamen einer Person suchen, aber nicht nach dem Vornamen
Auf eine oder mehrere Spalten kann ein Index angewendet werden: Zusätzliche Datenstruktur, die auf die Daten in bestimmter Ordnung verweist.
CREATE INDEX idx_name
ON element (name);
Es kann nun nach den Elementnamen schneller gesucht werden
CREATE TABLE element(
atomic_number INT,
symbol VARCHAR(2) NOT NULL UNIQUE,
name VARCHAR(20) NOT NULL UNIQUE,
atomic_mass REAL NOT NULL
);
CREATE INDEX idx_name
ON element (name);
INSERT INTO element(atomic_number, symbol, name, atomic_mass)
VALUES (1, 'H', 'Hydrogen', 1.008);
INSERT INTO element(atomic_number, symbol, name, atomic_mass)
VALUES (2, 'He', 'Helium', 4.0026);
SELECT *
FROM element
WHERE name='Hydrogen';
SQL: Skalierung hauptsächlich vertikal: Hinzufügen von zusätzlichen Resourcen zu einem vorhandenen Server
MongoDB: Skalierung hauptsächlich horizontal: Hinzufügen zusätzlicher Server (via Sharding)
SQL: Verwendet atomare Einträge (und erste Normalform)
MongoDB: Enthält oft zusammengesetzte Einträge (Arrays, Objekte):
{
"name": "sue",
"groups": ["news", "sports"]
}
1 : 1
1 : n
m : n
0..1 : 1..1
0..1 : 0..n
0..m : 0..n
https://en.wikipedia.org/wiki/Entity%E2%80%93relationship_model
Qualitätsmerkmale einer Datenbank (Sicherheit gegenüber Fehlern):
Tabellen:
Vorlage: Chinook Musikdatenbank
CREATE TABLE artist(
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
name VARCHAR(20) NOT NULL,
country VARCHAR(5) NOT NULL,
year SMALLINT NOT NULL
);
INSERT INTO artist (name, country, year)
VALUES ('The Beatles', 'UK', 1960);
INSERT INTO artist (name, country, year)
VALUES ('AC/DC', 'AUS', 1973);
Referenz auf jeweils einen Eintrag einer anderen Tabelle
z.B.: Jeder Eintrag in der Tabelle song kann über die Spalte artist_id mit der Tabelle artist verknüpft werden
Der Zusatz FOREIGN KEY(column) REFERENCES other_table(column)
garantiert, dass ein entsprechender Eintrag in der anderen Tabelle existiert
CREATE TABLE song(
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
title VARCHAR(30) NOT NULL,
artist_id INT,
FOREIGN KEY(artist_id) REFERENCES artist(id)
);
INSERT INTO song (title, artist_id)
VALUES ('Let it Be', 1);
Ein foreign key garantiert, dass ein entsprechender Eintrag in der zugehörigen anderen Tabelle existiert
INSERT INTO song (title, artist_id)
VALUES ('Wish You Were Here', 10);
→ Fehlermeldung
SELECT song.title, artist.name
FROM artist
INNER JOIN song
ON artist.id=song.artist_id;
Der obige Code listet alle Kombinationen auf, bei denen artist.id
und song.artist_id
übereinstimmen
SELECT song.title, artist.name
FROM song
LEFT JOIN artist
ON artist.id=song.artist_id;
Der obige Code listet alle Kombinationen auf und beinhaltet auch Lieder, für die kein Künstler definiert ist
SQL:
SELECT * FROM iris;
SQLAlchemy (Python):
session.query(Iris)
mongo shell (JS):
db.iris.find({});
Pandas (Python): N/A
SQL:
SELECT sepal_length, sepal_width FROM iris;
SQLAlchemy (Python):
session.query(Iris.sepal_length, Iris.sepal_width)
Mongo shell:
db.iris.find({}, { sepal_length: 1, sepal_width: 1 });
Pandas:
iris_data.loc[:,["sepal_length", "sepal_width"]]
SQL:
SELECT * FROM iris WHERE name='Iris-setosa';
SQLAlchemy (Python):
session.query(Iris).filter(Iris.name="Iris-setosa")
mongo shell:
db.iris.find({ name: 'Iris-setosa' });
pandas (Python):
iris_setosa_data = iris_data.loc[
iris_data["name"] == "Iris-setosa"
]
pandas (Python): Eine Reihe von Einträgen auswählen:
iris_data.iloc[10:20]
SQL:
SELECT sepal_length, sepal_width
FROM iris
WHERE name='Iris-setosa';
SQLAlchemy (Python):
session.query(
Iris.sepal_length, Iris.sepal_width
).filter(Iris.name="Iris-setosa")
mongo shell:
db.iris.find(
{ name: 'Iris-setosa' },
{ sepal_length: 1, sepal_width: 1 }
);
pandas (Python):
iris_data.loc[
[iris_data["name"] == "Iris-setosa"],
["sepal_length", "sepal_width"],
]
SQL:
SELECT sepal_length, sepal_width
FROM iris
ORDER BY sepal_length;
SQLAlchemy:
session.query(
Iris.sepal_length, Iris.sepal_width
).order_by(Iris.sepal_length)
mongo shell:
db.iris
.find({}, { sepal_length: 1, sepal_width: 1 })
.sort({ sepal_length: 1 });
pandas (Python):
iris_data.loc[["sepal_length", "sepal_width"]].sort_values(
by="sepal_length"
)