Montag, 26. November 2007

Hinzufügen von Spalten in SQL SELECT Befehlen / Adding columns to SQL SELECT statements

Eine häufig auftretende Anforderung bei der Arbeit mit Daten bzw. mit ihrer Datenquelle ist es, in der Lage zu sein 'on the fly' diese um zusätzliche Spalten zu ergänzen. 

Nun, dies ist ziemlich einfach, wenn es um das Hinzufügen einer neuen, leeren Spalte geht. Einfach in der Abfrage die SPACE()-Funktion wie folgt einsetzen:
 
SELECT SPACE(30) AS neuespalte FROM tabellenname

Hinweis: Das Wort 'AS' ist in VFP (oder bei SQL Server) eigentlich nicht notwendig, aber einige SQL Dialekte (->INFORMIX) erwarten es. Ganz abgesehen davon, dass es die Lesbarkeit des SQL-Befehls erhöht. 

Gehen wir also davon aus, dass sich in einer Tabelle namens 'muster' Daten wie die folgenden befinden:
 
cVorname  cNachname   iGanzzahl    nDez1    nDez2
Fritz     Müller              0   123.45  3456.78
Hans      Lüdenscheid         3  1111.65   654.32

Würden wir einen zusammenhängenden Namen benötigen um einen 'vollen Namen' zu erhalten, dann könnten wir folgendes machen:
 
SELECT (ALLTRIM( cVorname ) + [ ] + ALLTRIM( cNachname )) AS cKomplettname FROM muster

Auf den ersten Blick erscheint dies ziemlich logisch. Das Interessante daran ist, wenn eine 'künstliche Spalte' erzeugt wird, VFP auf Basis der existierenden zusammengefügten Spalten eine Spaltendefinition vornimmt. 

Da die Spalten 'cVorname' und 'cNachname' jeweils als C(20) definiert wurden ergibt sich hierdurch ein Feld vom Typ C(41) -> 

Mit anderen Worten: 

20 Zeichen für die erste Spalte (cVorname), eine für das Leerzeichen und weitere 20 Zeichen für die zweite Spalte (cNachname). 

Das ist aber ganz OK so, auch wenn es vielleicht ein klein wenig Platzverschwendung ist, aber dafür stellt dies auch sicher, dass wir keine Daten verlieren. Und wollen wir wirklich nur die minimal benötigte Spaltenbreite nutzen, dann können wir mit der PADR() Funktion den Fux wie folgt dazu bringen:
 
SELECT PADR( ALLTRIM( cVorname ) + [ ] + ALLTRIM( cNachname ), 30) AS Komplettname FROM muster

Wenn wir jedoch zwischen verschiedenen Feldtypen konvertieren müssen (bspw. Numerisch nach Character), dann stossen wir auf ein potentielles Problem, da VFP in diesem Fall nicht wissen kann, wie gross das neue Feld tatsächlich sein muss. Alles was der Fux in diesem Fall machen kann ist, den ersten Wert als Basis heranziehen. 

Solch ein Abfrage sähe wie folgt aus:
 
SELECT TRANSFORM( nDez1 ) AS cWert FROM muster

In diesem Fall erhielten wir für cWert eine definierte Spaltengröße von C(6), mit anderen Worten, die Zahl der Zeichen im ersten Wert der Tabelle. Dies bedeutet natürlich, dass der zweite Wert abgeschnitten würde, da er insgesamt 7 Zeichen enthält. An dieser Stelle wird es nun wichtig sicherzustellen, dass die Resultatgröße dem tatsächlichen Platzbedarf entspricht. Hierfür können wir die Funktion PADR() einsetzen um die Formatierung nach dem Transformieren vorzugeben. 

Dies sieht wie folgt aus:
 
SELECT PADR( TRANSFORM( nDez1 ), 10) AS cWert FROM muster

Aber die Einführung des CAST()-Befehls gibt uns eine Alternative die es uns erlaubt, VFP direkt vorzugeben, in welchem Format das Ergebnis aufgenommen werden soll. 

In VFP9 können wir die Abfrage wie folgt aufbauen:
 
SELECT CAST( ALLTRIM( cVorname ) + [ ] + ALLTRIM( cNachname ) AS CHAR(30)) AS Komplettname FROM muster

oder als Alternative: 

SELECT CAST( nDez1 AS CHAR(10)) AS cWert FROM muster

Wenn wir mit existierenden Spalten arbeiten ist dies auch soweit in Ordnung. Aber was passiert, wenn wir eine neue Spalte auf Basis eines bestimmten Datentyps erzeugen müssen? Bei Zeichenketten ist dies relativ einfach. 

Wir benutzen SPACE() oder auch PADL() um die Spalte zu erzeugen:
 
SELECT *, SPACE(30) AS cNeuertext FROM muster

Ähnlich verhält es sich bei Währungsspalten, die mit "$0" definiert werden, und einem neuen Datum mit Hilfe eines leeren Datumsstrings "{}", einer Dezimalspalte mit einem 0-String und logischen Felder mit einem .F., usw.:
 
SELECT $0 AS yGeldbetrag,
       {} AS dBezahlt,
       00000.00 AS nAusgleich,
       .F. AS lGeloescht
FROM muster

Nachdem dies so schön funktioniert, da könnte man versucht sein eine Integer-Spalte auf diese Art und Weise zu erzeugen: 

SELECT 0 AS nNeueGanzzahl FROM muster

Beim Erstellen einer Integer-Spalte wird deren Wert grundsätzlich mit "0" initialisiert. Aus diesem Grund erscheint es naheliegend, VFP mit "0" den Variablentyp als Integer mitzuteilen.Unglückerlicherweise ist dies aber nicht der Fall. Tatsächlich erzeugt VFP eine Dezimalspalte mit 1 Vorkomma und 0 Nachkommastellen. Als Ergebnis lassen sich dann nur Werte von 0-9 hinterlegen. Nicht wirklich das was gewünscht war. Wie bekommen wir also eine Integer-Spalte? Vor VFP 9 war dies ein wenig trickreich und es gab zwei Wege zum Ziel zu gelangen. Zunächst (und am einfachsten) kann die Spalte mit der benötigten Länge an Vorkommastellen definiert werden: 

SELECT *, 0000000000 AS nNeueGanzzahl FROM muster

Das Ergebnis ist nicht wirklich ein Integerwert sondern eine größere Dezimalspalte (N(10,0)) und das ist unter Umständen etwas problematisch. Um einen echten Integerwert zu erzeugen müssen wir den Umweg über ein Kartesisches Produkt gehen. 

Ein Kartesisches Produkt erhalten wir, wenn eine Abfrage zwei Tabellen ohne spezielle Bedingung miteinander verbindet. Das Ergebnis ist, das jeder Datensatz der ersten Tabelle mit jedem Datensatz der zweiten Tabelle verbunden wird. Die Ergebnismenge entspricht demzufolge der Anzahl der Datensätze der ersten Tabelle multipliziert mit der Anzahl der Datensätze der zweiten Tabelle. Auf diese Art und Weise entstehen sehr schnell sehr grosse Ergebnismengen.
 
SELECT * FROM tabelle1, tabelle2

Bei 100 Datensätzen in der ersten Tabelle und 1000 Datensätzen in der zweiten Tabelle würde dies einen Ergebniscursor mit 100.000 Datensätzen ergeben. 

Das klingt nicht wirklich brauchbar. Also wie soll uns das bei unserem Problem mit der Integer-Spalte helfen? Ganz einfach: Wenn wir einen Cursor mit Namen 'crsDummy' erzeugen, der als einzige Spalte einen Integerwert aufweist, und dann einen leeren Datensatz darin erzeugen und diesen Cursor 'crsDummy' in die FROM-Liste aufnimmt wird die Integerspalte zwangsweise integriert. Als Ergebnis erhalten wir somit einen Cursor, der in jedem Datensatz über eine zusätzliche INTEGER-Spalte verfügt. 

Das Ganze funktioniert wie folgt:
 
CREATE CURSOR dummy ( iNeueGanzzahl I )
INSERT INTO dummy VALUES (0)
SELECT * FROM muster, dummy

Derselbe Trick kann in Verbindung mit MEMO, GENERAL oder auch jeder anderen Art von Spalten mit beliebigen Datentypen verwendet werden. Aber, seit VFP9 benötigen wir diese Vorgehensweise nicht mehr. Wieder einmal hilft uns die CAST()-Funktion. 

Mit ihrer Hilfe können wir beliebige Datentypen erzeugen:
 
SELECT *, CAST( 0 AS INT) AS iNeueGanzzahl FROM muster

Tatsächlich können wir CAST() auch einsetzen, um spezielle Datentypen zu liefern. Für Anwender ist es bspw. irritierend, wenn vom Entwickler DatumZeit-Felder genutzt werden. Gerade in Hinblick auf SQL-Kompatibilitäten besteht ab und an diese Notwendigkeit. Wie so oft stehen im Fux viele Wege zur Verfügung. Aber CAST() ist die sauberste Möglichkeit solche Probleme in den Griff zu bekommen.
 
SELECT CAST( tDatumZeit AS date ) AS dDatum FROM muster

In der VFP9-Hilfe gibt es eine Tabelle in der hinterlegt ist, welche Typkonvertierungen mit CAST() durchgeführt werden können. Bspw. können wir mit CAST() Datumswerte als Character, VarChar, Datetime oder Memo, jedoch nicht als Decimal wandeln. Natürlich ist der übliche Grund für ein zusätzliches Feld zumeist darin begründet, dass wir eine Spalte benötigen, um Information über speziell zu verarbeitende Datensätze zu verwalten. Sei es während der Erfassung zur differenzierten Visualisierung von Informationen oder auch bei der Druckausgabe. Hierbei sollten wir jedoch bedenken, dass SQL-Select üblicherweise schreibgeschützt sind. Bis VFP7 mussten wir hierbei zu einem kleinen Trick greifen. 

Hintergrund ist, dass Cursor tatsächlich als temporäre Tabellen implementiert sind. Wird somit ein Cursor ein zweites Mal geöffnet, dann ist VFP gezwungen eine zweite Tabelle zu erzeugen und diese ist nicht mehr schreibgeschützt. 

Der folgende Code erzeugt eine beschreibbaren Cursor und funktioniert in ALLEN VFP und FP Versionen:
 
SELECT * FROM muster INTO CURSOR temp
USE DBF( [temp] ) AGAIN IN 0 ALIAS cur_readwrite
SELECT cur_readwrite
USE IN temp

Mit der Version 7 des Fux wurde der optionale Parameter READWRITE in den SQL-Sprachumfang von VFP aufgenommen und ermöglicht es uns, Select-Ergebnisse direkt beschreibbar zu machen.
 
SELECT * FROM muster INTO CURSOR temp READWRITE

Und wann immer es geht sollten wir diese Syntax auch nutzen. Nichts desto trotz, ist es immer gut, die alte Variante zu kennen. Wir wissen schliesslich nie, wann alter Code geändert werden muss... :-)

Keine Kommentare:

Kommentar veröffentlichen