sql >> Database teknologi >  >> RDS >> Access

Hvordan taler Access med ODBC-datakilder? Del 4

Hvad laver Access, når en bruger foretager ændringer i data på en ODBC-linket tabel?

Vores ODBC-sporingsserie fortsætter, og i denne fjerde artikel vil vi forklare, hvordan man indsætter og opdaterer en datapost i et postsæt, samt processen med at slette en post. I den forrige artikel lærte vi, hvordan Access håndterer udfyldning af data fra ODBC-kilder. Vi så, at recordset-typen har en vigtig effekt på, hvordan Access vil formulere forespørgslerne til ODBC-datakilden. Endnu vigtigere fandt vi ud af, at med et rekordsæt af dynaset-typen udfører Access yderligere arbejde for at have alle de nødvendige oplysninger for at kunne vælge en enkelt række ved hjælp af en nøgle. Dette gælder i denne artikel, hvor vi undersøger, hvordan dataændringer håndteres. Vi starter med indsættelser, som er den mest komplicerede operation, og går derefter videre til opdateringer og til sidst sletninger.

Indsættelse af en post i et postsæt

Indsættelsesadfærden for et rekordsæt af dynaset-typen vil afhænge af, hvordan Access opfatter nøglerne i den underliggende tabel. Der vil være 3 forskellige adfærd. De første to omhandler håndtering af primære nøgler, der på en eller anden måde er autogenereret af serveren. Det andet er et specialtilfælde af den første adfærd, som kun er anvendelig med SQL Server-backend, der bruger en IDENTITY kolonne. Den sidste omhandler det tilfælde, hvor nøglerne leveres af brugeren (f.eks. naturlige nøgler som en del af dataindtastning). Vi starter med det mere generelle tilfælde af server-genererede nøgler.

Indsættelse af en post; en tabel med servergenereret primærnøgle

Når vi indsætter et recordset (igen, hvordan vi gør dette, via Access UI eller VBA er ligegyldigt), skal Access gøre ting for at tilføje den nye række til den lokale cache.

Det vigtige at bemærke er, at Access har forskellig indsætningsadfærd afhængigt af, hvordan nøglen er sat op. I dette tilfælde er Cities tabellen har ikke en IDENTITY attribut, men bruger snarere en SEQUENCE objekt for at generere en ny nøgle. Her er den formaterede sporede SQL:

SQLExecDirect:
INSERT INTO  "Application"."Cities"  (
   "CityName"
  ,"StateProvinceID"
  ,"LatestRecordedPopulation"
  ,"LastEditedBy"
) VALUES (
   ?
  ,?
  ,?
  ,?)

SQLPrepare:
SELECT
   "CityID"
  ,"CityName"
  ,"StateProvinceID"
  ,"Location"
  ,"LatestRecordedPopulation"
  ,"LastEditedBy"
  ,"ValidFrom"
  ,"ValidTo"
FROM "Application"."Cities"
WHERE "CityID" IS NULL

SQLExecute: (GOTO BOOKMARK)

SQLExecDirect:
SELECT
  "Application"."Cities"."CityID"
FROM "Application"."Cities"
WHERE "CityName" = ?
  AND "StateProvinceID" = ?
  AND "LatestRecordedPopulation" = ?
  AND "LastEditedBy" = ?

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (MULTI-ROW FETCH)
Bemærk, at Access kun vil indsende kolonner, der faktisk blev ændret af brugeren. Selvom selve forespørgslen indeholdt flere kolonner, redigerede vi kun 4 kolonner, så Access vil kun inkludere dem. Det sikrer, at Access ikke forstyrrer standardadfærden indstillet for de andre kolonner, som brugeren ikke har ændret, da Access ikke har nogen specifik viden om, hvordan datakilden vil håndtere disse kolonner. Ud over det er indsættelseserklæringen stort set, hvad vi ville forvente.

Det andet udsagn er dog lidt mærkeligt. Den vælger WHERE "CityID" IS NULL . Det virker umuligt, da vi allerede ved, at CityID kolonne er en primær nøgle og kan pr. definition ikke være null. Men hvis du ser på skærmbilledet, har vi aldrig ændret CityID kolonne. Fra Access' POV er den NULL . Mest sandsynligt anvender Access en pessimistisk tilgang og vil ikke antage, at datakilden faktisk vil overholde SQL-standarden. Som vi så fra afsnittet, der diskuterede, hvordan Access vælger et indeks, der skal bruges til entydigt at identificere en række, er det muligvis ikke en primær nøgle, men blot en UNIQUE indeks, som kan tillade NULL . For det usandsynlige edge-tilfælde udfører den en forespørgsel bare for at sikre, at datakilden faktisk ikke oprettede en ny post med den værdi. Når den har undersøgt, at der ikke blev returneret nogen data, forsøger den at finde posten igen med følgende filter:

WHERE "CityName" = ?
  AND "StateProvinceID" = ?
  AND "LatestRecordedPopulation" = ?
  AND "LastEditedBy" = ?
som var de samme 4 kolonner, som brugeren faktisk ændrede. Da der kun var én by ved navn "Zeke", fik vi kun én post tilbage, og dermed kan Access så udfylde den lokale cache med den nye post med samme data som datakilden har den. Det vil inkorporere eventuelle ændringer til andre kolonner, da SELECT listen inkluderer kun CityID nøgle, som den derefter vil bruge i sin allerede forberedte erklæring for derefter at udfylde hele rækken ved hjælp af CityID nøgle.

Indsættelse af en post; en tabel med autoincrementing primær nøgle

Men hvad nu hvis tabellen kommer fra en SQL Server-database og har en autoinkrementerende kolonne såsom IDENTITY egenskab? Access opfører sig anderledes. Så lad os oprette en kopi af Cities tabel, men rediger så CityID kolonne er nu en IDENTITY kolonne.

Lad os se, hvordan Access håndterer dette:

SQLExecDirect:
INSERT INTO "Application"."Cities" (
   "CityName"
  ,"StateProvinceID"
  ,"LatestRecordedPopulation"
  ,"LastEditedBy"
  ,"ValidFrom"
  ,"ValidTo"
) VALUES (
   ?
  ,?
  ,?
  ,?
  ,?
  ,?)

SQLExecDirect:
SELECT @@IDENTITY

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (GOTO BOOKMARK)
Der er væsentligt mindre snak; vi laver simpelthen en SELECT @@IDENTITY at finde den nyligt indsatte identitet. Desværre er dette ikke en generel adfærd. For eksempel understøtter MySQL muligheden for at lave en SELECT @@IDENTITY Access vil dog ikke give denne adfærd. PostgreSQL ODBC-driveren har en tilstand til at emulere SQL Server for at narre Access til at sende @@IDENTITY til PostgreSQL, så det kan mappes til den tilsvarende serial datatype.

Indsættelse af en post med en eksplicit værdi for primær nøgle

Lad os lave et tredje eksperiment ved at bruge en tabel med en normal int kolonne uden en IDENTITY attribut. Selvom det stadig vil være en primær nøgle på bordet, vil vi gerne se, hvordan den opfører sig, når vi selv udtrykkeligt indsætter nøglen.

SQLExecDirect:
INSERT INTO  "Application"."Cities" (
   "CityID"
  ,"CityName"
  ,"StateProvinceID"
  ,"LatestRecordedPopulation"
  ,"LastEditedBy"
  ,"ValidFrom"
  ,"ValidTo"
) VALUES (
   ?
  ,?
  ,?
  ,?
  ,?
  ,?
  ,?
)

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (MULTI-ROW FETCH)
Denne gang er der ingen ekstra gymnastik; da vi allerede har angivet værdien for den primære nøgle, ved Access, at den ikke behøver at prøve at finde rækken igen; den udfører bare den forberedte sætning til resynkronisering af den indsatte række. Går tilbage til det originale design, hvor Cities tabel brugte en SEQUENCE objekt for at generere en ny nøgle, kan vi tilføje en VBA-funktion til at hente det nye nummer ved hjælp af NEXT VALUE FOR og dermed udfylde nøglen proaktivt for at få os denne adfærd. Dette svarer nærmere til, hvordan Access-databasemotoren fungerer; så snart vi snavser en post, henter den en ny nøgle fra AutoNumber datatype, i stedet for at vente, indtil posten faktisk er blevet indsat. Således, hvis din database bruger SEQUENCE eller andre måder at oprette nøgler på, kan det betale sig at tilbyde en mekanisme til proaktivt at hente nøglen for at hjælpe med at fjerne det gætværk, vi så Access gøre med det første eksempel.

Opdatering af en post i et postsæt

I modsætning til indlæg i forrige afsnit er opdateringer relativt nemmere, fordi vi allerede har nøglen til stede. Således opfører Access sig normalt mere ligetil, når det kommer til opdatering. Der er to hovedfunktioner, vi skal overveje, når vi opdaterer en post, som afhænger af tilstedeværelsen af ​​en rækkeversionskolonne.

Opdatering af en post uden en rækkeversionskolonne

Antag, at vi kun ændrer én kolonne. Dette er, hvad vi ser i ODBC.

SQLExecute: (GOTO BOOKMARK)

SQLExecDirect:
UPDATE "Application"."Cities"
SET "CityName"=?
WHERE "CityID" = ?
  AND "CityName" = ?
  AND "StateProvinceID" = ?
  AND "Location" IS NULL
  AND "LatestRecordedPopulation" = ?
  AND "LastEditedBy" = ?
  AND "ValidFrom" = ?
  AND "ValidTo" = ?
Hmm, hvad er der galt med alle de ekstra kolonner, vi ikke har ændret? Nå, igen, Access er nødt til at anlægge et pessimistisk syn. Det må antage, at nogen muligvis kunne have ændret dataene, mens brugeren langsomt fumlede gennem redigeringerne. Men hvordan ville Access vide, at en anden ændrede dataene på serveren? Nå, logisk, hvis alle kolonner er nøjagtigt ens, så burde den kun have opdateret én række, ikke? Det er det, Access leder efter, når den sammenligner alle kolonnerne; for at sikre, at opdateringen kun vil påvirke præcis én række. Hvis den opdager, at den opdaterede mere end én række eller nul rækker, ruller den opdateringen tilbage og returnerer enten en fejl eller #Deleted til brugeren.

Men ... det er lidt ineffektivt, er det ikke? Ydermere kan dette give problemer, hvis der er logik på serversiden, der kan ændre værdierne, som brugeren indtaster. For at illustrere, antag, at vi tilføjer en fjollet udløser, der ændrer bynavnet (vi anbefaler selvfølgelig ikke dette):

CREATE TRIGGER SillyTrigger
ON Application.Cities AFTER UPDATE AS
BEGIN
  UPDATE Application.Cities
  SET CityName = 'zzzzz'
  WHERE EXISTS (
    SELECT NULL
    FROM inserted AS i
    WHERE Cities.CityID = i.CityID
  );
END;
Så hvis vi så forsøger at opdatere en række ved at ændre bynavnet, ser det ud til at det er lykkedes.

Men hvis vi derefter prøver at redigere det igen, får vi en fejlmeddelelse med opdateret besked:

Dette er outputtet fra sqlout.txt :

SQLExecDirect:
UPDATE "Application"."Cities"
SET "CityName"=?
WHERE "CityID" = ?
  AND "CityName" = ?
  AND "StateProvinceID" = ?
  AND "Location" IS NULL
  AND "LatestRecordedPopulation" = ?
  AND "LastEditedBy" = ?
  AND "ValidFrom" = ?
  AND "ValidTo" = ?

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (MULTI-ROW FETCH)

SQLExecute: (MULTI-ROW FETCH)
Det er vigtigt at bemærke, at den 2. GOTO BOOKMARK og den efterfølgende MULTI-ROW FETCH Det skete ikke, før vi fik fejlmeddelelsen og afviste den. Årsagen er, at mens vi snavser en post, udfører Access en GOTO BOOKMARK , indse, at de returnerede data ikke længere matcher, hvad de har på cachen, hvilket får os til at få beskeden "Dataene er blevet ændret". Det forhindrer os i at spilde tid på at redigere en post, der er dømt til at mislykkes, fordi den allerede er gammel. Bemærk, at Access også i sidste ende ville opdage ændringen, hvis vi gav den tid nok til at opdatere dataene. I så fald ville der ikke være nogen fejlmeddelelse; dataarket ville simpelthen blive opdateret for at vise de korrekte data.

I disse tilfælde havde Access dog den rigtige nøgle, så det havde ingen problemer med at opdage de nye data. Men hvis det er nøglen, der er skrøbelig? Hvis udløseren havde ændret den primære nøgle, eller ODBC-datakilden ikke repræsenterede værdien nøjagtigt, som Access troede den ville, ville det få Access til at male posten som #Deleted da den ikke kan vide, om den er redigeret af serveren eller en anden i forhold til, om den lovligt er blevet slettet af en anden.

Opdatering af en post med rækkeversionskolonnen

Uanset hvad, får du en fejlmeddelelse eller #Deleted kan være ret irriterende. Men der er en måde at undgå Access fra at sammenligne alle kolonner. Lad os fjerne triggeren og tilføje en ny kolonne:

ALTER TABLE Application.Cities
ADD RV rowversion NOT NULL;
Vi tilføjer en rowversion som har egenskaben at blive eksponeret for ODBC som havende SQLSpecialColumns(SQL_ROWVER) , hvilket er, hvad Access skal vide, at det kan bruges som en måde at versionere rækken på. Lad os se på, hvordan opdateringer fungerer med denne ændring.

SQLExecDirect: UPDATE "Application"."Cities" 
SET "CityName"=?  
WHERE "CityID" = ? 
  AND "RV" = ?

SQLExecute: (GOTO BOOKMARK)
I modsætning til det foregående eksempel, hvor Access sammenlignede værdien i hver kolonne, uanset om brugeren har redigeret den eller ej, opdaterer vi kun posten ved hjælp af RV som filterkriterier. Begrundelsen er, at hvis RV stadig har den samme værdi som den, som Access sendte ind, så kan Access være sikker på, at denne række ikke blev redigeret af nogen anden, for hvis den var, så er RV ’s værdi ville have ændret sig.

Det betyder også, at hvis en trigger ændrede dataene, eller hvis SQL Server og Access ikke repræsenterede én værdi nøjagtigt på samme måde (f.eks. flydende tal), vil Access ikke afvise, når den genvælger den opdaterede række, og den kommer tilbage med forskellige værdier i andre kolonner, som brugerne ikke har redigeret.

BEMÆRK :Ikke alle DBMS-produkter vil bruge de samme vilkår. Som et eksempel, MySQL's timestamp kan bruges som en rækkeversion til ODBC's formål. Du bliver nødt til at konsultere produktets dokumentation for at se, om de understøtter rowversion-funktionen, så du kan udnytte denne adfærd med Access.

Visninger og rækkeversion

Visninger påvirkes også af tilstedeværelsen eller fraværet af en rækkeversion. Antag, at vi opretter en visning i SQL Server med definitionen:

CREATE VIEW dbo.vwCities AS
SELECT
	CityID,
	CityName
FROM Application.Cities;
Opdatering af en post på visningen ville vende tilbage til kolonne-for-kolonne sammenligningen, som om rækkeversionskolonnen ikke fandtes i tabellen:

SQLExecDirect: UPDATE "dbo"."vwCities" SET "CityName"=?  WHERE "CityID" = ? AND "CityName" = ?
Derfor, hvis du har brug for den rækkeversionsbaserede opdateringsadfærd, bør du sørge for at sikre, at rækkeversionskolonnerne er inkluderet i visningerne. I tilfælde af en visning, der indeholder flere tabeller i joinforbindelser, er det bedst at inkludere mindst rækkeversionskolonnerne fra tabel(r), hvor du har til hensigt at opdatere. For typisk kan kun én tabel opdateres, inklusive kun én rækkeversion kan som hovedregel være tilstrækkelig.

Sletning af en post i et postsæt

Sletningen af ​​en post opfører sig på samme måde som opdateringerne og vil også bruge rowversion, hvis den er tilgængelig. På en tabel uden en rækkeversion får vi:

SQLExecDirect: 
DELETE FROM "Application"."Cities" 
WHERE "CityID" = ? 
  AND "CityName" = ? 
  AND "StateProvinceID" = ? 
  AND "Location" IS NULL 
  AND "LatestRecordedPopulation" = ? 
  AND "LastEditedBy" = ? 
  AND "ValidFrom" = ? 
  AND "ValidTo" = ?
På en tabel med en rækkeversion får vi:

SQLExecDirect: 
DELETE FROM "Application"."Cities" 
WHERE "CityID" = ? 
  AND "RV" = ?
Igen skal Access være pessimistisk med hensyn til sletning, da det handler om opdatering; den ønsker ikke at slette en række, der er ændret af en anden. Den bruger derfor den samme adfærd, som vi så med opdateringen for at beskytte mod, at flere brugere ændrer de samme registreringer.

Konklusioner

Vi har lært, hvordan Access håndterer dataændringer og holder sin lokale cache synkroniseret med ODBC-datakilden. Vi så, hvor pessimistisk Access var, hvilket var drevet af nødvendigheden af ​​at understøtte så mange ODBC-datakilder som muligt uden at stole på specifikke antagelser eller forventninger om, at sådanne ODBC-datakilder vil understøtte en bestemt funktion. Af den grund så vi, at Access vil opføre sig anderledes afhængigt af, hvordan nøglen er defineret for en given ODBC-linket tabel. Hvis vi var i stand til eksplicit at indsætte en ny nøgle, krævede dette det minimale arbejde fra Access for at gensynkronisere den lokale cache for den nyligt indsatte post. Men hvis vi tillader serveren at udfylde nøglen, bliver Access nødt til at udføre yderligere arbejde i baggrunden for at gensynkronisere.

Vi så også, at det at have en kolonne på bordet, der kan bruges som en rækkeversion, kan hjælpe med at reducere chatten mellem Access og ODBC-datakilden på en opdatering. Du skal konsultere ODBC-driverdokumentationen for at afgøre, om den understøtter rækkeversionen på ODBC-laget, og hvis det er tilfældet, inkludere en sådan kolonne i tabellerne eller visningerne, før du linker til Access for at høste fordelene ved rækkeversionsbaserede opdateringer.

Vi ved nu, at for enhver opdatering eller sletning, vil Access altid forsøge at bekræfte, at rækken ikke er blevet ændret, siden den sidst blev hentet af Access, for at forhindre brugere i at foretage ændringer, der kan være uventede. Vi er dog nødt til at overveje de virkninger, der opstår ved at foretage ændringer andre steder (f.eks. trigger på serversiden, køre en anden forespørgsel i en anden forbindelse), hvilket kan få Access til at konkludere, at rækken blev ændret og dermed ikke tillade ændringen. Disse oplysninger vil hjælpe os med at analysere og undgå at skabe en sekvens af dataændringer, der kan modsige Access' forventninger, når den gensynkroniserer den lokale cache.

I den næste artikel vil vi se på virkningerne af at anvende filtre på et rekordsæt.

Få hjælp fra vores adgangseksperter i dag. Ring til vores team på 773-809-5456 eller send os en e-mail på [email protected].


  1. Erstat unicode-tegn i PostgreSQL

  2. Hvordan ændrer man en kolonne og ændrer standardværdien?

  3. SQL CASE-erklæring

  4. Administrer MDF-filer i SQL Server 2019