ユーザーがODBCリンクテーブルのデータに変更を加えた場合、Accessは何をしますか?
ODBCトレースシリーズは続きます。この4番目の記事では、レコードセットにデータレコードを挿入および更新する方法と、レコードを削除するプロセスについて説明します。前回の記事では、AccessがODBCソースからのデータの入力を処理する方法を学びました。レコードセットタイプは、AccessがODBCデータソースへのクエリを作成する方法に重要な影響を与えることがわかりました。さらに重要なことに、ダイナセットタイプのレコードセットを使用すると、Accessは追加の作業を実行して、キーを使用して単一の行を選択できるようにするために必要なすべての情報を取得できることがわかりました。これは、データの変更がどのように処理されるかを探るこの記事に当てはまります。最も複雑な操作である挿入から始めて、更新、最後に削除に進みます。
レコードセットにレコードを挿入する
ダイナセットタイプのレコードセットの挿入動作は、Accessが基になるテーブルのキーをどのように認識するかによって異なります。 3つの異なる動作があります。最初の2つは、サーバーによって何らかの方法で自動生成される主キーの処理を扱います。 2つ目は、IDENTITY
を使用するSQLServerバックエンドでのみ適用可能な最初の動作の特殊なケースです。 桁。最後は、キーがユーザーによって提供された場合を扱います(たとえば、データ入力の一部としての自然キー)。サーバーで生成されたキーのより一般的なケースから始めます。
レコードの挿入;サーバーで生成された主キーを含むテーブル
レコードセットを挿入する場合(ここでも、Access UIまたはVBAを介してこれを行う方法は重要ではありません)、Accessは新しい行をローカルキャッシュに追加するための処理を実行する必要があります。
注意すべき重要な点は、キーの設定方法に応じてAccessの挿入動作が異なることです。この場合、Cities
テーブルにIDENTITY
がありません 属性ではなく、SEQUENCE
を使用します 新しいキーを生成するオブジェクト。フォーマットされたトレース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)Accessは、ユーザーが実際に変更した列のみを送信することに注意してください。クエリ自体にはさらに多くの列が含まれていましたが、編集したのは4列のみであるため、Accessにはそれらのみが含まれます。これにより、Accessは、データソースがそれらの列を処理する方法について特定の知識を持たないため、ユーザーが変更しなかった他の列に設定されたデフォルトの動作に干渉しません。それを超えて、挿入ステートメントは私たちが期待するものとほぼ同じです。
ただし、2番目のステートメントは少し奇妙です。 WHERE "CityID" IS NULL
を選択します 。 CityID
はすでにわかっているので、それは不可能のようです。 列は主キーであり、定義上、nullにすることはできません。ただし、スクリーンショットを見ると、CityID
は変更されていません。 桁。 AccessのPOVからは、NULL
。ほとんどの場合、Accessは悲観的なアプローチを採用しており、データソースが実際にSQL標準に準拠しているとは想定していません。 Accessが行を一意に識別するために使用するインデックスを選択する方法について説明したセクションからわかるように、それは主キーではなく、単なるUNIQUE
である可能性があります。 NULL
を許可する可能性のあるインデックス 。そのありそうもないエッジケースでは、データソースが実際にその値で新しいレコードを作成していないことを確認するためだけにクエリを実行します。データが返されていないことを確認すると、次のフィルターを使用してレコードの検索を再試行します。
WHERE "CityName" = ? AND "StateProvinceID" = ? AND "LatestRecordedPopulation" = ? AND "LastEditedBy" = ?これは、ユーザーが実際に変更したのと同じ4列でした。 「Zeke」という名前の都市は1つしかなかったため、レコードは1つしか返されませんでした。したがって、Accessは、データソースと同じデータを含む新しいレコードをローカルキャッシュに追加できます。
SELECT
以降、他の列への変更が組み込まれます リストにはCityID
のみが含まれます キー。これは、すでに準備されているステートメントで使用され、CityID
を使用して行全体に入力されます。 キー。 レコードを挿入します。自動インクリメントの主キーを持つテーブル
ただし、テーブルがSQL Serverデータベースからのものであり、IDENTITY
などの自動インクリメント列がある場合はどうなりますか。 属性?アクセスの動作は異なります。それでは、Cities
のコピーを作成しましょう テーブルですが、CityID
になるように編集します 列がIDENTITY
になりました 列。
Accessがこれをどのように処理するか見てみましょう:
SQLExecDirect: INSERT INTO "Application"."Cities" ( "CityName" ,"StateProvinceID" ,"LatestRecordedPopulation" ,"LastEditedBy" ,"ValidFrom" ,"ValidTo" ) VALUES ( ? ,? ,? ,? ,? ,?) SQLExecDirect: SELECT @@IDENTITY SQLExecute: (GOTO BOOKMARK) SQLExecute: (GOTO BOOKMARK)おしゃべりが大幅に少なくなります。
SELECT @@IDENTITY
を実行するだけです 新しく挿入されたIDを検索します。残念ながら、これは一般的な動作ではありません。たとえば、MySQLはSELECT @@IDENTITY
を実行する機能をサポートしています ただし、Accessはこの動作を提供しません。 PostgreSQL ODBCドライバーには、Accessをだまして@@IDENTITY
を送信させるためにSQLServerをエミュレートするモードがあります。 PostgreSQLに変換して、同等のserial
にマップできるようにします データ型。 主キーの明示的な値を持つレコードの挿入
通常のint
のテーブルを使用して3番目の実験を行いましょう 列、IDENTITY
なし 属性。テーブルの主キーのままですが、キーを自分で明示的に挿入した場合の動作を確認する必要があります。
SQLExecDirect: INSERT INTO "Application"."Cities" ( "CityID" ,"CityName" ,"StateProvinceID" ,"LatestRecordedPopulation" ,"LastEditedBy" ,"ValidFrom" ,"ValidTo" ) VALUES ( ? ,? ,? ,? ,? ,? ,? ) SQLExecute: (GOTO BOOKMARK) SQLExecute: (MULTI-ROW FETCH)今回は、余分な体操はありません。主キーの値はすでに提供されているため、Accessは、行を再検索する必要がないことを認識しています。挿入された行を再同期するためのプリペアドステートメントを実行するだけです。
Cities
があった元のデザインに戻ります テーブルはSEQUENCE
を使用しました 新しいキーを生成するオブジェクトの場合、NEXT VALUE FOR
を使用して新しい番号をフェッチするVBA関数を追加できます。 したがって、この動作を実現するために、キーをプロアクティブに入力します。これは、Accessデータベースエンジンがどのように機能するかをより厳密に近似しています。レコードをダーティするとすぐに、AutoNumber
から新しいキーがフェッチされます。 レコードが実際に挿入されるまで待つのではなく、データ型。したがって、データベースがSEQUENCE
を使用している場合 またはキーを作成する他の方法では、Accessが最初の例で行っている推測を排除するために、キーをプロアクティブにフェッチするメカニズムを提供することで成果が得られる場合があります。 レコードセットのレコードを更新する
前のセクションの挿入とは異なり、キーがすでに存在するため、更新は比較的簡単です。したがって、Accessは通常、更新に関してはより簡単に動作します。行バージョン列の存在に依存するレコードを更新するときに考慮する必要がある2つの主要な動作があります。
行バージョン列のないレコードの更新
1つの列のみを変更するとします。これは、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" = ?うーん、変更しなかった余分な列はどうなるのでしょうか。繰り返しになりますが、Accessは悲観的な見通しを採用する必要があります。ユーザーが編集をゆっくりといじっている間に、誰かがデータを変更した可能性があると想定する必要があります。しかし、Accessは、他の誰かがサーバー上のデータを変更したことをどのようにして知るのでしょうか。まあ、論理的には、すべての列がまったく同じであれば、1行だけを更新する必要がありますよね?これが、Accessがすべての列を比較するときに探しているものです。更新が正確に1つの行にのみ影響することを確認します。複数の行または0行を更新したことがわかった場合は、更新をロールバックして、エラーまたは
#Deleted
を返します。 ユーザーに。 でも…それはちょっと非効率ですね。さらに、ユーザーが入力した値を変更する可能性のあるサーバー側のロジックがある場合、これは問題を引き起こす可能性があります。説明のために、都市名を変更するばかげたトリガーを追加するとします(もちろん、これはお勧めしません):
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;したがって、都市名を変更して行を更新しようとすると、成功したように見えます。
しかし、もう一度編集しようとすると、メッセージが更新されたエラーメッセージが表示されます:
これは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)2番目の
GOTO BOOKMARK
に注意することが重要です それに続くMULTI-ROW FETCH
エラーメッセージが表示されて却下されるまで、esは発生しませんでした。その理由は、レコードをダーティすると、AccessがGOTO BOOKMARK
を実行するためです。 、返されたデータがキャッシュにあるデータと一致しなくなったことに注意してください。これにより、「データが変更されました」というメッセージが表示されます。これにより、すでに古くなっているために失敗する運命にあるレコードの編集に時間を浪費することがなくなります。データを更新するのに十分な時間を与えた場合、Accessも最終的に変更を検出することに注意してください。その場合、エラーメッセージは表示されません。データシートは、正しいデータを表示するように更新されるだけです。
ただし、そのような場合、Accessは正しいキーを持っていたため、新しいデータの検出に問題はありませんでした。しかし、それが壊れやすい鍵である場合はどうでしょうか。トリガーが主キーを変更した場合、またはODBCデータソースがAccessが想定したとおりに値を表していない場合、Accessはレコードを#Deleted
として描画します。 サーバーまたは他の誰かによって編集されたのか、他の誰かによって合法的に削除されたのかを知ることができないためです。
行バージョン列を含むレコードの更新
いずれにせよ、エラーメッセージまたは#Deleted
を取得します かなり迷惑になることがあります。ただし、Accessがすべての列を比較しないようにする方法があります。トリガーを削除して、新しい列を追加しましょう:
ALTER TABLE Application.Cities ADD RV rowversion NOT NULL;
rowversion
を追加します これは、SQLSpecialColumns(SQL_ROWVER)
を持つものとしてODBCに公開されるというプロパティを持っています 、これは、行をバージョン管理する方法として使用できることをAccessが知る必要があることです。この変更で更新がどのように機能するかを見てみましょう。 SQLExecDirect: UPDATE "Application"."Cities" SET "CityName"=? WHERE "CityID" = ? AND "RV" = ? SQLExecute: (GOTO BOOKMARK)Accessが各列の値を比較した前の例とは異なり、ユーザーが編集したかどうかに関係なく、
RV
を使用してレコードを更新するだけです。 フィルタ基準として。理由は、RV
Accessが渡した値と同じ値のままである場合、Accessは、この行が他の人によって編集されていないことを確信できます。編集されている場合は、RV
の値は変更されたはずです。 また、トリガーによってデータが変更された場合、またはSQL ServerとAccessが1つの値をまったく同じように表していない場合(浮動小数点数など)、Accessは更新された行を再選択したときにボークせず、異なる値で返されます。ユーザーが編集しなかった他の列の値。
注 :すべてのDBMS製品が同じ用語を使用するわけではありません。例として、MySQLのtimestamp
ODBCの目的で行バージョンとして使用できます。 Accessでこの動作を活用できるように、製品のドキュメントを参照して、行バージョン機能がサポートされているかどうかを確認する必要があります。
ビューと行バージョン
ビューは、行バージョンの有無によっても影響を受けます。 SQLServerで次の定義を使用してビューを作成するとします。
CREATE VIEW dbo.vwCities AS SELECT CityID, CityName FROM Application.Cities;ビューのレコードを更新すると、rowversion列がテーブルに存在しないかのように、列ごとの比較に戻ります。
SQLExecDirect: UPDATE "dbo"."vwCities" SET "CityName"=? WHERE "CityID" = ? AND "CityName" = ?したがって、rowversionベースの更新動作が必要な場合は、rowversion列がビューに含まれていることを確認するように注意する必要があります。結合に複数のテーブルが含まれるビューの場合、更新するテーブルの行バージョン列を少なくとも含めるのが最適です。通常、更新できるテーブルは1つだけであるため、原則として、1つの行バージョンのみを含めるだけで十分な場合があります。
レコードセット内のレコードの削除
レコードの削除は更新と同様に動作し、可能な場合は行バージョンも使用します。行バージョンのないテーブルでは、次のようになります。
SQLExecDirect: DELETE FROM "Application"."Cities" WHERE "CityID" = ? AND "CityName" = ? AND "StateProvinceID" = ? AND "Location" IS NULL AND "LatestRecordedPopulation" = ? AND "LastEditedBy" = ? AND "ValidFrom" = ? AND "ValidTo" = ?行バージョンのあるテーブルでは、次のようになります。
SQLExecDirect: DELETE FROM "Application"."Cities" WHERE "CityID" = ? AND "RV" = ?繰り返しになりますが、Accessは更新に関するものであるため、削除については悲観的である必要があります。他の誰かによって変更された行を削除したくないでしょう。したがって、複数のユーザーが同じレコードを変更するのを防ぐために、更新で見たのと同じ動作を使用します。
結論
Accessがデータの変更を処理し、ローカルキャッシュをODBCデータソースと同期させる方法を学びました。このようなODBCデータソースが特定の機能をサポートするという特定の仮定や期待に依存することなく、できるだけ多くのODBCデータソースをサポートする必要性によって推進された、悲観的なアクセスがいかにあるかを見ました。そのため、特定のODBCリンクテーブルに対してキーがどのように定義されているかによって、Accessの動作が異なることがわかりました。新しいキーを明示的に挿入できた場合、新しく挿入されたレコードのローカルキャッシュを再同期するために、Accessからの最小限の作業が必要でした。ただし、サーバーにキーの入力を許可した場合、Accessは再同期するためにバックグラウンドで追加の作業を行う必要があります。
また、行バージョンとして使用できる列をテーブルに配置すると、更新時のAccessとODBCデータソース間のチャタリングを減らすのに役立つこともわかりました。 ODBCドライバーのドキュメントを参照して、ODBCレイヤーで行バージョンをサポートしているかどうかを判断する必要があります。サポートしている場合は、Accessにリンクして行バージョンベースの更新のメリットを享受する前に、そのような列をテーブルまたはビューに含めてください。
ユーザーが予期しない変更を加えないように、更新または削除の場合、Accessは常に行がAccessによって最後にフェッチされてから変更されていないことを確認しようとすることがわかりました。ただし、他の場所で変更を加えること(サーバー側のトリガー、別の接続で別のクエリを実行するなど)から生じる影響を考慮する必要があります。これにより、Accessは行が変更されたと判断し、変更を許可しなくなります。その情報は、ローカルキャッシュを再同期するときに、Accessの期待と矛盾する可能性のある一連のデータ変更を分析して作成することを回避するのに役立ちます。
次の記事では、レコードセットにフィルターを適用した場合の影響について説明します。
今すぐアクセスエキスパートからサポートを受けてください。 773-809-5456で私たちのチームに電話するか、[email protected]にメールを送ってください。