Start > Kursy on-line > Excel - makra i VBA

Excel - makra i VBA


Obiekt Recordset - odczyt danych

Obiekt typu Recordset przechowuje rezultaty poleceń realizowanych na bazie danych, z którą się łączymy. Co do zasady, w języku VBA, wszystkie operacje wykonywane na bazie danych są wykonywane "online" w trakcie trwania połączenia z bazą (czyli mówiąc wprost: po użyciu metody .Open i przed .Close). Nie mamy do dyspozycji obiektów, które potrafią np. odczytać i przechować zawartość, w celu jej edycji i późniejszego zapisania z powrotem do bazy: wszelkie operacje odbywają się w czasie rzeczywistym.
Obiektu Recordset używamy najczęściej wówczas, kiedy musimy z bazy danych odczytać określone informacje.

Najczęstszym przypadkiem wykorzystania tego obiektu będzie odczyt rekordów pobranych z bazy danych w wyniku użycia instrukcji SQL typu SELECT. Załóżmy, że chcielibyśmy z serwera SQL Server z bazy danych o nazwie "dane_import" wygenerować zawartość tabeli o tej samej nazwie. Polecenie SQL miałoby taką postać:

SELECT * FROM dane_import



To samo polecenie przełożone na język VBA pod Excel musielibyśmy przekazać używając metody .Open dla wcześniej zdefiniowanego obiektu typu Recordset. Całość wyglądałaby następująco:


Sub pobieranie_danych()

'definiujemy odpowiednie zmienne ADODB
Dim cn As ADODB.Connection
Dim rst As ADODB.Recordset

'tworzymy rerefencje do obiektów:
Set cn = New ADODB.Connection
Set rst = New ADODB.Recordset

'łączymy się ze źródełm danych:
cn.Open "Provider=SQLOLEDB;Data Source='10.10.10.1';Initial Catalog='moja_baza';User ID='admin';Password='haslo'"

'wykonujemy polecenie SQL typu SELECT:
rst.Open "SELECT * FROM dane_import", cn

'uwalniamy zasoby komputera
Set rst = Nothing

'zamykamy połączenie z bazą:
cn.Close

End Sub
lub także:


Sub pobieranie_danych()

'definiujemy odpowiednie zmienne ADODB
Dim cn As ADODB.Connection
Dim rst As ADODB.Recordset

'tworzymy rerefencje do obiektów:
Set cn = New ADODB.Connection
Set rst = New ADODB.Recordset

'łączymy się ze źródełm danych:
cn.Open "Provider=SQLOLEDB;Data Source='10.10.10.1';Initial Catalog='moja_baza';User ID='admin';Password='haslo'"

'wykonujemy polecenie SQL typu SELECT:
rst.Open "dane_import", cn

'uwalniamy zasoby komputera
Set rst = Nothing

'zamykamy połączenie z bazą:
cn.Close

End Sub
Zauważmy, że metoda .Open zawiera argumenty, które definiują: pierwszy - jakie jest źródło danych, drugi - połączenie, jakie ma być użyte do pobrania danych. W podanym połączeniu mamy już i adres serwera i nazwę bazy danych, w wyniku czego nie musimy tych elementów podawać ponownie. Jeśli natomiast chodzi o źródło danych, to może nim być zarówno polecenie SQL (pierwszy przykład z powyższych), jak i po prostu tabela bazy danych (drugi przykład z powyższych) - bez podawania całego sformułowania. Źródłem może być także tzw. "procedura składowana" oraz polecenia inne niż służące wybieraniu. Warto jednak pamiętać, iż do uruchamiania poleceń, w wyniku których dokonywane są operacje na bazie danych, ale nie są zwracane dane, lepsze są inne metody języka VBA - powiemy sobie o nich w następnym rozdziale.

Uruchomienie powyższych poleceń spowoduje otwarcie połączenia z bazą danych, następnie uruchomienie i załadowanie do obiektu "rst" wyników zapytania SELECT. Wynikiem tego zapytania jest cała tabela, która wygląda następująco (użyjemy zrzutu ekranu z programu "SQL Server Management Studio Express"):




Pokazujemy ją, aby łatwiej było nam zrozumieć kolejne kroki, omawiające jak z obiektu typu Recordset wyciągnąć konkretne dane. Zmienna "rst" po wykonaniu powyższych poleceń zawiera właśnie taki zestaw informacji, jak przedstawiony na powyższym obrazie. Gdybyśmy chcieli ograniczyć zestaw danych, musielibyśmy rozszerzyć polecenie SELECT o klauzulę WHERE. Innymi słowy, aby ograniczyć liczbę wierszy (rekordów), które zostaną zwrócone w wyniku wykonania instrukcji SELECT musimy zmodyfikować polecenie SQL, gdyż później operacje wyszukiwania w obiekcie typu Recordset są o wiele bardziej skomplikowane, a przede wszystkim wymagają o wiele większej ilości zasobów lokalnego komputera (ponieważ to on musiałby przeglądać kolejno uzyskane rekordy, zamiast tak jak być powinno przekazać odpowiednie polecenia do bazy danych). Przykładowo, w sytuacji gdybyśmy chcieli wybrać z powyższej tabeli jedynie rekordy, które dotyczą produktu "Produkt 1" nasze polecenie wyglądałoby następująco:

SELECT * FROM dane_import WHERE produkt="Produkt 1"



a po przełożeniu na język VBA, nasze dotychczasowe polecenie, zmieniłoby kształt na następujący (przy wszystkich pozostałych elementach kodu programu bez zmian - od tego miejsca do końca rozdziału modyfikujemy tylko fragment kodu zawarty pomiędzy komentarzami: "wykonujemy polecenie SQL typu SELECT:" oraz "uwalniamy zasoby komputera:"):


'wykonujemy polecenie SQL typu SELECT:
rst.Open "SELECT * FROM dane_import WHERE produkt='Produkt 1'", cn
Dodatkowo moglibyśmy sparametryzować nasze zapytanie w kodzie poprzez fakt, iż np. nazwa produktu, która będzie ograniczała zestaw pobieranych z bazy danych informacji będzie zawarta w komórce "A1" arkusza "Arkusz1":


'wykonujemy polecenie SQL typu SELECT:
rst.Open "SELECT * FROM dane_import WHERE produkt='" & Sheets("Arkusz 1").Cells(1, 1) & "'", cn
Zauważmy, że cała ta operacja sprowadza się do nieznacznej modyfikacji polecenia tak, jak można to zrobić ze zwykłymi łańcuchami znaków w kodzie VBA (czyli łącząc odwołania do komórek z tekstem znakami "&", używając tekstu w cudzysłowie). Jedyne, co jest najważniejsze i gdzie łatwo popełnić błąd to to, że musimy pamiętać, iż w przypadku kiedy argument klauzuli WHERE polecenia SELECT odwołuje się do pola i charakterze tekstowym (a tak jest, bo nazwy produktów w naszej tabeli są tekstem) to musimy łańcuch polecenia przygotować tak, aby odwołanie do komórki było "objęte" pojedynczym cudzysłowem. Stąd wynika obecność tego znaku po symbolu "=" oraz na końcu. W przypadku kiedy jednak nasz argument byłby wartością liczbową, znak cudzysłowu nie powinien być użyty, czyli odwołanie lub wpis wartości tego argumentu byłby wprost:


'wykonujemy polecenie SQL typu SELECT:
rst.Open "SELECT * FROM dane_import WHERE miesiac=1", cn
i przy użyciu odwołania do komórki odpowiednio:


'wykonujemy polecenie SQL typu SELECT:
rst.Open "SELECT * FROM dane_import WHERE miesiac=" & Sheets("Arkusz 1").Cells(1, 1), cn
Teraz pozostało dowiedzieć się jak wybrać konkretne informacje z uzyskanej z bazy danych listy. Mamy dwie możliwości, które kolejno omówimy:
W przypadku, gdy chcemy przechodzić kolejno przez wszystkie rekordy, używamy pętli Do"Loop:


'wykonujemy polecenie SQL typu SELECT:
rst.Open "SELECT * FROM dane_import", cn

licznik = 1

rst.MoveFirst

Do Until rst.EOF = True

	Cells(licznik, 1) = rst("nazwa_klienta").Value
	Cells(licznik, 2) = rst("produkt").Value
	Cells(licznik, 3) = rst("rok").Value
	Cells(licznik, 4) = rst("miesiac").Value
	Cells(licznik, 5) = rst("ilosc").Value
	Cells(licznik, 6) = rst("cena").Value
	Cells(licznik, 7) = rst("wartosc").Value

	licznik = licznik + 1
	rst.MoveNext
	
Loop
Wykorzystaliśmy w tej pętli właściwość obiektu typu Recordset o nazwie .EOF - która przyjmuje wartości "True" lub "False" oznacza po prostu czy aktualnie znajdujemy się na ostatniej pozycji danego zestawu danych czy też nie, przemierzając go przy użyciu metody .MoveNext (po uprzednim ustawieniu się na pierwszym metodą .MoveFirst). Innymi słowy pętla od pozycji 1 do ostatniej (bez względu na ich ilość) przechodzi przez kolejne rekordy (można rozumieć je jako wiersze) i kolejne kolumny (używając polecenia rst("nazwa kolumny").Value wpisuje te elementy do kolejnych wierszy i kolumn. W miejscu, które odwołuje się do kolumn, można też użyć indeksu (numeru kolumny w pobieranej tabeli) począwszy od 0. Wówczas nasza pętla przyjęłaby prostszą postać (pod warunkiem, że chcemy "przepisać" całą jej zawartość):


'wykonujemy polecenie SQL typu SELECT:
rst.Open "SELECT * FROM dane_import", cn

licznik = 1

rst.MoveFirst

Do Until rst.EOF = True

	For licznik2 = 0 To 6

		Cells(licznik, licznik2 + 1) = rst(licznik2).Value
	
	Next licznik2

	licznik = licznik + 1
	rst.MoveNext
	
Loop
Drugą możliwością jest po prostu "wrzucenie" całej zawartości do arkusza pliku Excel, która umieszcza całą zawartość obiektu Recordset począwszy od podanego miejsca. Znacząco skraca czas oczekiwania na wyniki, gdyż wyeliminowana jest tu w ogóle konieczność zastosowania jakiejkolwiek pętli:


'wykonujemy polecenie SQL typu SELECT:
rst.Open "SELECT * FROM dane_import", cn

Sheets("Arkusz 1").Cells(1, 1).CopyFromRecordset rst
Jak widać, użycie jest banalne - jedną linijką spowodowaliśmy wklejenie tej samej zawartości co wcześniej, począwszy od komórki A1 (czyli tak jak wcześniej).
Opcjonalnie możemy podać po nazwie obiektu (u nas "rst") maksymalną liczbę wierszy i kolumn, jaka może być użyta - wówczas wyniki wklejane do arkusza zostaną zawężone.