Prosty przykład wykorzystania iloczynu kartezjańskiego do uzyskania wszystkich możliwych kombinacji ze zbioru danych.
VBA Mania
Non sunt multiplicanda entia sine necessitate
czwartek, 31 maja 2012
niedziela, 2 października 2011
Szybkie sprawdzenie czy czy istnieje tabela o podanej nazwie
ADODB daje nam szereg możliwości. Jedną z nich jest możliwość pobrania informacji o strukturze bazy do której się podłączyliśmy. Przypadkiem szczególnym takich baz są bazy plikowe czyli popularne pliki mdb i accdb. Przypadkiem jeszcze bardziej szczególnym zaś są pliki Excel-a które można traktować jak pliki bazodanowe.
Po podłączeniu do do tkiego pliku wystarczy uruchomić jedną metodę aby uzyskać pełen komplet informacji na temat tego zo znajduje się w środku a co najważniejsze nie musimy takiego pliku otwierać za pomocą Excel-a co mogło by być naprawdę czasochłonne.
Metoda o której mówię to OpenSchema, zaś parametr odpowiadający za pobranie informacji o tabelach to: adSchemaTables.
Przykładowy skrypt wykorzystujący ta metodę:
Przykładowe wykorzystanie
Uzyskujemy w ten sposób informację o tym czy dany arkusz istnieje w bazie danych czy też nie.
Po podłączeniu do do tkiego pliku wystarczy uruchomić jedną metodę aby uzyskać pełen komplet informacji na temat tego zo znajduje się w środku a co najważniejsze nie musimy takiego pliku otwierać za pomocą Excel-a co mogło by być naprawdę czasochłonne.
Metoda o której mówię to OpenSchema, zaś parametr odpowiadający za pobranie informacji o tabelach to: adSchemaTables.
Przykładowy skrypt wykorzystujący ta metodę:
Function GetTablesFromDatabase(Plik As String, Tabela As String) As Boolean
Dim aRs As ADODB.Recordset
Dim aConn As ADODB.Connection
Dim sConn As String
Dim e As Long
Dim ext As String
e = InStrRev(Plik, ".")
ext = Right(Plik, Len(Plik) - e)
Select Case ext
Case "xls"
sConn = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" & Plik & "; Extended Properties =""Excel 8.0;HDR=Yes;IMEX=1"";"
Case "xlsx"
sConn = "Provider =Microsoft.ACE.OLEDB.12.0; Data Source =" & Plik & "; Extended Properties =""Excel 12.0 Xml;HDR=YES"";"
Case "mdb"
sConn = "Provider =Microsoft.Jet.OLEDB.4.0; Data Source =" & Plik & " ; User Id =admin; Password =;"
Case "accdb"
sConn = "Provider=Microsoft.ACE.OLEDB.12.0; Data Source =" & Plik & ";"
End Select
On Error GoTo ERR_Handler:
Set aConn = New ADODB.Connection
With aConn
.Mode = adModeShareDenyNone
.CursorLocation = adUseServer
.ConnectionString = sConn
.Open
Set aRs = aConn.OpenSchema(adSchemaTables)
aRs.MoveFirst
aRs.Filter = "TABLE_NAME='" & Tabela & "'"
Do While Not aRs.EOF
If aRs.Fields("TABLE_NAME").Value = Tabela Then
GetTablesFromDatabase = True
Exit Do
End If
aRs.MoveNext
Loop
.Close
End With
Exit Function
ERR_Handler:
MsgBox Err.Description
If aConn.State > 0 Then
aConn.Close
End If
End FunctionPrzykładowe wykorzystanie
Sub test()
Debug.Print GetTablesFromDatabase("E:\Dane\user\Moje Dokumenty\zeszyt1.xls", "Arkusz1$")
End SubUzyskujemy w ten sposób informację o tym czy dany arkusz istnieje w bazie danych czy też nie.
Zrzut danych z bazy do pliku tekstowego
Nieraz stajemy przed wyzwaniem jak sobie poradzić w przypadku braku jakiegoś narzędzia na naszym komputerze. Z doświadczenia wiem że w korporacjach komputery są dosyć rygorystycznie ograniczane pod kątem możliwości instalacji aplikacji, co może niestety dosyć utrudnić życie. Dlatego też trzeba często kombinować jak tu sobie poradzić w takiej ekstremalnej sytuacji. Dobrym przykładem moze być zrzut danych z bazy do pliku tekstowego. Do wielu baz danych są dostarczane odpowiednie narzędzia jak np. BCP.EXE albo SQLCMD.EXE do MSSQL-a. Problem w tym że trzeba te narzędzia zainstalować. Rozwiązaniem tego problemu może być prosty skrypt w VBS-e pobierający dane z bazy i zrzucający do pliku. Pozwoliłem sobie coś takiego napisać:
Skrypt ten można uruchomić w następujący sposób:
dostępne są następujące parametry:
/SQL:"select * from tabela" - zapytanie które chcemy uruchomić
/Conn:"DRIVER=SQL Server....." - ciąg połączenia do bazy danych, zaletą tego rozwiązania jest to że możemy pobrać dane z praktycznie dowolnej bazy danych
/Path:"d:\katalog\plik.csv" - ścieżka do pliku w którym chcemy przechowywać wynik. W przypadku gdy nie podamy pliku wynik zostanie przekierowany do strumienia STDOUT
/Charset:"utf-8" - domyślny marametr strony kodowej w której zapiszemy plik. Standardowo jest utf-8, ale można zastosować dowolną stronę kodową obsługiwaną przez ADODB.Stream np. windows-1250
/Header:"Yes" - dodaje wiersz z nagłówkami
/Delimiter:";" - ustala znak podziału poszczególnych kolumn
Mała uwaga: jeżeli chcemy wyłączyć Banner w programie CSCRIPT
Wykonajmy polecenie
Dim aConn, sConn , aRs, sSQL
Dim sPath
Dim oFld, sHeader, bHeader, sContent, sDelimiter
Dim sCharset
dim oArgs, oArg, sArg
dim oStdOut
Const adTypeText = 2
Const adSaveCreateOverWrite = 2
set oArgs=wscript.Arguments
Set oStdOut = WScript.StdOut
sPath = ""
sCharset = "utf-8"
sDelimiter = ";"
bHeader = 0
For Each oArg In oArgs
sArg = fGetParmName(oArg)
select case sArg
case "Sql", "S"
sSQL = fGetParmValue(oArg)
case "Path", "P"
sPath = fGetParmValue(oArg)
case "Conn", "C"
sConn = fGetParmValue(oArg)
case "Charset", "A"
sCharset = fGetParmValue(oArg)
case "Header" , "H"
bHeader = fGetParmValue(oArg)
case "Delimiter", "D"
sDelimiter = fGetParmValue(oArg)
End Select
Next
On Error Resume Next
Err.Clear
Set aConn = CreateObject("ADODB.Connection")
aConn.Open sConn
If Err.Number <> 0 Then call sError
Set aRs = aConn.Execute(sSQL)
If Err.Number <> 0 Then call sError
aRs.MoveFirst
If Err.Number <> 0 Then call sError
if bHeader = "Yes" Then
For Each oFld In aRs.Fields
sHeader = sHeader & oFld.Name & sDelimiter
Next
sHeader = Left(sHeader, Len(sHeader) - 1) & Chr(13) & Chr(10)
End If
sContent = sHeader & aRs.GetString(, , sDelimiter)
If Err.Number <> 0 Then call sError
if sPath<> "" Then
ExportToFile sPath, sContent
Else
oStdOut.Write sContent
end if
aConn.Close
If Err.Number <> 0 Then call sError
set oStdOut = Nothing
Set aRs = Nothing
Set aConn = Nothing
function fGetParmName (sIn)
fGetParmName= left(sIn, InStr(sIn,":") -1 )
if left(fGetParmName,1) ="/" Then fGetParmName = mid(fGetParmName,2)
End Function
function fGetParmValue (sIn)
fGetParmValue= mid(sIn, InStr(sIn,":") + 1 )
End Function
sub sError
Wscript.Echo Err.Description
On Error GoTo 0
Err.Clear
Wscript.Quit
End Sub
sub ExportToFile (sPath, sContent)
Dim aStream 'As ADODB.Stream
Set aStream = CreateObject("ADODB.Stream")
With aStream
.Open
.Type = adTypeText
.Charset = sCharset
If Err.Number <> 0 Then call sError
.Position = 0
.WriteText sContent
If Err.Number <> 0 Then call sError
.SaveToFile sPath, adSaveCreateOverWrite
If Err.Number <> 0 Then call sError
End With
Set aStream = Nothing
End SubSkrypt ten można uruchomić w następujący sposób:
eksport.vbs /Sql:"SELECT * FROM dbo.v_struktura_akt" /Conn:"DRIVER=SQL Server Native Client 10.0;SERVER=MASZYNA;UID=username;Trusted_Connection=Yes;WSID=MASZYNA;DATABASE=baza_danych;LANGUAGE=polski;" /Path:"E:\Roboczy\wynik.csv"
dostępne są następujące parametry:
/SQL:"select * from tabela" - zapytanie które chcemy uruchomić
/Conn:"DRIVER=SQL Server....." - ciąg połączenia do bazy danych, zaletą tego rozwiązania jest to że możemy pobrać dane z praktycznie dowolnej bazy danych
/Path:"d:\katalog\plik.csv" - ścieżka do pliku w którym chcemy przechowywać wynik. W przypadku gdy nie podamy pliku wynik zostanie przekierowany do strumienia STDOUT
/Charset:"utf-8" - domyślny marametr strony kodowej w której zapiszemy plik. Standardowo jest utf-8, ale można zastosować dowolną stronę kodową obsługiwaną przez ADODB.Stream np. windows-1250
/Header:"Yes" - dodaje wiersz z nagłówkami
/Delimiter:";" - ustala znak podziału poszczególnych kolumn
Mała uwaga: jeżeli chcemy wyłączyć Banner w programie CSCRIPT
Microsoft (R) Windows Script Host Version 5.7 Copyright (C) Microsoft Corporation. All rights reserved.
Wykonajmy polecenie
cscript //NoLogo //S
niedziela, 1 maja 2011
Automatyczne łączenie aplikacji i bibliotek w jeden plik podczas kompilacji
Zastanawiałem się w jaki sposób mógł bym sprawić żeby mój projekt odwołujący się do kilku bibliotek zewnętrznych nie był zbieraniną plików a jednym wygodnym do użycia plikiem. Głównym motywatorem takiego działania jest fakt że to aplikacji przeznaczona do pracy z linii poleceń.
Żeby wykonać scalić wszystkie pliki w jedną całość skorzystałem z narzędzia ILMerge.exe dostępnego na stronach Microsoft. Po ściągnięciu i zainstalowaniu możemy korzystać z tego narzędzia standardowo za pomocą linii poleceń i np. wykonać komendę:
Dzięki takiej operacji z trzech plików robi nam się jeden, ale tu uwaga - parametr /target:exe wskazuje na to że plik wynikowy będzie uruchamiany z linii poleceń, można też użyć opcji /target:winexe - dzięki czemu stworzymy aplikację okienkowa lub /target:library - dzięki czemu stworzymy bibliotekę.
Oczywiście takie łączenie da nam jak najbardziej prawidłowy plik, lecz na dłuższą metę nie jest to wygodne. Rozwiązaniem było by takie zmodyfikowanie naszego projektu aby łączenie nastąpiło automatycznie po skomplikowaniu projektu. Taką możliwość możemy uzyskać dzięki ręcznej modyfikacji pliku projektu *.vbproj polegającej na dodaniu następującej sekcji:
http://pastebin.com/L1eGGKYP
Taka konstrukcja sprawi że po wykonaniu opeacjii Build zostanie wykonana komenda ILMerge z parametrami. Dodatkowo dodałem parametr /targetplatform:v4,C:\Windows\Microsoft.NET\Framework\v4.0.30319 sprawiający że ILMerge bedzie działał z .NET w wersji 4.0
Dodanie takiej sekcji działa również w pliku projektu stworzonego w wersji Visual Studio Express .
Żeby wykonać scalić wszystkie pliki w jedną całość skorzystałem z narzędzia ILMerge.exe dostępnego na stronach Microsoft. Po ściągnięciu i zainstalowaniu możemy korzystać z tego narzędzia standardowo za pomocą linii poleceń i np. wykonać komendę:
ilmerge /target:exe /out:Aplikacja.exe
Program.exe ClassLibrary1.dll ClassLibrary2.dllDzięki takiej operacji z trzech plików robi nam się jeden, ale tu uwaga - parametr /target:exe wskazuje na to że plik wynikowy będzie uruchamiany z linii poleceń, można też użyć opcji /target:winexe - dzięki czemu stworzymy aplikację okienkowa lub /target:library - dzięki czemu stworzymy bibliotekę.
Oczywiście takie łączenie da nam jak najbardziej prawidłowy plik, lecz na dłuższą metę nie jest to wygodne. Rozwiązaniem było by takie zmodyfikowanie naszego projektu aby łączenie nastąpiło automatycznie po skomplikowaniu projektu. Taką możliwość możemy uzyskać dzięki ręcznej modyfikacji pliku projektu *.vbproj polegającej na dodaniu następującej sekcji:
http://pastebin.com/L1eGGKYP
Taka konstrukcja sprawi że po wykonaniu opeacjii Build zostanie wykonana komenda ILMerge z parametrami. Dodatkowo dodałem parametr /targetplatform:v4,C:\Windows\Microsoft.NET\Framework\v4.0.30319 sprawiający że ILMerge bedzie działał z .NET w wersji 4.0
Dodanie takiej sekcji działa również w pliku projektu stworzonego w wersji Visual Studio Express .
wtorek, 19 kwietnia 2011
Wielokrotny wybór w dialekcie JET SQL (Access)
Często zdarza mi się korzystać z konstrukcji IIF(warunek,wartość dla prawda,wartość dla fałsz) z pozycji kwerend w Access-e. Taka przykładowa kwerenda mogła by wyglądać np. tak:
Problem zaczyna się w momencie gdy musimy sprawdzić kilka niezależnych warunków i na podstawie takiego wyboru określić wynik końcowy. Oczywiście można zagnieżdżać IIF-y, ale to nie jest ani ładne, ani czytelne a tym bardziej wygodne. Idealnym rozwiązaniem było by zastosowanie CASE WHEN .... THEN .... ELSE END, ale JET SQL nie udostępnia nam bezpośrednio tego typu rozwiązania. Daje nam za to coś podobnego i działającego de fakto tak samo. jest to instrukcja SWITCH. Nasz wcześniejszy przykład możemy sobie rozbudować np. tak
W tym momencie jak widzimy mamy kilka opcji bez zbędnego komplikowania, ale to nie koniec możliwości ;)
t2.pole_w in ('A','B','C') - to klasyczne sprawdzenie czy element jest w podanym zbiorze
1=1 - to ostatni warunek logiczny będący zawsze prawdą i wykonany w momencie gdy którykolwiek z wcześniejszych nie został uwzględniony.
I tu małą uwaga: jeżeli SWITCH natrafi na kryterium które jest spełnione to zwraca wartość i kończy sprawdzanie.
Jako ciekawostkę podam że w instrukcji SWITCH można jako zbiór danych podzapytanie np.
t2.pole_w in (SELECT KLUCZ FROM SLOWNIK)
a jak znam życie to pewnie można też dać podzapytanie skorelowane, ale sprawdzenie tego pozostawiam już czytelnikom
SELECT t1.pole, IIF(t2.pole_w = 'TAK', 1,0) as FLAGA FROM TABELA_1 as t1 join TABELA_2 as t2 on t1.id = t2.id
Problem zaczyna się w momencie gdy musimy sprawdzić kilka niezależnych warunków i na podstawie takiego wyboru określić wynik końcowy. Oczywiście można zagnieżdżać IIF-y, ale to nie jest ani ładne, ani czytelne a tym bardziej wygodne. Idealnym rozwiązaniem było by zastosowanie CASE WHEN .... THEN .... ELSE END, ale JET SQL nie udostępnia nam bezpośrednio tego typu rozwiązania. Daje nam za to coś podobnego i działającego de fakto tak samo. jest to instrukcja SWITCH. Nasz wcześniejszy przykład możemy sobie rozbudować np. tak
SELECT t1.pole , SWITCH(t2.pole_w = 'TAK', 1, t2.pole_w = 'NIE' ,0, t2.pole_w = 'NIE WIEM', -1, t2.pole_w = 'INNA ODPOWIEDŹ', -2) as FLAGA FROM TABELA_1 as t1 join TABELA_2 as t2 on t1.id = t2.id
W tym momencie jak widzimy mamy kilka opcji bez zbędnego komplikowania, ale to nie koniec możliwości ;)
SELECT t1.pole
, SWITCH(t2.pole_w = 'TAK', 1, t2.pole_w = 'NIE' ,0, t2.pole_w = 'NIE WIEM', -1, t2.pole_w = 'INNA ODPOWIEDŹ', -2, t2.pole_w in ('A','B','C'), 'ABC' , 1=1, 'COKOLWIEK INNEGO') as FLAGA
FROM TABELA_1 as t1 join TABELA_2 as t2 on t1.id = t2.id
t2.pole_w in ('A','B','C') - to klasyczne sprawdzenie czy element jest w podanym zbiorze
1=1 - to ostatni warunek logiczny będący zawsze prawdą i wykonany w momencie gdy którykolwiek z wcześniejszych nie został uwzględniony.
I tu małą uwaga: jeżeli SWITCH natrafi na kryterium które jest spełnione to zwraca wartość i kończy sprawdzanie.
Jako ciekawostkę podam że w instrukcji SWITCH można jako zbiór danych podzapytanie np.
t2.pole_w in (SELECT KLUCZ FROM SLOWNIK)
a jak znam życie to pewnie można też dać podzapytanie skorelowane, ale sprawdzenie tego pozostawiam już czytelnikom
Mapowanie dysku sieciowego z pozycji T-SQL-a
Dziś pokażę w jaki prosty sposób zmapować dysk sieciowy z poziomu MSSQL-a. Jest to szczególnie przydatne w momencie jak chcemy pobrać jakiś plik ze zdalnego serwera zabezpieczonego za pomocą loginu i hasła np. w celu załadowania za pomocą BULK INSERT
jeżeli z jakiegoś powodu nie chcemy angażować dysku sieciowego, to wykorzystamy UNC
xp_cmdshell 'cmd /c IF EXIST W: net use W: /DELETE' xp_cmdshell 'cmd /c net use W: \\maszyna\udzial haslo /USER:maszyna\login /PERSISTENT:YES' BULK INSERT dbo.tabela FROM 'W:\plik.txt' WITH ( FIELDTERMINATOR =';', ROWTERMINATOR ='\n', FIRSTROW = 2, CODEPAGE = 1250 )
jeżeli z jakiegoś powodu nie chcemy angażować dysku sieciowego, to wykorzystamy UNC
xp_cmdshell 'cmd /c IF EXIST \\maszyna\udzial net use \\maszyna\udzial /DELETE' xp_cmdshell 'cmd /c net use W: \\maszyna\udzial haslo /USER:maszyna\login /PERSISTENT:YES' BULK INSERT dbo.tabela FROM '\\maszyna\udzial\plik.txt' WITH ( FIELDTERMINATOR =';', ROWTERMINATOR ='\n', FIRSTROW = 2, CODEPAGE = 1250 )
czwartek, 17 lutego 2011
Synchronizacja tabeli z polem w Excelu
Taka mała magiczna sztuczka dla tych, którzy by chcieli bez użycia kodu VBA zsynchronizować tabelę w Excelu z filtrami umieszczonymi w polach.
Subskrybuj:
Komentarze (Atom)