Access
 sql >> Datenbank >  >> RDS >> Access

Verwenden großer Parameter für gespeicherte Microsoft SQL-Prozeduren mit DAO

Große Parameter für gespeicherte Microsoft SQL-Prozeduren mit DAO verwenden

Wie viele von Ihnen bereits wissen, hat das SQL Server-Team die Einstellung von OLEDB für die SQL Server-Datenbank-Engine angekündigt (Lesen Sie:Wir können ADO nicht verwenden, weil ADO OLEDB verwendet). Darüber hinaus unterstützt SQL Azure ADO nicht offiziell, obwohl man mit SQL Server Native Client immer noch damit durchkommen kann. Der neue 13.1 ODBC-Treiber enthält jedoch eine Reihe von Funktionen, die im SQL Server Native Client nicht verfügbar sind, und möglicherweise kommen noch weitere hinzu.

Fazit:Wir müssen mit reinem DAO arbeiten. Es gibt bereits mehrere User Voice Items zum Thema Access / ODBC oder Access / SQL Server… zum Beispiel:

Datenkonnektor SQL Server
Bessere Integration mit SQL Server
Bessere Integration mit SQL Azure
Bitte machen Sie Access in der Lage, mehr Datentypen zu verarbeiten, die üblicherweise in Serverdatenbanken verwendet werden
Machen Sie Access besser ODBC-Client

(Wenn Sie nicht abgestimmt oder access.uservoice.com besucht haben, gehen Sie dorthin und stimmen Sie ab, ob das Access-Team Ihre Lieblingsfunktion implementieren soll)

Aber selbst wenn Microsoft DAO in der nächsten Version erweitert, müssen wir uns immer noch mit den bestehenden Anwendungen unserer Kunden auseinandersetzen. Wir erwogen, ODBC über OLEDB-Anbieter (MSDASQL) zu verwenden, aber wir hatten das Gefühl, dass es so wäre, als würde man ein Pony auf ein sterbendes Pferd setzen. Es könnte funktionieren, aber es könnte nur einen kurzen Weg nach unten sterben.

Zum größten Teil wird eine Passthrough-Abfrage das tun, was wir tun müssen, und es ist einfach, eine Funktion zusammenzuwerfen, um die Funktionalität von ADO mit einer DAO-Passthrough-Abfrage nachzuahmen. Aber es gibt eine bedeutende Lücke, die nicht leicht zu schließen ist – große Parameter für gespeicherte Prozeduren. Wie ich bereits geschrieben habe, verwenden wir manchmal XML-Parameter, um große Datenmengen zu übergeben, was viel schneller ist, als wenn Access tatsächlich alle Daten einzeln einfügt. Eine DAO-Abfrage ist jedoch auf etwa 64.000 Zeichen für den SQL-Befehl beschränkt und kann in der Praxis sogar noch weniger sein. Wir brauchten einen Weg, um Parameter zu übergeben, die länger als 64.000 Zeichen sein könnten, also mussten wir über eine Problemumgehung nachdenken.

TblExecuteStoredProcedure-Tabelle eingeben

Der von uns gewählte Ansatz bestand darin, eine Tabelle zu verwenden, denn wenn wir neuere ODBC-Treiber oder SQL Server Native Client verwenden, kann DAO problemlos große Textmengen (auch bekannt als Memo) verarbeiten, indem es direkt in die Tabelle eingefügt wird. Um einen großen XML-Parameter auszuführen, schreiben wir daher die auszuführende Prozedur und ihren Parameter in die Tabelle und lassen sie dann vom Trigger aufnehmen. Hier ist das Tabellenerstellungsskript:

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
);

Natürlich haben wir nicht vor, dies wie einen richtigen Tisch zu verwenden. Wir setzen auch willkürlich 10 Parameter, obwohl eine gespeicherte Prozedur viel mehr haben kann. Viel mehr als 10 sind unserer Erfahrung nach jedoch recht selten, besonders wenn wir es mit XML-Parametern zu tun haben. An sich wäre die Tabelle nicht sehr nützlich. Wir brauchen einen Trigger:

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;

–Verarbeite nur einen einzigen Datensatz, der der letzte eingefügte sein sollte
DECLARE @ProcedureSchema sysname,
@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.ProcedureName) ),
@Parameter1 =p.Parameter1,
@Parameter2 =p.Parameter2
FROM eingefügt AS p
WHERE p.RV =(
SELECT MAX(x. RV)
FROM eingefügt AS x
);

SET @Params =STUFF((
SELECT
CONCAT(
N',',
p.name,
N' =',
p. name
)
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 @ParamList =STUFF((
SELECT
CONCAT(
N',',
p.name,
N' ',
t.name ,
CASE
WHEN t.name LIKE N'%char%' ODER t.name LIKE '%binary%'
THEN CONCAT(N'(', IIF(p.max_length =- 1, N'MAX', CAST(p.max_length AS nvarchar(11))), N')')
WHEN t.name ='decimal' OR t.name ='numeric'
THEN CONCAT(N'(', p.precision, N',', p.scale, N')')
ELSE N”
END
)
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 '@Parameter1′),
(2, N'@Parameter2′),
(3, N'@Parameter3′),
(4, N'@Parameter4′),
/> (5, N'@Parameter5′),
(6, N'@Parameter6′),
(7, N'@Parameter7′),
(8, N'@ Parameter8′),
(9, N'@Parameter9′),
(10, N'@Parameter10′)
) AS p(ParameterID, ParameterName)
WHERE p. ParameterID> @ParamCount
FOR XML PATH(N”)
));

SET @Sql =CONCAT(N’EXEC ‘, @FullyQualifiedProcedureName, N’ ‘, @Params, N’;’);

– Verhindern, dass Ergebnismengen von einem Trigger zurückgegeben werden (was veraltet ist)
– Wenn eine gespeicherte Prozedur eine zurückgibt, endet der Trigger mit einem Fehler
EXECUTE sys.sp_executesql @Sql, @ParamList, @ Parameter1, @Parameter2, @Parameter3, @Parameter4, @Parameter5, @Parameter6, @Parameter7, @Parameter8, @Parameter9, @Parameter10
WITH RESULT SETS NONE;

DELETE FROM dbo.tblExecuteStoredProcedure
WO VORHANDEN (
SELECT NULL
FROM inserted
WHERE inserted.ExecuteID =tblExecuteStoredProcedure.ExecuteID
);
END;

Ein ziemlicher Schluck, dieser Auslöser. Im Grunde dauert es eine einzelne Einfügung und findet dann heraus, wie die Parameter von ihrem nvarchar (MAX) wie in der Tabelle tblExecuteStoredProcedure definiert in den tatsächlichen Typ konvertiert werden, der von der gespeicherten Prozedur benötigt wird. Es werden implizite Konvertierungen verwendet, und da es in ein sys.sp_executesql eingeschlossen ist, funktioniert es gut für eine Vielzahl von Datentypen, solange die Parameterwerte selbst gültig sind. Beachten Sie, dass wir verlangen, dass die gespeicherte Prozedur KEINE Ergebnismengen zurückgibt. Microsoft lässt zu, dass Trigger Resultsets zurückgeben, aber wie bereits erwähnt, ist dies kein Standard und wurde als veraltet markiert. Um Probleme mit zukünftigen Versionen von SQL Server zu vermeiden, blockieren wir diese Möglichkeit. Zum Schluss räumen wir den Tisch ab, sodass er immer leer ist. Schließlich missbrauchen wir den Tisch; wir speichern keine Daten.

Ich habe mich für die Verwendung eines Triggers entschieden, weil er die Anzahl der Roundtrips zwischen Access und SQL Server reduziert. Hätte ich eine gespeicherte Prozedur verwendet, um das T-SQL aus dem Hauptteil des Triggers zu verarbeiten, hätte das bedeutet, dass ich es aufrufen müsste, nachdem ich in die Tabelle eingefügt habe, und mich auch mit potenziellen Nebenwirkungen befassen müsste, z. B. wenn zwei Benutzer gleichzeitig einfügen oder ein Fehler hinterlässt einen Datensatz und so weiter.

OK, aber wie verwenden wir die „Tabelle“ und ihren Auslöser? Hier brauchen wir ein bisschen VBA-Code, um die ganze Anordnung einzurichten …

Public Sub ExecuteWithLargeParameters( _
ProcedureSchema As String, _
ProcedureName As String, _
ParamArray Parameters() _
)
Dim db As DAO.Database
Dim rs As DAO.Recordset

Dim i As Long
Dim l As Long
Dim u As Long

Set db =CurrentDb
Set rs =db.OpenRecordset("SELECT * FROM tblExecuteStoredProcedure;", dbOpenDynaset, dbAppendOnly Or dbSeeChanges)

rs.AddNew
rs.Fields(“ProcedureSchema”).Value =ProcedureSchema
rs.Fields(“ProcedureName”).Value =ProcedureName

l =LBound(Parameter)
u =UBound(Parameter)
For i =l To u
rs.Fields(“Parameter” &i).Value =Parameters(i)
Weiter

rs.Update
End Sub

Beachten Sie, dass wir ParamArray verwenden, mit dem wir so viele Parameter angeben können, wie wir tatsächlich für eine gespeicherte Prozedur benötigen. Wenn Sie verrückt werden und 20 weitere Parameter haben wollten, könnten Sie einfach weitere Felder zur Tabelle hinzufügen und den Trigger aktualisieren, und der VBA-Code würde immer noch funktionieren. Sie könnten so etwas tun:

ExecuteWithLargeParameters "dbo", "uspMyStoredProcedure", dteStartDate, dteEndDate, strSomeBigXMLDocument

Hoffentlich wird die Problemumgehung für lange Zeit nicht erforderlich sein (insbesondere wenn Sie zu Access UserVoice gehen und verschiedene Elemente in Bezug auf Access + SQL / ODBC positiv bewerten), aber wir hoffen, dass Sie es nützlich finden, wenn Sie sich in unserer Situation befinden in. Wir würden auch gerne von Verbesserungen hören, die Sie für diese Lösung oder einen besseren Ansatz haben könnten!