Pracując z bazami danych uświadamiamy sobie w pewnym momencie, że pewne operacje powtarzają się lub wręcz są identyczne. Dużym ułatwieniem w takim wypadku może być parametryzacja zapytań przesyłanych do bazy zarówno tych stricte SQL-owych jak i procedur składowanych po stronie serwera.
W klasycznym podejściu do sprawy zapytanie SQL-owe jest sklejane w stringu tekstowym kilku elementów oraz zmiennych reprezentujących parametry. Sporym problemem może być tutaj korzystanie z parametrów niebędących tekstem. Niejawna konwersja np. daty do tekstu po stronie serwera może doprowadzić do nieoczekiwanych efektów np. błędnego wybierania daty. Pośrednim rozwiązaniem tego problemu może być jawna konwersja, lecz ta skutecznie wydłuża kod SQL i czyni go zdecydowanie mniej przejrzystym.
Sub test_00()
Dim aConn As ADODB.Connection
Dim aRs As ADODB.Recordset
Dim sSql As String
Dim sPar(1) As String
Set aConn = New ADODB.Connection
sSql = "SELECT * from sys.objects where name = "
sPar(0) = Array("sysftinds", "sysprivs")
With aConn
.ConnectionString = "Provider=SQLNCLI10;Server=.\SQLEXPRESS;Database=master;Integrated Security=SSPI;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;"
.Open
Set aRs = .Execute(sSql & Chr(39) & sPar(0) & Chr(39))
Debug.Print aRs.Fields(0).Value
Set aRs = .Execute(sSql & Chr(39) & sPar(1) & Chr(39))
Debug.Print aRs.Fields(0).Value
.Close
End With
Set aConn = Nothing
Set aRs = Nothing
End SubSytuację taka możemy rozwiązać np. za pomocą parametryzowanego SQL-a i obiektu Command. Operacja parametryzacji polega w zasadzie na wstawieniu znaków zapytania w odpowiednie miejsca zapytania a następnie dodaniu parametrów w dokładnie takiej samej kolejności jak wcześniej dodane znaki zapytania. Może wydawać się to dłuższe na pierwszy rzut oka, lecz całość jest zdecydowanie prostsza już przy kolejnym wywołaniu takiego zapytania. Rola programisty ogranicza się wtedy tylko do nadania nowych wartości stworzonym parametrom i ponownym wywołaniu.Sub test_01()
Dim aConn As ADODB.Connection
Dim aComm As ADODB.Command
Dim aRs As ADODB.Recordset
Set aConn = New ADODB.Connection
With aConn
.ConnectionString = "Provider=SQLNCLI10;Server=.\SQLEXPRESS;Database=master;Integrated Security=SSPI;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;"
.Open
Set aComm = New ADODB.Command
With aComm
Set .ActiveConnection = aConn
.CommandText = "SELECT * from sys.objects where name = ? "
.CommandType = adCmdText
.Prepared = True
.Parameters.Append .CreateParameter("1", adVarChar, adParamInput, 200)
.Parameters("1").Value = "sysftinds"
Set aRs = .Execute
Debug.Print aRs.Fields(0).Value
.Parameters("1").Value = "sysprivs"
Set aRs = .Execute
Debug.Print aRs.Fields(0).Value
End With
.Close
End With
Set aConn = Nothing
Set aComm = Nothing
Set aRs = Nothing
End SubW przypadku korzystania z procedur sytuacja jest trochę inna. Istnieje Możliwość wykonania procedury za pomocą samego tylko stringa SQL-owego, wykorzystamy w takim przypadku komendę EXEC, zaś parametry możemy przekazać w postaci kolejnych elementów po nazwie procedury
EXEC usp_test 'sysprivs'
Możemy też przypisać konkretne wartości do wybranych parametrów
EXEC usp_test @name = 'sysprivs'
Rozwiązanie takie ma spore ograniczenia, np. przekazanie parametru będącego datą jest dosyć skomplikowane i może być obarczone błędem konwersji niejawnej oraz nie mamy dostępu do parametrów typu OUTPUT.
Wykonanie tej samej procedury za pomocą obiektu Command jest stosunkowo proste i wymaga w zasadzie tylko ustawienia odpowiedniego typu wysyłanej komendy. Dodatkowym plusem jest fakt że kolekcja parametrów jest tworzona na podstawie informacji pobranych z serwera (o ile mamy do tego uprawnienia, więcej informacji znajduje się w tym poście). Parametry mogą przekazywać dane w obu kierunkach – zależy to tylko od ich zdefiniowania na serwerze.
Sub test_02()
Dim aConn As ADODB.Connection
Dim aComm As ADODB.Command
Dim aRs As ADODB.Recordset
Set aConn = New ADODB.Connection
With aConn
.ConnectionString = "Provider=SQLNCLI10;Server=.\SQLEXPRESS;Database=master;Integrated Security=SSPI;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;"
.Open
Set aComm = New ADODB.Command
With aComm
Set .ActiveConnection = aConn
.CommandType = adCmdStoredProc
.CommandText = "usp_test"
.Parameters("@name").Value = "sysftinds"
Set aRs = .Execute
Debug.Print aRs.Fields(0).Value
Debug.Print .Parameters("@name_count").Value
.Parameters("@name").Value = "sysprivs"
Set aRs = .Execute
Debug.Print aRs.Fields(0).Value
Debug.Print .Parameters("@name_count").Value
End With
.Close
End With
Set aConn = Nothing
Set aComm = Nothing
Set aRs = Nothing
End SubKod procedury SQL
CREATE PROCEDURE usp_test @name nvarchar(200) , @name_count int = Null OUTPUT AS BEGIN SET NOCOUNT ON; set @name_count = (select COUNT(*) from sys.objects where name = @name) SELECT * from sys.objects where name = @name ENDUwaga: Istotnym szczegółem jest kolejność ustawiania parametrów tak aby kolekcja
parametrów utworzyła się poprawnie. Tak jak w przykładzie pierwsze podpinamy
obiekt połączenia, następnie ustalamy typ zapytania na adCmdStoredProc i dopiero na
końcu ustawiamy nazwę procedury w jako parametr CommandText
Opisane tutaj metody działają z większością baz danych. osobiście sprawdzałem poprawność pracy z MySQL, MSSQL i Access
Konkluzją tego postu może być to, że sklejanie SQL-a nie jest jedyną drogą do dynamizacji zapytań. Dokładnie ten sam efekt nawet z większymi możliwościami uzyskamy za pomocą parametryzacji zapytań.
ostatni przypadek szczegolnie szeroko stosuje w kodzie ktory produkuje
OdpowiedzUsuńnie wiedzialem ze da sie tak latwo przypisac wartosc dla parametru
.Parameters("@name").Value = "sysftinds"
wydaje mi sie ze zawsze kozystałem z
.Parameters.Append .CreateParameter("1", adVarChar, adParamInput, 200)
dobrze wiedziec
dzieki
ps. Dobre tematy poruszasz... tak trzymac
Tomek