sql >> Databasteknik >  >> RDS >> Access

Använda stora parametrar för Microsoft SQL lagrad procedur med DAO

Använda stora parametrar för Microsoft SQL lagrad procedur med DAO

Som många av er redan vet har SQL Server-teamet tillkännagett utfasning av OLEDB för SQL Server-databasmotor (Läs:vi kan inte använda ADO eftersom ADO använder OLEDB). Dessutom stöder SQL Azure inte officiellt ADO, även om man fortfarande kan komma undan med det med SQL Server Native Client. Den nya 13.1 ODBC-drivrutinen kommer dock med ett antal funktioner som inte kommer att vara tillgängliga i SQL Server Native Client, och det kan komma fler.

Summan av kardemumman:vi måste arbeta med ren DAO. Det finns redan flera användares röstobjekt som rör ämnet Access / ODBC eller Access / SQL Server ... till exempel:

Dataanslutare SQL Server
Bättre integration med SQL Server
Bättre integration med SQL Azure
Vänligen gör Access i stånd att hantera fler datatyper som vanligtvis används i serverdatabaser
Gör Access till en bättre ODBC-klient

(Om du inte har röstat eller besökt access.uservoice.com, gå dit och rösta om du vill att Access-teamet ska implementera din favoritfunktion)

Men även om Microsoft förbättrar DAO i nästa version, måste vi fortfarande hantera våra kunders befintliga applikationer. Vi övervägde att använda ODBC över OLEDB-leverantören (MSDASQL) men vi kände att det liknade att gå över en ponny på en döende häst. Det kanske fungerar men det kanske bara dör en kort bit ner.

För det mesta kommer en passthrough-fråga att göra vad vi behöver göra och det är lätt att sätta ihop en funktion för att efterlikna ADO:s funktionalitet med hjälp av en DAO-genomgångsfråga. Men det finns en betydande lucka som inte är lätt att åtgärda — stora parametrar för lagrade procedurer. Som jag skrev tidigare använder vi ibland XML-parametern som ett sätt att skicka stora mängder data, vilket är mycket snabbare än att låta Access faktiskt infoga all data en efter en. En DAO-fråga är dock begränsad till cirka 64K tecken för SQL-kommandot och kan i praktiken vara ännu mindre. Vi behövde ett sätt att skicka parametrar som kunde vara större än 64K tecken, så vi var tvungna att tänka på en lösning.

Ange tabellen tblExecuteStoredProcedure

Tillvägagångssättet vi valde var att använda en tabell eftersom när vi använder nyare ODBC-drivrutiner eller SQL Server Native Client kan DAO enkelt hantera stora mängder text (aka Memo) genom att infoga direkt i tabellen. Därför, för att exekvera en stor XML-parameter, kommer vi att skriva proceduren för att exekvera och dess parameter till tabellen, och sedan låta triggern plocka upp den. Här är skriptet för att skapa tabeller:

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

Naturligtvis har vi inte för avsikt att använda det här som ett riktigt bord. Vi ställer också godtyckligt in 10 parametrar även om en lagrad procedur kan ha många fler. Men enligt vår erfarenhet är det ganska sällsynt att ha mycket mer än 10, särskilt när vi har att göra med XML-parametrar. I sig skulle bordet inte vara särskilt användbart. Vi behöver en 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;

–Bearbeta endast en enda post som ska vara den senast infogade
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);

VÄLJ
@ProcedureSchema =p.ProcedureSchema,
@ProcedureName =p.ProcedureName,
@FullyQualifiedProcedureName =CONCAT(QUOTENAME(p.ProcedureSchema), N'.', QUOTENAME(p.ProcedureName) ),
@Parameter1 =p.Parameter1,
@Parameter2 =p.Parameter2
FRÅN infogat SOM p
WHERE p.RV =(
VÄLJ MAX(x. RV)
FRÅN infogat AS x
);

SET @Params =STUFF((
SELECT
CONCAT(
N',',
p.name,
N' =',
p. namn
)
FRÅN 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)
FÖR XML PATH(N”)
), 1, 1, N”);

SET @ParamList =STUFF((
SELECT
CONCAT(
N',',
p.name,
N' ',
t.name ,
CASE
NÄR t.name LIKE N'%char%' OR t.name LIKE '%binary%'
DÅ CONCAT(N'(', IIF(p.max_length =- 1, N'MAX', CAST(p.max_length AS nvarchar(11))), N')')
NÄR t.name ='decimal' ELLER t.name ='numerisk'
DÅ 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)
FÖR XML PATH(N”)
), 1, 1, N”);

SET @ParamCount =(
VÄLJ ANTAL(*)
FRÅN sys.parameters AS p
WHERE p.object_id =OBJECT_ID(@FullyQualifiedProcedureName)
);

SET @ParamList +=((
SELECT
CONCAT(N',', p.ParameterName, N' nvarchar(1)')
FROM (VÄRDEN
(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 sid. ParameterID> @ParamCount
FÖR XML PATH(N”)
));

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

–Förhindra att några resultatuppsättningar returneras från en utlösare (som är utfasad)
–Om en lagrad procedur returnerar någon, kommer utlösaren att sluta med ett fel
UTFÖR sys.sp_executesql @Sql, @ParamList, @ Parameter1, @Parameter2, @Parameter3, @Parameter4, @Parameter5, @Parameter6, @Parameter7, @Parameter8, @Parameter9, @Parameter10
MED INGEN RESULTATSÄTTNINGAR;

DELETE FROM dbo.tblExecuteStoredProcedure
WHERE EXISTS (
VÄLJ NULL
FROM inserted
WHERE inserted.ExecuteID =tblExecuteStoredProcedure.ExecuteID
END;
END;
P>

En ganska munfull, den utlösaren. I grund och botten tar det en enstaka infogning, för att sedan ta reda på hur man konverterar parametrarna från deras nvarchar(MAX) som definierats i tabellen tblExecuteStoredProcedure till den faktiska typen som krävs av den lagrade proceduren. Implicita konverteringar används, och eftersom det är insvept i en sys.sp_executesql fungerar det bra för en mängd olika datatyper så länge själva parametervärdena är giltiga. Observera att vi kräver att den lagrade proceduren INTE returnerar några resultatuppsättningar. Microsoft tillåter att utlösare returnerar resultatuppsättningar, men som nämnts är det icke-standardiserat och har fasats ut. Så för att undvika problem med framtida versioner av SQL Server blockerar vi den möjligheten. Till slut rensar vi bordet, så det är alltid tomt. När allt kommer omkring missbrukar vi bordet; vi lagrar ingen data.

Jag valde att använda en trigger eftersom den minskar antalet tur- och returresor mellan Access och SQL Server. Hade jag använt en lagrad procedur för att bearbeta T-SQL från utlösarens kropp, skulle det ha inneburit att jag skulle behöva anropa den efter att jag satt in i tabellen och även hantera potentiella biverkningar som att två användare sätter in samtidigt eller ett fel som lämnar en post och så vidare.

OK, men hur använder vi "tabellen" och dess utlösare? Det är där vi behöver lite VBA-kod för att ställa in hela arrangemanget...

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 Eller dbSeeChanges)

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

l =LBound(Parameters)
u =UBound(Parameters)
For i =l Till u
rs.Fields(“Parameter” &i).Value =Parametrar(i)
Nästa

rs.Update
End Sub

Observera att vi använder ParamArray som tillåter oss att specificera så många parametrar som vi faktiskt behöver för en lagrad procedur. Om du ville bli galen och ha 20 parametrar till kunde du bara lägga till fler fält i tabellen och uppdatera triggern så skulle VBA-koden fortfarande fungera. Du skulle kunna göra något så här:

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

Förhoppningsvis kommer lösningen inte att behövas under lång tid (speciellt om du går till Access UserVoice och röstar upp olika objekt relaterade till Access + SQL / ODBC), men vi hoppas att du tycker att det är användbart om du skulle befinna dig i situationen vi är Vi vill också gärna höra om förbättringar du kan ha för den här lösningen eller ett bättre tillvägagångssätt!


  1. Lista kolumner med index i PostgreSQL

  2. Ansluter Oracle till SQL Server från Windows

  3. Oracle pivot med underfråga

  4. Hänvisar till ett kolumnalias i en WHERE-klausul