Access
 sql >> Datenbank >  >> RDS >> Access

Wie kommuniziert Access mit ODBC-Datenquellen? Teil 4

Was macht Access, wenn ein Benutzer Änderungen an Daten in einer ODBC-verknüpften Tabelle vornimmt?

Unsere ODBC-Tracing-Serie geht weiter, und in diesem vierten Artikel erklären wir, wie Sie einen Datensatz in ein Recordset einfügen und aktualisieren sowie den Vorgang zum Löschen eines Datensatzes. Im vorherigen Artikel haben wir gelernt, wie Access das Auffüllen der Daten aus ODBC-Quellen handhabt. Wir haben gesehen, dass der Datensatztyp einen wichtigen Einfluss darauf hat, wie Access die Abfragen an die ODBC-Datenquelle formuliert. Noch wichtiger ist, dass wir festgestellt haben, dass Access bei einem Recordset vom Typ Dynaset zusätzliche Arbeit leistet, um über alle Informationen zu verfügen, die erforderlich sind, um eine einzelne Zeile mithilfe eines Schlüssels auswählen zu können. Dies gilt für diesen Artikel, in dem wir untersuchen, wie Datenänderungen gehandhabt werden. Wir beginnen mit Einfügungen, was die komplizierteste Operation ist, und fahren dann mit Aktualisierungen und schließlich Löschungen fort.

Einfügen eines Datensatzes in einen Datensatz

Das Einfügeverhalten eines Recordsets vom Typ Dynaset hängt davon ab, wie Access die Schlüssel der zugrunde liegenden Tabelle erkennt. Es wird 3 unterschiedliche Verhaltensweisen geben. Die ersten beiden behandeln den Umgang mit Primärschlüsseln, die vom Server auf irgendeine Weise automatisch generiert werden. Das zweite ist ein Sonderfall des ersten Verhaltens, das nur mit dem SQL Server-Backend anwendbar ist, das eine IDENTITY verwendet Säule. Letzteres behandelt den Fall, wenn die Schlüssel vom Benutzer bereitgestellt werden (z. B. natürliche Schlüssel als Teil der Dateneingabe). Wir beginnen mit dem allgemeineren Fall von servergenerierten Schlüsseln.

Einfügen eines Datensatzes; eine Tabelle mit servergeneriertem Primärschlüssel

Wenn wir ein Recordset einfügen (auch hier spielt es keine Rolle, wie wir dies tun, über die Access-Benutzeroberfläche oder VBA), muss Access Dinge tun, um die neue Zeile zum lokalen Cache hinzuzufügen.

Es ist wichtig zu beachten, dass Access je nach Einrichtung des Schlüssels unterschiedliche Einfügeverhalten hat. In diesem Fall die Cities Tabelle hat keine IDENTITY -Attribut, sondern verwendet eine SEQUENCE Objekt, um einen neuen Schlüssel zu generieren. Hier ist das formatierte nachverfolgte 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)
Beachten Sie, dass Access nur Spalten übermittelt, die tatsächlich vom Benutzer geändert wurden. Obwohl die Abfrage selbst mehr Spalten enthielt, haben wir nur 4 Spalten bearbeitet, sodass Access nur diese enthält. Dadurch wird sichergestellt, dass Access nicht das Standardverhalten beeinträchtigt, das für die anderen Spalten festgelegt wurde, die der Benutzer nicht geändert hat, da Access keine spezifischen Kenntnisse darüber hat, wie die Datenquelle diese Spalten behandelt. Abgesehen davon ist die Insert-Anweisung ziemlich genau das, was wir erwarten würden.

Die zweite Aussage ist jedoch etwas seltsam. Es wählt für WHERE "CityID" IS NULL aus . Das scheint unmöglich, da wir bereits wissen, dass die CityID Spalte ist ein Primärschlüssel und kann per Definition nicht null sein. Wenn Sie sich jedoch den Screenshot ansehen, haben wir die CityID nie geändert Säule. Aus dem POV von Access ist es NULL . Höchstwahrscheinlich verfolgt Access einen pessimistischen Ansatz und geht nicht davon aus, dass die Datenquelle tatsächlich dem SQL-Standard entspricht. Wie wir in dem Abschnitt gesehen haben, in dem besprochen wird, wie Access einen Index auswählt, um eine Zeile eindeutig zu identifizieren, ist es möglicherweise kein Primärschlüssel, sondern lediglich ein UNIQUE Index, der NULL zulassen kann . Für diesen unwahrscheinlichen Grenzfall führt es eine Abfrage durch, nur um sicherzustellen, dass die Datenquelle nicht tatsächlich einen neuen Datensatz mit diesem Wert erstellt hat. Nachdem es überprüft hat, dass keine Daten zurückgegeben wurden, versucht es mit dem folgenden Filter erneut, den Datensatz zu finden:

WHERE "CityName" = ?
  AND "StateProvinceID" = ?
  AND "LatestRecordedPopulation" = ?
  AND "LastEditedBy" = ?
das waren die gleichen 4 Spalten, die der Benutzer tatsächlich geändert hat. Da es nur eine Stadt mit dem Namen „Zeke“ gab, haben wir nur einen Datensatz zurückerhalten, und Access kann dann den lokalen Cache mit dem neuen Datensatz mit denselben Daten füllen, die die Datenquelle hat. Es werden alle Änderungen an anderen Spalten seit dem SELECT übernommen Liste enthält nur die CityID Schlüssel, den es dann in seiner bereits vorbereiteten Anweisung verwendet, um dann die gesamte Zeile mit der CityID zu füllen Schlüssel.

Einfügen eines Datensatzes; eine Tabelle mit automatisch inkrementierendem Primärschlüssel

Was ist jedoch, wenn die Tabelle aus einer SQL Server-Datenbank stammt und eine automatisch inkrementierende Spalte wie IDENTITY hat Attribut? Der Zugriff verhält sich anders. Erstellen wir also eine Kopie der Cities Tabelle, aber bearbeiten Sie so, dass die CityID Spalte ist jetzt eine IDENTITY Spalte.

Mal sehen, wie Access damit umgeht:

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

SQLExecDirect:
SELECT @@IDENTITY

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (GOTO BOOKMARK)
Es gibt deutlich weniger Geschwätz; wir machen einfach eine SELECT @@IDENTITY um die neu eingefügte Identität zu finden. Leider ist dies kein allgemeines Verhalten. Beispielsweise unterstützt MySQL die Möglichkeit, eine SELECT @@IDENTITY durchzuführen , Access stellt dieses Verhalten jedoch nicht bereit. Der PostgreSQL-ODBC-Treiber verfügt über einen Modus zum Emulieren von SQL Server, um Access dazu zu bringen, den @@IDENTITY zu senden zu PostgreSQL, damit es dem entsprechenden serial zugeordnet werden kann Datentyp.

Einfügen eines Datensatzes mit einem expliziten Wert für den Primärschlüssel

Machen wir ein drittes Experiment mit einer Tabelle mit einem normalen int Spalte ohne IDENTITY Attribut. Obwohl es immer noch ein Primärschlüssel in der Tabelle sein wird, wollen wir sehen, wie es sich verhält, wenn wir den Schlüssel explizit selbst einfügen.

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

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (MULTI-ROW FETCH)
Diesmal gibt es keine zusätzliche Gymnastik; Da wir den Wert für den Primärschlüssel bereits angegeben haben, weiß Access, dass es nicht versuchen muss, die Zeile erneut zu finden. es führt nur die vorbereitete Anweisung zum erneuten Synchronisieren der eingefügten Zeile aus. Zurück zum ursprünglichen Design, in dem die Cities Tabelle verwendet eine SEQUENCE Objekt einen neuen Schlüssel generieren, können wir eine VBA-Funktion hinzufügen, um die neue Zahl mit NEXT VALUE FOR abzurufen und füllen Sie daher den Schlüssel proaktiv aus, um uns dieses Verhalten zu verschaffen. Dies kommt der Funktionsweise der Access-Datenbank-Engine näher; Sobald wir einen Datensatz schmutzig machen, holt er sich einen neuen Schlüssel aus AutoNumber Datentyp, anstatt zu warten, bis der Datensatz tatsächlich eingefügt wurde. Wenn Ihre Datenbank also SEQUENCE verwendet oder andere Möglichkeiten zum Erstellen von Schlüsseln, kann es sich auszahlen, einen Mechanismus zum proaktiven Abrufen des Schlüssels bereitzustellen, um das Rätselraten zu vermeiden, das Access beim ersten Beispiel gemacht hat.

Aktualisieren eines Datensatzes in einem Recordset

Im Gegensatz zu Einfügungen im vorherigen Abschnitt sind Aktualisierungen relativ einfacher, da der Schlüssel bereits vorhanden ist. Daher verhält sich Access in der Regel unkomplizierter, wenn es um Updates geht. Es gibt zwei wichtige Verhaltensweisen, die wir berücksichtigen müssen, wenn wir einen Datensatz aktualisieren, der vom Vorhandensein einer rowversion-Spalte abhängt.

Aktualisieren eines Datensatzes ohne Zeilenversionsspalte

Angenommen, wir ändern nur eine Spalte. Das sehen wir 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, was hat es mit all den zusätzlichen Spalten auf sich, die wir nicht geändert haben? Nun, wieder muss Access einen pessimistischen Ausblick annehmen. Es muss davon ausgegangen werden, dass möglicherweise jemand die Daten geändert hat, während der Benutzer langsam durch die Bearbeitungen fummelte. Aber wie würde Access wissen, dass jemand anderes die Daten auf dem Server geändert hat? Nun, logischerweise, wenn alle Spalten genau gleich sind, hätte es nur eine Zeile aktualisieren sollen, oder? Das ist es, wonach Access sucht, wenn es alle Spalten vergleicht; um sicherzustellen, dass die Aktualisierung nur genau eine Zeile betrifft. Wenn festgestellt wird, dass mehr als eine Zeile oder null Zeilen aktualisiert wurden, wird die Aktualisierung rückgängig gemacht und entweder ein Fehler oder #Deleted zurückgegeben an den Benutzer.

Aber … das ist irgendwie ineffizient, oder? Darüber hinaus könnte dies zu Problemen führen, wenn es eine serverseitige Logik gibt, die die vom Benutzer eingegebenen Werte ändern könnte. Nehmen wir zur Veranschaulichung an, wir fügen einen dummen Trigger hinzu, der den Namen der Stadt ändert (wir empfehlen das natürlich nicht):

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;
Wenn wir also versuchen, eine Zeile zu aktualisieren, indem wir den Namen der Stadt ändern, scheint dies erfolgreich gewesen zu sein.

Aber wenn wir dann versuchen, es erneut zu bearbeiten, erhalten wir eine Fehlermeldung mit aktualisierter Meldung:

Dies ist die Ausgabe von 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)
Es ist wichtig zu beachten, dass das 2. GOTO BOOKMARK und dem anschließenden MULTI-ROW FETCH Es ist nicht passiert, bis wir die Fehlermeldung erhalten und verworfen haben. Der Grund dafür ist, dass Access ein GOTO BOOKMARK ausführt, wenn wir einen Datensatz verfälschen , feststellen, dass die zurückgegebenen Daten nicht mehr mit denen im Cache übereinstimmen, was dazu führt, dass wir die Meldung „Die Daten wurden geändert“ erhalten. Das verhindert, dass wir Zeit damit verschwenden, einen Datensatz zu bearbeiten, der zum Scheitern verurteilt ist, weil er bereits veraltet ist. Beachten Sie, dass Access die Änderung schließlich auch entdecken würde, wenn wir genügend Zeit zum Aktualisieren der Daten gegeben hätten. In diesem Fall würde es keine Fehlermeldung geben; das Datenblatt würde einfach aktualisiert werden, um die korrekten Daten anzuzeigen.

In diesen Fällen hatte Access jedoch den richtigen Schlüssel, sodass es keine Probleme hatte, die neuen Daten zu entdecken. Aber wenn es der Schlüssel ist, der zerbrechlich ist? Wenn der Auslöser den Primärschlüssel geändert hatte oder die ODBC-Datenquelle den Wert nicht genau so darstellte, wie Access dachte, würde dies dazu führen, dass Access den Datensatz als #Deleted zeichnet da es nicht wissen kann, ob es vom Server oder jemand anderem bearbeitet wurde oder ob es rechtmäßig von jemand anderem gelöscht wurde.

Aktualisieren eines Datensatzes mit der rowversion-Spalte

In beiden Fällen erhalten Sie eine Fehlermeldung oder #Deleted kann ganz schön nervig sein. Es gibt jedoch eine Möglichkeit, Access daran zu hindern, alle Spalten zu vergleichen. Lassen Sie uns den Trigger entfernen und eine neue Spalte hinzufügen:

ALTER TABLE Application.Cities
ADD RV rowversion NOT NULL;
Wir fügen eine rowversion hinzu die die Eigenschaft hat, ODBC als SQLSpecialColumns(SQL_ROWVER) verfügbar gemacht zu werden , was Access wissen muss, damit es als Möglichkeit zum Versionieren der Zeile verwendet werden kann. Sehen wir uns an, wie Updates mit dieser Änderung funktionieren.

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

SQLExecute: (GOTO BOOKMARK)
Im Gegensatz zum vorherigen Beispiel, in dem Access den Wert in jeder Spalte verglichen hat, unabhängig davon, ob der Benutzer ihn bearbeitet hat oder nicht, aktualisieren wir den Datensatz nur mit dem RV als Filterkriterium. Der Grund dafür ist, dass wenn der RV immer noch den gleichen Wert hat wie der, den Access übergeben hat, dann kann Access sicher sein, dass diese Zeile von niemand anderem bearbeitet wurde, denn wenn ja, dann der RV hätte sich der Wert geändert.

Dies bedeutet auch, dass, wenn ein Trigger die Daten geändert hat oder wenn SQL Server und Access einen Wert nicht genau auf die gleiche Weise dargestellt haben (z Werte in anderen Spalten, die die Benutzer nicht bearbeitet haben.

HINWEIS :Nicht alle DBMS-Produkte verwenden dieselben Begriffe. Als Beispiel der timestamp von MySQL kann als Zeilenversion für ODBC-Zwecke verwendet werden. Sie müssen in der Produktdokumentation nachsehen, ob sie die Rowversion-Funktion unterstützen, damit Sie dieses Verhalten mit Access nutzen können.

Ansichten und Zeilenversion

Ansichten werden auch durch das Vorhandensein oder Fehlen einer Zeilenversion beeinflusst. Angenommen, wir erstellen eine Ansicht in SQL Server mit der Definition:

CREATE VIEW dbo.vwCities AS
SELECT
	CityID,
	CityName
FROM Application.Cities;
Das Aktualisieren eines Datensatzes in der Ansicht würde zum spaltenweisen Vergleich zurückkehren, als ob die rowversion-Spalte in der Tabelle nicht vorhanden wäre:

SQLExecDirect: UPDATE "dbo"."vwCities" SET "CityName"=?  WHERE "CityID" = ? AND "CityName" = ?
Wenn Sie das rowversion-basierte Aktualisierungsverhalten benötigen, sollten Sie daher darauf achten, dass die rowversion-Spalten in den Ansichten enthalten sind. Im Fall einer Ansicht, die mehrere Tabellen in Joins enthält, ist es am besten, mindestens die rowversion-Spalten aus Tabelle(n) einzubeziehen, die Sie aktualisieren möchten. Da typischerweise nur eine Tabelle aktualisiert werden kann, kann es in der Regel ausreichen, nur eine Zeilenversion aufzunehmen.

Einen Datensatz in einem Recordset löschen

Das Löschen eines Datensatzes verhält sich ähnlich wie das Aktualisieren und verwendet auch die Zeilenversion, sofern verfügbar. Bei einer Tabelle ohne Zeilenversion erhalten wir:

SQLExecDirect: 
DELETE FROM "Application"."Cities" 
WHERE "CityID" = ? 
  AND "CityName" = ? 
  AND "StateProvinceID" = ? 
  AND "Location" IS NULL 
  AND "LatestRecordedPopulation" = ? 
  AND "LastEditedBy" = ? 
  AND "ValidFrom" = ? 
  AND "ValidTo" = ?
Auf einer Tabelle mit einer Zeilenversion erhalten wir:

SQLExecDirect: 
DELETE FROM "Application"."Cities" 
WHERE "CityID" = ? 
  AND "RV" = ?
Auch hier muss Access beim Löschen pessimistisch sein, da es um das Aktualisieren geht. Es möchte keine Zeile löschen, die von jemand anderem geändert wurde. Daher verwendet es das gleiche Verhalten, das wir bei der Aktualisierung gesehen haben, um zu verhindern, dass mehrere Benutzer dieselben Datensätze ändern.

Schlussfolgerungen

Wir haben gelernt, wie Access mit Datenänderungen umgeht und seinen lokalen Cache mit der ODBC-Datenquelle synchron hält. Wir haben gesehen, wie pessimistisch Access war, was von der Notwendigkeit getrieben wurde, so viele ODBC-Datenquellen wie möglich zu unterstützen, ohne sich auf bestimmte Annahmen oder Erwartungen zu verlassen, dass solche ODBC-Datenquellen eine bestimmte Funktion unterstützen werden. Aus diesem Grund haben wir gesehen, dass Access sich unterschiedlich verhält, je nachdem, wie der Schlüssel für eine bestimmte ODBC-verknüpfte Tabelle definiert ist. Wenn wir explizit einen neuen Schlüssel einfügen konnten, erforderte dies die minimale Arbeit von Access, um den lokalen Cache für den neu eingefügten Datensatz neu zu synchronisieren. Wenn wir dem Server jedoch erlauben, den Schlüssel zu füllen, muss Access im Hintergrund zusätzliche Arbeit leisten, um die Neusynchronisierung durchzuführen.

Wir haben auch gesehen, dass eine Spalte in der Tabelle, die als Zeilenversion verwendet werden kann, dazu beitragen kann, Chatter zwischen Access und der ODBC-Datenquelle bei einer Aktualisierung zu reduzieren. Sie müssten die ODBC-Treiberdokumentation konsultieren, um festzustellen, ob sie die Zeilenversion auf ODBC-Ebene unterstützt, und wenn ja, diese Spalte in die Tabellen oder Ansichten aufnehmen, bevor Sie eine Verknüpfung mit Access herstellen, um die Vorteile von Zeilenversions-basierten Aktualisierungen zu nutzen.

Wir wissen jetzt, dass Access bei allen Aktualisierungen oder Löschungen immer versucht zu überprüfen, ob die Zeile seit dem letzten Abruf durch Access nicht geändert wurde, um zu verhindern, dass Benutzer Änderungen vornehmen, die möglicherweise unerwartet sind. Wir müssen jedoch die Auswirkungen berücksichtigen, die sich aus Änderungen an anderen Stellen ergeben (z. B. serverseitiger Trigger, Ausführen einer anderen Abfrage in einer anderen Verbindung), die dazu führen können, dass Access zu dem Schluss kommt, dass die Zeile geändert wurde, und die Änderung daher nicht zulässt. Diese Informationen helfen uns, eine Folge von Datenänderungen zu analysieren und zu vermeiden, die den Erwartungen von Access widersprechen, wenn der lokale Cache neu synchronisiert wird.

Im nächsten Artikel werden wir uns die Auswirkungen der Anwendung von Filtern auf ein Recordset ansehen.

Holen Sie sich noch heute Hilfe von unseren Access-Experten. Rufen Sie unser Team unter 773-809-5456 an oder schreiben Sie uns eine E-Mail an [email protected].