sql >> Databáze >  >> RDS >> Access

Použití velkých parametrů pro uloženou proceduru Microsoft SQL s DAO

Použití velkých parametrů pro uloženou proceduru Microsoft SQL s DAO

Jak mnozí z vás již vědí, tým SQL Server oznámil ukončení podpory OLEDB pro databázový stroj SQL Server (Přečtěte si:nemůžeme použít ADO, protože ADO používá OLEDB). SQL Azure navíc oficiálně nepodporuje ADO, i když se s tím lze stále obejít pomocí SQL Server Native Client. Nový ovladač ODBC 13.1 však přichází s řadou funkcí, které nebudou dostupné v SQL Server Native Client, a mohou přibýt další.

Závěr:musíme pracovat s čistým DAO. Existuje již několik uživatelských hlasových položek, které se dotýkají tématu Access / ODBC nebo Access / SQL Server… například:

Datový konektor SQL Server
Lepší integrace se serverem SQL
Lepší integrace s SQL Azure
Umožněte Accessu zpracovávat více datových typů, které se běžně používají v databázích serveru
Zlepšit přístup Klient ODBC

(Pokud jste nehlasovali nebo jste nenavštívili access.uservoice.com, přejděte tam a hlasujte, pokud chcete, aby tým Access implementoval vaši oblíbenou funkci)

Ale i když Microsoft vylepší DAO v příští verzi, stále se musíme vypořádat se stávajícími aplikacemi našich zákazníků. Uvažovali jsme o použití ODBC přes poskytovatele OLEDB (MSDASQL), ale měli jsme pocit, že je to podobné obkročování poníka na umírajícím koni. Mohlo by to fungovat, ale mohlo by to zemřít kousek dolů.

Průchozí dotaz z větší části udělá to, co potřebujeme, a je snadné dát dohromady funkci, která napodobí funkcionalitu ADO pomocí průchozího dotazu DAO. Existuje však jedna významná mezera, kterou nelze snadno odstranit – velké parametry pro uložené procedury. Jak jsem psal dříve, někdy používáme parametr XML jako způsob, jak předat velké množství dat, což je mnohem rychlejší, než kdyby Access skutečně vkládal všechna data jedno po druhém. Dotaz DAO je však omezen na přibližně 64 kB znaků pro příkaz SQL a v praxi to může být ještě méně. Potřebovali jsme způsob, jak předávat parametry, které by mohly být větší než 64 000 znaků, takže jsme museli přemýšlet o náhradním řešení.

Zadejte tabulku tblExecuteStoredProcedure

Přístup, který jsme zvolili, byl použití tabulky, protože když používáme novější ovladače ODBC nebo SQL Server Native Client, DAO je snadno schopno zpracovat velké množství textu (aka Memo) vložením přímo do tabulky. Proto, abychom provedli velký XML parametr, zapíšeme proceduru, která se má provést, a její parametr do tabulky, poté necháme spouštěč, aby si to vybral. Zde je skript pro vytvoření tabulky:

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

Samozřejmě to ve skutečnosti nemáme v úmyslu použít jako skutečný stůl. Také jsme libovolně nastavili 10 parametrů, i když uložená procedura může mít mnohem více. Podle našich zkušeností je však poměrně vzácné mít mnohem více než 10, zvláště když se zabýváme parametry XML. Samotný stůl by nebyl příliš užitečný. Potřebujeme spoušť:

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;

–Zpracovat pouze jeden záznam, který by měl být naposledy vložen
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) ),
@Parametr1 =p.Parametr1,
@Parametr2 =p.Parametr2
FROM vloženo JAKO p
WHERE p.RV =(
SELECT MAX(x. RV)
FROM vloženo AS x
);

SET @Params =STUFF((
SELECT
CONCAT(
N',',
jméno p.,
N' =',
p. jméno
)
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)
PRO CESTA XML(N”)
), 1, 1, N”);

SET @ParamList =STUFF((
SELECT
CONCAT(
N',',
p.name,
N' ',
t.name ,
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')')
KDYŽ t.name ='desítkové' NEBO t.name ='numeric'
THEN CONCAT(N'(', p.precision, N',', p.scale, N')')
ELSE N”
END
)
FROM sys.parameters AS p
VNITŘNÍ PŘIPOJENÍ 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
PRO XML PATH(N”)
));

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

–Zabrání vracení jakýchkoli sad výsledků ze spouštěče (který je zastaralý)
–Pokud uložená procedura vrátí nějakou, spouštěč skončí chybou
EXECUTE sys.sp_executesql @Sql, @ParamList, @ Parametr1, @Parametr2, @Parametr3, @Parametr4, @Parametr5, @Parametr6, @Parametr7, @Parametr8, @Parametr9, @Parametr10
S VÝSLEDKY NENÍ ŽÁDNÉ;

DELETE FROM dbo.tblExecuteStoredProcedure
WHERE EXISTS (
SELECT NULL
FROM vložen
WHERE insert.ExecuteID =tblExecuteStoredProcedure.ExecuteID
);
END; P>

Docela sousto, ta spoušť. V podstatě to trvá jednu vložku, pak se zjistí, jak převést parametry z jejich nvarchar(MAX), jak je definováno v tabulce tblExecuteStoredProcedure, na skutečný typ vyžadovaný uloženou procedurou. Používají se implicitní převody, a protože je to zabaleno do souboru sys.sp_executesql, funguje dobře pro různé typy dat, pokud jsou samotné hodnoty parametrů platné. Všimněte si, že požadujeme, aby uložená procedura NEvracela žádné sady výsledků. Microsoft umožňuje spouštěcím mechanismům vracet sady výsledků, ale jak bylo uvedeno, je to nestandardní a bylo zastaralé. Abychom se vyhnuli problémům s budoucími verzemi SQL Serveru, tuto možnost blokujeme. Nakonec stůl vyčistíme, takže je vždy prázdný. Koneckonců, zneužíváme stůl; neukládáme žádná data.

Rozhodl jsem se použít spoušť, protože snižuje počet zpátečních cest mezi Accessem a SQL Serverem. Kdybych použil uloženou proceduru ke zpracování T-SQL z těla spouštěče, znamenalo by to, že bych ji musel zavolat po vložení do tabulky a také se vypořádat s potenciálními vedlejšími účinky, jako je vkládání dvou uživatelů současně nebo chyba zanechání záznamu a tak dále.

Dobře, ale jak použijeme „tabulku“ a její spouštěč? To je místo, kde potřebujeme trochu kódu VBA k nastavení celého uspořádání…

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)
Další

rs.Update
End Sub

Všimněte si, že používáme ParamArray, který nám umožňuje zadat tolik parametrů, kolik skutečně potřebujeme pro uloženou proceduru. Pokud byste se chtěli zbláznit a mít 20 dalších parametrů, mohli byste do tabulky přidat další pole a aktualizovat trigger a kód VBA by stále fungoval. Byli byste schopni udělat něco takového:

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

Doufejme, že toto řešení nebude nutné po dlouhou dobu (zejména pokud přejdete na Access UserVoice a budete hlasovat pro různé položky týkající se Access + SQL / ODBC), ale doufáme, že to pro vás bude užitečné, pokud se ocitnete v situaci, ve které jsme Také bychom rádi slyšeli o vylepšeních, která byste mohli mít pro toto řešení nebo lepší přístup!


  1. Jak CONCAT_WS() funguje v PostgreSQL

  2. Přejmenování primárního klíče v SQL Server (T-SQL)

  3. Cena za nečištění

  4. Závažná chyba:Volání nedefinované funkce session_register()