version=1.6.0
vdate=22.01.2018
fname=lib_adodb_for_xls.bas
ns=%NAMESPACE%
fpath=/vba/excel
====== [Excel][VBA][SQL] SQL in Excel einsetzen ======
Datenverarbeitung in Excel mittels SQL.
==Version %%version%% - %%vdate%%==
{{%%fname%%|Download %%fname%% (V-%%version%%)}}
Excel ist nett, aber auch unübersichtlich. Vor allem wenn man mit Datenmengen und VBA arbeiten will.
Um Daten anzureichern, in grösseren Mengen zu verarbeiten etc. sehe ich immer wieder Script die Zellenadressen zusammensetzen und dadurch unlesbar sind. Wer weiss beim lesen schon, was die A4 ist?
Nungut, ich komme von der Datenbankseite. Dort arbeite ich mit SQL um die Datan abzufragen, zusammenzusetzen etc.. Zum Glück für mich, ist das alles auch in Excel VBA möglich.
ADODB hiesst das Losungswort.
\\ Ich erkläre hier nicht alle Details. Das WWW gibt gut auskunft. Nein, ich zeige wie ich es anwende
Den Code zu den Funktionen gibts am Schluss
===== Definitionen =====
==== axConnParams ====
Sammlung von steuerparametern. Es werden, ev. in Zukunft noch mehr.
Public Enum axConnParams
axcNone = 0
axcReconnect = 2 ^ 0 'Ein Reconnect wird erzwungen
axcNoHeader = 2 ^ 1 'Die erste Zeile ist keine Kopfzeile. Die Felder werden mit f1, f2...fx angesprochen
End Enum
==== axWriteParams ====
Paramters zum Schreiben der Daten
'/**
Public Enum axWriteParams
axwNone = 2 ^ 0 'Keine umwandlung
axwHeaderRedable = 2 ^ 1 'Titel mit Unterlinien werden aufgetrennt "HAUS_NUMMER" -> "Haus Nummer"
End Enum
===== Initialisieren =====
==== Property connection ====
Als erstes habe ich mir ein Property geschrieben, das eine ADODB-Connection auf das aktuelle Workbook zurückgibt. Da ich mich später nicht darum kümmern will, ob ich die Connection schon geöffnet habe, ist es nach dem Tutorial [[vba:tutorials:cachedobjectforfunction#Funktion mit Cache als Property]] umgesetzt.
'/**
' * Handle dhe adodb-connection to the current workbook
' * @param axConnParams Paramters für die Connection
' * @param String Pfad zur QuellDatei. Standard ist der Pfad des ausführenden Workbooks
' * @return ADODB.Conection
' */
Private Property Get connection(Optional ByVal iConnParams As axConnParams = axcNone, Optional ByVal iFilePath As String = Empty) As Object
==== Function openRs() ====
Der Nächste Schritt ist das Öffnen eines ADODB.Recordsets aus dem aktuellen Worksheet. Dazu brauchen wir natürlich die oben erwähnte Connection
'/**
' * Open a adodb recordset from the current workbook
' * @param String SQL-String
' * @param axConnParams Paramters für die Connection
' * @return ADODB.Recordset
' */
Public Function openRs(ByVal iSql As String, Optional ByVal iConnParams As axConnParams = axcNone) As Object
==== Function writeHeader() ====
Schreibt den Header in eine Zeile
'/**
' * Write the Header of a adodb.recordset
' * @param Worksheet/Range/Address
' * @param Recordset
' * @param axWriteParams
' */
Public Sub writeHeader(ByRef iRange As Variant, ByRef ioRs As Object, Optional ByVal iWriteParams As axWriteParams = axwNone)
==== Function writeFullData() ====
Mit dem einfachen Worksheet.CopyFromRecordset() werden nur die Daten geschrieben. Die folgende Funktion schreibt mittels writeHeader() zuerst die Kopfzeile und dann die Daten
'/**
' * Schreibt ins Excel inkl Header
' * @param Worksheet/Range/Address
' * @param Recordset
' * @param axWriteParams
' */
Public Sub writeFullData(ByRef iRange As Variant, ByRef ioRs As Object, Optional ByVal iWriteParams As axWriteParams = axwNone)
===== Anwenden von openRs() =====
Doch wie greift man mittels SQL jetzt auf die Daten zu?
Es gibt mehrere Varianten um die Daten jetzt als Source in einem SQL anzusprechen
==== Quellen ====
=== Ganzes Worksheet als Tabelle ===
Häufig habe ich von Exports aus anderen Systemen gleich ein Worksheet, das genau einer Tabelle entspricht. Die erste Zeile hat die Titel, die folgenden Zeilen sind die Daten.
Ich kann mit dem SQL gleich auf diese Daten zugriefen. Die Titel sind dann auch im SQL die Namen der Spalten.
Als Tabellennamen wird einfach der Sheet-Name gefolgt von einem $ verwendet.
-- Auflistung der Vornamen aus dem Sheet 'adress_sheet'
SELECT DISTINCT
[firstname]
FROM
[adress_sheet$]
=== Benannter Range als Tablle ===
Ich arbeite gerne mit benannten Ranges. so lassens ich sehr einfach Datenbereiche aus einem Sheet auslesen. Dazu einfach in einem Excelsheet einen Range selektionieren und bennen (Dazugibts diverse anleitungen im Web. zB. [[http://spreadsheets.about.com/od/excelformulas/ss/2011-01-15-excel-range-name-overview.htm|How to Define a Named Range in Excel]]).
Am besten wählt man ein Bereich mit einer Titelzeile.
Für kleinere Mapping-Tabellen, Stammdaten etc. verwende ich jeweils nur ein Sheet und arbeite dann mit Benannten Ranges.
Da der Benannte Range für das ganze Workbook gültigkeit hat, muss auch kein Sheet angegeben werdn
Im SQL kann dann einfach gleich dieser Name als Quelle verwendet werden.
-- Zugriff auf den Range 'cities', der mitten in einem Sheet ist
SELECT
[plz],
[city]
FROM
[cities]
=== Beliebiger Range ===
Man kann auch auf einen beliebigen Bereich auf einem Sheet zugreifen. Ich rate jedoch davon ab. Adressen Hardcodiert - Never.
''sheetName$adress''
SELECT *
FROM [my_mappings$A3:G17]
=== Zusammenfassung der Sourcen ===
* Ganzes Sheet: ''[sheetName$]''
* Benannter Range: ''[rangeName]''
* Beliebiger Range: ''[sheetName$adress]''
==== Zugriff auf die Felder ====
Je nachdem, ob Feldernamen gefunden wurden oder nicht, kann Direkt auf den Feldnamen oder auf die Feldnummer zugegriffen werden.
* Feld Name: ''[feldName]''
* Feld-Nr: ''F#''
==== Beispiel ====
Ich lese die Daten aus dem Sheet ADDRESS und schreibe eine sie in das Sheet TARGET
> Meine Library braucht zwar keine Referenz. Im Beispiel habe ich jedoch den Recordset mit Early Binding gemacht und muss darum eine Referenz auf ''Microsoft ActiveX Data Objects 6.1 Library'' setzen. Ansonsten den rs als Object definieren
Public Sub sqlTest()
Dim rs As ADODB.Recordset
Dim ws As Worksheet
Set rs = openRs("SELECT [id], [vorname] & ' ' & [nachname] AS [name] " &_
"FROM [address$] " & _
"WHERE id BETWEEN 2 AND 3")
Set ws = ActiveWorkbook.Sheets("TARGET")
writeFullData ws.Range("A1"), rs
rs.Close
End Sub
=== Handhabung der Header ===
Bei writeFullData() und beim writeHeader() kann man den Paramter iWriteParams auf axwHeaderRedable setzen. Ich zeige hier mal schnell noch den Unterschied.
Die Tabelle Hat die 2 Felder
ID | SECURITY_TPE | SECURITY_ID
-------------------------------
1 | T3 | AX3456Z
2 | T3 | AX4564R
Mit dem Standard: iWriteParams = axwNone
Die Titelzeile belibt unverändert
writeFullData Sheets("trg").Range("A1"), openRs("select * from [src$]")ID | SECURITY_TPE | SECURITY_ID
-------------------------------
1 | T3 | AX3456Z
2 | T3 | AX4564R
Mit axwHeaderRedable wird die Titelzeile in eine lesbarere Form gebracht
writeFullData Sheets("trg").Range("A1"), openRs("select * from [src$]"), axwHeaderRedableId | Security Tpe | Security Id
-------------------------------
1 | T3 | AX3456Z
2 | T3 | AX4564R
===== Code =====
Und hier noch der Code zu den oben bechriebenen Funktionen