sql >> Database >  >> RDS >> Access

Hoe praat Access met ODBC-gegevensbronnen? Deel 4

Wat doet Access wanneer een gebruiker wijzigingen aanbrengt in gegevens in een ODBC-gekoppelde tabel?

Onze reeks ODBC-tracering gaat verder en in dit vierde artikel leggen we uit hoe u een gegevensrecord in een recordset invoegt en bijwerkt, en hoe u een record verwijdert. In het vorige artikel hebben we geleerd hoe Access omgaat met het vullen van de gegevens uit ODBC-bronnen. We hebben gezien dat het type recordset een belangrijk effect heeft op de manier waarop Access de queries naar de ODBC-gegevensbron formuleert. Wat nog belangrijker is, we ontdekten dat met een dynaset-type recordset, Access extra werk verricht om alle informatie te hebben die nodig is om een ​​enkele rij te kunnen selecteren met behulp van een sleutel. Dit is van toepassing in dit artikel, waar we onderzoeken hoe gegevenswijzigingen worden afgehandeld. We beginnen met invoegingen, wat de meest gecompliceerde operatie is, en gaan dan verder met updates en uiteindelijk verwijderingen.

Een record in een recordset invoegen

Het invoeggedrag van een recordset van het type dynaset is afhankelijk van hoe Access de sleutels van de onderliggende tabel waarneemt. Er zullen 3 verschillende gedragingen zijn. De eerste twee hebben betrekking op het omgaan met primaire sleutels die op de een of andere manier automatisch door de server worden gegenereerd. Het tweede is een speciaal geval van het eerste gedrag dat alleen van toepassing is op SQL Server-backend met een IDENTITY kolom. De laatste behandelt het geval waarin de sleutels door de gebruiker worden verstrekt (bijvoorbeeld natuurlijke sleutels als onderdeel van gegevensinvoer). We beginnen met het meer algemene geval van door de server gegenereerde sleutels.

Een record invoegen; een tabel met door de server gegenereerde primaire sleutel

Wanneer we een recordset invoegen (nogmaals, hoe we dit doen, via Access UI of VBA maakt niet uit), moet Access dingen doen om de nieuwe rij toe te voegen aan de lokale cache.

Het belangrijkste om op te merken is dat Access verschillende invoeggedragingen heeft, afhankelijk van hoe de sleutel is ingesteld. In dit geval zijn de Cities tabel heeft geen IDENTITY attribuut maar gebruikt eerder een SEQUENCE object om een ​​nieuwe sleutel te genereren. Hier is de geformatteerde getraceerde 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)
Houd er rekening mee dat Access alleen kolommen verzendt die daadwerkelijk door de gebruiker zijn gewijzigd. Hoewel de query zelf meer kolommen bevatte, hebben we slechts 4 kolommen bewerkt, dus Access zal alleen die kolommen bevatten. Dat zorgt ervoor dat Access het standaardgedrag dat is ingesteld voor de andere kolommen die de gebruiker niet heeft gewijzigd, niet verstoort, aangezien Access geen specifieke kennis heeft over hoe de gegevensbron met die kolom omgaat. Verder is de insert-instructie ongeveer wat we zouden verwachten.

De tweede verklaring is echter een beetje vreemd. Het selecteert voor WHERE "CityID" IS NULL . Dat lijkt onmogelijk, aangezien we al weten dat de CityID kolom is een primaire sleutel en kan per definitie niet null zijn. Als u echter naar de schermafbeelding kijkt, hebben we de CityID . nooit gewijzigd kolom. Van Access' POV is het NULL . Hoogstwaarschijnlijk hanteert Access een pessimistische benadering en gaat het er niet vanuit dat de gegevensbron zich daadwerkelijk aan de SQL-standaard zal houden. Zoals we hebben gezien in de sectie waarin wordt besproken hoe Access een index selecteert om te gebruiken om een ​​rij uniek te identificeren, is het misschien geen primaire sleutel, maar slechts een UNIQUE index die NULL . kan toestaan . Voor dat onwaarschijnlijke geval voert het een query uit om er zeker van te zijn dat de gegevensbron niet echt een nieuw record met die waarde heeft gemaakt. Nadat het heeft onderzocht dat er geen gegevens zijn geretourneerd, probeert het het record opnieuw te lokaliseren met het volgende filter:

WHERE "CityName" = ?
  AND "StateProvinceID" = ?
  AND "LatestRecordedPopulation" = ?
  AND "LastEditedBy" = ?
welke dezelfde 4 kolommen waren die de gebruiker daadwerkelijk heeft gewijzigd. Omdat er maar één stad was met de naam "Zeke", hebben we maar één record terug, en dus kan Access de lokale cache vullen met het nieuwe record met dezelfde gegevens als de gegevensbron die heeft. Het zal alle wijzigingen in andere kolommen bevatten, aangezien de SELECT lijst bevat alleen de CityID sleutel, die het vervolgens zal gebruiken in zijn reeds voorbereide instructie om vervolgens de hele rij te vullen met behulp van de CityID sleutel.

Een record invoegen; een tabel met automatisch oplopende primaire sleutel

Maar wat als de tabel afkomstig is uit een SQL Server-database en een kolom voor automatisch ophogen heeft, zoals IDENTITY attribuut? Access gedraagt ​​zich anders. Laten we dus een kopie maken van de Cities tabel maar bewerk zodat de CityID kolom is nu een IDENTITY kolom.

Laten we eens kijken hoe Access hiermee omgaat:

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

SQLExecDirect:
SELECT @@IDENTITY

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (GOTO BOOKMARK)
Er is beduidend minder geklets; we doen gewoon een SELECT @@IDENTITY om de nieuw ingevoegde identiteit te vinden. Helaas is dit geen algemeen gedrag. MySQL ondersteunt bijvoorbeeld de mogelijkheid om een ​​SELECT @@IDENTITY . te doen Access biedt dit gedrag echter niet. PostgreSQL ODBC-stuurprogramma heeft een modus om SQL Server te emuleren om Access te misleiden tot het verzenden van de @@IDENTITY naar PostgreSQL zodat het kan worden toegewezen aan het equivalente serial gegevenstype.

Een record invoegen met een expliciete waarde voor de primaire sleutel

Laten we een derde experiment doen met een tabel met een normale int kolom, zonder een IDENTITY attribuut. Hoewel het nog steeds een primaire sleutel op tafel zal zijn, willen we zien hoe het zich gedraagt ​​wanneer we de sleutel zelf expliciet invoegen.

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

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (MULTI-ROW FETCH)
Deze keer is er geen extra gymnastiek; omdat we de waarde voor de primaire sleutel al hebben opgegeven, weet Access dat het niet opnieuw hoeft te proberen de rij te vinden; het voert gewoon de voorbereide instructie uit voor het opnieuw synchroniseren van de ingevoegde rij. Teruggaan naar het oorspronkelijke ontwerp waar de Cities tabel gebruikte een SEQUENCE object om een ​​nieuwe sleutel te genereren, kunnen we een VBA-functie toevoegen om het nieuwe nummer op te halen met NEXT VALUE FOR en zo de sleutel proactief invullen om ons dit gedrag te bezorgen. Dit benadert beter hoe de Access-database-engine werkt; zodra we een record vervuilen, haalt het een nieuwe sleutel uit de AutoNumber gegevenstype, in plaats van te wachten tot de record daadwerkelijk is ingevoegd. Dus als uw database SEQUENCE . gebruikt of andere manieren om sleutels te maken, kan het de moeite waard zijn om een ​​mechanisme te bieden om de sleutel proactief op te halen om het giswerk te elimineren dat we Access zagen doen met het eerste voorbeeld.

Een record in een recordset bijwerken

In tegenstelling tot de inserts in de vorige sectie, zijn updates relatief eenvoudiger omdat we de sleutel al aanwezig hebben. Access gedraagt ​​zich dus meestal eenvoudiger als het gaat om updates. Er zijn twee belangrijke gedragingen waarmee we rekening moeten houden bij het bijwerken van een record, die afhankelijk is van de aanwezigheid van een rijversiekolom.

Een record bijwerken zonder een rijversiekolom

Stel dat we slechts één kolom wijzigen. Dit is wat we zien in 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, wat is de deal met al die extra kolommen die we niet hebben gewijzigd? Nou, nogmaals, Access moet een pessimistische kijk aannemen. Het moet aannemen dat iemand mogelijk de gegevens heeft gewijzigd terwijl de gebruiker langzaam door de bewerkingen rommelde. Maar hoe zou Access weten dat iemand anders de gegevens op de server heeft gewijzigd? Nou, logischerwijs, als alle kolommen precies hetzelfde zijn, dan had er maar één rij moeten worden bijgewerkt, toch? Dat is waar Access naar op zoek is wanneer het alle kolommen vergelijkt; om ervoor te zorgen dat alleen de update van invloed is op precies één rij. Als het ontdekt dat het meer dan één rij of nul rijen heeft bijgewerkt, wordt de update ongedaan gemaakt en wordt een fout geretourneerd of #Deleted aan de gebruiker.

Maar... dat is nogal inefficiënt, nietwaar? Bovendien kan dit problemen opleveren als er logica aan de serverzijde is die de door de gebruiker ingevoerde waarden zou kunnen veranderen. Ter illustratie, stel dat we een domme trigger toevoegen die de naam van de stad verandert (we raden dit natuurlijk niet aan):

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;
Dus als we vervolgens proberen een rij bij te werken door de plaatsnaam te wijzigen, lijkt het erop dat dit is gelukt.

Maar als we het dan opnieuw proberen te bewerken, krijgen we een foutmelding met vernieuwd bericht:

Dit is de uitvoer van 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)
Het is belangrijk op te merken dat de 2e GOTO BOOKMARK en de daaropvolgende MULTI-ROW FETCH es is niet gebeurd totdat we de foutmelding kregen en deze hebben afgewezen. De reden is dat als we een record vervuilen, Access een GOTO BOOKMARK uitvoert , realiseer je dat de geretourneerde gegevens niet langer overeenkomen met wat het in de cache heeft, waardoor we het bericht "De gegevens zijn gewijzigd" krijgen. Dat voorkomt dat we tijd verspillen aan het bewerken van een record dat gedoemd is te mislukken omdat het al oud is. Houd er rekening mee dat Access de wijziging uiteindelijk ook zou ontdekken als we het voldoende tijd zouden geven om de gegevens te vernieuwen. In dat geval zou er geen foutmelding zijn; de datasheet zou gewoon worden bijgewerkt om de juiste gegevens te tonen.

In die gevallen had Access echter de juiste sleutel, dus het had geen probleem om de nieuwe gegevens te ontdekken. Maar als het de sleutel is die kwetsbaar is? Als de trigger de primaire sleutel had gewijzigd of als de ODBC-gegevensbron de waarde niet precies weergaf zoals Access dacht dat het zou zijn, zou dat ertoe leiden dat Access het record zou schilderen als #Deleted omdat het niet kan weten of het door de server of iemand anders is bewerkt of dat het legitiem door iemand anders is verwijderd.

Een record bijwerken met kolom rijversie

Hoe dan ook, krijg een foutmelding of #Deleted kan behoorlijk vervelend zijn. Maar er is een manier om te voorkomen dat Access alle kolommen vergelijkt. Laten we de trigger verwijderen en een nieuwe kolom toevoegen:

ALTER TABLE Application.Cities
ADD RV rowversion NOT NULL;
We voegen een rowversion . toe die de eigenschap heeft te worden blootgesteld aan ODBC met SQLSpecialColumns(SQL_ROWVER) , wat Access nodig heeft om te weten dat het kan worden gebruikt als een manier om de rij te versies. Laten we eens kijken hoe updates werken met deze wijziging.

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

SQLExecute: (GOTO BOOKMARK)
In tegenstelling tot het vorige voorbeeld waarin Access de waarde in elke kolom vergeleek, of de gebruiker deze nu heeft bewerkt of niet, werken we de record alleen bij met de RV als de filtercriteria. De redenering is dat als de RV nog steeds dezelfde waarde heeft als degene die Access heeft doorgegeven, dan kan Access erop vertrouwen dat deze rij niet door iemand anders is bewerkt, want als dat zo was, dan is de RV 's waarde zou zijn veranderd.

Het betekent ook dat als een trigger de gegevens heeft gewijzigd of als SQL Server en Access één waarde niet op precies dezelfde manier vertegenwoordigden (bijv. waarden in andere kolommen die de gebruikers niet hebben bewerkt.

OPMERKING :Niet alle DBMS-producten zullen dezelfde termen gebruiken. Als voorbeeld, MySQL's timestamp kan worden gebruikt als een rijversie voor de doeleinden van ODBC. U moet de documentatie van het product raadplegen om te zien of deze de functie rowversion ondersteunen, zodat u dit gedrag met Access kunt benutten.

Views en rijversie

Weergaven worden ook beïnvloed door de aan- of afwezigheid van een rijversie. Stel dat we een weergave maken in SQL Server met de definitie:

CREATE VIEW dbo.vwCities AS
SELECT
	CityID,
	CityName
FROM Application.Cities;
Het bijwerken van een record in de weergave zou terugkeren naar de kolom-voor-kolom vergelijking alsof de rijversiekolom niet bestond in de tabel:

SQLExecDirect: UPDATE "dbo"."vwCities" SET "CityName"=?  WHERE "CityID" = ? AND "CityName" = ?
Als u het op rowversion gebaseerde updategedrag nodig hebt, moet u er daarom voor zorgen dat de rowversion-kolommen in de weergaven worden opgenomen. In het geval van een weergave die meerdere tabellen in joins bevat, is het het beste om ten minste de rijversiekolommen op te nemen uit de tabel(len) waar u wilt bijwerken. Omdat er normaal gesproken maar één tabel kan worden bijgewerkt, kan als algemene regel worden volstaan ​​met slechts één rijversie.

Een record in een recordset verwijderen

Het verwijderen van een record gedraagt ​​zich op dezelfde manier als de updates en zal, indien beschikbaar, ook rowversion gebruiken. Op een tafel zonder rijversie krijgen we:

SQLExecDirect: 
DELETE FROM "Application"."Cities" 
WHERE "CityID" = ? 
  AND "CityName" = ? 
  AND "StateProvinceID" = ? 
  AND "Location" IS NULL 
  AND "LatestRecordedPopulation" = ? 
  AND "LastEditedBy" = ? 
  AND "ValidFrom" = ? 
  AND "ValidTo" = ?
Op een tafel met een rijversie krijgen we:

SQLExecDirect: 
DELETE FROM "Application"."Cities" 
WHERE "CityID" = ? 
  AND "RV" = ?
Nogmaals, Access moet pessimistisch zijn over verwijderen omdat het over bijwerken gaat; het zou geen rij willen verwijderen die door iemand anders is gewijzigd. Het gebruikt dus hetzelfde gedrag dat we zagen bij het updaten om te voorkomen dat meerdere gebruikers dezelfde records wijzigen.

Conclusies

We hebben geleerd hoe Access omgaat met gegevenswijzigingen en hoe de lokale cache gesynchroniseerd blijft met de ODBC-gegevensbron. We zagen hoe pessimistisch Access was, gedreven door de noodzaak om zoveel mogelijk ODBC-gegevensbronnen te ondersteunen zonder te vertrouwen op specifieke aannames of verwachtingen dat dergelijke ODBC-gegevensbronnen een bepaalde functie zullen ondersteunen. Om die reden hebben we gezien dat Access zich anders zal gedragen, afhankelijk van hoe de sleutel is gedefinieerd voor een bepaalde ODBC-gekoppelde tabel. Als we expliciet een nieuwe sleutel konden invoegen, vergde dit het minimale werk van Access om de lokale cache voor het nieuw ingevoegde record opnieuw te synchroniseren. Als we echter toestaan ​​dat de server de sleutel vult, zal Access extra werk op de achtergrond moeten doen om opnieuw te synchroniseren.

We hebben ook gezien dat het hebben van een kolom op de tabel die kan worden gebruikt als een rijversie, kan helpen bij het verminderen van chatter tussen Access en de ODBC-gegevensbron bij een update. U moet de documentatie van het ODBC-stuurprogramma raadplegen om te bepalen of het de rowversion op de ODBC-laag ondersteunt en zo ja, een dergelijke kolom opnemen in de tabellen of de views voordat u een koppeling maakt naar Access om de voordelen van op rowversion gebaseerde updates te benutten.

We weten nu dat Access bij updates of verwijderingen altijd zal proberen te verifiëren dat de rij niet is gewijzigd sinds deze voor het laatst is opgehaald door Access, om te voorkomen dat gebruikers wijzigingen aanbrengen die mogelijk onverwacht zijn. We moeten echter rekening houden met de effecten die voortvloeien uit het aanbrengen van wijzigingen op andere plaatsen (bijv. server-side trigger, het uitvoeren van een andere query in een andere verbinding), waardoor Access kan concluderen dat de rij is gewijzigd en de wijziging dus niet toestaat. Die informatie helpt ons bij het analyseren en vermijden van het creëren van een reeks gegevenswijzigingen die in tegenspraak kunnen zijn met de verwachtingen van Access wanneer het de lokale cache opnieuw synchroniseert.

In het volgende artikel zullen we kijken naar de effecten van het toepassen van filters op een recordset.

Krijg vandaag nog hulp van onze toegangsexperts. Bel ons team op 773-809-5456 of stuur ons een e-mail op [email protected].


  1. overwinteren dialect voor orakel 12c

  2. SQL Server:voeg onjuiste versie 661 toe

  3. Oracle pivot-operator

  4. Problemen oplossen:MySQL/MariaDB-fout #1044 Е Toegang geweigerd voor gebruiker