Access
 sql >> Base de données >  >> RDS >> Access

Utilisation de grands paramètres pour la procédure stockée Microsoft SQL avec DAO

Utilisation de grands paramètres pour la procédure stockée Microsoft SQL avec DAO

Comme beaucoup d'entre vous le savent déjà, l'équipe SQL Server a annoncé la dépréciation d'OLEDB pour le moteur de base de données SQL Server (Lire :nous ne pouvons pas utiliser ADO car ADO utilise OLEDB). De plus, SQL Azure ne prend pas officiellement en charge ADO, bien que l'on puisse toujours s'en tirer en utilisant SQL Server Native Client. Cependant, le nouveau pilote ODBC 13.1 est livré avec un certain nombre de fonctionnalités qui ne seront pas disponibles dans SQL Server Native Client, et il pourrait y en avoir d'autres à venir.

L'essentiel :nous devons travailler avec du DAO pur. Il existe déjà plusieurs éléments de voix d'utilisateur abordant le sujet d'Access / ODBC ou Access / SQL Server… par exemple :

Connecteur de données SQL Server
Meilleure intégration avec SQL Server
Meilleure intégration avec SQL Azure
Veuillez rendre Access capable de gérer plus de types de données que ceux couramment utilisés dans les bases de données Server
Améliorez Access Client ODBC

(Si vous n'avez pas voté ou visité access.uservoice.com, allez-y et votez si vous voulez que l'équipe Access implémente votre fonctionnalité préférée)

Mais même si Microsoft améliore DAO dans la prochaine version, nous devons encore nous occuper des applications existantes de nos clients. Nous avons envisagé d'utiliser ODBC sur le fournisseur OLEDB (MSDASQL), mais nous avons estimé que cela revenait à chevaucher un poney sur un cheval mourant. Cela pourrait fonctionner, mais cela pourrait s'éteindre rapidement.

Pour la plupart, une requête directe fera ce que nous devons faire et il est facile de créer une fonction pour imiter la fonctionnalité d'ADO à l'aide d'une requête directe DAO. Mais il existe une lacune importante à laquelle il n'est pas facile de remédier :de grands paramètres pour les procédures stockées. Comme je l'ai écrit plus tôt, nous utilisons parfois le paramètre XML comme moyen de transmettre une grande quantité de données, ce qui est beaucoup plus rapide que si Access insère toutes les données une par une. Cependant, une requête DAO est limitée à environ 64K caractères pour la commande SQL et en pratique peut être encore moins. Nous avions besoin d'un moyen de transmettre des paramètres pouvant dépasser 64 000 caractères. Nous avons donc dû réfléchir à une solution de contournement.

Entrez le tableau tblExecuteStoredProcedure

L'approche que nous avons choisie était d'utiliser une table car lorsque nous utilisons des pilotes ODBC plus récents ou SQL Server Native Client, DAO est facilement capable de gérer une grande quantité de texte (aka Memo) en insérant directement dans la table. Par conséquent, pour exécuter un grand paramètre XML, nous allons écrire la procédure à exécuter et son paramètre dans la table, puis laisser le déclencheur le récupérer. Voici le script de création de table :

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

Bien sûr, nous n'avons pas vraiment l'intention de l'utiliser comme une vraie table. Nous définissons également arbitrairement 10 paramètres même si une procédure stockée peut en avoir beaucoup plus. Cependant, d'après notre expérience, il est assez rare d'en avoir beaucoup plus que 10, surtout lorsqu'il s'agit de paramètres XML. En soi, le tableau ne serait pas très utile. Nous avons besoin d'un déclencheur :

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;

–Traiter un seul enregistrement qui doit être le dernier inséré
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) ),
@Paramètre1 =p.Paramètre1,
@Paramètre2 =p.Paramètre2
FROM inséré AS p
WHERE p.RV =(
SELECT MAX(x. RV)
FROM inséré AS x
);

SET @Params =STUFF((
SELECT
CONCAT(
N',',
p.name,
N' =',
p. nom
)
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 ,
CAS
QUAND t.name LIKE N'%char%' OU t.name LIKE '%binary%'
ALORS CONCAT(N'(', IIF(p.max_length =- 1, N'MAX', CAST(p.max_length AS nvarchar(11))), N')')
QUAND t.name ='decimal' OR t.name ='numeric'
ALORS 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 '@Paramètre1′),
(2, N'@Paramètre2′),
(3, N'@Paramètre3′),
(4, N'@Paramètre4′),
/> (5, N'@Paramètre5′),
(6, N'@Paramètre6′),
(7, N'@Paramètre7′),
(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';');

– Empêcher tout ensemble de résultats d'être renvoyé à partir d'un déclencheur (qui est obsolète)
– Si une procédure stockée en renvoie, le déclencheur se terminera par une erreur
EXECUTE sys.sp_executesql @Sql, @ParamList, @ Paramètre1, @Paramètre2, @Paramètre3, @Paramètre4, @Paramètre5, @Paramètre6, @Paramètre7, @Paramètre8, @Paramètre9, @Paramètre10
AVEC DES JEUX DE RÉSULTATS AUCUN ;

SUPPRIMER DE dbo.tblExecuteStoredProcedure
WHERE EXISTS (
SELECT NULL
FROM inséré
WHERE inséré.ExecuteID =tblExecuteStoredProcedure.ExecuteID
);
END;

Une bouchée assez, qui déclenche. Fondamentalement, il prend une seule insertion, puis détermine comment convertir les paramètres de leur nvarchar(MAX) tel que défini sur la table tblExecuteStoredProcedure au type réel requis par la procédure stockée. Des conversions implicites sont utilisées, et comme il est encapsulé dans un sys.sp_executesql fonctionne bien pour une variété de types de données tant que les valeurs des paramètres elles-mêmes sont valides. Notez que nous exigeons que la procédure stockée NE renvoie PAS d'ensembles de résultats. Microsoft autorise les déclencheurs à renvoyer des ensembles de résultats, mais comme indiqué, il n'est pas standard et a été obsolète. Donc, pour éviter des problèmes avec les futures versions de SQL Server, nous bloquons cette possibilité. Enfin, nous débarrassons la table, elle est donc toujours vide. Après tout, nous abusons de la table; nous ne stockons aucune donnée.

J'ai choisi d'utiliser un déclencheur car il réduit le nombre d'allers-retours entre Access et SQL Server. Si j'avais utilisé une procédure stockée pour traiter le T-SQL à partir du corps du déclencheur, cela aurait signifié que j'aurais dû l'appeler après l'avoir inséré dans la table et également gérer les effets secondaires potentiels tels que deux utilisateurs insérant en même temps ou une erreur laissant un enregistrement derrière et ainsi de suite.

OK, mais comment utilise-t-on la « table » et son déclencheur ? C'est là que nous avons besoin d'un peu de code VBA pour mettre en place tout l'arrangement…

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(Parameters)
u =UBound(Parameters)
For i =l To u
rs.Fields("Parameter" &i).Value =Parameters(i)
Suivant

rs.Update
End Sub

Notez que nous utilisons ParamArray qui nous permet de spécifier autant de paramètres que nous avons réellement besoin pour une procédure stockée. Si vous vouliez devenir fou et avoir 20 paramètres supplémentaires, vous pourriez simplement ajouter plus de champs à la table et mettre à jour le déclencheur et le code VBA fonctionnerait toujours. Vous pourriez faire quelque chose comme ceci :

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

Espérons que la solution de contournement ne sera pas nécessaire pendant longtemps (surtout si vous accédez à Access UserVoice et votez pour divers éléments relatifs à Access + SQL / ODBC), mais nous espérons que vous la trouverez utile si vous vous trouvez dans la situation où nous sommes Nous aimerions également connaître les améliorations que vous pourriez apporter à cette solution ou une meilleure approche !