Co dělá Access, když uživatel provede změny dat v propojené tabulce ODBC?
Naše série trasování ODBC pokračuje a v tomto čtvrtém článku vysvětlíme, jak vložit a aktualizovat datový záznam v sadě záznamů, a také proces odstranění záznamu. V předchozím článku jsme se dozvěděli, jak Access zpracovává naplnění dat ze zdrojů ODBC. Viděli jsme, že typ sady záznamů má důležitý vliv na to, jak bude Access formulovat dotazy na zdroj dat ODBC. Ještě důležitější je, že jsme zjistili, že se sadou záznamů typu dynaset provádí Access další práci, aby měl všechny informace potřebné k tomu, aby bylo možné vybrat jeden řádek pomocí klíče. To bude platit v tomto článku, kde prozkoumáme, jak se zachází s úpravami dat. Začneme vkládáním, což je nejsložitější operace, pak přejdeme k aktualizacím a nakonec k mazání.
Vložení záznamu do sady záznamů
Chování vkládání sady záznamů typu dynaset bude záviset na tom, jak Access vnímá klíče podkladové tabulky. Budou existovat 3 odlišná chování. První dva se zabývají zpracováním primárních klíčů, které jsou nějakým způsobem automaticky generovány serverem. Druhý je speciální případ prvního chování, který je použitelný pouze s backendem SQL Server pomocí IDENTITY
sloupec. Poslední se zabývá případem, kdy klíče poskytuje uživatel (např. přirozené klíče jako součást zadávání dat). Začneme obecnějším případem klíčů generovaných serverem.
Vložení záznamu; tabulka s primárním klíčem generovaným serverem
Když vložíme sadu záznamů (opět, jak to uděláme, prostřednictvím uživatelského rozhraní Access nebo VBA nezáleží), Access musí udělat věci, aby přidal nový řádek do místní mezipaměti.
Důležitou věcí je poznamenat, že Access má různé chování vkládání v závislosti na tom, jak je klíč nastaven. V tomto případě Cities
tabulka nemá IDENTITY
atribut, ale spíše používá SEQUENCE
objekt pro vygenerování nového klíče. Zde je formátovaný trasovaný 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)Všimněte si, že Access odešle pouze sloupce, které byly skutečně upraveny uživatelem. I když samotný dotaz obsahoval více sloupců, upravili jsme pouze 4 sloupce, takže Access zahrne pouze ty. To zajišťuje, že Access nenaruší výchozí chování nastavené pro ostatní sloupce, které uživatel neupravil, protože Access nemá žádné konkrétní znalosti o tom, jak bude zdroj dat s těmito sloupci pracovat. Kromě toho je příkaz insert v podstatě to, co bychom očekávali.
Druhé tvrzení je však poněkud zvláštní. Vybere pro WHERE "CityID" IS NULL
. Zdá se to nemožné, protože již víme, že CityID
sloupec je primární klíč a podle definice nemůže být null. Pokud se však podíváte na snímek obrazovky, nikdy jsme neupravili CityID
sloupec. Z POV aplikace Access je to NULL
. Access s největší pravděpodobností zaujímá pesimistický přístup a nebude předpokládat, že zdroj dat bude ve skutečnosti dodržovat standard SQL. Jak jsme viděli v části pojednávající o tom, jak Access vybírá index, který má použít k jedinečné identifikaci řádku, nemusí to být primární klíč, ale pouze UNIQUE
index, který může povolit NULL
. Pro tento nepravděpodobný okrajový případ provede dotaz, jen aby se ujistil, že zdroj dat ve skutečnosti nevytvořil nový záznam s touto hodnotou. Jakmile zjistí, že nebyla vrácena žádná data, pokusí se záznam znovu najít pomocí následujícího filtru:
WHERE "CityName" = ? AND "StateProvinceID" = ? AND "LatestRecordedPopulation" = ? AND "LastEditedBy" = ?což byly stejné 4 sloupce, které uživatel skutečně upravil. Vzhledem k tomu, že existovalo pouze jedno město s názvem „Zeke“, dostali jsme zpět pouze jeden záznam, a proto může Access naplnit místní mezipaměť novým záznamem stejnými daty, jaké má zdroj dat. Od
SELECT
začlení všechny změny do ostatních sloupců seznam obsahuje pouze CityID
klíč, který pak použije ve svém již připraveném příkazu k vyplnění celého řádku pomocí CityID
klíč. Vložení záznamu; tabulka s autoinkrementujícím primárním klíčem
Co když však tabulka pochází z databáze SQL Server a má sloupec s automatickým přírůstkem, jako je IDENTITY
atribut? Access se chová jinak. Vytvořme tedy kopii Cities
tabulku, ale upravte tak, aby CityID
sloupec je nyní IDENTITY
sloupec.
Podívejme se, jak si s tím Access poradí:
SQLExecDirect: INSERT INTO "Application"."Cities" ( "CityName" ,"StateProvinceID" ,"LatestRecordedPopulation" ,"LastEditedBy" ,"ValidFrom" ,"ValidTo" ) VALUES ( ? ,? ,? ,? ,? ,?) SQLExecDirect: SELECT @@IDENTITY SQLExecute: (GOTO BOOKMARK) SQLExecute: (GOTO BOOKMARK)Je tu výrazně méně brblání; jednoduše provedeme
SELECT @@IDENTITY
najít nově vloženou identitu. Bohužel se nejedná o obecné chování. Například MySQL podporuje možnost provést SELECT @@IDENTITY
Access však toto chování neposkytuje. Ovladač PostgreSQL ODBC má režim pro emulaci SQL Serveru, aby přiměl přístup k odeslání @@IDENTITY
na PostgreSQL, aby se mohl mapovat na ekvivalentní serial
datový typ. Vložení záznamu s explicitní hodnotou primárního klíče
Udělejme 3. experiment pomocí tabulky s normálním int
sloupec bez IDENTITY
atribut. Ačkoli to bude stále primární klíč na stole, budeme chtít vidět, jak se chová, když klíč explicitně vložíme sami.
SQLExecDirect: INSERT INTO "Application"."Cities" ( "CityID" ,"CityName" ,"StateProvinceID" ,"LatestRecordedPopulation" ,"LastEditedBy" ,"ValidFrom" ,"ValidTo" ) VALUES ( ? ,? ,? ,? ,? ,? ,? ) SQLExecute: (GOTO BOOKMARK) SQLExecute: (MULTI-ROW FETCH)Tentokrát se nekoná žádná extra gymnastika; protože jsme již poskytli hodnotu pro primární klíč, Access ví, že se nemusí pokoušet znovu najít řádek; pouze provede připravený příkaz pro resynchronizaci vloženého řádku. Vracíme se k původnímu návrhu, kde je
Cities
tabulka používá SEQUENCE
objekt pro vygenerování nového klíče, můžeme přidat funkci VBA pro načtení nového čísla pomocí NEXT VALUE FOR
a tak naplnit klíč proaktivně, abychom získali toto chování. To přesněji přibližuje, jak funguje databázový stroj Access; jakmile záznam zašpiníme, načte nový klíč z AutoNumber
datový typ, spíše než čekat, až bude záznam skutečně vložen. Pokud tedy vaše databáze používá SEQUENCE
nebo jinými způsoby vytváření klíčů, může se vyplatit poskytnout mechanismus pro aktivní načítání klíče, který pomůže vyhnout se dohadům, které jsme viděli dělat Access v prvním příkladu. Aktualizace záznamu v sadě záznamů
Na rozdíl od insertů v předchozí části jsou aktualizace relativně jednodušší, protože klíč již máme. Access se tedy při aktualizaci obvykle chová přímočařeji. Při aktualizaci záznamu musíme vzít v úvahu dvě hlavní chování, která závisí na přítomnosti sloupce rowversion.
Aktualizace záznamu bez sloupce rowversion
Předpokládejme, že upravíme pouze jeden sloupec. To je to, co vidíme v 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, jak je to se všemi těmi extra sloupci, které jsme neupravili? No, znovu, Access musí přijmout pesimistický výhled. Musí se předpokládat, že někdo mohl změnit data, zatímco uživatel pomalu tápal v úpravách. Ale jak by Access věděl, že někdo jiný změnil data na serveru? No, logicky, pokud jsou všechny sloupce úplně stejné, měl by se aktualizovat pouze jeden řádek, ne? To je to, co Access hledá, když porovnává všechny sloupce; abyste zajistili, že aktualizace ovlivní pouze jeden řádek. Pokud zjistí, že aktualizoval více než jeden řádek nebo nula řádků, vrátí aktualizaci a vrátí buď chybu nebo
#Deleted
uživateli. Ale… to je trochu neefektivní, že? Kromě toho by to mohlo přinést problém, pokud existuje logika na straně serveru, která by mohla změnit hodnoty zadané uživatelem. Pro ilustraci předpokládejme, že přidáme hloupý spouštěč, který změní název města (samozřejmě to nedoporučujeme):
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;Pokud se tedy pokusíme aktualizovat řádek změnou názvu města, bude se zdát, že se to podařilo.
Pokud se jej pak pokusíme upravit znovu, zobrazí se chybová zpráva s obnovenou zprávou:
Toto je výstup z 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)Je důležité si uvědomit, že 2.
GOTO BOOKMARK
a následný MULTI-ROW FETCH
es se nestalo, dokud jsme nedostali chybovou zprávu a neodmítli ji. Důvodem je, že když zašpiníme záznam, Access provede GOTO BOOKMARK
uvědomíme si, že vrácená data již neodpovídají tomu, co mají v mezipaměti, což způsobí, že se nám zobrazí zpráva „Data byla změněna“. To nám brání ztrácet čas úpravou záznamu, který je odsouzen k neúspěchu, protože je již zastaralý. Všimněte si, že Access by také nakonec zjistil změnu, pokud bychom mu dali dostatek času na obnovení dat. V takovém případě by se neobjevila žádná chybová zpráva; datový list by se jednoduše aktualizoval, aby zobrazoval správná data.
V těchto případech však měl Access správný klíč, takže neměl problém objevit nová data. Ale pokud je to klíč, který je křehký? Pokud by spouštěč změnil primární klíč nebo zdroj dat ODBC nereprezentoval hodnotu přesně tak, jak si Access představoval, způsobilo by to, že Access vykreslí záznam jako #Deleted
protože nemůže vědět, zda byla upravena serverem nebo někým jiným, a zda byla oprávněně smazána někým jiným.
Aktualizace záznamu se sloupcem rowversion
V obou případech se zobrazí chybová zpráva nebo #Deleted
může být docela nepříjemné. Existuje však způsob, jak zabránit Accessu v porovnávání všech sloupců. Odstraníme spouštěč a přidáme nový sloupec:
ALTER TABLE Application.Cities ADD RV rowversion NOT NULL;Přidáme
rowversion
který má vlastnost vystavení ODBC jako SQLSpecialColumns(SQL_ROWVER)
, což je to, co Access potřebuje vědět, že jej lze použít jako způsob verze řádku. Podívejme se, jak s touto změnou fungují aktualizace. SQLExecDirect: UPDATE "Application"."Cities" SET "CityName"=? WHERE "CityID" = ? AND "RV" = ? SQLExecute: (GOTO BOOKMARK)Na rozdíl od předchozího příkladu, kde Access porovnával hodnoty v každém sloupci, ať už je uživatel upravil nebo ne, aktualizujeme záznam pouze pomocí
RV
jako kritérium filtru. Důvodem je, že pokud RV
má stále stejnou hodnotu jako ten, který Access předal, pak si Access může být jistý, že tento řádek nebyl upraven nikým jiným, protože pokud ano, pak RV
Hodnota ‘ by se změnila. Znamená to také, že pokud spouštěč změnil data nebo pokud SQL Server a Access nereprezentovaly jednu hodnotu přesně stejným způsobem (např. plovoucí čísla), Access se nezastaví, když znovu vybere aktualizovaný řádek a vrátí se s odlišným hodnoty v jiných sloupcích, které uživatelé neupravili.
POZNÁMKA :Ne všechny produkty DBMS budou používat stejné výrazy. Například timestamp
MySQL lze použít jako rowversion pro účely ODBC. Budete si muset prostudovat dokumentaci k produktu, abyste zjistili, zda podporují funkci rowversion, abyste toto chování mohli využít v aplikaci Access.
Zobrazení a verze řádků
Zobrazení jsou také ovlivněna přítomností nebo nepřítomností rowversion. Předpokládejme, že vytvoříme pohled na SQL Server s definicí:
CREATE VIEW dbo.vwCities AS SELECT CityID, CityName FROM Application.Cities;Aktualizace záznamu v zobrazení by se vrátila k porovnání sloupců po sloupcích, jako by sloupec rowversion v tabulce neexistoval:
SQLExecDirect: UPDATE "dbo"."vwCities" SET "CityName"=? WHERE "CityID" = ? AND "CityName" = ?Pokud tedy potřebujete chování aktualizace založené na verzi řádku, měli byste se postarat o to, aby byly v zobrazeních zahrnuty sloupce verze řádku. V případě zobrazení, které obsahuje více tabulek ve spojeních, je nejlepší zahrnout alespoň sloupce verze řádku z tabulek, kde chcete aktualizovat. Protože obvykle lze aktualizovat pouze jednu tabulku, včetně pouze jedné verze řádku může stačit jako obecné pravidlo.
Odstranění záznamu v sadě záznamů
Odstranění záznamu se chová podobně jako aktualizace a také použije rowversion, pokud je k dispozici. Na stole bez rowversion dostaneme:
SQLExecDirect: DELETE FROM "Application"."Cities" WHERE "CityID" = ? AND "CityName" = ? AND "StateProvinceID" = ? AND "Location" IS NULL AND "LatestRecordedPopulation" = ? AND "LastEditedBy" = ? AND "ValidFrom" = ? AND "ValidTo" = ?Na tabulce s rowversion dostaneme:
SQLExecDirect: DELETE FROM "Application"."Cities" WHERE "CityID" = ? AND "RV" = ?Opět platí, že Access musí být ohledně mazání pesimistický, protože jde o aktualizaci; nebude chtít smazat řádek, který změnil někdo jiný. Používá tedy stejné chování, jaké jsme viděli při aktualizaci, aby se zabránilo tomu, že několik uživatelů změní stejné záznamy.
Závěry
Zjistili jsme, jak Access zpracovává úpravy dat a udržuje svou místní mezipaměť synchronizovanou se zdrojem dat ODBC. Viděli jsme, jak pesimistický byl Access, který byl veden nutností podporovat co nejvíce zdrojů dat ODBC, aniž bychom se spoléhali na konkrétní předpoklady nebo očekávání, že takové zdroje dat ODBC budou podporovat určitou funkci. Z tohoto důvodu jsme viděli, že Access se bude chovat odlišně v závislosti na tom, jak je klíč definován pro danou propojenou tabulku ODBC. Pokud jsme byli schopni explicitně vložit nový klíč, vyžadovalo to od Accessu minimální práci na opětovné synchronizaci místní mezipaměti pro nově vložený záznam. Pokud však serveru umožníme naplnit klíč, Access bude muset provést další práci na pozadí, aby se znovu synchronizoval.
Také jsme viděli, že sloupec v tabulce, který lze použít jako rowversion, může pomoci omezit chatování mezi Accessem a zdrojem dat ODBC při aktualizaci. Budete se muset podívat do dokumentace ovladače ODBC, abyste zjistili, zda podporuje rowversion na vrstvě ODBC, a pokud ano, zahrňte takový sloupec do tabulek nebo zobrazení před propojením s Accessem, abyste mohli využít výhod aktualizací založených na rowversion.
Nyní víme, že u všech aktualizací nebo odstranění se Access vždy pokusí ověřit, že řádek nebyl změněn od jeho posledního načtení aplikací Access, aby uživatelé nemohli provádět změny, které by mohly být neočekávané. Musíme však vzít v úvahu účinky vyplývající z provádění změn na jiných místech (např. spouštěč na straně serveru, spuštění jiného dotazu v jiném připojení), které mohou způsobit, že Access dojde k závěru, že řádek byl změněn, a tím změnu nepovolí. Tyto informace nám pomohou analyzovat a vyhnout se vytváření posloupnosti úprav dat, které mohou odporovat očekáváním Accessu, když znovu synchronizuje místní mezipaměť.
V příštím článku se podíváme na účinky použití filtrů na sadu záznamů.
Získejte pomoc od našich odborníků na přístup ještě dnes. Zavolejte našemu týmu na číslo 773-809-5456 nebo nám napište e-mail na adresu [email protected].