Index und Zugriff

Aus Wiki des Deutschsprachige Xbaseentwickler e. V.
Zur Navigation springen Zur Suche springen

Datenzugriff - warum?

Daten, die nicht ausgewertet werden können, haben wenig Wert in der Datenverarbeitung. Xbase++ bietet zwei grundsätzliche Zugriffsmethoden an:


in der Reihenfolge der Satzerstellung

Grob vereinfacht kann man eine dBase-Datei als einen Karteikasten betrachten, dessen Karten man in der Reihenfolge, in der sie in den Kasten gelegt wurden, auch lesen kann.

Der entsprechende Wert, nach dem die Datensätze gelesen werden, ist die sogenannte Satznummer. Der erste Satz hat die Satznummer 1, der zweite Satz die Satznummer 2, und so weiter. Die Funktionen in diesem Zusammenhang sind Recno() und LastRec().

Mit diesem Zugriffsmechanismus kann auch auf einen beliebigen Satz zugegriffen werden, wenn seine Satznummer bekannt ist.


in beliebiger (wahlfreier) Folge

Sortieren

Der klassische Weg wäre es, die Datensätze nach bestimmten Kriterien zu sortieren. Bei einer Einzelplatz-Anwendung mag ein solches Verfahren noch akzeptabel sein, wenn die Datenmenge nicht zu gross ist, aber ab einem bestimmten Datenvolumen, oder spätestens wenn wir mit Multithreading oder von mehreren Arbeitsplätzen in einem Netzwerk auf die Daten zugreifen, entfällt der Sort zwangsweise.

Nichtsdestotrotz gibt es heute immer noch einen Sort-Befehl im Sprachumfang von Xbase++.


Über einen Index

Ein Index ist - vereinfacht gesprochen - eine Liste, die einem bestimmten Such- oder Sortierkriterium die Nummer des Datensatzes zuweist, in dem eine Übereinstimmung mit dem Suchkriterium vorhanden ist.

Beispiel (Die Zahl in Klammern gibt die Satznummer an):

Müller  | Peter | (1)
Baumann | Klaus | (2)
Bender  | Erika | (3)
Adeno   | Rita  | (4)

Ein Index über den Nachnamen könnte (grob vereinfacht) so aussehen:

Adeno   | (4)
Baumann | (2)
Bender  | (3)
Müller  | (1)

Ein Programm, dass die Datei nach Kundenname lesen soll, liest den Index und greift dann über die Satznummer auf die eigentliche Datei zu (die DBE vereinfachen diesen Prozess, indem die Suche im Index mit der Positionierung in der Datei verknüpft wird). Wie gesagt, diese Darstellung ist grob vereinfacht.

Während SQL von Hause aus auf solche Zugriffe spezialisiert ist, müssen wir in Xbase++ einen solchen Index immer selbst erzeugen.

Für den Aufbau des Index und die interne Wartung des Index ist die Xbase++-Laufzeitumgebung verantwortlich, so dass wir uns darum nicht kümmern müssen.

Die Wartung der Index-Dateien erfolgt dezentral, d.h. jedes Programm nimmt, abhängig von der Datenänderung, die Änderungen am Index selbst vor (bzw. wird dies durch die DBE vorgenommen).

Diese dezentrale Wartung stellt aber auch ein Problem dar, wenn Änderungen an einer Datei vorgenommen werden, und keiner oder nicht alle Index-Dateien aktiv sind. Die nicht aktiven Index-Dateien bleiben unverändert, was auf des korrupten Index unter anderem zu inkosistenten Sucheregbnissen führen kann.

Index-Arten

NDX

Die ursprünglichen dBase III-Indexdateien hatten die Erweiterung NDX und waren quasi der erste Wurf.

NTX

Clipper brachte mit der NTX-Datei eine verbesserte und robustere Variante auf den Markt, die auch heute noch von Xbase++ unterstützt wird.

Ursprünglich konnte eine NTX-Datei nur einen Index-Begriff unterstützen, dies ist jedoch inzwischen überholt, so dass mit einer NTX-Datei mehrere Index-Begriffe in so genannten Tags verwaltet werden können.

CDX

FoxPro brachte eine Erweitung der Index-Dateien, in dem CDX-Dateien als erste mehr als einen Index-Begriff verwalten konnten, und auch das "starre" Verhältnis von einem Datensatz zu einem Index-Satz aufgebrochen wurde durch die sogenannten "roll-your-own" Index-Dateien.


Struktur einer Index-Datei

Index-Datei

Darunter verstehen wir hier die physische, auf einem beliebigen Medium gespeicherte Datei. Der Name der Datei kann, muss aber nicht dem Namen der zugrundeliegenden Datendatei entsprechen. Viele Programmierer benennen die Index-Datei jedoch so, wie auch die Datendatei heisst.


TAG

Unter Tag (englisch, nicht der deutsche Tag) versteht man eine Kombination aus Index-Begriff und Zugriffsweg.

Eine NTX-Datei als auch eine CDX-Datei können eine theoretisch unbeschränkte Anzahl Tags enthalten kann (wobei das Speichermedium auf der einen Seite und die Perfomance auf der anderen Seite deutliche Grenzen setzen können).


Index-Begriff

Der Index-Begriff beschreibt, aus welchen Feldern und/oder Funktionen sich der Such- bzw. Sortier-Begriff zusammensetzen soll.

Dieser Begriff muss in der Xbase++-Sprache sinnvoll auszuwerten sein.

Beispiel:

Dateiaufbau:
NACHNAME   | CHAR | 30
VORNAME    | CHAR | 30
STRASSE    | CHAR | 30
PLZ        | CHAR | 10
ORT        | CHAR | 40
KUNDNUMMER | NUM  | 10,0
RABATT     | NUM  | 5,2

Der Indexbegriff muss immer durchgehend den gleichen Typ haben. Sinnvollerweise nimmt man eine Konvertierung in String vor.

Gültige Indexbegriffe wären:

NACHNAME+VORNAME
VORNAME+NACHNAME
PLZ+NACHNAME
KUNDNUMMER
Str(KUNDNUMMER)
Left(NACHNAME, 10)
NACHNAME+"PETER"

Ungültige Indexbegriffe wären:

NACHNAME+LAND (Land ist in der Datei nicht definiert)
ORT+KUNDNUMMER (alle Elemente im Index-Begriff müssen den gleichen Typ haben)

Kritische Indexbegriffe wären:

Trim(NACHNAME) (führt zu unterschiedlich langen Indexbegriffen in der Index-Datei)
KUNDNUMMER+RABATT (würde die beiden Werte addieren und die Summe als Indexbegriff verwenden) 

Verwendung von Funktionen in Indexbegriffen:

Wie man dem Beispiel entnehmen kann, ist auch die Verwendung von Funktionen möglich. Standard-Xbase-Funktionen sind immer zulässig, werden jedoch selbst definierte Funktionen (UDF) verwendet, müssen die UDFs in allen Programmen, die den entsprechenden Index verwenden, vorhanden sein.

An dieser Stelle darf der Hinweis nicht fehlen, dass bestimmte Xbase-Funktionen durch eine #PRAGMA-Anweisung eingebunden werden müssen und daher nicht immer zur Verfügung stehen.


Zugriffsweg

Diesen Begriff habe ich aus einem Host-System übernommen. Es beschreibt die Reihenfolge der Schlüssel und die zugeordneten Satznummern, um auf die korrespondieren Datensätze zugreifen zu können.

Die Xbase++-Laufzeitumgebung übernimmt die Verwaltung und Aktualisierung des Zugriffsweges, sofern es nicht ein "roll-your-own" Index ist.


Erstellen einer Index-Datei

Ausgehend von dem obigen Beispiel würde eine Index-Datei so erstellt:

NTX:
INDEX ON NACHNAME TO KUNDEN
In diesem Fall wird eine Index-Date mit dem Namen "KUNDEN" (TO KUNDEN) angelegt;
sowie ein Tag mit dem Namen "KUNDEN" angelegt (implizit, da die TAG Anweisung fehlt);
und der Index-Begriff wird mit "NACHNAME" festgelegt
CDX:
INDEX ON NACHNAME TAG "A1" TO KUNDEN
In diesem Fall wird eine Index-Date mit dem Namen "KUNDEN" (TO KUNDEN) angelegt;
sowie ein Tag mit dem Namen "A1" angelegt;
und der Index-Begriff wird mit "NACHNAME" festgelegt


Zugriff auf indexierte Daten

Neben den unter Datenzugriff genannten Funktionen gibt es noch die Funktion dbSeek(), die ein gezieltes Suchen in einer indexierten Datei erlaubt.

// Annahme: der Index-Begriff ist Upper(NACHNAME), das Tag heisst "A1"
cKey := PadR("MEIER", 30)
lFound := dbSeek(cKey, .F., "A1", .F.)

Betrachten wir die Suchanweisung.

lFound wird mit einem logischen Wert belegt, der .T. ist, wenn ein Datensatz gefunden wurde, und .F., wenn kein passender Datensatz gefunden wurde.

cKey ist der Begriff, der gesucht werden soll. Anstelle einer Variablen kann auch ein Literal verwendet werden. In jedem Fall muss der Typ des Begriffs dem Index-Begriff entsprechen.

.F. besagt, dass wir einen exakten Treffer wollen. (siehe Softseek)

"A1" gibt an, dass der Tag "A1" verwendet werden soll. Es kann auch die Index-Position (bei mehreren aktiven Index-Dateien bzw. Tags) verwendet werden. Ist dieser Parameter leer (nicht angegeben), wird der aktive Index verwendet.

.F. besagt, dass er erste übereinstimmende Datensatz geliefert werden soll. Bei .T. wird der letzte übereinstimmende Datensatz geliefert, wenn es mehrere Datensätze gibt, welche den gleichen Schlüssel haben.


Softseek

Standardmässig wird auf exakte Übereinstimmung gesucht.

Unter den Programm-Schaltern gibt es SET SOFTSEEK, mit dem dieses Verhalten für den jeweiligen Thread gesteuert werden kann.

Der Parameter für Softseek hat Vorrang über dem Programm-Schalter SET SOFTSEEK.

Parameter SOFTSEEK gefunden Found() EoF()
.F. egal ja .T. .F.
.F. egal nein .F. .T.
.T. egal ja .T. .F.
.T. egal nein .F. .F.
NIL ON ja .T. .F.
NIL ON nein .F. .F.
NIL OFF ja .T. .F.
NIL OFF nein .F. .T.

Nach der Positionierung mit dbSeek() kann mittels dbSkip() ab diesem Satz durch den Index manövriert werden.

Index-Umfang

Grundsätzlich umfasst ein Index alle Datensätze der zugeordneten Datei.

Die INDEX Anweisung enthält mögliche Ausdrücke, mit denen der Fokus auf die Datensätze eingeschränkt werden kann, dazu gehören:

FOR
WHILE
NEXT
RECORD
SUBINDEX

So übernimmt NEXT 500 nur den aktuellen Datensatz sowie die nächsten 499 Datensätze in den Index auf.

Index-Attribute

Es existieren zwei Attribute, die Einfluss darauf haben, welche Datensätze in den Index aufgenommen werden und welche nicht:

UNIQUE
CANDIDATE

An dieser Stelle muss erst einmal darauf hingewiesen werden, dass die xBase-Sprache kein Äquivalent zum PRIMARY KEY einer SQL-Tabelle kennt. Die Struktur des verwendeten DBE teilt diese in einen Daten- und einen Indexteil, und das kann fatale Folgen haben, da bei Änderungen zuerst der Datenaspekt bearbeitet wird, und erst danach der Indexaspekt. Das bedeutet, dass der Datenaspekt Vorrang hat, während bei einem SQL-Server durch eine PRIMARY KEY Anweisung der Indexaspekt den Vorrang erhält und darüber entscheidet, ob ein Datensatz angelegt werden kann oder nicht.

Das UNIQUE-Attribute legt fest, dass ein Schlüssel nur einmal in der Indexdatei vorkommen kann. Dies bedeutet im Umkehrschluss, dass ein Schlüssel mehrmals in der Datendatei vorkommen kann.

Hierzu ein einfaches Beispiel einer Kundendatei:

Feld Typ Länge
NAME CHAR 32
NUMMER DEC 10,0

Mit INDEX ON NUMMER TAG A1 TO Kunden UNIQUE wird ein Index mit dem Attribut UNIQUE erzeugt. Wir gehen von einer leeren Datendatei aus. Wir erstellen mit INDEX ON NUMMER TAG A2 TO Kunden einen zweiten Index ohne das UNIQUE Attribut.

Wir fügen nun drei Datensätze zur Datei hinzu:

Name Nummer
Becker 1
Honk 2
Trumm 2

Wenn wir die Daten dann lesen, erhalten wir folgende Ergebnisse:

1. TAG A1

Name Nummer
Becker 1
Honk 2

2. TAG A2

Feld Nummer
Becker 1
Honk 2
Trumm 2

Die Datendatei enthält drei Datensätze, der erste Index (mit UNIQUE Attribut) enthält aber nur zwei Verweise, während der zweite Index (ohne UNIQUE Attribut) für jeden Datensatz einen Verweis enthält.

Betrachten wir das Verhalten der Index-DBE.

1. Datensatz - Erstellen eines Verweis in A1, da kein Datensatz gleichen Schlüssels in der Indexdatei existiert 2. Datensatz - Erstellen eines Verweis in A1, da kein Datensatz gleichen Schlüssels in der Indexdatei existiert 3. Datensatz - es wird kein Verweis in A1 erstellt, da bereits ein Datensatz mit dem gleichen Schlüssel existiert, und das UNIQUE Attribut die Anlage von Verweisen nur dann erlaubt, wenn kein anderer Verweis den gleichen Schlüssel hat.

Die Index-DBE reagiert nur auf das, was die Daten-DBE bereits ausgeführt hat, sie kann also nicht verhindernd eingreifen und die ANLAGE eines Datensatzes verhindern, wenn der Schlüssel bereits existiert.

Die Index-DBE hat während der Arbeit mit einer Datei immer nur den aktuellen Datensatz "im Sichtfeld". Würden wir den zweiten Datensatz löschen, entfernt die Index-DBE den Verweis aus ihrem System. Der dritte Datensatz, der den gleichen Schlüssel hat, bleibt unberücksichtigt, da er nicht im Fokus der Index-DBE ist.

Dieses Verhalten macht auch Sinn, da eine Kontrolle auf "nachrückende" Datensätze gerade bei grossen
Datenbestände eine massive Auswirkung auf die Performance haben würde. Es liegt also in der Aufgabe des
Programmierers, sicherzustellen, dass auch die Daten dem UNIQUE Attribut entsprechen.

Das CANDIDATE Attribut verhält sich so wie das UNIQUE Attribut.

Wenn echte PRIMARY KEYs gebraucht werden, sollte eine Datenspeicherung in einem SQL-Server in Betracht gezogen werden.