Używanie dużych parametrów dla procedury składowanej Microsoft SQL z DAO
Jak wielu z Was już wie, zespół SQL Server ogłosił wycofanie OLEDB dla silnika bazy danych SQL Server (Czytaj:nie możemy używać ADO, ponieważ ADO używa OLEDB). Ponadto SQL Azure nie obsługuje oficjalnie ADO, chociaż nadal można go uniknąć, korzystając z SQL Server Native Client. Jednak nowy sterownik ODBC 13.1 zawiera wiele funkcji, które nie będą dostępne w kliencie SQL Server Native Client, a może być ich więcej.
Podsumowując:musimy pracować z czystym DAO. Istnieje już wiele elementów głosowych użytkownika dotyczących tematu Access / ODBC lub Access / SQL Server… na przykład:
Łącznik danych SQL Server
Lepsza integracja z SQL Server
Lepsza integracja z SQL Azure
Proszę, aby Access mógł obsługiwać więcej typów danych, które są powszechnie używane w bazach danych serwera
Popraw dostęp Klient ODBC
(Jeśli nie głosowałeś ani nie odwiedzałeś access.uservoice.com, wejdź tam i zagłosuj, jeśli chcesz, aby zespół Access zaimplementował Twoją ulubioną funkcję)
Ale nawet jeśli Microsoft ulepszy DAO w następnej wersji, nadal mamy do czynienia z istniejącymi aplikacjami naszych klientów. Rozważaliśmy użycie ODBC zamiast dostawcy OLEDB (MSDASQL), ale czuliśmy, że jest to podobne do siadania okrakiem na kucyku na umierającym koniu. To może działać, ale może po prostu umrzeć w niewielkiej odległości.
W większości przypadków zapytanie przekazujące wykona to, co musimy zrobić, i łatwo jest połączyć funkcję naśladującą funkcjonalność ADO za pomocą zapytania przekazującego DAO. Ale jest jedna znacząca luka, której nie da się łatwo usunąć — duże parametry procedur składowanych. Jak pisałem wcześniej, czasami używamy parametru XML jako sposobu na przekazywanie dużej ilości danych, co jest znacznie szybsze niż faktyczne wstawianie przez program Access wszystkich danych jeden po drugim. Jednak zapytanie DAO jest ograniczone do około 64K znaków dla polecenia SQL, aw praktyce może być jeszcze mniej. Potrzebowaliśmy sposobu na przekazywanie parametrów, które mogą być większe niż 64 tys. znaków, więc musieliśmy pomyśleć o obejściu.
Wpisz tabelę tblExecuteStoredProcedure
Wybrane przez nas podejście polegało na użyciu tabeli, ponieważ gdy używamy nowszych sterowników ODBC lub SQL Server Native Client, DAO jest w stanie z łatwością obsłużyć dużą ilość tekstu (aka Memo) poprzez wstawienie go bezpośrednio do tabeli. Dlatego, aby wykonać duży parametr XML, napiszemy procedurę do wykonania i jej parametr w tabeli, a następnie pozwolimy wyzwalaczowi ją przejąć. Oto skrypt tworzenia tabeli:
CREATE TABLE dbo.tblExecuteStoredProcedure (
ExecuteID int NOT NULL IDENTITY
CONSTRAINT PK_tblExecuteStoredProcedure PRIMARY KEY CLUSTERED,
ProcedureSchema sysname NOT NULL
CONSTRAINT DF_tblExecuteStoredProcedure DEFAULT 'dbo',
ProcedureName sysname NOT NULL,
Parameter1 nvarchar(MAX) NULL,
Parameter2 nvarchar(MAX) NULL,
Parameter3 nvarchar(MAX) NULL,
Parameter4 nvarchar(MAX) NULL,
Parameter5 nvarchar(MAX) NULL,
Parameter6 nvarchar(MAX) NULL,
Parameter7 nvarchar(MAX) NULL,
Parameter8 nvarchar(MAX) NULL,
Parameter9 nvarchar(MAX) NULL,
Parameter10 nvarchar(MAX) NULL,
RV rowversion NOT NULL
);
Oczywiście nie zamierzamy używać tego jak prawdziwego stołu. Ustawiamy również arbitralnie 10 parametrów, mimo że procedura składowana może mieć ich znacznie więcej. Z naszego doświadczenia wynika jednak, że dość rzadko zdarza się, aby było ich znacznie więcej niż 10, zwłaszcza gdy mamy do czynienia z parametrami XML. Sam stół nie byłby zbyt przydatny. Potrzebujemy wyzwalacza:
CREATE TRIGGER dbo.tblExecuteStoredProcedureAfterInsert
ON dbo.tblExecuteStoredProcedure AFTER INSERT AS
BEGIN
--Throw if multiple inserts were performed
IF 1 < (
SELECT COUNT(*)
FROM inserted
)
BEGIN
ROLLBACK TRANSACTION;
THROW 50000, N'Cannot perform multiple-row inserts on the table `tblExecuteStoredProcedure`.', 1;
RETURN;
END;
–Przetwarzaj tylko pojedynczy rekord, który powinien być ostatnim wstawionym
DECLARE @ProcedureSchema nazwa sys,
@ProcedureName sysname,
@FullyQualifiedProcedureName nvarchar(MAX),
@Parameter1 nvarchar(MAX),
@Parameter2 nvarchar(MAX),
@Parameter3 nvarchar(MAX),
@Parameter4 nvarchar(MAX),
@Parameter5 nvarchar(MAX),
@Parameter6 nvarchar(MAX),
@Parameter7 nvarchar(MAX),
@Parameter8 nvarchar(MAX),
@Parameter9 nvarchar(MAX),
@Parameter10 nvarchar(MAX),
@Params nvarchar(MAX),
@ParamCount int,
@ParamList nvarchar(MAX),
@Sql nvarchar(MAX);
SELECT
@ProcedureSchema =p.ProcedureSchema,
@ProcedureName =p.ProcedureName,
@FullyQualifiedProcedureName =CONCAT(QUOTENAME(p.ProcedureSchema), N'.', QUOTENAME(p.Procedure) ),
@Parametr1 =p.Parametr1,
@Parametr2 =p.Parametr2
FROM wstawiono jako p
WHERE p.RV =(
SELECT MAX(x. RV)
Z wstawiono jako x
);
SET @Params =STUFF((
SELECT
CONCAT(
N',',
nazwa p.,
N' =',
str. name
)
FROM sys.parameters AS p
INNER JOIN sys.types AS t
ON p.user_type_id =t.user_type_id
GDZIE p.object_id =OBJECT_ID( @FullyQualifiedProcedureName)
DLA ŚCIEŻKI XML (N”)
), 1, 1, N”);
SET @ParamList =STUFF((
SELECT
CONCAT(
N',',
p.nazwa,
N' ',
t.nazwa ,
CASE
WHEN t.name LIKE N'%char%' OR t.name LIKE '%binary%'
THEN CONCAT(N'(', IIF(p.max_length =- 1, N'MAX', CAST(p.max_length AS nvarchar(11))), N')')
KIEDY t.name ='dziesiętny' LUB t.name ='numeric'
THEN CONCAT(N'(', p.precision, N',', p.scale, N')')
JESZCZE N”
KONIEC
)
FROM sys.parameters AS p
INNER JOIN sys.types AS t
ON p.user_type_id =t.user_type_id
WHERE p.object_id =OBJECT_ID(@FullyQualifiedProcedureName)
FOR XML PATH(N”)
), 1, 1, N”);
SET @ParamCount =(
SELECT COUNT(*)
FROM sys.parameters AS p
WHERE p.object_id =OBJECT_ID(@FullyQualifiedProcedureName)
);
SET @ParamList +=((
SELECT
CONCAT(N',', p.ParameterName, N' nvarchar(1)')
FROM (VALUES
(1, N '@Parametr1′),
(2, N'@Parametr2′),
(3, N'@Parametr3′),
(4, N'@Parametr4′),
(5, N'@Parametr5′),
(6, N'@Parametr6′),
(7, N'@Parametr7′),
(8, N'@ Parametr8′),
(9, N'@Parametr9′),
(10, N'@Parametr10′)
) AS p(IdParametru, NazwaParametru)
GDZIE p. ParameterID> @ParamCount
DLA ŚCIEŻKI XML (N”)
));
SET @Sql =CONCAT(N’EXEC ‘, @FullyQualifiedProcedureName, N’’, @Params, N’;’);
–Zapobiegaj zwracaniu jakichkolwiek zestawów wyników z wyzwalacza (który jest przestarzały)
–Jeśli procedura składowana zwróci jakikolwiek, wyzwalacz zakończy się błędem
EXECUTE sys.sp_executesql @Sql, @ParamList, @ Parametr1, @Parameter2, @Parameter3, @Parameter4, @Parameter5, @Parameter6, @Parameter7, @Parameter8, @Parameter9, @Parameter10
Z USTAWIENIAMI WYNIKÓW BRAK;
DELETE FROM dbo.tblExecuteStoredProcedure
WHERE EXISTS (
SELECT NULL
FROM wstawiony
WHERE wstawiony.ExecuteID =tblExecuteStoredProcedure.ExecuteID
);
END;
Dość kęs, ten spust. Zasadniczo zajmuje to pojedynczą wstawkę, a następnie zastanawia się, jak przekonwertować parametry z ich nvarchar(MAX) zgodnie z definicją w tabeli tblExecuteStoredProcedure na rzeczywisty typ wymagany przez procedurę składowaną. Używane są niejawne konwersje, a ponieważ są one opakowane w sys.sp_executesql, działają dobrze dla różnych typów danych, o ile same wartości parametrów są prawidłowe. Należy zauważyć, że wymagamy, aby procedura składowana NIE zwracała żadnych zestawów wyników. Microsoft umożliwia wyzwalacze zwracania zestawów wyników, ale jak wspomniano, jest to niestandardowe i zostało przestarzałe. Aby więc uniknąć problemów z przyszłymi wersjami SQL Server, blokujemy taką możliwość. Na koniec sprzątamy stół, aby zawsze był pusty. W końcu nadużywamy stołu; nie przechowujemy żadnych danych.
Wybrałem wyzwalacz, ponieważ zmniejsza on liczbę podróży w obie strony między programem Access i SQL Server. Gdybym użył procedury składowanej do przetworzenia T-SQL z ciała wyzwalacza, oznaczałoby to, że musiałbym wywołać go po wstawieniu do tabeli, a także poradzić sobie z potencjalnymi skutkami ubocznymi, takimi jak dwóch użytkowników wstawiających w tym samym czasie lub błąd pozostawiając zapis i tak dalej.
OK, ale jak używamy „tabeli” i jej wyzwalacza? W tym miejscu potrzebujemy trochę kodu VBA, aby skonfigurować całą aranżację…
Public Sub ExecuteWithLargeParameters( _
ProcedureSchema As String, _
ProcedureName As String, _
ParamArray Parameters() _
)
Dim db As DAO.Database
Dim rs As DAO.Recordset
Przyciemnij i tak długo
przyciemnij l tak długo
przyciemnij jak długo
Set db =CurrentDb
Set rs =db.OpenRecordset("SELECT * FROM tblExecuteStoredProcedure;", dbOpenDynaset, dbAppendOnly lub dbSeeChanges)
rs.AddNew
rs.Fields(„Schemat procedury”).Wartość =Schemat procedury
rs.Fields(„Nazwa procedury”).Wartość =Nazwa procedury
l =LBound(Parameters)
u =UBound(Parameters)
For i =l To u
rs.Fields(“Parameter” &i).Value =Parameters(i)
Dalej
rs.Update
Zakończ napisy
Zauważ, że używamy ParamArray, które pozwalają nam określić tyle parametrów, ile faktycznie potrzebujemy dla procedury składowanej. Jeśli chcesz zaszaleć i mieć jeszcze 20 parametrów, możesz po prostu dodać więcej pól do tabeli i zaktualizować wyzwalacz, a kod VBA nadal będzie działał. Mógłbyś zrobić coś takiego:
ExecuteWithLargeParameters "dbo", "uspMyStoredProcedure", dteStartDate, dteEndDate, strSomeBigXMLDocument
Mamy nadzieję, że obejście to nie będzie potrzebne przez długi czas (zwłaszcza jeśli przejdziesz do Access UserVoice i zagłosujesz za różnymi pozycjami dotyczącymi Access + SQL / ODBC), ale mamy nadzieję, że okaże się to przydatne, jeśli znajdziesz się w sytuacji, w której jesteśmy in. Chcielibyśmy również usłyszeć o ulepszeniach tego rozwiązania lub lepszym podejściu!