sql >> Databasteknik >  >> RDS >> Access

Hur pratar Access med ODBC-datakällor? Del 4

Vad gör Access när en användare gör ändringar i data på en ODBC-länkad tabell?

Vår ODBC-spårningsserie fortsätter, och i den här fjärde artikeln kommer vi att förklara hur man infogar och uppdaterar en datapost i en postuppsättning, samt processen för att radera en post. I den tidigare artikeln lärde vi oss hur Access hanterar att fylla i data från ODBC-källor. Vi såg att postuppsättningstypen har en viktig effekt på hur Access kommer att formulera frågorna till ODBC-datakällan. Ännu viktigare, vi fann att med en dynaset-typ recordset utför Access ytterligare arbete för att ha all information som krävs för att kunna välja en enskild rad med en nyckel. Detta kommer att gälla i den här artikeln där vi utforskar hur dataändringar hanteras. Vi börjar med infogningar, vilket är den mest komplicerade operationen, och går sedan vidare till uppdateringar och slutligen borttagningar.

Infoga en post i en postuppsättning

Insättningsbeteendet för en dynaset-typ postuppsättning kommer att bero på hur Access uppfattar nycklarna i den underliggande tabellen. Det kommer att finnas 3 olika beteenden. De två första handlar om att hantera primärnycklar som är autogenererade av servern på något sätt. Det andra är ett specialfall av det första beteendet som endast är tillämpligt med SQL Server-backend som använder en IDENTITY kolumn. Den sista handlar om fallet när nycklarna tillhandahålls av användaren (t.ex. naturliga nycklar som en del av datainmatning). Vi börjar med det mer allmänna fallet med servergenererade nycklar.

Infoga en post; en tabell med servergenererad primärnyckel

När vi infogar en postuppsättning (igen, hur vi gör detta, via Access UI eller VBA spelar ingen roll), måste Access göra saker för att lägga till den nya raden i den lokala cachen.

Det viktiga att notera är att Access har olika insättningsbeteenden beroende på hur nyckeln är inställd. I det här fallet, Cities tabellen har ingen IDENTITY attribut utan använder snarare en SEQUENCE objekt för att generera en ny nyckel. Här är den formaterade spårade 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)
Observera att Access endast kommer att skicka kolumner som faktiskt har ändrats av användaren. Även om själva frågan inkluderade fler kolumner, redigerade vi bara fyra kolumner, så Access kommer bara att inkludera dessa. Det säkerställer att Access inte stör standardbeteendet för de andra kolumnerna som användaren inte ändrade, eftersom Access inte har någon specifik kunskap om hur datakällan kommer att hantera dessa kolumner. Utöver det är infogningen i stort sett vad vi kan förvänta oss.

Det andra påståendet är dock lite märkligt. Den väljer för WHERE "CityID" IS NULL . Det verkar omöjligt, eftersom vi redan vet att CityID kolumn är en primärnyckel och kan per definition inte vara null. Men om du tittar på skärmdumpen har vi aldrig ändrat CityID kolumn. Från Access POV är det NULL . Troligtvis antar Access ett pessimistiskt tillvägagångssätt och kommer inte att anta att datakällan faktiskt kommer att följa SQL-standarden. Som vi såg från avsnittet som diskuterade hur Access väljer ett index att använda för att unikt identifiera en rad, kanske det inte är en primärnyckel utan bara en UNIQUE index som kan tillåta NULL . För det osannolika edge-fallet gör den en fråga bara för att se till att datakällan faktiskt inte skapade en ny post med det värdet. När den har undersökt att ingen data returnerades, försöker den sedan hitta posten igen med följande filter:

WHERE "CityName" = ?
  AND "StateProvinceID" = ?
  AND "LatestRecordedPopulation" = ?
  AND "LastEditedBy" = ?
som var samma 4 kolumner som användaren faktiskt ändrade. Eftersom det bara fanns en stad som hette "Zeke" fick vi bara en post tillbaka, och därmed kan Access fylla den lokala cachen med den nya posten med samma data som datakällan har den. Den kommer att införliva alla ändringar i andra kolumner, eftersom SELECT listan innehåller bara CityID nyckel, som den sedan kommer att använda i sin redan förberedda sats för att sedan fylla i hela raden med CityID nyckel.

Infoga en post; en tabell med autoinkrementerande primärnyckel

Men vad händer om tabellen kommer från en SQL Server-databas och har en kolumn med autoinkrementering som IDENTITY attribut? Access beter sig annorlunda. Så låt oss skapa en kopia av Cities tabell men redigera så att CityID kolumnen är nu en IDENTITY kolumn.

Låt oss se hur Access hanterar detta:

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

SQLExecDirect:
SELECT @@IDENTITY

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (GOTO BOOKMARK)
Det är betydligt mindre prat; vi gör helt enkelt en SELECT @@IDENTITY för att hitta den nyligen införda identiteten. Tyvärr är detta inte ett allmänt beteende. Till exempel stöder MySQL möjligheten att göra en SELECT @@IDENTITY Access kommer dock inte att tillhandahålla detta beteende. PostgreSQL ODBC-drivrutinen har ett läge för att emulera SQL Server för att lura Access att skicka @@IDENTITY till PostgreSQL så att den kan mappas till motsvarande serial datatyp.

Infoga en post med ett explicit värde för primärnyckel

Låt oss göra ett tredje experiment med en tabell med en normal int kolumn, utan en IDENTITY attribut. Även om det fortfarande kommer att vara en primärnyckel på bordet, vill vi se hur den beter sig när vi uttryckligen infogar nyckeln själva.

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

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (MULTI-ROW FETCH)
Den här gången är det ingen extra gymnastik; eftersom vi redan har angett värdet för primärnyckeln vet Access att den inte behöver försöka hitta raden igen; den kör bara den förberedda satsen för omsynkronisering av den infogade raden. Går tillbaka till den ursprungliga designen där Cities Tabellen använde en SEQUENCE objekt för att generera en ny nyckel, kan vi lägga till en VBA-funktion för att hämta det nya numret med NEXT VALUE FOR och därmed fylla i nyckeln proaktivt för att ge oss detta beteende. Detta motsvarar närmare hur Access-databasmotorn fungerar; så snart vi smutsar ner en post hämtar den en ny nyckel från AutoNumber datatyp, istället för att vänta tills posten faktiskt har infogats. Således, om din databas använder SEQUENCE eller andra sätt att skapa nycklar, kan det löna sig att tillhandahålla en mekanism för att hämta nyckeln proaktivt för att hjälpa till att ta bort gissningarna vi såg Access göra med det första exemplet.

Uppdatera en post i en postuppsättning

Till skillnad från inlägg i föregående avsnitt är uppdateringar relativt enklare eftersom vi redan har nyckeln närvarande. Således beter Access vanligtvis mer okomplicerat när det kommer till uppdatering. Det finns två huvudsakliga beteenden vi måste tänka på när vi uppdaterar en post som beror på närvaron av en radversionskolumn.

Uppdatera en post utan en radversionskolumn

Anta att vi bara ändrar en kolumn. Detta är vad 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, vad är grejen med alla extra kolumner som vi inte ändrade? Nåväl, återigen, Access måste anta en pessimistisk syn. Den måste anta att någon möjligen kunde ha ändrat data medan användaren långsamt fumlade igenom redigeringarna. Men hur skulle Access veta att någon annan ändrade data på servern? Tja, logiskt, om alla kolumner är exakt likadana, borde den bara ha uppdaterat en rad, eller hur? Det är vad Access letar efter när den jämför alla kolumner; för att säkerställa att uppdateringen endast påverkar exakt en rad. Om den upptäcker att den uppdaterade mer än en rad eller noll rader, återställer den uppdateringen och returnerar antingen ett fel eller #Deleted till användaren.

Men... det är lite ineffektivt, eller hur? Dessutom kan detta skapa problem om det finns logik på serversidan som kan ändra de värden som användaren matar in. För att illustrera, anta att vi lägger till en fånig utlösare som ändrar stadsnamnet (vi rekommenderar naturligtvis inte detta):

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å om vi sedan försöker uppdatera en rad genom att ändra stadsnamnet så verkar det ha lyckats.

Men om vi sedan försöker redigera den igen får vi ett felmeddelande med uppdaterat meddelande:

Detta är utdata från 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 är viktigt att notera att den andra GOTO BOOKMARK och den efterföljande MULTI-ROW FETCH Det hände inte förrän vi fick felmeddelandet och avvisade det. Anledningen är att när vi smutsar ner en post utför Access ett GOTO BOOKMARK , inser att den data som returneras inte längre matchar vad den har i cachen, vilket gör att vi får meddelandet "Datan har ändrats". Det hindrar oss från att slösa tid på att redigera en skiva som är dömd att misslyckas eftersom den redan är inaktuell. Observera att Access också så småningom skulle upptäcka ändringen om vi gav den tillräckligt med tid för att uppdatera data. I så fall skulle det inte finnas något felmeddelande; databladet skulle helt enkelt uppdateras för att visa rätt data.

I dessa fall hade Access dock rätt nyckel så det hade inga problem att upptäcka den nya datan. Men om det är nyckeln som är ömtålig? Om utlösaren hade ändrat primärnyckeln eller om ODBC-datakällan inte representerade värdet exakt som Access trodde att det skulle göra, skulle det få Access att måla posten som #Deleted eftersom den inte kan veta om den har redigerats av servern eller någon annan eller om den har tagits bort av någon annan.

Uppdatera en post med kolumn rowversion

Hur som helst, får ett felmeddelande eller #Deleted kan vara ganska irriterande. Men det finns ett sätt att undvika att Access jämför alla kolumner. Låt oss ta bort utlösaren och lägga till en ny kolumn:

ALTER TABLE Application.Cities
ADD RV rowversion NOT NULL;
Vi lägger till en rowversion som har egenskapen att exponeras för ODBC som har SQLSpecialColumns(SQL_ROWVER) , vilket är vad Access behöver veta att det kan användas som ett sätt att versionera raden. Låt oss titta på hur uppdateringar fungerar med denna förändring.

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

SQLExecute: (GOTO BOOKMARK)
Till skillnad från föregående exempel där Access jämförde värdet i varje kolumn, oavsett om användaren redigerade det eller inte, uppdaterar vi endast posten med RV som filterkriterier. Resonemanget är att om RV fortfarande har samma värde som det som Access skickade in, då kan Access vara säker på att den här raden inte har redigerats av någon annan, för om den var det, då är RV s värde skulle ha ändrats.

Det betyder också att om en utlösare ändrade data eller om SQL Server och Access inte representerade ett värde exakt på samma sätt (t.ex. flytande tal), kommer Access inte att böja sig när den återväljer den uppdaterade raden och den kommer tillbaka med olika värden i andra kolumner som användarna inte redigerade.

OBS :Inte alla DBMS-produkter kommer att använda samma termer. Som ett exempel, MySQL:s timestamp kan användas som en radversion för ODBC:s ändamål. Du måste konsultera produktens dokumentation för att se om de stöder rowversion-funktionen så att du kan utnyttja detta beteende med Access.

Visningar och radversion

Visningar påverkas också av närvaron eller frånvaron av en radversion. Anta att vi skapar en vy i SQL Server med definitionen:

CREATE VIEW dbo.vwCities AS
SELECT
	CityID,
	CityName
FROM Application.Cities;
Uppdatering av en post i vyn skulle återgå till kolumn-för-kolumn-jämförelsen som om radversionskolumnen inte fanns i tabellen:

SQLExecDirect: UPDATE "dbo"."vwCities" SET "CityName"=?  WHERE "CityID" = ? AND "CityName" = ?
Därför, om du behöver det radversionsbaserade uppdateringsbeteendet, bör du se till att radversionskolumnerna ingår i vyerna. I fallet med en vy som innehåller flera tabeller i kopplingar är det bäst att inkludera åtminstone radversionskolumnerna från tabell(er) där du tänker uppdatera. Eftersom vanligtvis bara en tabell kan uppdateras, kan det räcka med endast en radversion som en allmän regel.

Ta bort en post i en postuppsättning

Raderingen av en post fungerar på samma sätt som uppdateringarna och kommer även att använda rowversion om tillgänglig. På en tabell utan radversion 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 tabell med en radversion får vi:

SQLExecDirect: 
DELETE FROM "Application"."Cities" 
WHERE "CityID" = ? 
  AND "RV" = ?
Återigen, Access måste vara pessimistisk när det gäller radering eftersom det handlar om att uppdatera; den vill inte ta bort en rad som har ändrats av någon annan. Den använder alltså samma beteende som vi såg vid uppdatering för att skydda mot att flera användare ändrar samma poster.

Slutsatser

Vi har lärt oss hur Access hanterar dataändringar och håller sin lokala cache synkroniserad med ODBC-datakällan. Vi såg hur pessimistisk Access var, vilket drevs av nödvändigheten att stödja så många ODBC-datakällor som möjligt utan att förlita sig på specifika antaganden eller förväntningar om att sådana ODBC-datakällor kommer att stödja en viss funktion. Av den anledningen såg vi att Access kommer att bete sig annorlunda beroende på hur nyckeln är definierad för en given ODBC-länkad tabell. Om vi ​​explicit kunde infoga en ny nyckel, krävde detta minimalt arbete från Access för att omsynkronisera den lokala cachen för den nyligen infogade posten. Men om vi tillåter servern att fylla i nyckeln måste Access göra ytterligare arbete i bakgrunden för att synkronisera om.

Vi såg också att att ha en kolumn i tabellen som kan användas som en radversion kan hjälpa till att minska chatter mellan Access och ODBC-datakällan vid en uppdatering. Du skulle behöva konsultera dokumentationen för ODBC-drivrutinen för att avgöra om den stöder radversionen vid ODBC-lagret och i så fall inkludera en sådan kolumn i tabellerna eller vyerna innan du länkar till Access för att dra nytta av rowversion-baserade uppdateringar.

Vi vet nu att för alla uppdateringar eller borttagningar kommer Access alltid att försöka verifiera att raden inte har ändrats sedan den senast hämtades av Access, för att förhindra användare från att göra ändringar som kan vara oväntade. Vi måste dock överväga effekterna av att göra ändringar på andra ställen (t.ex. utlösare på serversidan, köra en annan fråga i en annan anslutning) vilket kan få Access att dra slutsatsen att raden har ändrats och därmed inte tillåta ändringen. Den informationen hjälper oss att analysera och undvika att skapa en sekvens av dataändringar som kan motsäga Accesss förväntningar när den omsynkroniserar den lokala cachen.

I nästa artikel kommer vi att titta på effekterna av att använda filter på en postuppsättning.

Få hjälp av våra åtkomstexperter idag. Ring vårt team på 773-809-5456 eller skicka ett e-postmeddelande till [email protected].


  1. Senast körda frågor för en specifik databas

  2. 5 sätt att uppdatera data med en underfråga i Oracle SQL

  3. SQLite Node.js

  4. Ingen dialektmappning för JDBC-typ:-9