Kapitel 4. CockroachDB SQL
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
Die Sprache von CockroachDB ist SQL. Es gibt zwar einige Befehlszeilen-Dienstprogramme, aber alle Interaktionen zwischen einer Anwendung und der Datenbank werden über SQL-Befehle abgewickelt.
SQL ist eine reichhaltige Sprache mit einer langen Geschichte - wir haben in Kapitel 1 bereits einige dieser Geschichte angesprochen. Eine vollständige Definition aller SQL-Funktionen würde ein eigenes Buch erfordern und wäre fast sofort veraltet, da sich die SQL-Sprache mit jeder neuen Version weiterentwickelt.
Dieses Kapitel soll dir daher einen umfassenden Überblick über die in CockroachDB verwendete SQL-Sprache geben, ohne den Anspruch zu erheben, eine vollständige Referenz zu sein. Wir werden einen aufgabenorientierten Ansatz für SQL wählen und die häufigsten Aufgaben der SQL-Sprache mit besonderem Bezug auf die Besonderheiten der CockroachDB-SQL-Implementierung behandeln.
Wie wir in Kapitel 1 beschrieben haben, ist SQL eine deklarative Sprache. SQL-Anweisungen stellen logische Anfragen für Abfragen und Datenmanipulationsoperationen dar, ohne zu spezifizieren, wie die Datenbank diese Anfragen umsetzen soll.
Eine vollständige Referenz für die CockroachDB-SQL-Sprache findest du in der CockroachDB-Dokumentation. Eine umfassendere Übersicht über die SQL-Sprache findest du im O'Reilly-Buch SQL in a Nutshell.
Einige der Beispiele in diesem Kapitel verwenden den MovR-Beispieldatensatz in diesem Kapitel, um verschiedene Funktionen der SQL-Sprache zu veranschaulichen. Wir haben in Kapitel 2 gezeigt, wie man Beispieldaten installiert.
SQL-Sprachkompatibilität
CockroachDB ist weitgehend kompatibel mit , der PostgreSQL-Implementierung des SQL:2016-Standards. Der SQL:2016-Standard enthält eine Reihe unabhängiger Module, und keine große Datenbank implementiert alle Standards. Die PostgreSQL-Implementierung von SQL ist jedoch so nah am "Standard", wie es ihn in der Datenbankgemeinschaft gibt.
CockroachDB unterscheidet sich in einigen Bereichen von PostgreSQL:
-
CockroachDB unterstützt derzeit keine Stored Procedures, Events oder Trigger. In PostgreSQL werden diese Stored Procedures in der Sprache
PL/pgSQL
geschrieben und ermöglichen die Ausführung von Programmlogik innerhalb des Datenbankservers, entweder bei Bedarf oder als Reaktion auf ein auslösendes Ereignis. -
CockroachDB unterstützt derzeit keine benutzerdefinierten Funktionen.
-
CockroachDB unterstützt keine PostgreSQL XML-Funktionen.
-
CockroachDB unterstützt keine PostgreSQL-Volltextindizes und -funktionen.
Daten mit SELECT abfragen
Obwohl wir Tabellen erstellen und auffüllen müssen, bevor wir sie abfragen können, ist es logisch, mit der SELECT
Anweisung zu beginnen, da viele Funktionen der SELECT
Anweisung auch in anderen Arten von SQL vorkommen - zum Beispiel Unterabfragen in UPDATE
- und für Datenwissenschaftler und Analysten ist die SELECT
Anweisung oft die einzige SQL-Anweisung, die sie jemals lernen müssen.
Die Anweisung SELECT
(Abbildung 4-1) ist das Arbeitspferd der relationalen Abfragen und hat eine komplexe und umfangreiche Syntax. Die Anweisung CockroachDB SELECT
implementiert die Standardfunktionen der Standardanweisung SELECT
mit einigen wenigen CockroachDB-spezifischen Funktionen.
In den folgenden Abschnitten werden wir die wichtigsten Elemente der Anweisung SELECT
sowie die Funktionen und Operatoren, die darin enthalten sein können, untersuchen.
Die SELECT-Liste
Eine einfache SQL-Anweisung besteht aus nichts anderem als einer SELECT
Anweisung zusammen mit skalaren Ausdrücken (z.B. Ausdrücke, die einen einzelnen Wert zurückgeben). Zum Beispiel:
SELECT
CONCAT
(
'Hello from CockroachDB at '
,
CAST
(
NOW
()
as
STRING
))
as
hello
;
Die Liste SELECT
enthält eine kommagetrennte Liste von Ausdrücken, die Kombinationen von Konstanten, Funktionen und Operatoren enthalten können. Die CockroachDB-SQL-Sprache unterstützt alle bekannten SQL-Operatoren. Eine vollständige Liste der Funktionen und Operatoren findest du in der CockroachDB-Dokumentation.
Die FROM-Klausel
Die FROM
Klausel ist die primäre Methode, um Tabellendaten mit der SELECT
Anweisung zu verknüpfen. In ihrer einfachsten Ausprägung können alle Zeilen und Spalten einer Tabelle über einen Full Table Scan abgerufen werden:
SELECT
*
FROM
rides
;
Tabellennamen können mit der AS
Klausel oder einfach durch einen Alias nach dem Tabellennamen verknüpft werden. Dieser Alias kann dann überall in der Abfrage verwendet werden, um auf die Tabelle zu verweisen. Auch Spaltennamen können als Alias verwendet werden. Die folgenden Namen sind zum Beispiel alle gleichwertig:
SELECT
name
FROM
users
;
SELECT
u
.
name
FROM
users
u
;
SELECT
users
.
name
FROM
users
;
SELECT
users
.
name
AS
user_name
FROM
users
;
SELECT
u
.
name
FROM
users
AS
u
;
JOINS
Mit Joins können die Ergebnisse aus zwei oder mehr Tabellen auf der Grundlage einiger gemeinsamer Spaltenwerte zusammengeführt werden.
Die INNER
JOIN
ist die Standardoperation JOIN
. Bei dieser Verknüpfung werden Zeilen aus einer Tabelle mit Zeilen aus einer anderen Tabelle auf der Grundlage gemeinsamer ("Schlüssel") Werte verbunden. Zeilen, die in beiden Tabellen nicht übereinstimmen, werden nicht in die Ergebnisse aufgenommen. Die folgende Abfrage verknüpft zum Beispiel Fahrzeug- und Fahrtinformationen in der Datenbank movr
:
SELECT
v
.
id
,
v
.
ext
,
r
.
start_time
,
r
.
start_address
FROM
vehicles
v
INNER
JOIN
rides
r
ON
(
r
.
vehicle_id
=
v
.
id
);
Beachte, dass ein Fahrzeug, das nicht an einer Fahrt beteiligt war, nicht in die Ergebnismenge aufgenommen wird.
Die ON
Klausel gibt die Bedingungen an, die die beiden Tabellen verbinden - in der vorherigen Abfrage wurden die Spalten vehicle_id
in der Tabelle rider
mit der Spalte id
in der Tabelle vehicles
abgeglichen. Wenn die JOIN
in beiden Tabellen eine gleichnamige Spalte enthält, bietet die USING
Klausel eine praktische Abkürzung. Hier verbinden wir users
und user_ride_counts
über die gemeinsame Spalte name
:1
SELECT
*
FROM
users
u
JOIN
user_ride_counts
urc
USING
(
name
);
Die OUTER
JOIN
ermöglicht es, dass Zeilen auch dann in aufgenommen werden, wenn sie in der anderen Tabelle keine Übereinstimmung haben. Zeilen, die in der Tabelle OUTER
JOIN
nicht gefunden werden, werden durch die Werte von NULL
dargestellt. LEFT
und RIGHT
bestimmen, welche Tabelle fehlende Werte haben kann. Die folgende Abfrage gibt zum Beispiel alle Benutzer in der Tabelle users
aus, auch wenn einige nicht mit einem Promo-Code verbunden sind:
SELECT
u
.
name
,
upc
.
code
FROM
users
u
LEFT
OUTER
JOIN
user_promo_codes
upc
ON
(
u
.
id
=
upc
.
user_id
);
Die RIGHT
OUTER
JOIN
kehrt den Standard (LEFT
) OUTER
JOIN
um. Diese Abfrage ist also identisch mit der vorherigen Abfrage, weil die Tabelle users
jetzt die "richtige" Tabelle in der Verknüpfung ist:
SELECT
DISTINCT
u
.
name
,
upc
.
code
FROM
user_promo_codes
upc
RIGHT
OUTER
JOIN
users
u
ON
(
u
.
id
=
upc
.
user_id
);
Anti-Joins
Oft ist es erforderlich, alle Zeilen aus einer Tabelle auszuwählen, für die es keine passende Zeile in einer anderen Ergebnismenge gibt. Dies wird als Anti-Join bezeichnet, und obwohl es keine SQL-Syntax für dieses Konzept gibt, wird es in der Regel mit einer Unterabfrage und der Klausel IN
oder EXISTS
implementiert. Das folgende Beispiel veranschaulicht einen Anti-Join mit den Operatoren EXISTS
und IN
.
Jedes Beispiel wählt Nutzer aus, die nicht gleichzeitig Arbeitnehmer sind:
SELECT
*
FROM
users
WHERE
id
NOT
IN
(
SELECT
id
FROM
employees
);
Diese Abfrage liefert dieselben Ergebnisse, aber mit einer korrelierten Unterabfrage (wir werden Unterabfragen in einem späteren Abschnitt ausführlicher besprechen ):
SELECT
*
FROM
users
u
WHERE
NOT
EXISTS
(
SELECT
id
FROM
employees
e
WHERE
e
.
id
=
u
.
id
);
Kreuzfugen
CROSS
JOIN
bedeutet, dass jede Zeile in der linken Tabelle mit jeder Zeile in der rechten Tabelle verknüpft werden soll. Normalerweise ist das ein Rezept für eine Katastrophe, es sei denn, eine der Tabellen hat nur eine Zeile oder ist eine lateral korrelierte Unterabfrage (siehe "Korrelierte Unterabfrage").
Operationen einstellen
SQL implementiert eine Reihe von Operationen, die direkt mit Ergebnismengen arbeiten. Diese Operationen, die zusammen als "Mengenoperationen" bezeichnet werden, ermöglichen es, Ergebnismengen zu verketten, zu subtrahieren oder zu überlagern.
Die gebräuchlichste dieser Operationen ist der UNION
Operator, der die Summe von zwei Ergebnismengen liefert. Standardmäßig werden Duplikate in jeder Ergebnismenge eliminiert. Im Gegensatz dazu gibt die Operation UNION
ALL
die Summe der beiden Ergebnismengen zurück, einschließlich aller Duplikate. Das folgende Beispiel gibt eine Liste von Kunden und Mitarbeitern zurück. Mitarbeiter, die auch Kunden sind, werden nur einmal aufgeführt:
SELECT
name
,
address
FROM
customers
UNION
SELECT
name
,
address
FROM
employees
;
INTERSECT
gibt die Zeilen zurück, die in beiden Ergebnismengen enthalten sind. Diese Abfrage liefert Kunden, die auch Mitarbeiter sind:
SELECT
name
,
address
FROM
customers
INTERSECT
SELECT
name
,
address
FROM
employees
;
EXCEPT
gibt Zeilen in der ersten Ergebnismenge zurück, die in der zweiten nicht vorhanden sind. Diese Abfrage gibt Kunden zurück, die nicht gleichzeitig Mitarbeiter sind:
SELECT
name
,
address
FROM
customers
EXCEPT
SELECT
name
,
address
FROM
employees
;
Alle Mengenoperationen setzen voraus, dass die Komponentenabfragen die gleiche Anzahl von Spalten zurückgeben und dass diese Spalten einen kompatiblen Datentyp haben.
Gruppe Operationen
Mit Hilfe von Aggregatoperationen können zusammenfassende Informationen erstellt werden, in der Regel nach Gruppierung von Zeilen. Zeilen können mit dem GROUP
BY
Operator gruppiert werden. In diesem Fall darf die Auswahlliste nur aus Spalten bestehen, die in der GROUP
BY
Klausel und den Aggregatfunktionen enthalten sind.
Die gängigsten Aggregatfunktionen sind in Tabelle 4-1 aufgeführt.
|
Berechne den Durchschnittswert für die Gruppe. |
|
Gibt die Anzahl der Zeilen in der Gruppe zurück. |
|
Gib den maximalen Wert in der Gruppe zurück. |
|
Gibt den Mindestwert in der Gruppe zurück. |
|
Gib die Standardabweichung für die Gruppe zurück. |
|
Gibt die Summe aller Werte für die Gruppe zurück. |
Das folgende Beispiel generiert zusammenfassende Fahrteninformationen für jede Stadt:
SELECT
u
.
city
,
SUM
(
urc
.
rides
),
AVG
(
urc
.
rides
),
max
(
urc
.
rides
)
FROM
users
u
JOIN
user_ride_counts
urc
USING
(
name
)
GROUP
BY
u
.
city
;
Unterabfragen
Eine Subquery ist eine SELECT
Anweisung, die innerhalb einer anderen SQL-Anweisung steht. Eine solche "verschachtelte" SELECT
Anweisung kann in einer Vielzahl von SQL-Kontexten verwendet werden, einschließlich SELECT
, DELETE
, UPDATE
und INSERT
Anweisungen.
Die folgende Anweisung verwendet eine Unterabfrage, um die Anzahl der Fahrten zu zählen, die die maximale Fahrtdauer teilen:
SELECT
COUNT
(
*
)
FROM
rides
WHERE
(
end_time
-
start_time
)
=
(
SELECT
MAX
(
end_time
-
start_time
)
FROM
rides
);
Unterabfragen können auch in der FROM
Klausel verwendet werden, wo immer eine Tabellen- oder View-Definition erscheinen könnte. Diese Abfrage erzeugt ein Ergebnis, das jede Fahrt mit der durchschnittlichen Fahrtdauer für die Stadt vergleicht:
SELECT
id
,
city
,(
end_time
-
start_time
)
ride_duration
,
avg_ride_duration
FROM
rides
JOIN
(
SELECT
city
,
AVG
(
end_time
-
start_time
)
avg_ride_duration
FROM
rides
GROUP
BY
city
)
USING
(
city
)
;
Seitliche Unterabfrage
Wenn eine Subquery in einer Verknüpfung verwendet wird, zeigt das LATERAL
Schlüsselwort an, dass die Subquery auf Spalten zugreifen kann, die in vorangegangenen FROM
Tabellenausdrücken erzeugt wurden. In der folgenden Abfrage erlaubt das Schlüsselwort LATERAL
der Subquery zum Beispiel den Zugriff auf Spalten aus der Tabelle users
:
SELECT
name
,
address
,
start_time
FROM
users
CROSS
JOIN
LATERAL
(
SELECT
*
FROM
rides
WHERE
rides
.
start_address
=
users
.
address
)
r
;
Dieses Beispiel ist etwas konstruiert, und wir könnten natürlich eine einfache JOIN
konstruieren, die diese Abfrage natürlicher ausführt. Wo LATERAL
Joins wirklich glänzen, ist, wenn Unterabfragen auf berechnete Spalten in anderen Unterabfragen innerhalb einer FROM
Klausel zugreifen können. Andy Woods' CockroachDB-Blogpost beschreibt ein ernsthafteres Beispiel für laterale Unterabfragen.
Gemeinsame Tabellenausdrücke
SQL-Anweisungen mit vielen Unterabfragen können schwer zu lesen und zu pflegen sein, besonders wenn dieselbe Unterabfrage in mehreren Kontexten innerhalb der Abfrage benötigt wird. Aus diesem Grund unterstützt SQL Common Table Expressions mit der WITH
Klausel. Abbildung 4-2 zeigt die Syntax eines Common Table Expression.
In seiner einfachsten Form ist ein Common Table Expression einfach ein benannter Abfrageblock, der überall dort angewendet werden kann, wo ein Tabellenausdruck verwendet werden kann. In diesem Beispiel verwenden wir die WITH
Klausel, um einen Common Table Expression riderRevenue
zu erstellen, auf den wir dann in der FROM
Klausel der Hauptabfrage verweisen:
WITH
riderRevenue
AS
(
SELECT
u
.
id
,
SUM
(
r
.
revenue
)
AS
sumRevenue
FROM
rides
r
JOIN
"users"
u
ON
(
r
.
rider_id
=
u
.
id
)
GROUP
BY
u
.
id
)
SELECT
*
FROM
"users"
u2
JOIN
riderRevenue
rr
USING
(
id
)
ORDER
BY
sumrevenue
DESC
;
Mit der RECURSIVE
Klausel kann der Common Table Expression auf sich selbst verweisen, so dass eine Abfrage eine beliebig große (oder sogar unendliche) Menge von Ergebnissen liefern kann. Wenn zum Beispiel die Tabelle employees
eine Spalte manager_id
enthält, die auf die Zeile des Managers in derselben Tabelle verweist, könnten wir eine Hierarchie von Angestellten und Managern wie folgt ausgeben:
WITH
RECURSIVE
employeeMgr
AS
(
SELECT
id
,
manager_id
,
name
,
NULL
AS
manager_name
,
1
AS
level
FROM
employees
managers
WHERE
manager_id
IS
NULL
UNION
ALL
SELECT
subordinates
.
id
,
subordinates
.
manager_id
,
subordinates
.
name
,
managers
.
name
,
managers
.
LEVEL
+
1
FROM
employeeMgr
managers
JOIN
employees
subordinates
ON
(
subordinates
.
manager_id
=
managers
.
id
)
)
SELECT
*
FROM
employeeMgr
;
Die MATERIALIZED
Klausel zwingt CockroachDB dazu, die Ergebnisse der Common Table Expression als temporäre Tabelle zu speichern, anstatt sie bei jedem Auftreten neu auszuführen. Dies kann nützlich sein, wenn die Common Table Expression mehrfach in der Abfrage referenziert wird.
ORDER BY
Mit der ORDER
BY
Klausel können die Abfrageergebnisse in sortierter Reihenfolge zurückgegeben werden. Abbildung 4-3 zeigt die ORDER
BY
Syntax.
In der einfachsten Form nimmt ORDER
BY
einen oder mehrere Spaltenausdrücke oder Spaltennummern aus der Liste SELECT
.
In diesem Beispiel sortieren wir nach Spaltennummern:
SELECT
city
,
start_time
,
(
end_time
-
start_time
)
duration
FROM
rides
r
ORDER
BY
1
,
3
DESC
;
Und in diesem Fall durch Spaltenausdrücke:
SELECT
city
,
start_time
,
(
end_time
-
start_time
)
duration
FROM
rides
r
ORDER
BY
city
,(
end_time
-
start_time
)
DESC
;
Du kannst auch nach einem Index sortieren. Im folgenden Beispiel werden die Zeilen nach city
und start_time
sortiert, da dies die im Index angegebenen Spalten sind:
CREATE
INDEX
rides_start_time
ON
rides
(
city
,
start_time
);
SELECT
city
,
start_time
,
(
end_time
-
start_time
)
duration
FROM
rides
ORDER
BY
INDEX
rides
@
rides_start_time
;
Die Verwendung von ORDER
BY
INDEX
garantiert, dass der Index verwendet wird, um die Zeilen direkt in sortierter Reihenfolge zurückzugeben, anstatt eine Sortieroperation an den Zeilen durchzuführen, nachdem sie abgerufen wurden. In Kapitel 8 findest du weitere Hinweise zur Optimierung von Anweisungen, die einen ORDER
BY
enthalten.
Fensterfunktionen
Fensterfunktionen sind Funktionen, die auf einer Teilmenge - einem "Fenster" - der gesamten Ergebnismenge arbeiten. Abbildung 4-4 zeigt die Syntax einer Fensterfunktion.
PARTITION
BY
und ORDER
BY
erstellen eine Art "virtuelle Tabelle", mit der die Funktion arbeitet. Diese Abfrage listet zum Beispiel die 10 umsatzstärksten Fahrten auf, wobei der prozentuale Anteil am Gesamtumsatz und der Umsatz der Stadt angezeigt wird:
SELECT
city
,
r
.
start_time
,
revenue
,
revenue
*
100
/
SUM
(
revenue
)
OVER
()
AS
pct_total_revenue
,
revenue
*
100
/
SUM
(
revenue
)
OVER
(
PARTITION
BY
city
)
AS
pct_city_revenue
FROM
rides
r
ORDER
BY
5
DESC
LIMIT
10
;
Es gibt einige Aggregationsfunktionen die spezifisch für Fensterfunktionen sind. RANK()
ordnet die vorhandene Zeile innerhalb des jeweiligen Fensters ein und DENSE_RANK()
tut dasselbe, lässt aber keine "fehlenden" Ränge zu. LEAD
und LAG
ermöglichen den Zugriff auf Funktionen in benachbarten Partitionen.
Diese Abfrage liefert zum Beispiel die Top 10 Fahrten, wobei der Gesamtrang jeder Fahrt und der Rang innerhalb der Stadt angezeigt wird:
SELECT
city
,
r
.
start_time
,
revenue
,
RANK
()
OVER
(
ORDER
BY
revenue
DESC
)
AS
total_revenue_rank
,
RANK
()
OVER
(
PARTITION
BY
city
ORDER
BY
revenue
DESC
)
AS
city_revenue_rank
FROM
rides
r
ORDER
BY
revenue
DESC
LIMIT
10
;
Andere SELECT-Klauseln
Die LIMIT
Klausel begrenzt die Anzahl der Zeilen, die von einer SELECT
zurückgegeben werden, während die OFFSET
Klausel eine bestimmte Anzahl von Zeilen "vorspringt". Dies kann praktisch sein, um durch eine Ergebnismenge zu paginieren, obwohl es fast immer effizienter ist, eine Filterbedingung zu verwenden, um zur nächsten Teilmenge der Ergebnisse zu navigieren, da sonst bei jeder Anfrage eine zunehmende Anzahl von Zeilen erneut gelesen und verworfen werden muss.
CockroachDB-Arrays
Mit dem Typ ARRAY
kann eine Spalte als eindimensionales Array von Elementen definiert werden, die alle einen gemeinsamen Datentyp haben. Wir werden im nächsten Kapitel über Arrays im Zusammenhang mit der Datenmodellierung sprechen. Obwohl sie nützlich sein können, sind sie streng genommen ein Verstoß gegen das relationale Modell und sollten mit Vorsicht verwendet werden.
Eine ARRAY
-Variable wird definiert, indem "[]
" oder das Wort "ARRAY
" an den Datentyp einer Spalte angehängt wird. Zum Beispiel:
CREATE
TABLE
arrayTable
(
arrayColumn
STRING
[]);
CREATE
TABLE
anotherTable
(
integerArray
INT
ARRAY
);
Mit der Funktion ARRAY
können wir mehrere Artikel in die ARRAY
einfügen:
INSERT
INTO
arrayTable
VALUES
(
ARRAY
[
'sky'
,
'road'
,
'car'
]);
SELECT
*
FROM
arrayTable
;
arraycolumn
------------------
{
sky
,
road
,
car
}
Wir können auf ein einzelnes Element eines Arrays mit der folgenden bekannten Notation für Arrayelemente zugreifen:
SELECT
arrayColumn
[
2
]
FROM
arrayTable
;
arraycolumn
---------------
road
Der @>
Operator kann verwendet werden, um Arrays zu finden, die ein oder mehrere Elemente enthalten:
SELECT
*
FROM
arrayTable
WHERE
arrayColumn
@>
ARRAY
[
'road'
];
arraycolumn
------------------
{
sky
,
road
,
car
}
Wir können mit der Funktion array_append
Elemente zu einem bestehenden Array hinzufügen und mit array_remove
Elemente entfernen:
UPDATE
arrayTable
SET
arrayColumn
=
array_append
(
arrayColumn
,
'cat'
)
WHERE
arrayColumn
@>
ARRAY
[
'car'
]
RETURNING
arrayColumn
;
arraycolumn
----------------------
{
sky
,
road
,
car
,
cat
}
UPDATE
arrayTable
SET
arrayColumn
=
array_remove
(
arrayColumn
,
'car'
)
WHERE
arrayColumn
@>
ARRAY
[
'car'
]
RETURNING
arrayColumn
;
arraycolumn
------------------
{
sky
,
road
,
cat
}
Schließlich wandelt die Funktion unnest
ein Array in ein tabellarisches Ergebnis um - eine Zeile für jedes Element des Arrays. Damit kann der Inhalt eines Arrays mit Daten in relationaler Form an anderer Stelle in der Datenbank "verknüpft" werden. Ein Beispiel dafür zeigen wir im nächsten Kapitel:
SELECT
unnest
(
arrayColumn
)
FROM
(((
"queries"
,
"arrays"
,
startref
=
"qarys"
)))
arrayTable
;
unnest
----------
sky
road
cat
Arbeiten mit JSON
Der Datentyp JSONB
ermöglicht es uns, JSON-Dokumente in einer Spalte zu speichern, und CockroachDB bietet Operatoren und Funktionen, die uns bei der Arbeit mit JSON helfen.
Für diese Beispiele haben wir eine Tabelle mit einem Primärschlüssel customerid
und allen Daten in einer JSONB
Spalte, jsondata
, erstellt. Mit der Funktion jsonb_pretty
können wir das JSON in einer schön formatierten Form abrufen:
SELECT
jsonb_pretty
(
jsondata
)
FROM
customersjson
WHERE
customerid
=
1
;
jsonb_pretty
------------------------------------------------------
{
"Address"
:
"1913 Hanoi Way"
,
"City"
:
"Sasebo"
,
"Country"
:
"Japan"
,
"District"
:
"Nagasaki"
,
"FirstName"
:
"MARY"
,
"LastName"
:
"Smith"
,
"Phone"
:
886780309
,
"_id"
:
"5a0518aa5a4e1c8bf9a53761"
,
"dateOfBirth"
:
"1982-02-20T13:00:00.000Z"
,
"dob"
:
"1982-02-20T13:00:00.000Z"
,
"randValue"
:
0
.
47025846594884335
,
"views"
:
[
{
"filmId"
:
611
,
"title"
:
"MUSKETEERS WAIT"
,
"viewDate"
:
"2013-03-02T05:26:17.645Z"
}
,
{
"filmId"
:
308
,
"title"
:
"FERRIS MOTHER"
,
"viewDate"
:
"2015-07-05T20:06:58.891Z"
}
,
{
"filmId"
:
159
,
"title"
:
"CLOSER BANG"
,
"viewDate"
:
"2012-08-04T19:31:51.698Z"
}
,
/* Some data removed */
]
}
Jedes JSON-Dokument enthält einige Top-Level-Attribute und ein verschachteltes Array von Dokumenten, die Details zu den gestreamten Filmen enthalten.
In der SELECT
Klausel können wir mit dem ->
Operator auf bestimmte JSON-Attribute verweisen:
SELECT
jsondata
->
'City'
AS
City
FROM
customersjson
WHERE
customerid
=
1
;
city
------------
"Sasebo"
Der ->>
Operator ist ähnlich, gibt die Daten aber als Text und nicht als JSON formatiert zurück.
Wenn wir innerhalb einer JSONB
Spalte suchen wollen, können wir den @>
Operator verwenden:
SELECT
COUNT
(
*
)
FROM
customersjson
WHERE
jsondata
@>
'{"City": "London"}'
;
count
---------
3
Wir können das gleiche Ergebnis mit dem ->>
Operator erzielen:
SELECT
COUNT
(
*
)
FROM
customersjson
WHERE
jsondata
->>
'City'
=
'London'
;
count
---------
3
Die Operatoren ->>
und @>
können unterschiedliche Leistungsmerkmale haben. So kann ->>
einen invertierten Index nutzen, während @>
einen Tabellenscan verwenden würde.
Wir können die Struktur des JSON-Dokuments mit den Funktionen jsonb_each
und jsonb_object_keys
abfragen. jsonb_each
gibt eine Zeile pro Attribut im JSON-Dokument zurück, während jsonb_object_keys
nur die Attributschlüssel zurückgibt. Das ist nützlich, wenn du nicht weißt, was in der Spalte JSONB
gespeichert ist.
jsonb_array_elements
gibt eine Zeile für jedes Element in einem JSON-Array zurück. Hier erweitern wir zum Beispiel das Array views
für einen bestimmten Kunden und zählen die Anzahl der Filme, die er gesehen hat:
SELECT
COUNT
(
jsonb_array_elements
(
jsondata
->
'views'
))
FROM
customersjson
WHERE
customerid
=
1
;
count
---------
37
(
1
row
)
Zusammenfassung von SELECT
Die Anweisung SELECT
ist wahrscheinlich die am häufigsten verwendete Anweisung in der Datenbankprogrammierung und bietet eine große Bandbreite an Funktionen. Selbst nach jahrzehntelanger Arbeit in diesem Bereich kennen wir drei nicht jede Nuance der SELECT
Funktionalität. Dennoch haben wir versucht, dir hier die wichtigsten Aspekte der Sprache näher zu bringen. Wenn du mehr wissen willst, schau dir die Dokumentation von CockroachDB an.
Obwohl einige Datenbankexperten fast ausschließlich SELECT
verwenden, werden die meisten auch Daten erstellen und bearbeiten. In den folgenden Abschnitten werden wir uns die Sprachfunktionen ansehen, die diese Tätigkeiten unterstützen.
Tabellen und Indizes erstellen
In einer relationalen Datenbank können Daten nur zu vordefinierten Tabellen hinzugefügt werden. Diese Tabellen werden mit der Anweisung CREATE
TABLE
erstellt. Indizes können erstellt werden, um eindeutige Beschränkungen durchzusetzen oder einen schnellen Zugriff auf die Daten zu ermöglichen. Indizes können innerhalb der Anweisung CREATE
TABLE
oder durch eine separate Anweisung CREATE
INDEX
definiert werden.
Die Struktur eines Datenbankschemas ist ein entscheidender Faktor für die Leistung der Datenbank und auch für die Wartbarkeit und den Nutzen der Datenbank. Wir werden die wichtigsten Überlegungen zum Datenbankdesign in Kapitel 5 besprechen. Lass uns zunächst ein paar einfache Tabellen erstellen.
Wir verwenden CREATE
TABLE
, um eine Tabelle in einer Datenbank zu erstellen. Abbildung 4-5 zeigt eine vereinfachte Syntax für die Anweisung CREATE
TABLE
.
Ein einfaches CREATE
TABLE
wird im nächsten Beispiel gezeigt. Es erstellt eine Tabelle, mytable
, mit einer einzigen Spalte, mycolumn
. Die Spalte mycolumn
kann nur Integer-Werte speichern:
CREATE
TABLE
mytable
(
mycolumn
int
);
Die CREATE
TABLE
Anweisung muss die Spalten definieren, die in der Tabelle vorkommen, und kann optional Indizes, Spaltenfamilien, Beschränkungen und Partitionen für die Tabelle definieren. Die CREATE
TABLE
Anweisung für die Tabelle rides
in der Datenbank movr
würde zum Beispiel so aussehen:
CREATE
TABLE
public
.
rides
(
id
UUID
NOT
NULL
,
city
VARCHAR
NOT
NULL
,
vehicle_city
VARCHAR
NULL
,
rider_id
UUID
NULL
,
vehicle_id
UUID
NULL
,
start_address
VARCHAR
NULL
,
end_address
VARCHAR
NULL
,
start_time
TIMESTAMP
NULL
,
end_time
TIMESTAMP
NULL
,
revenue
DECIMAL
(
10
,
2
)
NULL
,
CONSTRAINT
"primary"
PRIMARY
KEY
(
city
ASC
,
id
ASC
),
CONSTRAINT
fk_city_ref_users
FOREIGN
KEY
(
city
,
rider_id
)
REFERENCES
public
.
users
(
city
,
id
),
CONSTRAINT
fk_vehicle_city_ref_vehicles
FOREIGN
KEY
(
vehicle_city
,
vehicle_id
)
REFERENCES
public
.
vehicles
(
city
,
id
),
INDEX
rides_auto_index_fk_city_ref_users
(
city
ASC
,
rider_id
ASC
),
INDEX
rides_auto_index_fk_vehicle_city_ref_vehicles
(
vehicle_city
ASC
,
vehicle_id
ASC
),
CONSTRAINT
check_vehicle_city_city
CHECK
(
vehicle_city
=
city
)
);
Diese CREATE
TABLE
Anweisung spezifizierte zusätzliche Spalten, ihre Nullbarkeit, Primär- und Fremdschlüssel, Indizes und Beschränkungen für Tabellenwerte.
Die relevanten Klauseln in Abbildung 4-5 sind in Tabelle 4-2 aufgeführt.
|
Die Definition einer Spalte. Dazu gehören der Spaltenname, der Datentyp und die Nullbarkeit. Auch spaltenspezifische Einschränkungen können hier angegeben werden, obwohl es besser ist, alle Einschränkungen separat aufzulisten. |
|
Definition eines Index, der für die Tabelle erstellt werden soll. Wie |
|
Eine Einschränkung für die Tabelle, z. B. |
|
Weist die Spalten einer Spaltenfamilie zu. In Kapitel 2 findest du weitere Informationen über Spaltenfamilien. |
Schauen wir uns nun jede dieser CREATE
TABLE
Optionen an.
Spaltendefinitionen
Eine Spaltendefinition besteht aus einem Spaltennamen, einem Datentyp, einem Nullbarkeitsstatus, einem Standardwert und möglicherweise aus Constraint-Definitionen auf Spaltenebene. Mindestens der Name und der Datentyp müssen angegeben werden. Abbildung 4-6 zeigt die Syntax für eine Spaltendefinition.
Obwohl Beschränkungen direkt mit den Spaltendefinitionen angegeben werden können, können sie auch unabhängig davon unter den Spaltendefinitionen aufgeführt werden. Viele Praktiker ziehen es vor, die Beschränkungen auf diese Weise getrennt aufzulisten, weil so alle Beschränkungen, auch die mehrspaltigen, zusammen gefunden werden können.
Berechnete Spalten
Mit CockroachDB können Tabellen berechnete Spalten enthalten, die in anderen Datenbanken eine View-Definition erfordern würden:
column_name
AS
expression
[
STORED
|
VIRTUAL
]
Eine VIRTUAL
berechnete Spalte wird immer dann ausgewertet, wenn auf sie verwiesen wird. Ein STORED
Ausdruck wird bei seiner Erstellung in der Datenbank gespeichert und muss nicht immerneu berechnet werden.
In dieser Tabellendefinition sind zum Beispiel die Spalten firstName
und lastName
zu einer Spalte fullName
verkettet:
CREATE
TABLE
people
(
id
INT
PRIMARY
KEY
,
firstName
VARCHAR
NOT
NULL
,
lastName
VARCHAR
NOT
NULL
,
dateOfBirth
DATE
NOT
NULL
,
fullName
STRING
AS
(
CONCAT
(
firstName
,
' '
,
lastName
)
)
STORED
);
Die berechneten Spalten dürfen nicht kontextabhängig sein. Das heißt, der berechnete Wert darf sich nicht im Laufe der Zeit ändern oder anderweitig nicht deterministisch sein. Die berechnete Spalte im folgenden Beispiel würde zum Beispiel nicht funktionieren, da die Spalte age
statisch wäre und nicht jedes Mal neu berechnet würde. Auch wenn es schön wäre, das Altern im echten Leben zu stoppen, wollen wir wahrscheinlich, dass die Spalte age
im Laufe der Zeit wächst.
CREATE
TABLE
people
(
id
INT
PRIMARY
KEY
,
firstName
VARCHAR
NOT
NULL
,
lastName
VARCHAR
NOT
NULL
,
dateOfBirth
timestamp
NOT
NULL
,
fullName
STRING
AS
(
CONCAT
(
firstName
,
' '
,
lastName
)
)
STORED
,
age
int
AS
(
now
()
-
dateOfBirth
)
STORED
);
Datenarten
Die Basis-Datentypen der CockroachDB sind in Tabelle 4-3 aufgeführt.
Type |
Beschreibung | Beispiel |
---|---|---|
|
Ein 1-dimensionales, 1-indiziertes, homogenes Array eines beliebigen Nicht-Array-Datentyps. |
{"Himmel", "Straße", "Auto"} |
|
Eine Kette von binären Ziffern (Bits). |
B'10010101' |
|
Ein boolescher Wert. |
wahr |
|
Eine Zeichenkette aus binären Zeichen. |
b’\141\061\142\062\143\063’ |
|
Mit der Funktion |
a1b2c3 |
|
Eine Verabredung. |
DATUM 2016-01-25 |
|
Neu in v20.2: Ein benutzerdefinierter Datentyp, der aus einer Reihe von statischen Werten besteht. |
ENUM(Kreuz, Karo, Herz, Pik) |
|
Eine exakte Festkommazahl. |
1.2345 |
|
Eine 64-Bit, ungenaue Fließkommazahl. |
3.141592653589793 |
|
Eine IPv4- oder IPv6-Adresse. |
192.168.0.1 |
|
Eine ganze Zahl mit Vorzeichen, bis zu 64 Bit. |
12345 |
|
Eine Zeitspanne. |
INTERVALL 2h30m30s |
|
JSON-Daten. |
{"first_name": "Lola", "last_name": "Hund", "Ort": "NYC", "online" : true, "Freunde" : 547} |
|
Ein Pseudotyp, der eindeutige aufsteigende Zahlen erzeugt. |
148591304110702593 |
|
Eine Kette von Unicode-Zeichen. |
a1b2c3 |
|
|
|
|
|
|
|
Ein 128-Bit-Hexadezimalwert. |
7f9c24e8-3b12-4fef-91e0-56a2d5a246ec |
Beachte, dass diese CockroachDB-Basistypen auch für andere Datentypen verwendet werden können. Zum Beispiel sind die PostgreSQL-Typen BIGINT
und SMALLINT
mit dem CockroachDB-Typ INT
verknüpft.
In CockroachDB können Datentypen gecastet - oder konvertiert - werden, indem der Datentyp mit "::
" an einen Ausdruck angehängt wird. Zum Beispiel:
SELECT
revenue
::
int
FROM
rides
;
Die Funktion CAST
kann auch verwendet werden, um Datentypen zu konvertieren und ist mit anderen Datenbanken und SQL-Standards weitgehend kompatibel. Zum Beispiel:
SELECT
CAST
(
revenue
AS
int
)
FROM
rides
;
Primärschlüssel
Wie wir wissen, definiert ein Primärschlüssel eindeutig eine Zeile innerhalb einer Tabelle. In CockroachDB ist ein Primärschlüssel obligatorisch, da alle Tabellen auf der Grundlage der Bereiche ihres Primärschlüssels über den Cluster verteilt werden. Wenn du keinen Primärschlüssel angibst, wird automatisch ein Schlüssel für dich erzeugt.
In anderen Datenbanken ist es üblich, einen automatisch generierten Primärschlüssel mit Klauseln wie AUTOINCREMENT
zu definieren. Die Generierung von Primärschlüsseln in verteilten Datenbanken ist ein wichtiges Thema, denn der Primärschlüssel wird verwendet, um die Daten auf die Knoten im Cluster zu verteilen. Wir werden die Optionen für die Erzeugung von Primärschlüsseln im nächsten Kapitel besprechen, aber für den Moment reicht es aus, wenn du feststellst, dass du zufällige Primärschlüsselwerte mit dem Datentyp UUID
und der Funktion gen_random_uuid()
als Standardwert erzeugen kannst:
CREATE
TABLE
people
(
id
UUID
NOT
NULL
DEFAULT
gen_random_uuid
(),
firstName
VARCHAR
NOT
NULL
,
lastName
VARCHAR
NOT
NULL
,
dateOfBirth
DATE
NOT
NULL
);
Diese Methode gilt als bewährte Methode, um eine gleichmäßige Verteilung der Schlüssel im Cluster zu gewährleisten. Andere Optionen für die automatische Generierung von Primärschlüsseln werden in Kapitel 5 besprochen.
Zwänge
Die CONSTRAINT
Klausel legt Bedingungen fest, die von allen Zeilen in einer Tabelle erfüllt werden müssen. Unter bestimmten Umständen kann das Schlüsselwort CONSTRAINT
weggelassen werden, z.B. wenn eine Spaltenbeschränkung oder bestimmte Beschränkungstypen wie PRIMARY
KEY
oder FOREIGN
KEY
definiert werden. Abbildung 4-7 zeigt die allgemeine Form einer Constraint-Definition.
Eine UNIQUE
-Beschränkung erfordert, dass alle -Werte für column
oder column_list
eindeutig sind.
PRIMARY
KEY
implementiert einen Satz von Spalten, die eindeutig sein müssen und die auch Gegenstand einer FOREIGN
KEY
Einschränkung in einer anderen Tabelle sein können. Sowohl die PRIMARY
KEY
als auch die UNIQUE
Constraints erfordern die Erstellung eines impliziten Indexes. Falls gewünscht, können die Eigenschaften der physischen Speicherung des Indexes in der USING
Klausel angegeben werden. Die Optionen der USING
INDEX
Klausel haben die gleiche Bedeutung wie in der CREATE
INDEX
Anweisung.
NOT
NULL
zeigt an, dass die betreffende Spalte nicht NULL
sein darf. Diese Option steht nur für Spaltenbeschränkungen zur Verfügung, aber der gleiche Effekt kann mit einer Tabellenbeschränkung CHECK
erzielt werden.
CHECK
definiert einen Ausdruck, der für jede Zeile der Tabelle den Wert true
haben muss. Die bewährten Methoden zur Erstellung von Constraints werden in Kapitel 5 besprochen.
Der sinnvolle Einsatz von Constraints kann dazu beitragen, die Datenqualität sicherzustellen und der Datenbank ein gewisses Maß an Selbstdokumentation zu geben. Einige Constraints haben jedoch erhebliche Auswirkungen auf die Leistung; wir werden diese Auswirkungen in Kapitel 5 erörtern.
Indizes
Indizes können durch die CREATE
INDEX
Anweisung erstellt werden oder eine INDEX
Definition kann in die CREATE
TABLE
Anweisung aufgenommen werden.
Wir haben in Kapitel 2 viel über Indizes gesprochen und werden in den Kapiteln über Schemadesign und Performance-Tuning (Kapitel 5 bzw. 8) weiter über Indizes sprechen. Eine effektive Indexierung ist einer der wichtigsten Erfolgsfaktoren für eine leistungsfähige CockroachDB-Implementierung.
Abbildung 4-8 zeigt eine vereinfachte Syntax für dieAnweisung CockroachDB CREATE
INDEX
.
In Kapitel 2 haben wir uns mit den Interna der CockroachDB-Indizes beschäftigt. Aus Sicht der Leistung verhalten sich CockroachDB-Indizes ähnlich wie Indizes in anderen Datenbanken - sie bieten eine schnelle Zugriffsmethode zum Auffinden von Zeilen mit einem bestimmten Satz von nicht primären Schlüsselwerten. Wenn wir zum Beispiel einfach eine Zeile mit einem bestimmten Namen und Geburtsdatum finden wollen, könnten wir den folgenden mehrspaltigen Index erstellen:
CREATE
INDEX
people_namedob_ix
ON
people
(
lastName
,
firstName
,
dateOfBirth
);
Wenn wir zusätzlich sicherstellen wollen, dass keine zwei Zeilen den gleichen Wert für Name und Geburtsdatum haben, können wir einen eindeutigen Index erstellen:
CREATE
UNIQUE
INDEX
people_namedob_ix
ON
people
(
lastName
,
firstName
,
dateOfBirth
);
Die STORING
Klausel erlaubt es uns, zusätzliche Daten im Index zu speichern, die es uns ermöglichen, Abfragen allein mit dem Index zu erfüllen. Dieser Index kann zum Beispiel Abfragen erfüllen, die Telefonnummern für einen bestimmten Namen und ein bestimmtes Geburtsdatum abfragen:
CREATE
UNIQUE
INDEX
people_namedob_ix
ON
people
(
lastName
,
firstName
,
dateOfBirth
)
STORING
(
phoneNumber
);
Invertierte Indizes
Ein invertierter Index kann verwendet werden, um die Elemente in einem Array oder die Attribute in einem JSON-Dokument zu indizieren. In Kapitel 2 haben wir uns die Interna von invertierten Indizes angesehen. Invertierte Indizes können auch für räumliche Daten verwendet werden.
Nehmen wir zum Beispiel an, unsere Tabelle people
verwendet ein JSON-Dokument, um die Attribute einer Person zu speichern:
CREATE
TABLE
people
(
id
UUID
NOT
NULL
DEFAULT
gen_random_uuid
(),
personData
JSONB
);
INSERT
INTO
people
(
personData
)
VALUES
(
'{
"firstName":"Guy",
"lastName":"Harrison",
"dob":"21-Jun-1960",
"phone":"0419533988",
"photo":"eyJhbGciOiJIUzI1NiIsI..."
}'
);
Wir könnten einen invertierten Index wie folgt erstellen:
CREATE
INVERTED
INDEX
people_inv_idx
ON
people
(
personData
);
Das würde Abfragen des JSON-Dokuments wie die folgende unterstützen:
SELECT
*
FROM
people
WHERE
personData
@>
'{"phone":"0419533988"}'
;
Bedenke, dass invertierte Indizes jedes Attribut im JSON-Dokument indizieren, nicht nur die, nach denen du suchen willst. Das kann zu einem sehr großen Index führen. Daher ist es vielleicht sinnvoller, eine berechnete Spalte für das JSON-Attribut zu erstellen und dann einen Index für diese berechnete Spalte zu erstellen:
ALTER
TABLE
people
ADD
phone
STRING
AS
(
personData
->>
'phone'
)
VIRTUAL
;
CREATE
INDEX
people_phone_idx
ON
people
(
phone
);
Hash-sharded-Indizes
Wenn du mit einer Tabelle arbeitest, die auf sequenziellen Schlüsseln indiziert werden muss, solltest du Hash-Sharded-Indizes verwenden. Hash-Sharded-Indizes verteilen den sequentiellen Datenverkehr gleichmäßig über die Bereiche. Dadurch werden Hotspots in einzelnen Bereichen vermieden und die Schreibleistung von Indizes mit sequentiellen Schlüsseln verbessert, ohne die Leseleistung zu beeinträchtigen:
CREATE
TABLE
people
(
id
INT
PRIMARY
KEY
,
firstName
VARCHAR
NOT
NULL
,
lastName
VARCHAR
NOT
NULL
,
dateOfBirth
timestamp
NOT
NULL
,
phoneNumber
VARCHAR
NOT
NULL
,
serialNo
SERIAL
,
INDEX
serialNo_idx
(
serialNo
)
USING
HASH
WITH
BUCKET_COUNT
=
4
);
Wir werden Hash-Sharded-Indizes und andere fortgeschrittene Indizierungsthemen im nächsten Abschnitt genauer besprechen.
TABELLE ERSTELLEN AS SELECT
Die AS
SELECT
Klausel von CREATE
TABLE
ermöglicht es uns, eine neue Tabelle zu erstellen, die die Daten und Attribute einer SQL SELECT
Anweisung enthält. Spalten, Beschränkungen und Indizes können für eine bestehende Tabelle angegeben werden, müssen aber mit den Datentypen und der Anzahl der Spalten übereinstimmen, die von der SELECT
Anweisung zurückgegeben werden. In diesem Beispiel erstellen wir eine Tabelle, die auf einer JOIN
und einer Aggregation von zwei Tabellen in der Datenbank movr
basiert:
CREATE
TABLE
user_ride_counts
AS
SELECT
u
.
name
,
COUNT
(
u
.
name
)
AS
rides
FROM
"users"
AS
u
JOIN
"rides"
AS
r
ON
(
u
.
id
=
r
.
rider_id
)
GROUP
BY
u
.
name
;
Beachte, dass CREATE
TABLE
AS
SELECT
zwar zum Erstellen von Übersichtstabellen und ähnlichem verwendet werden kann, CREATE
MATERIALIZED
VIEW
aber eine funktionalere Alternative darstellt.
Tische ändern
Die Anweisung ALTER
TABLE
ermöglicht es, Spalten der Tabelle oder Constraints hinzuzufügen, zu ändern, umzubenennen oder zu entfernen sowie Constraints zu überprüfen und zu partitionieren. Abbildung 4-9 zeigt die Syntax.
Die Online-Änderung von Tabellenstrukturen sollte nicht auf die leichte Schulter genommen werden, obwohl CockroachDB sehr fortschrittliche Mechanismen bietet, um solche Änderungen ohne Beeinträchtigung der Verfügbarkeit und mit minimalen Auswirkungen auf die Leistung weiterzugeben. Wir werden die Verfahren für Online-Schemaänderungen in späteren Kapiteln besprechen.
Tische fallen lassen
Tabellen können mit der Anweisung DROP
TABLE
gelöscht werden. Abbildung 4-10 zeigt die Syntax.
Mehrere Tabellen können mit einer einzigen DROP
TABLE
Anweisung entfernt werden. Das Schlüsselwort CASCADE
bewirkt, dass auch abhängige Objekte wie Views oder Fremdschlüsselbegrenzungen gelöscht werden. RESTRICT
-der Standardwert- hat den gegenteiligen Effekt: Wenn es abhängige Objekte gibt, wird die Tabelle nicht gelöscht.
Ansichten
Eine Standardansicht ist eine Abfragedefinition die in der Datenbank gespeichert ist und eine virtuelle Tabelle definiert. Auf diese virtuelle Tabelle kann auf die gleiche Weise wie auf eine reguläre Tabelle verwiesen werden. Common Table Expressions können als eine Art temporärer View für eine einzelne SQL-Anweisung betrachtet werden. Wenn du einen Common Table Expression hast, den du in verschiedenen SQL-Anweisungen verwenden möchtest, wäre ein View eine logische Lösung.
Eine materialisierte Ansicht speichert die Ergebnisse der Ansichtsdefinition in der Datenbank, so dass die Ansicht nicht jedes Mal neu ausgeführt werden muss, wenn sie aufgerufen wird. Das verbessert die Leistung, kann aber zu veralteten Ergebnissen führen. Wenn du dir eine Ansicht als eine gespeicherte Abfrage vorstellst, kann eine materialisierte Ansicht als ein gespeichertes Ergebnis betrachtet werden.
Abbildung 4-11 zeigt die Syntax der Anweisung CREATE VIEW
.
Die Anweisung REFRESH
MATERIALIZED
VIEW
kann verwendet werden, um die Daten zu aktualisieren, die einer materialisierten Ansicht zugrunde liegen.
Daten einfügen
Wir können Daten in eine neue Tabelle laden, indem wir die bereits erwähnte CREATE
TABLE
AS
SELECT
Anweisung verwenden, die INSERT
Anweisung innerhalb eines Programms oder von der Kommandozeile aus verwenden oder externe Daten mit der IMPORT
Anweisung laden. Es gibt auch Dienstprogramme, die keine SQL-Anweisungen sind, um Daten einzufügen - wir werden uns diese in Kapitel 7 ansehen.
Die altehrwürdige Anweisung INSERT
fügt Daten zu einer bestehenden Tabelle hinzu. Abbildung 4-12 zeigt eine vereinfachte Syntax für die Anweisung INSERT
.
INSERT
nimmt entweder eine Reihe von Werten oder eine SELECT
Anweisung an. Im folgenden Beispiel fügen wir zum Beispiel eine einzelne Zeile in die Tabelle people
ein:
INSERT
INTO
people
(
firstName
,
lastName
,
dateOfBirth
)
VALUES
(
'Guy'
,
'Harrison'
,
'21-JUN-1960'
);
Die VALUES
Klausel der INSERT
Anweisung kann Array-Werte akzeptieren, so dass mehr als eine Zeile in einer einzigen Ausführung eingefügt werden kann:
INSERT
INTO
people
(
firstName
,
lastName
,
dateOfBirth
)
VALUES
(
'Guy'
,
'Harrison'
,
'21-JUN-1960'
),
(
'Michael'
,
'Harrison'
,
'19-APR-1994'
),
(
'Oriana'
,
'Harrison'
,
'18-JUN-2020'
);
Es gibt alternative Möglichkeiten, Stapel in die verschiedenen Programmsprachtreiber einzufügen, und wir werden in Kapitel 7 einige Beispiele zeigen.
Als Quelle für die eingefügten Daten kann eine SELECT
Anweisung angegeben werden:
INSERT
INTO
people
(
firstName
,
lastName
,
dateOfBirth
)
SELECT
firstName
,
lastName
,
dateOfBirth
FROM
peopleStagingData
;
Mit der RETURNING
Klausel können die eingefügten Daten an den Benutzer zurückgegeben werden. Zu den zurückgegebenen Daten gehören nicht nur die eingefügten Variablen, sondern auch alle automatisch generierten Daten. In diesem Fall haben wir zum Beispiel INSERT
Daten ohne Angabe eines ID-Werts eingefügt und bekommen die ID-Werte, die erstellt wurden, zurückgegeben:
INSERT
INTO
people
(
firstName
,
lastName
,
dateOfBirth
)
VALUES
(
'Guy'
,
'Harrison'
,
'21-JUN-1960'
),
(
'Michael'
,
'Harrison'
,
'19-APR-1994'
),
(
'Oriana'
,
'Harrison'
,
'18-JUN-2020'
)
RETURNING
id
;
Mit der ON
CONFLICT
Klausel kannst du steuern, was passiert, wenn eine INSERT
eine Eindeutigkeitsbeschränkung verletzt. Abbildung 4-13 zeigt die Syntax.
Ohne die ON
CONFLICT
Klausel führt eine Verletzung der Einzigartigkeitsbeschränkung zum Abbruch der gesamten INSERT
Anweisung. DO
NOTHING
lässt die INSERT
Anweisung als Ganzes erfolgreich sein, ignoriert aber alle Einfügungen, die gegen die Einzigartigkeitsklausel verstoßen. Mit der DO
UPDATE
Klausel kannst du eine UPDATE
Anweisung angeben, die anstelle der INSERT
Anweisung ausgeführt wird. Die DO
UPDATE
Funktion ähnelt in ihrer Funktionsweise der UPSERT
Anweisung, die weiter unten in diesem Kapitel besprochen wird.
UPDATE
Die Anweisung UPDATE
ändert bestehende Daten in einer Tabelle.Abbildung 4-14 zeigt eine vereinfachte Syntax für die Anweisung UPDATE
.
Eine UPDATE
Anweisung kann statische Werte angeben, wie im folgenden Beispiel:
UPDATE
users
SET
address
=
'201 E Randolph St'
,
city
=
'amsterdam'
WHERE
name
=
'Maria Weber'
;
Alternativ können die Werte auch ein Ausdruck sein, der auf bestehende Werte verweist:
UPDATE
user_promo_codes
SET
usage_count
=
usage_count
+
1
WHERE
user_id
=
'297fcb80-b67a-4c8b-bf9f-72c404f97fe8'
;
Oder die UPDATE
kann eine Unterabfrage verwenden, um die Werte zu erhalten:
UPDATE
rides
SET
(
revenue
,
start_address
)
=
(
SELECT
revenue
,
end_address
FROM
rides
WHERE
id
=
'94fdf3b6-45a1-4800-8000-000000000123'
)
WHERE
id
=
'851eb851-eb85-4000-8000-000000000104'
;
Die RETURNING
Klausel kann verwendet werden, um die geänderten Spalten anzuzeigen. Dies ist besonders nützlich, wenn eine Spalte durch eine Funktion aktualisiert wird und wir den geänderten Wert an die Anwendung zurückgeben wollen:
UPDATE
user_promo_codes
SET
usage_count
=
usage_count
+
1
WHERE
user_id
=
'297fcb80-b67a-4c8b-bf9f-72c404f97fe8'
RETURNING
(
usage_count
);
UPSERT
UPSERT
kann in einem einzigen Vorgang neue Daten einfügen und bestehende Daten in einer Tabelle aktualisieren. Wenn die Eingabedaten keine Eindeutigkeitsbeschränkungen verletzen, werden sie eingefügt. Wenn eine Eingabe mit einem vorhandenen Primärschlüssel übereinstimmt, werden die Werte der betreffenden Zeile aktualisiert.
In CockroachDB bietet die ON
CONFLICT
Klausel von INSERT
einen ähnlichen - wenn auch flexibleren - Mechanismus. Wenn diese Flexibilität nicht benötigt wird, ist UPSERT
wahrscheinlich schneller als eine ähnliche INSERT...ON
CONFLICT
DO
UPDATE
Anweisung.
Abbildung 4-15 zeigt die Syntax der Anweisung UPSERT
.
Die UPSERT
vergleicht den Primärschlüsselwert jeder angegebenen Zeile. Wenn der Primärschlüssel in der vorhandenen Tabelle nicht gefunden wird, wird eine neue Zeile erstellt. Andernfalls wird die vorhandene Zeile mit den neuen Werten aktualisiert.
Die RETURNING
Klausel kann verwendet werden, um eine Liste der aktualisierten oder eingefügten Zeilen zurückzugeben.
In diesem Beispiel ist der Primärschlüssel von user_promo_codes
(city, user_id, code)
. Wenn ein Benutzer bereits einen Eintrag für diese Kombination in der Tabelle hat, wird diese Zeile mit einem user_count
von 0 aktualisiert. Ansonsten wird eine neue Zeile mit diesen Werten erstellt.
UPSERT
INTO
user_promo_codes
(
user_id
,
city
,
code
,
timestamp
,
usage_count
)
SELECT
id
,
city
,
'NewPromo'
,
now
(),
0
FROM
"users"
;
LÖSCHEN
DELETE
können Daten aus einer Tabelle entfernt werden. Abbildung 4-16 zeigt eine vereinfachte Syntax für die Anweisung DELETE
.
Meistens akzeptiert eine DELETE
Anweisung eine WHERE
Klausel und nicht viel mehr. Hier löschen wir zum Beispiel eine einzelne Zeile in der Tabelle people
:
DELETE
FROM
people
WHERE
firstName
=
'Guy'
AND
lastName
=
'Harrison'
;
Die RETURNING
Klausel kann Details zu den entfernten Zeilen zurückgeben. Zum Beispiel:
DELETE
FROM
user_promo_codes
WHERE
code
=
'NewPromo'
RETURNING
(
user_id
);
Du kannst auch eine ORDER
BY
und LIMIT
Klausel einfügen, um Batch-Löschungen auf inkrementelle Weise durchzuführen. Du kannst zum Beispiel eine DELETE
Anweisung erstellen, um die ältesten 1.000 Zeilen zu löschen. Weitere Informationen findest du in der CockroachDB-Dokumentation.
IMPORTIEREN/IMPORTIEREN IN
Die Anweisung IMPORT
importiert die folgenden Datentypen in CockroachDB:
-
Avro
-
Kommagetrennte Werte (CSV)/Tabellengetrennte Werte (TSV)
-
Postgres Dump-Dateien
-
MySQL-Dump-Dateien
-
CockroachDB Dump-Dateien
IMPORT
erstellt eine neue Tabelle, während IMPORT
INTO
einen Import in eine bestehende Tabelle ermöglicht.
Die zu importierenden Dateien sollten entweder in einem Bucket der Cloud Speicherung - Google Cloud Storage, Amazon S3 oder Azure Blob Storage - über eine HTTP-Adresse oder im lokalen Dateisystem ("nodelocal") vorhanden sein.
Die verschiedenen Möglichkeiten, Daten in CockroachDB zu laden, werden wir in Kapitel 7 besprechen. Für den Moment wollen wir jedoch eine neue Tabelle customers
aus einer CSV-Datei erstellen:
IMPORT
INTO
TABLE
customers
(
id
INT
PRIMARY
KEY
,
name
STRING
,
INDEX
name_idx
(
name
)
);
CSV
DATA
(
'nodelocal://1/customers.csv'
);
job_id
|
status
|
fra
|
rows
|
index_entries
|
bytes
---------------------+-----------+-----+------+---------------+--------
659162639684534273
|
succeeded
|
1
|
1
|
1
|
47
(
1
row
)
Time
:
934
ms
total
(
execution
933
ms
/
network
1
ms
)
Bei einem Ein-Knoten-Demo-Cluster hängt der Speicherort nodelocal
ein wenig von deiner Installation ab, befindet sich aber häufig in einem extern
Verzeichnis unterhalb des CockroachDB-Installationsverzeichnisses.
Transaktionsanweisungen
Wir haben in Kapitel 2 viel über CockroachDB-Transaktionen gesprochen. Wenn du also noch einmal wissen willst, wie CockroachDB-Transaktionen funktionieren, solltest du dieses Kapitel lesen. Aus Sicht der SQL-Sprache unterstützt CockroachDB die Standard-SQL-Anweisungen zur Transaktionssteuerung.
BEGIN-Transaktion
Die Anweisung BEGIN
leitet eine Transaktion ein und legt deren Eigenschaften fest. Abbildung 4-17 zeigt die Syntax.
PRIORITY
legt die Priorität der Transaktion fest. Im Falle eines Konflikts ist es weniger wahrscheinlich, dass Transaktionen mit der Priorität HIGH
erneut versucht werden.
READ
ONLY
legt fest, dass die Transaktion schreibgeschützt ist und die Daten nicht verändert werden.
AS
OF
SYSTEM
TIME
ermöglicht es einer READ
ONLY
Transaktion, Daten aus einem Snapshot der Datenbankhistorie einzusehen. Darauf werden wir auf den nächsten Seiten zurückkommen.
COMMIT
Die Anweisung COMMIT
überträgt die aktuellen Transaktionen und macht die Änderungen dauerhaft.
Beachte, dass bei einigen Transaktionen ein client-seitiger Eingriff erforderlich sein kann, um Wiederholungen zu ermöglichen. Diese Muster werden in Kapitel 6 näher erläutert.
ROLLBACK
ROLLBACK
bricht die aktuelle Transaktion ab. Optional können wir ROLLBACK
zu einem Savepoint zurückkehren, der nur die Anweisungen zurücknimmt, die nach dem SAVEPOINT
ausgegeben wurden.
Im folgenden Beispiel wird die Einfügung des falsch geschriebenen Zahlenbaums zurückgenommen und korrigiert, ohne dass die Transaktion als Ganzes abgebrochen wird:
BEGIN
;
INSERT
INTO
numbers
VALUES
(
1
,
'one'
);
INSERT
INTO
numbers
VALUES
(
2
,
'two'
);
SAVEPOINT
two
;
INSERT
INTO
numbers
VALUES
(
3
,
'tree'
);
ROLLBACK
TO
SAVEPOINT
two
;
INSERT
INTO
numbers
VALUES
(
3
,
'three'
);
COMMIT
;
ZUM UPDATE AUSWÄHLEN
Die FOR
UPDATE
Klausel einer SELECT
Anweisung sperrt die von einer Abfrage zurückgegebenen Zeilen und stellt sicher, dass sie zwischen dem Zeitpunkt des Lesens und dem Ende der Transaktion nicht von einer anderen Transaktion geändert werden können. Diese Klausel wird normalerweise verwendet, um das pessimistische Sperrmuster zu implementieren, das wir in Kapitel 6 besprechen werden.
Eine FOR
UPDATE
Abfrage sollte innerhalb einer Transaktion ausgeführt werden. Andernfalls werden die Sperren nach Beendigung der SELECT
Anweisung freigegeben.
Eine FOR
UPDATE
innerhalb einer Transaktion blockiert standardmäßig andere FOR
UPDATE
Anweisungen für dieselben Zeilen oder andere Transaktionen, die diese Zeilen aktualisieren wollen, bis eine COMMIT
oder ROLLBACK
ausgegeben wird. Wenn jedoch eine Transaktion mit höherer Priorität versucht, die Zeilen zu aktualisieren oder eine FOR
UPDATE
auszustellen, wird die Transaktion mit niedrigerer Priorität abgebrochen und muss erneut versucht werden.
Wir werden die Mechanismen der Transaktionswiederholungen in Kapitel 6 besprechen.
Abbildung 4-18 veranschaulicht zwei FOR
UPDATE
Anweisungen, die gleichzeitig ausgeführt werden. Die erste FOR
UPDATE
hält Sperren für die betroffenen Zeilen und verhindert, dass die zweite Sitzung diese Sperren erhält, bis die erste Sitzung ihre Transaktion abgeschlossen hat.
AB DER SYSTEMZEIT
Die AS
OF
SYSTEM
TIME
Klausel kann auf SELECT
und BEGIN
TRANSACTION
Anweisungen sowie in BACKUP
und RESTORE
Operationen angewendet werden. AS
OF
SYSTEM
TIME
gibt an, dass eine SELECT
Anweisung oder alle Anweisungen in einer READ
ONLY
Transaktion auf einem Snapshot der Datenbank zu diesem Systemzeitpunkt ausgeführt werden sollen. Diese Snapshots werden durch die in Kapitel 2 beschriebene MVCC-Architektur zur Verfügung gestellt.
Die Zeit kann als Offset oder als absoluter Zeitstempel angegeben werden, wie in den folgenden zwei Beispielen:
SELECT
*
FROM
rides
r
AS
OF
SYSTEM
TIME
'-1d'
;
SELECT
*
FROM
rides
r
AS
OF
SYSTEM
TIME
'2021-5-22 18:02:52.0+00:00'
;
Die angegebene Zeit darf in Sekunden nicht älter sein als der Konfigurationsparameter der Replikationszone ttlseconds
, der das maximale Alter der MVCC-Snapshots bestimmt.
Es ist auch möglich, mit dem Argument with_max_staleness
begrenzte stale reads anzugeben:
SELECT
*
FROM
rides
r
AS
OF
SYSTEM
TIME
with_max_staleness
(
'10s'
)
WHERE
city
=
'amsterdam'
AND
id
=
'aaaae297-396d-4800-8000-0000000208d6'
;
Mit Bounded Stale Reads kann die Leistung in verteilten Implementierungen optimiert werden, indem CockroachDB die Leseanforderungen von lokalen Replikaten erfüllt, die möglicherweise leicht veraltete Daten enthalten. Wir werden in Kapitel 11 auf Bounded Stale Reads zurückkommen.
Andere Ziele der Datendefinitionssprache
Bisher haben wir uns mit SQL beschäftigt, um Daten in Tabellen und Indizes zu erstellen, zu ändern und zu manipulieren. Diese Objekte stellen in CockroachDB, wie auch in anderen SQL-Datenbanken, den Kern der Datenbankfunktionalität dar. Die CockroachDB Data Definition Language (DDL) bietet jedoch Unterstützung für eine Vielzahl anderer, weniger häufig verwendeter Objekte. Eine vollständige Referenz für alle diese Objekte würde mehr Platz beanspruchen, als wir hier zur Verfügung haben - eine vollständige Liste der CockroachDB-SQL-Objekte findest du in der CockroachDB-Dokumentation.
In Tabelle 4-4 sind einige der anderen Objekte aufgeführt, die in den Anweisungen CREATE
, ALTER
und DROP
manipuliert werden können.
Object |
Beschreibung |
---|---|
|
Eine Datenbank ist ein Namensraum innerhalb eines CockroachDB-Clusters, der Schemas, Tabellen, Indizes und andere Objekte enthält. Datenbanken werden in der Regel verwendet, um Objekte zu trennen, die unterschiedliche Anwendungszuständigkeiten oder Sicherheitsrichtlinien haben. |
|
Ein Schema ist eine Sammlung von Tabellen und Indizes, die zu demselben relationalen Modell gehören. In den meisten Datenbanken werden die Tabellen standardmäßig im Schema |
|
Oft werden Sequenzen verwendet, um Primärschlüsselwerte zu erzeugen; in CockroachDB gibt es jedoch oft bessere Alternativen. In Kapitel 5 findest du weitere Hinweise zur Erzeugung von Primärschlüsseln. |
|
Eine Rolle wird verwendet, um Datenbank- und Schemarechte zu gruppieren, die dann den Benutzern als Einheit gewährt werden können. In Kapitel 12 findest du weitere Informationen zu den Sicherheitspraktiken von CockroachDB. |
|
In CockroachDB ist ein Typ eine Aufzählung von Werten, die in einer |
|
Ein Benutzer ist ein Konto, mit dem man sich bei der Datenbank anmelden kann und dem man bestimmte Rechte zuweisen kann. In Kapitel 12 findest du weitere Details zu den Sicherheitspraktiken der CockroachDB. |
|
Statistiken bestehen aus Informationen über die Daten in einer bestimmten Tabelle, die der SQL-Optimierer verwendet, um den bestmöglichen Ausführungsplan für eine SQL-Anweisung zu ermitteln. In Kapitel 8 findest du weitere Informationen zur Abfrageoptimierung. |
|
Ein Changefeed überträgt Änderungen auf Zeilenebene für nominierte Tabellen an ein Client-Programm. In Kapitel 7 findest du weitere Informationen zur Implementierung von Changefeeds. |
|
Ein Zeitplan steuert die regelmäßige Ausführung von Backups. In Kapitel 11 findest du Hinweise zu Backup-Richtlinien. |
Administrative Befehle
CockroachDB unterstützt Befehle zur Authentifizierung von Benutzern und deren Berechtigung, Datenbankoperationen durchzuführen. Außerdem verfügt es über ein Zeitplannungsprogramm, mit dem Sicherungs- und Wiederherstellungsvorgänge sowie geplante Schemaänderungen geplant werden können. Andere Befehle unterstützen die Verwaltung der Clustertopologie.
Diese Befehle sind in der Regel eng mit bestimmten Verwaltungsvorgängen verknüpft, die wir in den folgenden Kapiteln besprechen werden, weshalb wir sie hier nicht im Detail definieren. Du kannst die Definitionen für diese Befehle jederzeit in der CockroachDB-Dokumentation nachlesen. Tabelle 4-5 fasst die wichtigsten dieser Befehle zusammen.
Befehl | Beschreibung |
---|---|
|
Abbrechen von Aufträgen, die lange laufen, wie z.B. Backups, Schemaänderungen oder Statistiksammlungen. |
|
Abbrechen einer laufenden Abfrage. |
|
Abbrechen und Trennen einer aktuell verbundenen Sitzung. |
|
|
|
Ändere einen Parameter der Clusterkonfiguration. |
|
Zeigt einen Ausführungsplan für eine SQL-Anweisung an. Wir werden uns |
|
Dump der SQL-Ausgabe in CSV-Dateien. |
|
Verwalte Hintergrundaufträge - Importe, Backups, Schemaänderungen usw. - in der Datenbank. |
|
Ändere die Lokalität einer Tabelle in einer multiregionalen Datenbank. Siehe Kapitel 10 für weitere Informationen. |
|
Aktiviere das Tracing für eine Sitzung. Wir werden dies in Kapitel 8 besprechen. |
|
Zeigt, wie eine Tabelle, ein Index oder eine Datenbank in Bereiche unterteilt wird. In Kapitel 2 wird erläutert, wie CockroachDB Daten in Bereiche aufteilt. |
|
Erzwinge einen Bereichssplit an der angegebenen Zeile in einer Tabelle oder einem Index. |
|
Erstelle eine konsistente Sicherung für eine Tabelle oder Datenbank. In Kapitel 11 findest du Hinweise zu Backups und Hochverfügbarkeit. |
|
Optimierungsstatistiken für eine Tabelle anzeigen. |
|
Zeigt Tracing-Informationen für eine Sitzung an, die mit dem Befehl |
|
Aktuell laufende Transaktionen anzeigen |
|
Zeigt Sitzungen auf dem lokalen Knoten oder im gesamten Cluster an. |
Das Informationsschema
Das Informationsschema ist ein spezielles Schema in jeder Datenbank, das Metadaten über die anderen Objekte in der Datenbank enthält - in CockroachDB heißt es INFORMATION_SCHEMA
. Du kannst das Informationsschema verwenden, um die Namen und Typen der Objekte in der Datenbank zu ermitteln. Du kannst das Informationsschema zum Beispiel verwenden, um alle Objekte im Schema information_schema
aufzulisten:
SELECT
*
FROM
information_schema
.
"tables"
WHERE
table_schema
=
'information_schema'
;
Oder du kannst information_schema
verwenden, um die Spalten in einer Tabelle anzuzeigen:
SELECT
column_name
,
data_type
,
is_nullable
,
column_default
FROM
information_schema
.
COLUMNS
WHERE
TABLE_NAME
=
'customers'
;
Das Informationsschema ist besonders nützlich, wenn du Anwendungen für ein unbekanntes Datenmodell schreibst. GUI-Tools wie DBeaver nutzen das Informationsschema zum Beispiel, um den Datenbankbaum zu füllen und Informationen über Tabellen und Indizes anzuzeigen.
Das Informationsschema ist durch ANSI-Standards definiert und wird von vielen relationalen Datenbanken implementiert. CockroachDB enthält auch einige interne Tabellen, die speziell für das CockroachDB-System im crdb_internal
Schema enthalten sind. Informationen über diese Tabellen findest du in der CockroachDB-Dokumentation.
Zusammenfassung
In diesem Kapitel haben wir die Grundlagen der SQL-Sprache zum Erstellen, Abfragen und Ändern von Daten in der CockroachDB-Datenbank besprochen.
Eine vollständige Definition aller Syntaxelemente von CockroachDB SQL würde ein ganzes Buch in Anspruch nehmen, daher haben wir uns hauptsächlich auf die Kernfunktionen der SQL-Sprache konzentriert und einige CockroachDB-spezifische Funktionen hervorgehoben. Eine ausführliche Syntax und Details zu den CockroachDB-Verwaltungsbefehlen findest du in derCockroachDB-Online-Dokumentation.
SQL ist die Sprache von CockroachDB. Natürlich werden wir die CockroachDB-SQL-Sprache weiter ausarbeiten, wenn wir tiefer in die Welt von CockroachDB eintauchen.
1 Beachte, dass user_ride_counts
nicht im Standardschema rides
definiert ist. Es ist definiert als SELECT
u.name,
COUNT(u.name)
AS
rides
FROM
"users"
AS
u
JOIN
"rides"
AS
r
ON
(u.id=r.rider_id)
GROUP
BY
u.name
.
Get CockroachDB: Der endgültige Leitfaden now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.