String-Theorie

Zeichenketten sind auf den ersten Blick einer der simpelsten Datentypen. Wer sie in Datenbanken nutzt, sollte sich jedoch über einige Regeln im Klaren sein und nützliche Funktionen kennen.

In Pocket speichern vorlesen Druckansicht 6 Kommentare lesen
Lesezeit: 10 Min.
Von
  • Carsten Czarski
Inhaltsverzeichnis

Das Speichern von Zeichenketten scheint die einfachste Aufgabe einer relationalen Datenbank zu sein – Spalten für alphanumerische Daten können schließlich so ziemlich alles aufnehmen. Bei genauerer Betrachtung ergeben sich jedoch viele Detailfragen. Etwa, warum es beispielsweise in Oracles Datenbank mit CHAR, VARCHAR und VARCHAR2 drei Datentypen für Zeichenketten gibt – nimmt man CLOB und NCLOB dazu, sind es sogar fünf. Das ist keine Oracle-Spezialität, andere Datenbanksysteme bieten ebenfalls mehrere String-Typen an.

In nahezu allen Datenbanksystemen heißt der Datentyp für Strings fester Länge CHAR. In Oracle kann er bis zu 2000 Byte aufnehmen, der SQL Server schafft 8000 und CHAR in DB2 bietet Platz für 255 Zeichen. Interessant an diesem Datentypen ist, dass der Server stets die angegebene Länge verwendet. Kürzere Inhalte füllt er gegebenenfalls mit Leerzeichen auf. Listing 1 zeigt den Effekt in Oracle. Dieses Auffüllen dürfte der Grund dafür sein, dass kaum jemand CHAR verwendet – schließlich will man seine Datenbank nicht zum Speichern von Leerzeichen benutzen.

Am häufigsten wird sicherlich der Datentyp für Strings variabler Länge eingesetzt: VARCHAR bietet in DB2 Platz für bis zu 32 704 Byte, im SQL Server fasst es bis zu 8000 Byte. In Oracle heißt das Äquivalent VARCHAR2 und nimmt bis zu 4000 Byte auf. VARCHAR gibt es dort zwar auch, dort passen aber nur 255 Byte hinein. In Oracles Programmiersprache PL/SQL kann eine VARCHAR2-Variable dagegen bis zu 32 767 Byte aufnehmen – in der nächsten Version sollen Tabellen mit dieser Grenze gleichziehen. Alle VARCHAR-Spalten verbrauchen nur den für die Zeichenkette tatsächlich benötigten Platz. Die Längenangabe im CREATE TABLE hat keine Auswirkungen auf Platzbelegung oder Performance, sondert ist als Constraint zu betrachten.

MySQL und PostgreSQL fassen die Datentyplänge in Zeichen auf, DB2 und SQL Server in Byte. Bei Byte-Angaben können unerwartete Effekte auftreten, denn in einer Unicode-Datenbank ist das nicht zwingend mit der Zeichenanzahl identisch (siehe Kasten „Zeichenketten und Zeichensätze“). Oracle erlaubt bei Definition eines VARCHAR2 die Längenangabe in Byte oder Zeichen. Listing 2 zeigt die möglichen Varianten. Eine einfache Zahl ohne Angabe von BYTE oder CHAR deutet jedoch nicht zwingend auf BYTE hin, denn der Datenbankparameter NLS_LENGTH_SEMANTICS legt fest, was die dimensionslose Angabe bedeutet. Er steht fast überall auf BYTE.

Für noch längere Zeichenketten müssen Large-Object-Datentypen (LOB) her. Ein CLOB (Character Large Object) kann typischerweise bis zu 2 GByte große Zeichenketten aufnehmen; in Oracle geht es je nach Blockgröße bis zu 128 TByte. LOBs werden allerdings physisch anders gespeichert als CHARs und VARCHARs: Sie kommen in eigene Speichersegmente, und die Tabellenspalte enthält nur einen Pointer darauf. Davon bekommt der Nutzer jedoch nichts mit: Beim Selektieren ruft der Server zunächst nur den Pointer ab. Anschließend liest er die Inhalte blockweise. Damit ist sichergestellt, dass das versehentliche Selektieren eines großen LOB nicht den Hauptspeicher des Servers sprengt.

Neben diesen Datentypen sind in allen wichtigen Datenbanken mit NCHAR, NVARCHAR oder NCLOB auch Varianten vorhanden, die Unicode-Zeichen in einer Nicht-Unicode-Datenbank speichern können. Allerdings erfordert dies meist aufwendige Änderungen am Datenmodell und im Code von Stored Procedures. Deshalb ist die Umstellung der gesamten Datenbank auf Unicode meist die bessere und nachhaltigere Option.

SQL-Anweisungen nutzen Zeichenketten fast genauso wie Programmiersprachen, allerdings haben Datenbanken, wie immer, ihre Besonderheiten. So verkettet man Zeichenfolgen in SQL mit der „Doppel-Pipe“ (||), und Strings stehen nicht in doppelten, sondern ausschließlich in einfachen Anführungszeichen. Doppelte Anführungszeichen sind Objektnamen (Tabellen, Views, Prozeduren) vorbehalten. Gehört ein einfaches Anführungszeichen zum String, muss man es durch Verdoppelung maskieren.

Bei der Suche nach Teil-Strings und Wildcards kommt der LIKE-Operator zum Einsatz (s. Listing 3). Der Unterstrich (_) steht dabei für ein einzelnes beliebiges Zeichen, das Prozentzeichen (%) für mehrere davon. Auch diese beiden Symbole können maskiert werden. Dazu dient in allen aktuellen Datenbanken das Schlüsselwort ESCAPE: Geht das mit ESCAPE definierte Zeichen dem % oder _ voran, fasst der Server es nicht als Joker auf, sondern als normales Zeichen.

Ein Index auf eine Textspalte beschleunigt viele Suchanfragen, so etwa die erste in Listing 3. Sie steigt mit „SC“ in den Index ein und führt einen „Range Scan“ darin durch. Das Durchsuchen der Tabelle (Full Table Scan) ist unnötig. Mit der zweiten Abfrage funktioniert das jedoch nicht: Da die Wildcard am Anfang steht, gibt es keinen „Einstiegspunkt“ in den Index. Der Datenbank bleibt also nichts anderes übrig als die komplette Tabelle zu durchsuchen. Bei großen Datenmengen sollte man mit Wildcards am Anfang deshalb vorsichtig sein und die Laufzeiten vorher testen (s. Kasten „LIKE und Indizes“).

Geht es darum, die Groß- und Kleinschreibung einer Zeichenkette zu verändern, kommen die Funktionen LOWER, UPPER und INITCAP zum Einsatz. LOWER oder UPPER braucht man recht häufig in der WHERE-Klausel einer SQL-Abfrage, denn nicht immer weiß man genau, was in einer Tabellenspalte groß oder klein geschrieben ist. Mit einem funktionalen Index können solche Abfragen sogar auf großen Datenmengen performant laufen. INITCAP, das MySQL bislang nicht kennt, dient dagegen vor allem zur Formatierung der Ausgabe. Listing 4 zeigt ein Beispiel.

Vielfach stehen auch andere Mittel zur Verfügung, Groß- und Kleinschreibung identisch zu behandeln. So bestimmen in Oracle Session-Parameter, wie sich die Datenbank bei Vergleichen und Sortierungen verhält. Das Beispiel in Listing 5 bewirkt, dass die WHERE-Klausel nicht nur die Groß-/Kleinschreibung, sondern auch Akzente und Umlaute ignoriert. Zur performanten Ausführung auch auf großen Tabellen muss die Spalte nun nur noch mit einem funktionalen, „linguistischen“ Index versehen werden. Ähnliches lässt sich in MySQL durch Einstellen einer Collation wie latin1_german1_ci bewirken – das ci steht für „case insensitive“.

NLS_COMP stellt bei Oracle das Verhalten der Datenbank für Vergleiche ein. BINARY verwendet einfach die Bytes, mit denen der String codiert ist. Dabei unterscheidet sich „Ä“ von „A“. LINGUISTIC bedeutet, dass die Datenbank diakritische Zeichen besonders berücksichtigt; der Parameter NLS_SORT legt die Regeln dafür fest. Das in Listing 5 verwendete GERMAN_AI wählt die in Deutschland übliche Sortierung, bei der „Ä“ auf „A“ folgt. Das angehängte _AI stellt sicher, dass Oracle Akzente sowie die Groß- und Kleinschreibung bei Vergleichen ignoriert.

Noch ausgefeilter kann man mit regulären Ausdrücken suchen. Einige Datenbanken bieten sie als eingebaute SQL-Funktionen oder -Operatoren an; in anderen lassen sie sich mit wenig Aufwand als User Defined Functions einbinden. Die Abfrage in Listing 6 zeigt die Suche nach Einträgen, bei denen ENAME mit B, K oder J beginnt, mit einem regulären Ausdruck. Diese Syntax ist Oracle-spezifisch, anderswo heißt der Operator unter Umständen anders.

Verwendet man reguläre Ausdrücke in Datenbanken, sollte man jedoch nicht nur an SELECT-Abfragen denken, sondern auch an das Hinterlegen von CHECK-Constraints: Durch reguläre Ausdrücke lassen sich solche Regeln für zulässige Mail- oder URL-Einträge recht einfach schreiben und leicht warten:

ALTER TABLE kunden 
ADD CONSTRAINT ck_valid_email
CHECK (REGEXP_LIKE(email,
'^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,}$', 'i'))

In diesem Fall stellt der dritte Parameter ’i’ sicher, dass die Prüfung ohne Berücksichtigung der Groß-/Kleinschreibung erfolgt. Neben dem einfachen Vergleich mit einem regulären Ausdruck bieten einige Datenbanksysteme weitere Funktionen, etwa zum Ersetzen regulärer Ausdrücke.

REPLACE führt ein einfaches Suchen-und-Ersetzen durch. Die Funktion erwartet drei Parameter: Innerhalb der ersten Zeichenkette sucht sie die zweite und ersetzt die Fundstelle durch den dritten String. Kombiniert mit INITCAP lassen sich aus dem oft genutzten Mail-Schema „vorname.nachname@firma.de“ recht einfach Klarnamen generieren (s. Listing 7).

Dagegen tauscht TRANSLATE stets einzelne Zeichen aus und arbeitet die Parameter zum Suchen und Ersetzen zeichenweise ab. So ersetzt die folgende SQL-Abfrage Leerzeichen durch Punkte und „a“ durch „A“:

SELECT TRANSLATE('Datenbanken machen Spass', ' a',   '.A') 
FROM dual;

sodass die Ausgabe „DAtenbAnken.mAchen.SpAss“ lautet.

In der Praxis recht nützlich sind die Padding-Funktionen RPAD und LPAD, die man in vielen anderen Programmiersprachen nicht findet. Es gibt sie jedoch nicht in allen Datenbanken. Für den SQL Server kann man eine Alternative selber schreiben (s. „Alle Links“). LPAD füllt eine Zeichenkette von links, RPAD von rechts mit einem übergebenen Zeichen auf. Die als Zweites übergebene Länge bezieht sich auf das Ergebnis. Man gibt also an, wie lang der sich ergebende String sein soll, und muss das nicht selbst ausrechnen (s. Listing 8).

Mehr Infos

iX-Tract

  • Alle SQL-Datenbanken bieten verschiedene Datentypen zum Speichern von Zeichenketten. Sie unterscheiden sich in ihrer Maximallänge und teilweise in der Interpretation der Zeichencodierung.
  • Daten kann man nach genauen und ungefähren Treffern suchen. Viele Systeme bieten auch die Suche mit regulären Ausdrücken.
  • Beim Verwenden von Wildcards kann der Server einen einfachen Index nicht immer verwenden – dann hilft ein Trick oder ein Volltextindex weiter.

SUBSTR schneidet eine Teilzeichenkette aus dem String aus; INSTR liefert die Position des gesuchten Teil-Strings. Man sollte es in SQL-Abfragen jedoch nur dann nutzen, wenn man wirklich an der Position des gefundenen Teil-Strings interessiert ist. Andernfalls ist LIKE besser geeignet, da es einen Index nutzen kann – das ist bei INSTR nicht möglich.

Viele Aggregatfunktionen wie SUM und AVG funktionieren mit Strings nicht. Einige lassen sich jedoch durchaus anwenden. So zählt COUNT auch Zeichenketten, und MIN/MAX liefert den ersten oder letzten Eintrag einer sortierten Liste zurück. Einige Systeme bieten eine Funktion zum Zusammenfassen mehrerer Strings in einer Gruppe. Bei Oracle heißt sie LISTAGG, bei MySQL GROUP_CONCAT (s. Listing 9).

Datenbanken beherrschen nicht nur einfache Textoperationen wie Vergleiche oder Teil-String-Suche mit LIKE. Zudem können Suchabfragen Groß- und Kleinschreibung oder diakritische Zeichen ignorieren. Das macht sie mächtiger und erfordert keine aufwendigen Änderungen an der Anwendung. In Views eingesetzt, können String-Funktionen Daten für jeden Client passend aufbereiten.

[1] Roman Schneider; Volltextverarbeitung; Auf den Index setzen; Information Retrieval mit Oracle Text; iX 9/2009, S. 144

[2] Carsten Czarski; Datenbanken; Einheit der Zeichen; Unicode in Oracle nutzen; iX 1/2011, S. 112

Alle Links: www.ix.de/ix1305136