{"id":55000482,"date":"2025-08-01T00:00:00","date_gmt":"2025-10-12T10:57:12","guid":{"rendered":"http:\/\/access-im-unternehmen.aix-dev.de\/aiu\/?p=482"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-30T00:00:00","slug":"Airtable_per_RestAPI_synchronisieren","status":"publish","type":"post","link":"https:\/\/vbentwickler.de\/Airtable_per_RestAPI_synchronisieren\/","title":{"rendered":"Airtable per Rest-API synchronisieren"},"content":{"rendered":"<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2025_04\/pic_482_001.png\" alt=\"&Ouml;ffnen des Builder Hubs\" width=\"649,627\" height=\"429,6336\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 1: &Ouml;ffnen des Builder Hubs<\/span><\/b><\/p>\n<p><b>Das Datenbanksystem Airtable bietet eine Menge Funktionen an, aber offeriert leider keine direkte Schnittstelle, mit der man direkt etwa von Access auf die enthaltenen Daten zugreifen kann. Es gibt zwar kostenpflichtige ODBC-Schnittstellen von Drittanbietern, aber wir wollen den Zugriff selbst programmieren. Wie f&uuml;r moderne SaaS-Tools &uuml;blich, bietet auch Airtable eine Rest-API als Schnittstelle f&uuml;r den Zugriff auf die Daten an. Diese wollen wir im vorliegenden Artikel untersuchen und zeigen, wie wir auf die enthaltenen Daten zugreifen und Informationen aus einer lokalen Datenbank in eine Airtable-Datenbank schreiben k&ouml;nnen.<\/b><\/p>\n<p>Der erste Schritt f&uuml;r den Zugriff auf eine Rest-API ist immer die Ermittlung des dazu notwendigen API-Schl&uuml;ssels. Diesen holen wir uns nach dem Anmelden auf <b>airtable.com<\/b>, indem wir zun&auml;chst oben rechts auf den Button f&uuml;r unser Konto klicken und aus dem nun erscheinenden Men&uuml; den Eintrag <b>Builder Hub <\/b>ausw&auml;hlen (siehe Bild 1).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2025_04\/pic_482_001.png\" alt=\"&Ouml;ffnen des Builder Hubs\" width=\"649,627\" height=\"429,6336\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 1: &Ouml;ffnen des Builder Hubs<\/span><\/b><\/p>\n<p>Danach landen wir im <b>Builder Hub<\/b>, wo wir &uuml;ber den Men&uuml;befehl <b>Pers&ouml;nliche Zugangstoken <\/b>eine Schaltfl&auml;che namens <b>Token erstellen <\/b>einblenden k&ouml;nnen (siehe Bild 2).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2025_04\/pic_482_002.png\" alt=\"Anlegen eines Tokens\" width=\"700\" height=\"308,2278\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 2: Anlegen eines Tokens<\/span><\/b><\/p>\n<h2>Pers&ouml;nlicher Zugangstoken<\/h2>\n<p>Neben dem pers&ouml;nlichen Zugangstoken gibt es noch OAuth-Token. F&uuml;r das Synchronisieren einer Access-Datenbank mit einer Airtable-Datenbank ist das pers&ouml;nliche Zugangstoken die beste L&ouml;sung.<\/p>\n<p>Das OAuth-Token ben&ouml;tigen wir nur, wenn wir eine App entwickeln, mit der wir im Namen andere Benutzer auf deren Airtable-Datenbanken zugreifen wollen. <\/p>\n<h2>Bereiche f&uuml;r das Token festlegen<\/h2>\n<p>Nach dem Anlegen des pers&ouml;nlichen Tokens legen wir fest, f&uuml;r welche Bereiche dieses Token gelten soll. Diese w&auml;hlen wir aus, indem wir die Liste der verf&uuml;gbaren Bereiche mit einem Klick auf <b>Einen Bereich hinzuf&uuml;gen <\/b>&ouml;ffnen und dort die gew&uuml;nschten Bereiche hinzuf&uuml;gen (siehe Bild 3).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2025_04\/pic_482_003.png\" alt=\"Hinzuf&uuml;gen von Bereichen\" width=\"649,627\" height=\"701,8215\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 3: Hinzuf&uuml;gen von Bereichen<\/span><\/b><\/p>\n<p>Wir wollen ausgiebig experimentieren, also f&uuml;gen wir die Bereiche aus Bild 4 hinzu.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2025_04\/pic_482_004.png\" alt=\"Alle notwendigen Bereiche\" width=\"424,6267\" height=\"364,8449\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 4: Alle notwendigen Bereiche<\/span><\/b><\/p>\n<p>Anschlie&szlig;end wollen wir noch festlegen, auf welche Datenbanken in unserer Airtable-Instanz sich die hinzugef&uuml;gten Berechtigungen auswirken sollen. Das erledigen wir im gleichen Bereich weiter unten, wo wir mit einem Klick auf <b>Alle Ressourcen hinzuf&uuml;gen <\/b>direkt alle Datenbanken gleichzeitig freischalten oder mit <b>Eine Base hinzuf&uuml;gen <\/b>gezielt eine Datenbank f&uuml;r den Zugriff ausw&auml;hlen (siehe Bild 5).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2025_04\/pic_482_006.png\" alt=\"Hinzuf&uuml;gen der Datenbank, auf die wir zugreifen wollen\" width=\"649,627\" height=\"171,4421\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 5: Hinzuf&uuml;gen der Datenbank, auf die wir zugreifen wollen<\/span><\/b><\/p>\n<p>Nun wird das Token erstellt und wir k&ouml;nnen es aus dem nun erscheinenden Dialog in die Zwischenablage kopieren (siehe Bild 6).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2025_04\/pic_482_005.png\" alt=\"Das fertige Token\" width=\"424,6267\" height=\"256,3936\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 6: Das fertige Token<\/span><\/b><\/p>\n<p>Achtung: Das Token k&ouml;nnen wir nur einmalig abrufen. Also f&uuml;gen wir es direkt als Wert einer Konstanten namens <b>cStrAPIToken<\/b> in ein neues VBA-Modul im VBA-Projekt der Datenbank ein, von der aus wir auf die Airtable-Datenbank zugreifen wollen.<\/p>\n<p>Im gleichen Zuge legen wir auch direkt noch eine weitere Konstante f&uuml;r die Basis-URL f&uuml;r die Rest-API-Zugriffe fest:<\/p>\n<pre><span style=\"color:blue;\">Private <\/span>Const cStrBaseURL<span style=\"color:blue;\"> As String<\/span> = \"https:\/\/api.airtable.com\/v0\/\"\r\n<span style=\"color:blue;\">Private <\/span>Const cStrAPIToken<span style=\"color:blue;\"> As String<\/span> = \"pat6TTvfq5Uq8avqs.7baxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxc7553910b58\"<\/pre>\n<h2>ID der Airtable-Datenbank ermitteln<\/h2>\n<p>Au&szlig;erdem ben&ouml;tigen wir die ID der Airtable-Datenbank, mit der wir arbeiten wollen. Dazu &ouml;ffnen wir die folgende URL unter der wir auch noch andere wichtige Informationen finden:<\/p>\n<pre>https:\/\/airtable.com\/developers\/web\/api\/introduction<\/pre>\n<p>Hier sehen wir alle Datenbanken des aktuellen Kontos (siehe Bild 7). Also klicken wir die gew&uuml;nschte Datenbank an.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2025_04\/pic_482_007.png\" alt=\"&Uuml;bersicht der Airtable-Datenbanken\" width=\"649,627\" height=\"365,4674\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 7: &Uuml;bersicht der Airtable-Datenbanken<\/span><\/b><\/p>\n<p>Anschlie&szlig;end erscheint eine Ansicht, die unscheinbar in einem Absatz die ID der zu verwendenden Datenbank enth&auml;lt (siehe Bild 8).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2025_04\/pic_482_008.png\" alt=\"Ermitteln der ID der Datenbank\" width=\"499,6267\" height=\"452,0433\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 8: Ermitteln der ID der Datenbank<\/span><\/b><\/p>\n<p>Auch diese kopieren wir zun&auml;chst in die Zwischenablage und f&uuml;gen sie als Wert einer weiteren Konstanten namens <b>cStrBaseID <\/b>ein:<\/p>\n<pre><span style=\"color:blue;\">Private <\/span>Const cStrBaseID<span style=\"color:blue;\"> As String<\/span> = \"appxxxxxxxxxxxxxxx\" ''Kundenverwaltung<\/pre>\n<p><b>Achtung: <\/b>Wir ben&ouml;tigen die ID ohne den abschlie&szlig;enden Punkt.<\/p>\n<h2>Beispiele f&uuml;r die verschiedenen Datenbankoperationen<\/h2>\n<p>Danach schauen wir uns diese Seite genauer an. Links in der &Uuml;bersichtsleiste sehen wir n&auml;mlich die Tabellen unserer Datenbank (siehe Bild 9). &Ouml;ffnen wir einen dieser Eintr&auml;ge, sehen wir Untereintr&auml;ge, die nach dem Anklicken weitere Informationen liefern &#8211; zum Beispiel, wie man Datens&auml;tze der jeweiligen Tabelle ausgibt, einzelne Datens&auml;tze ermittelt, diese erstellt, aktualisiert oder l&ouml;scht.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2025_04\/pic_482_009.png\" alt=\"Beispiele f&uuml;r die verschiedenen Datenbank-Operationen\" width=\"700\" height=\"440,18\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 9: Beispiele f&uuml;r die verschiedenen Datenbank-Operationen<\/span><\/b><\/p>\n<p>Auf der rechten Seite sehen wir eine Beschreibung und den notwendigen JSON-Code, den wir mit dem jeweiligen Aufruf der Rest-API &uuml;bergeben m&uuml;ssen.<\/p>\n<h2>Liste der Tabellen ermitteln<\/h2>\n<p>Mit der Rest-API k&ouml;nnen wir verschiedene Informationen unserer Airtable-Datenbank abrufen. Bevor wir in das Abfragen, Bearbeiten oder L&ouml;schen von Daten aus Airtable-Tabellen einsteigen, ermitteln wir erst einmal die grundlegenden Informationen der in der Datenbank enthaltenen Tabellen.<\/p>\n<p>Dazu verwenden wir die Prozedur <b>GetAirtableTables<\/b> aus Listing 1. Hier erstellen wir ein Objekt des Typs <b>XMLHTTP60<\/b>, mit dem wir auf die Rest-API zugreifen. Wir stellen in <b>strURL <\/b>die Adresse f&uuml;r den Zugriff auf unsere Datenbank zusammen, wobei wir die ID der Datenbank aus der oben definierten Konstanten <b>cStrBaseID <\/b>integrieren. Die URL sieht nun beispielsweise wie folgt aus:<\/p>\n<pre><span style=\"color:blue;\">Sub <\/span>GetAirtableTables()\r\n     <span style=\"color:blue;\">Dim <\/span>objXMLHTTP<span style=\"color:blue;\"> As <\/span>MSXML2.XMLHTTP60\r\n     <span style=\"color:blue;\">Dim <\/span>objJSON<span style=\"color:blue;\"> As Object<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strURL<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>i<span style=\"color:blue;\"> As Integer<\/span>\r\n     \r\n     <span style=\"color:blue;\">Set<\/span> objXMLHTTP = <span style=\"color:blue;\">New<\/span> MSXML2.XMLHTTP60\r\n     strURL = cStrBaseURL & \"meta\/bases\/\" & cStrBaseID & \"\/tables\"\r\n     \r\n     objXMLHTTP.Open \"GET\", strURL, <span style=\"color:blue;\">False<\/span>\r\n     objXMLHTTP.setRequestHeader \"Authorization\", \"Bearer \" & cStrAPIToken\r\n     objXMLHTTP.send\r\n     <span style=\"color:blue;\">Debug.Print<\/span> GetJSONDOM(objXMLHTTP.responseText, <span style=\"color:blue;\">True<\/span>)\r\n     \r\n     <span style=\"color:blue;\">Set<\/span> objJSON = ParseJson(objXMLHTTP.responseText)\r\n     \r\n     <span style=\"color:blue;\">Debug.Print<\/span> \"TabelleID:\" & vbTab & vbTab & vbTab & vbTab & vbTab & \"Tabellenname:\"\r\n     \r\n     For i = 1 To objJSON.Item(\"tables\").Count\r\n         <span style=\"color:blue;\">Debug.Print<\/span> objJSON.Item(\"tables\").Item(i).Item(\"id\"), objJSON.Item(\"tables\").Item(i).Item(\"name\")\r\n     <span style=\"color:blue;\">Next<\/span> i\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 1: Prozedur zum Auslesen aller Tabellen der Airtable-Datenbank<\/span><\/b><\/p>\n<pre>https:\/\/api.airtable.com\/v0\/\/meta\/bases\/app...RrJh\/tables<\/pre>\n<p>Das API-Token aus <b>cStrAPIToken <\/b>&uuml;bergeben wie mit dem <b>RequestHeader <\/b>namens <b>Authorization<\/b>. Dann starten wir den Aufruf und erhalten das Ergebnis mit <b>objXMLHTTP.Response <\/b>im JSON-Format.<\/p>\n<p>Dieses verarbeiten wir mit der Funktion <b>ParseJson <\/b>aus dem Modul <b>mdlJSON <\/b>in ein Objekt aus <b>Dictionary<\/b>&#8211; und <b>Collection<\/b>-Elementen, auf das wir dann zugreifen k&ouml;nnen. Damit wir wissen, wie wir auf die enthaltenen Elemente zugreifen k&ouml;nnen, geben wir diese mit der <b>GetJSONDOM<\/b>-Funktion im Direktbereich aus. Dadurch sehen wir, dass wir auf die Tabellendaten beispielsweise wie folgt zugreifen k&ouml;nnen:<\/p>\n<pre>objJson.Item(\"tables\").Item(1).Item(\"id\"): tblMUktvNEo0FPRYb\r\nobjJson.Item(\"tables\").Item(1).Item(\"name\"): Bestellungen<\/pre>\n<p>Daraus k&ouml;nnen wir eine <b>For&#8230;Next<\/b>-Schleife ableiten, um die Werte f&uuml;r die Felder <b>id <\/b>und <b>name <\/b>auszulesen und einzeln im Direktbereich auszugeben. Das Ergebnis sieht wie folgt aus:<\/p>\n<pre>TabelleID:                  Tabellenname:\r\ntblMUktvNEo0FPRYb           Bestellungen<\/pre>\n<p>Damit haben wir nun auch die ID der Tabelle, mit der wir in den folgenden Abschnitten auf die Tabelle <b>Bestellungen <\/b>zugreifen und diese auslesen, bearbeiten oder um neue Datens&auml;tze erg&auml;nzen k&ouml;nnen.<\/p>\n<h2>Grundlegender Aufbau eines Aufrufs der Rest-API f&uuml;r den Tabellenzugriff<\/h2>\n<p>Airtable stellt eine Basis-URL f&uuml;r den Zugriff auf die Tabellendaten einer Airtable-Datenbank zur Verf&uuml;gung.<\/p>\n<p>Wir erstellen eine Basisfunktion mit verschiedenen Parametern, um alle Operationen abzudecken. Den ersten Teil dieser Funktion finden wir in Listing 2. Hier sehen wir zun&auml;chst die Parameter der Funktion:<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>Airtable_CRUD(strTable<span style=\"color:blue;\"> As String<\/span>, strMethod<span style=\"color:blue;\"> As String<\/span>, strRequest<span style=\"color:blue;\"> As String<\/span>, _\r\n         <span style=\"color:blue;\">Optional<\/span> strId<span style=\"color:blue;\"> As String<\/span>, <span style=\"color:blue;\">Optional<\/span> strResponse<span style=\"color:blue;\"> As String<\/span>, <span style=\"color:blue;\">Optional<\/span> intErrorNumber<span style=\"color:blue;\"> As Integer<\/span>, _\r\n         <span style=\"color:blue;\">Optional<\/span> strErrorDescription<span style=\"color:blue;\"> As String<\/span> , ParamArray varParameters()<span style=\"color:blue;\"> As Variant<\/span>)<span style=\"color:blue;\"> As Boolean<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strURL<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>objRequest<span style=\"color:blue;\"> As <\/span>MSXML2.XMLHTTP60\r\n     strURL = \"https:\/\/api.airtable.com\/v0\/\" & cStrBaseID & \"\/\" & strTable\r\n     \r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> <span style=\"color:blue;\">Len<\/span>(strId) = 0<span style=\"color:blue;\"> Then<\/span>\r\n         strURL = strURL & \"\/\" & strId\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> IsMissing(varParameters)<span style=\"color:blue;\"> Then<\/span>\r\n         strURL = strURL & \"?\" & ParseParameters(varParameters)\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">Set<\/span> objRequest = <span style=\"color:blue;\">New<\/span> MSXML2.XMLHTTP60\r\n     <span style=\"color:blue;\">With<\/span> objRequest\r\n         .Open strMethod, strURL, <span style=\"color:blue;\">False<\/span>\r\n         .setRequestHeader \"Authorization\", \"Bearer \" & cStrAPIToken\r\n         .setRequestHeader \"Content-Type\", \"application\/json\"\r\n         .send strRequest\r\n     End <span style=\"color:blue;\">With<\/span>\r\n     strResponse = objRequest.responseText\r\n     intErrorNumber = objRequest.status\r\n     ...<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 2: Funktion zum Erstellen, Aktualisieren, Lesen und L&ouml;schen von Datens&auml;tzen in Tabellen (Teil 1)<\/span><\/b><\/p>\n<ul>\n<li><b>strTable<\/b>: Nimmt den Namen der zu bearbeitenden oder zu lesenden Tabelle entgegen.<\/li>\n<li><b>strMethod<\/b>: Nimmt die Methode f&uuml;r die auszuf&uuml;hrende Operation entgegen. Die zu verwendenden Werte inklusive weiterer ben&ouml;tigter Informationen beschreiben wir weiter unten.<\/li>\n<li><b>strRequest<\/b>: JSON-Dokument mit dem Inhalt der gew&uuml;nschten &Auml;nderung, also zum Beispiel der Daten f&uuml;r den anzulegenden Datensatz oder eines zu &auml;ndernden Datensatzes<\/li>\n<li><b>strId<\/b>: ID des zu bearbeitenden oder zu l&ouml;schenden Datensatzes<\/li>\n<li><b>strResponse<\/b>: Antwort der Rest-API, falls eine geliefert wird<\/li>\n<li><b>intErrorNumber<\/b>: Fehlernummer beziehungsweise Statuswert. Im Erfolgsfall <b>200<\/b>, die &uuml;brigen Werte sind im Code beschrieben.<\/li>\n<li><b>strErrorDescription<\/b>: Beschreibung des Fehlers, falls der Statuswert nicht <b>200 <\/b>lautet.<\/li>\n<li><b>varParameters<\/b>: Paramarray, das die Name-Wert-Paare f&uuml;r eventuelle URL-Parameter enth&auml;lt.<\/li>\n<\/ul>\n<p>Die Funktion stellt im ersten Schritt die URL f&uuml;r den Zugriff auf die Rest-API zusammen. Diese besteht aus der Basis-URL (<b>cStrBaseURL<\/b>), der ID f&uuml;r die zu verwendende Datenbank (<b>cStrBaseID<\/b>), einem Slash und dem Namen der Tabelle oder der ID der Tabelle &#8211; hier verwenden wir der Einfachheit halber den Namen.<\/p>\n<p>Die Verwendung der ID h&auml;tte den Vorteil, dass wir in der Datenbank auch einmal den Namen der Tabelle &auml;ndern k&ouml;nnten und der Code immer noch funktioniert.  Die IDs haben wir zuvor mit der Prozedur <b>GetAirtableTables <\/b>ermittelt. Die URL k&ouml;nnte nun wie folgt aussehen:<\/p>\n<pre>https:\/\/api.airtable.com\/v0\/app...RrJh\/Bestellungen<\/pre>\n<p>Wenn wir einen vorhandenen Datensatz bearbeiten oder l&ouml;schen wollen, k&ouml;nnen wir den Parameter <b>strID <\/b>verwenden. Damit &uuml;bergeben wir die ID des zu bearbeitenden Datensatzes.<\/p>\n<p>Diese bekommen wir, indem wir den Datensatz zun&auml;chst abfragen. Auch dazu sehen wir uns sp&auml;ter ein Beispiel an.<\/p>\n<p>Die ID aus <b>strID <\/b>h&auml;ngen wir, soweit vorhanden, im n&auml;chsten Schritt an die URL an. Die URL zum Bearbeiten oder L&ouml;schen eines Datensatzes w&uuml;rde wie folgt aussehen:<\/p>\n<pre>https:\/\/api.airtable.com\/v0\/app...RrJh\/Bestellungen\/recElpIemkgW9bBQC<\/pre>\n<p>Danach folgt die Verarbeitung eventueller Parameter. Wie wir einen solchen Parameter zusammenstellen, beschreiben wir weiter unten. <\/p>\n<p>Da wir auch mehrere Parameter &uuml;bergeben k&ouml;nnen, verwenden wir dazu ein <b>ParamArray<\/b>. Wir pr&uuml;fen im n&auml;chsten Schritt mit <b>IsMissung<\/b>, ob <b>varParameters<\/b> Parameter enth&auml;lt. Ist das der Fall, parsen wir diese in der Funktion <b>ParseParameters<\/b>, die wir im Anschluss beschreiben.<\/p>\n<p>Nun erstellen wir ein Objekt des Typs <b>MSXML2.XMLHTTP<\/b>. Seiner <b>Open<\/b>-Funktion &uuml;bergeben wir die Methode aus <b>strMethod <\/b>(also <b>GET<\/b>, <b>POST<\/b>, <b>PUT <\/b>oder <b>DELETE<\/b>), die vorher zusammengestellte URL und den Wert <b>False <\/b>f&uuml;r den Parameter <b>varAsync<\/b>, damit der Aufruf asynchron ausgef&uuml;hrt und der Code erst nach dem Beenden des Aufrufs weiterl&auml;uft.<\/p>\n<p>F&uuml;r das <b>XMLHTTP60<\/b>-Objekt legen wir nun mit <b>setRequestHeader <\/b>das Authentifizierungstoken fest sowie den Inhaltstyp, hier <b>application\/json<\/b>.<\/p>\n<p>Schlie&szlig;lich senden wir den Aufruf mit der <b>send<\/b>-Methode ab. Diese erh&auml;lt den Wert des Parameters <b>strRequest<\/b>. Diesen ben&ouml;tigen wir nur, wenn wir beispielsweise &Auml;nderungen an einem Datensatz durchf&uuml;hren oder einen Datensatz hinzuf&uuml;gen wollen. <b>strRequest<\/b> f&uuml;llen wir dann vor dem Aufruf mit einem JSON-Dokument, das die Werte f&uuml;r den zu &auml;ndernden oder anzulegenden Datensatz enth&auml;lt &#8211; mehr dazu in den Beispielen weiter unten.<\/p>\n<p>Die Antwort erhalten wir mit dem <b>responseText<\/b>-Parameter von <b>objXMLHTTP60<\/b>.<\/p>\n<p>Au&szlig;erdem liefert <b>status<\/b> eine Information dar&uuml;ber, ob der Aufruf erfolgreich war (<b>200<\/b>) oder ob ein Fehler ausgel&ouml;st wurde &#8211; beispielsweise durch ein ung&uuml;ltiges JSON-Dokument in <b>strRequest<\/b>, eine ung&uuml;ltige URL oder einen anderen Fehler.<\/p>\n<p>Den Wert der Eigenschaft <b>status <\/b>werten wir im zweiten Teil der Funktion aus Listing 3 in einer ersten <b>Select Case<\/b>-Bedingung aus, die zun&auml;chst pr&uuml;ft, ob <b>status<\/b> den Wert <b>200 <\/b>(erfolgreicher Aufruf, R&uuml;ckgabewert <b>True<\/b>) oder einen anderen Wert enth&auml;lt (R&uuml;ckgabewert <b>False<\/b>).<\/p>\n<pre>....\r\n     Select Case objRequest.status\r\n         <span style=\"color:blue;\">Case <\/span>200\r\n             Airtable_CRUD = <span style=\"color:blue;\">True<\/span>\r\n         <span style=\"color:blue;\">Case Else<\/span>\r\n             Select Case objRequest.status\r\n                 <span style=\"color:blue;\">Case <\/span>400\r\n                     strErrorDescription = \"Bad Request. The request encoding is invalid; the request can''t be \" _\r\n                         & \"parsed as a valid JSON.\"\r\n                 <span style=\"color:blue;\">Case <\/span>401\r\n                     strErrorDescription = \"Unauthorized. Accessing a protected resource without authorization \" _\r\n                         & \"or with invalid credentials.\"\r\n                 <span style=\"color:blue;\">Case <\/span>402\r\n                     strErrorDescription = \"Payment Required. The account associated with the API key making \" _\r\n                         & \"requests hits a quota that can be increased by upgrading the Airtable account plan.\"\r\n                 <span style=\"color:blue;\">Case <\/span>403\r\n                     strErrorDescription = \"Forbidden. Accessing a protected resource with API credentials that \" _\r\n                         & \"don''t have access to that resource.\"\r\n                 <span style=\"color:blue;\">Case <\/span>404\r\n                     strErrorDescription = \"<span style=\"color:blue;\">Not<\/span> Found. Route or resource is not found. This error is returned \" _\r\n                         & \"when the request hits an undefined route, or if the resource doesn''t exist (e.g. has \" _\r\n                         & \"been deleted).\"\r\n                 <span style=\"color:blue;\">Case <\/span>413\r\n                     strErrorDescription = \"Request Entity Too LargeThe request exceeded the maximum allowed \" _\r\n                         & \"payload size. You shouldn''t encounter this under normal use.\"\r\n                 <span style=\"color:blue;\">Case <\/span>422\r\n                     strErrorDescription = \"Invalid Request. The request data is invalid. This includes most of \" _\r\n                         & \"the base-specific validations. You will receive a detailed error message and code \" _\r\n                         & \"pointing to the exact issue.\"\r\n                 <span style=\"color:blue;\">Case <\/span>429\r\n                     strErrorDescription = \"Too Many Requests. The API is limited to 5 requests per second per \" _\r\n                         & \"base. You will receive a 429 status code and a message ''Rate limit exceeded. Please \" _\r\n                         & \"try again later'' and will need to wait 30 seconds before subsequent requests will \" _\r\n                         & \"succeed. To learn more about rate limits, please visit our Rate Limits guide.\"\r\n                 <span style=\"color:blue;\">Case <\/span>500\r\n                     strErrorDescription = \"Internal Server ErrorThe server encountered an unexpected condition.\"\r\n                 <span style=\"color:blue;\">Case <\/span>502\r\n                     strErrorDescription = \"Bad GatewayAirtable ''s servers are restarting or an unexpected outage is \" _\r\n                         & \"in progress. You should generally not receive this error, and requests are safe to retry.\"\r\n                 <span style=\"color:blue;\">Case <\/span>503\r\n                     strErrorDescription = \"Service UnavailableThe server could not process your request in time. \" _\r\n                         & \"The server could be temporarily unavailable, or it could have timed out processing \" _\r\n                         & \"your request. You should retry the request with backoffs.\"\r\n             <span style=\"color:blue;\">End Select<\/span>\r\n             Airtable_CRUD = <span style=\"color:blue;\">False<\/span>\r\n     <span style=\"color:blue;\">End Select<\/span>\r\n     <span style=\"color:blue;\">Set<\/span> objRequest = Nothing\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 3: Funktion zum Erstellen, Aktualisieren, Lesen und L&ouml;schen von Datens&auml;tzen in Tabellen (Teil 2)<\/span><\/b><\/p>\n<p>In diesem Fall folgt eine zweite <b>Select Case<\/b>-Bedingung, in der wir den Fehlercode auswerten und in den R&uuml;ckgabeparameter <b>strErrorDescription <\/b>eintragen.<\/p>\n<h2>Parameter f&uuml;r das Auflisten von Datens&auml;tzen<\/h2>\n<p>Wenn wir eine Liste von Datens&auml;tzen ermitteln wollen, &uuml;bergeben wir f&uuml;r den Parameter <b>strMethod <\/b>den Wert <b>GET<\/b>. Au&szlig;erdem k&ouml;nnen wir &uuml;ber <b>varParameters <\/b>mit Name-Wert-Paaren weitere Einstellungen f&uuml;r das Ermitteln der gew&uuml;nschten Datens&auml;tze festlegen. Diese lauten folgenderma&szlig;en:<\/p>\n<ul>\n<li><b>fields<\/b>: Hiermit k&ouml;nnen wir angegeben, welche Feldinhalte zur&uuml;ckgegeben werden sollen. Beispiel: <b>fields=Name <\/b>liefert nur die Daten des Feldes <b>Name <\/b>zur&uuml;ck. Wenn wir zwei Felder erhalten wollen, geben wir diese als zwei Parameter an: <b>&#8220;fields=Name&#8221;, &#8220;fields=Produkt&#8221;<\/b>.<\/li>\n<li><b>filterByFormula<\/b>: Hier geben wir einen Filterausdruck an. Ein Beispiel lautet: <b>{Name} = &#8221;Visual Basic Entwickler&#8221;<\/b>. Dieser muss URL-kodiert werden, sodass zusammen mit dem Parameternamen anschlie&szlig;end ein Ausdruck wie dieser herauskommt: <b>filterByFormula=%7BName%7D%20%3D%20%27Visual%20Basic%20Entwickler%27<\/b><\/li>\n<li><b>maxRecords<\/b>: Hiermit geben wir die maximale Anzahl zur&uuml;ckzuliefernder Datens&auml;tze an, zum Beispiel <b>maxRecords=5<\/b>.<\/li>\n<li><b>pageSize<\/b>: Dies gibt die Anzahl der Eintr&auml;ge pro zur&uuml;ckgelieferter Seite an, zum Beispiel <b>pageSize=10<\/b>. Der Standardwert lautet <b>100<\/b>. Dies ist auch der maximale Wert.<\/li>\n<li><b>sort<\/b>: Gibt an, in welcher Reihenfolge die Eintr&auml;ge sortiert werden. Der Wert f&uuml;r <b>sort<\/b> lautet beispielsweise <b>[{field: &#8220;Name&#8221;, direction: &#8220;desc&#8221;}] <\/b>f&uuml;r eine absteigende Sortierung nach dem Inhalt des Feldes <b>Name<\/b>. Auch dies muss wieder URL-kodiert werden, sodass sich insgesamt dieser Ausdruck ergeben w&uuml;rde: <b>sort=sort%5B0%5D%5Bfield%5D=Name <\/b>und <b>sort%5B0%5D%5Bdirection%5D=desc<\/b>.<\/li>\n<\/ul>\n<p>Wenn wir beispielsweise nur den Wert eines Feldes zur&uuml;ckgeben wollen, verwenden wir die folgenden Parameter:<\/p>\n<pre>\"sort[0][field]=Firma\", \"sort[0][direction]=asc\"<\/pre>\n<p>Diese m&uuml;ssen wir noch parsen, was wir in der Funktion <b>ParseParameters <\/b>erledigen (siehe Listing 4). Diese nimmt das ParamArray aus <b>varParameters <\/b>als einfachen <b>Variant<\/b>-Parameter entgegen.<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>CreateParameters(ByVal varParameters<span style=\"color:blue;\"> As Variant<\/span>)\r\n     <span style=\"color:blue;\">Dim <\/span>varParameter<span style=\"color:blue;\"> As Variant<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>lngPosEqual<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strParameterName<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strParameterValue<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strParameters<span style=\"color:blue;\"> As String<\/span>\r\n     For Each varParameter In varParameters\r\n         lngPosEqual = <span style=\"color:blue;\">InStr<\/span>(1, varParameter, \"=\")\r\n         strParameterName = <span style=\"color:blue;\">Left<\/span>(varParameter, lngPosEqual - 1)\r\n         strParameterValue = <span style=\"color:blue;\">Mid<\/span>(varParameter, lngPosEqual + 1)\r\n         strParameterName = <span style=\"color:blue;\">Replace<\/span>(strParameterName, \"fields\", \"fields%5B%5D\")\r\n         varParameter = strParameterName & \"=\" & URLEncode_UTF8(strParameterValue)\r\n         strParameters = strParameters & \"&\" & varParameter\r\n     <span style=\"color:blue;\">Next<\/span> varParameter\r\n     CreateParameters = strParameters\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 4: Funktion zum Zusammenstellen und Kodieren der Parameterwerte<\/span><\/b><\/p>\n<p>Sie durchl&auml;uft jedes Element aus <b>varParameters <\/b>in einer <b>For Each<\/b>-Schleife. Darin ermitteln wir die Position des Gleichheitszeichens und speichern sie in <b>lngPosEqual<\/b>. Mit der <b>Left<\/b>-Funktion lesen wir den Inhalt des aktuellen Parameters aus <b>varParameter <\/b>aus, der sich vor dem Gleichheitszeichen befindet, und speichern ihn in <b>strParameterName<\/b>. Mit der <b>Mid<\/b>-Funktion holen wir den Wert des Parameters hinter dem Gleichheitszeichen und schreiben ihn in <b>strParameterValue<\/b>.<\/p>\n<p>Lautet der Parametername <b>fields<\/b>, h&auml;ngen wir mit <b>%5B%5D<\/b> noch den Code f&uuml;r ein &ouml;ffnendes und ein schlie&szlig;endes Paar eckiger Klammern an (<b>[]<\/b>).<\/p>\n<p>Schlie&szlig;lich stellen wir den Parameter in <b>varParameter <\/b>aus <b>strParameterName <\/b>und dem kodierten Wert aus <b>strParameterValue <\/b>zusammen und h&auml;ngen ihn an die Liste der Parameter in <b>strParameters <\/b>an.<\/p>\n<p>Nach dem Durchlaufen aller Parameter geben wir <b>strParameters <\/b>als Funktionsergebnis zur&uuml;ck.<\/p>\n<h2>Funktionen der Rest-API f&uuml;r Tabellen und Tabellendaten<\/h2>\n<p>Die Rest-API stellt die folgenden Funktionen f&uuml;r den Zugriff auf die Tabellendaten bereit:<\/p>\n<ul>\n<li>Datens&auml;tze auflisten<\/li>\n<li>Datensatz ermitteln <\/li>\n<li>Datensatz erstellen<\/li>\n<li>Datensatz aktualisieren<\/li>\n<li>Datensatz l&ouml;schen<\/li>\n<\/ul>\n<p>F&uuml;r diese Funktionen sind verschiedene Parameter erforderlich, die wir in den folgenden Abschnitten erl&auml;utern.<\/p>\n<h2>Daten der Tabelle Bestellungen abrufen<\/h2>\n<p>Damit sind wir nun grunds&auml;tzlich ger&uuml;stet, die Daten der Tabelle <b>Bestellungen<\/b> aus der Airtable-Datenbank abzurufen.<\/p>\n<p>Dazu ben&ouml;tigen wir nur noch den passenden Aufruf der weitere oben bereits vorgestellten Funktion <b>Airtable_CRUD<\/b>.<\/p>\n<p>Diese rufen wir wie in der folgenden Beispielprozedur auf:<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>Test_Airtable_ListRecords()\r\n     <span style=\"color:blue;\">Dim <\/span>strRequest<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strResponse<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>intErrorNumber<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strErrorDescription<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strID<span style=\"color:blue;\"> As String<\/span>\r\n     \r\n     If Airtable_CRUD(\"Bestellungen\", \"GET\", strRequest, _\r\n             strID, strResponse, intErrorNumber, _\r\n             strErrorDescription) = <span style=\"color:blue;\">True<\/span> Then\r\n         <span style=\"color:blue;\">Debug.Print<\/span> GetJSONDOM(strResponse, <span style=\"color:blue;\">True<\/span>)\r\n     <span style=\"color:blue;\">Else<\/span>\r\n         <span style=\"color:blue;\">MsgBox<\/span> strErrorDescription & <span style=\"color:blue;\">vbCrLf<\/span> & <span style=\"color:blue;\">vbCrLf<\/span> _\r\n             & strResponse, <span style=\"color:blue;\">vbCr<\/span>itical, \"Fehler \" _\r\n             & intErrorNumber\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Hier &uuml;bergeben wir die folgenden Parameterwerte:<\/p>\n<ul>\n<li><b>strTable<\/b>: Bestellungen<\/li>\n<li><b>strMethod<\/b>: GET (da wir Daten holen wollen)<\/li>\n<li><b>strResponse<\/b>: Variable zur R&uuml;ckgabe des Ergebnisses<\/li>\n<li><b>intErrorNumber<\/b>: Variable f&uuml;r eventuelle Fehlernummern<\/li>\n<li><b>strErrorDescription<\/b>: Variable f&uuml;r eventuelle Fehlermeldungen<\/li>\n<\/ul>\n<p>Damit erhalten wir zun&auml;chst die Ausgabe der Funktion <b>GetJSONDOM<\/b> f&uuml;r den Wert aus <b>strResponse<\/b>, die f&uuml;r den ersten der zur&uuml;ckgelieferten Datens&auml;tze wie in Listing 5 aussieht.<\/p>\n<pre>objJson.Item(\"records\").Item(1).Item(\"id\"): recElpIemkgW9bBQC\r\nobjJson.Item(\"records\").Item(1).Item(\"createdTime\"): 2025-10-09T19:22:45.000Z\r\nobjJson.Item(\"records\").Item(1).Item(\"fields\").Item(\"Name\"): Access [basics] 31.12.2024\r\nobjJson.Item(\"records\").Item(1).Item(\"fields\").Item(\"Bestelldatum\"): 2024-12-31T20:22:00.000Z\r\nobjJson.Item(\"records\").Item(1).Item(\"fields\").Item(\"Produkt\"): Access [basics]\r\nobjJson.Item(\"records\").Item(1).Item(\"fields\").Item(\"Firma\"): Test GmbH\r\nobjJson.Item(\"records\").Item(1).Item(\"fields\").Item(\"Vorname\"): Lisa\r\nobjJson.Item(\"records\").Item(1).Item(\"fields\").Item(\"Nachname\"): Schmitz\r\nobjJson.Item(\"records\").Item(1).Item(\"fields\").Item(\"Stra&szlig;e\"): Teststr. 2\r\nobjJson.Item(\"records\").Item(1).Item(\"fields\").Item(\"PLZ\"): 80899\r\nobjJson.Item(\"records\").Item(1).Item(\"fields\").Item(\"Ort\"): M&uuml;nchen\r\nobjJson.Item(\"records\").Item(1).Item(\"fields\").Item(\"Land\"): Deutschland\r\nobjJson.Item(\"records\").Item(1).Item(\"fields\").Item(\"Anrede\"): Frau\r\nobjJson.Item(\"records\").Item(1).Item(\"fields\").Item(\"E-Mail\"): lisa@schmitz.de\r\nobjJson.Item(\"records\").Item(1).Item(\"fields\").Item(\"Bestellstatus\"): Eingegangen<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 5: Ergebnis des Abrufs der Tabelleninhalte f&uuml;r den ersten at<\/span><\/b><\/p>\n<p>Wir k&ouml;nnen also zum Beispiel mit dem folgenden Ausdruck auf die Datensatz-ID des ersten Elements zugreifen:<\/p>\n<pre>objJson.Item(\"records\").Item(1).Item(\"id\")<\/pre>\n<p>Nun wollen wir die Prozedur so erweitern, dass die Daten direkt im Direktbereich ausgegeben werden k&ouml;nnen. Dazu f&uuml;gen wir die folgenden beiden Deklarationszeilen hinzu:<\/p>\n<pre><span style=\"color:blue;\">Dim <\/span>i<span style=\"color:blue;\"> As Integer<\/span>\r\n<span style=\"color:blue;\">Dim <\/span>objJSON<span style=\"color:blue;\"> As Object<\/span> <\/pre>\n<p>Danach addieren wir die folgenden Zeilen inklusive einer <b>For&#8230;Next<\/b>-Schleife. Zuvor initialisieren wir noch die Variable <b>objJSON <\/b>mit dem Ergebnis der Prozedur <b>ParseJSON<\/b>:<\/p>\n<pre><span style=\"color:blue;\">Set<\/span> objJSON = ParseJson(strResponse) \r\nFor i = 1 To objJSON.Item(\"records\").Count\r\n     <span style=\"color:blue;\">With<\/span> objJSON.Item(\"records\").Item(i)\r\n         <span style=\"color:blue;\">Debug.Print<\/span> .Item(\"id\")\r\n         <span style=\"color:blue;\">Debug.Print<\/span> .Item(\"createdTime\")\r\n         <span style=\"color:blue;\">Debug.Print<\/span> .Item(\"fields\").Item(\"Name\")\r\n         <span style=\"color:blue;\">Debug.Print<\/span> .Item(\"fields\").Item(\"Bestelldatum\")\r\n         <span style=\"color:blue;\">Debug.Print<\/span> .Item(\"fields\").Item(\"Produkt\")\r\n         <span style=\"color:blue;\">Debug.Print<\/span> .Item(\"fields\").Item(\"Firma\")\r\n         <span style=\"color:blue;\">Debug.Print<\/span> .Item(\"fields\").Item(\"Vorname\")\r\n         <span style=\"color:blue;\">Debug.Print<\/span> .Item(\"fields\").Item(\"Nachname\")\r\n         <span style=\"color:blue;\">Debug.Print<\/span> .Item(\"fields\").Item(\"Stra&szlig;e\")\r\n         <span style=\"color:blue;\">Debug.Print<\/span> .Item(\"fields\").Item(\"PLZ\")\r\n         <span style=\"color:blue;\">Debug.Print<\/span> .Item(\"fields\").Item(\"Ort\")\r\n         <span style=\"color:blue;\">Debug.Print<\/span> .Item(\"fields\").Item(\"Land\")\r\n         <span style=\"color:blue;\">Debug.Print<\/span> .Item(\"fields\").Item(\"Anrede\")\r\n         <span style=\"color:blue;\">Debug.Print<\/span> .Item(\"fields\").Item(\"E-Mail\")\r\n         <span style=\"color:blue;\">Debug.Print<\/span> .Item(\"fields\").Item(\"Bestellstatus\")\r\n     End <span style=\"color:blue;\">With<\/span>\r\n<span style=\"color:blue;\">Next<\/span> i<\/pre>\n<p>Die Anzahl der Datens&auml;tze ermitteln wir mit <b>objJSON.Item(&#8220;records&#8221;).Count<\/b> und durchlaufen eine <b>For&#8230;Next<\/b>-Schleife von <b>1 <\/b>bis zu diesem Wert.<\/p>\n<p>Innerhalb der Schleife referenzieren wir das aktuelle Element mit <b>With objJSON.Item(&#8220;records&#8221;).Item(i) <\/b>und geben darin beispielsweise mit <b>Debug.Print .Item(&#8220;id&#8221;)<\/b> den Wert des jeweiligen Feldes im Direktbereich aus.<\/p>\n<h2>Daten in eine Access-Tabelle eintragen<\/h2>\n<p>Um die Daten aus der Airtable-Tabelle in eine Access-Tabelle einzutragen, erstellen wir als Erstes die Tabelle <b>tblBestellungenAusAirtable <\/b>aus Bild 11. Hier f&uuml;gen wir dem Feld <b>AirtableID<\/b>, das den eindeutigen Bezeichner aus der Airtable-Tabelle aufnehmen soll, mit einem eindeutigen Index, damit kein Eintrag doppelt eingetragen wird.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2025_04\/pic_482_010.png\" alt=\"Tabelle zum Speichern der Bestellungen aus Airtable\" width=\"499,6267\" height=\"500,4525\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 10: Tabelle zum Speichern der Bestellungen aus Airtable<\/span><\/b><\/p>\n<p>Dann erweitern wir unsere Prozedur um die Deklaration der <b>Database<\/b>&#8211; und <b>Recordset<\/b>-Variablen und initialisieren diese:<\/p>\n<pre><span style=\"color:blue;\">Dim <\/span>db<span style=\"color:blue;\"> As <\/span>DAO.Database\r\n<span style=\"color:blue;\">Dim <\/span>rst<span style=\"color:blue;\"> As <\/span>DAO.Recordset\r\n<span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n<span style=\"color:blue;\">Set<\/span> rst = db.OpenRecordset(\"tblBestellungenVonAirtable\", _\r\n     dbOpenDynaset)<\/pre>\n<p>Danach erweitern wir die Schleife wie folgt, wobei wir jeweils einen neuen Datensatz in der Tabelle <b>tblBestellungenVonAirtable <\/b>hinzuf&uuml;gen und dann die Felder mit den Werten aus dem JSON-Objekt f&uuml;llen:<\/p>\n<pre>For i = 1 To objJSON.Item(\"records\").Count\r\n     rst.Add<span style=\"color:blue;\">New<\/span>\r\n     <span style=\"color:blue;\">With<\/span> objJSON.Item(\"records\").Item(i)\r\n         rst!AirtableID = .Item(\"id\")\r\n         rst!Bestelldatum = GetDateFromAirtable( _\r\n            .Item(\"fields\").Item(\"Bestelldatum\"))\r\n         rst!Produkt = .Item(\"fields\").Item(\"Produkt\")\r\n         rst!Firma = .Item(\"fields\").Item(\"Firma\")\r\n         rst!Anrede = .Item(\"fields\").Item(\"Anrede\")\r\n         rst!Vorname = .Item(\"fields\").Item(\"Vorname\")\r\n         rst!Nachname = .Item(\"fields\").Item(\"Nachname\")\r\n         rst!Strasse = .Item(\"fields\").Item(\"Stra&szlig;e\")\r\n         rst!PLZ = .Item(\"fields\").Item(\"PLZ\")\r\n         rst!Ort = .Item(\"fields\").Item(\"Ort\")\r\n         rst!Land = .Item(\"fields\").Item(\"Land\")\r\n         rst!EMail = .Item(\"fields\").Item(\"E-Mail\")\r\n         rst!Bestellstatus = _\r\n             .Item(\"fields\").Item(\"Bestellstatus\")        \r\n     End <span style=\"color:blue;\">With<\/span>\r\n     rst.Update\r\n<span style=\"color:blue;\">Next<\/span> i<\/pre>\n<p>Das Ergebnis nach einem erneuten Aufruf finden wir in Bild 10. Damit k&ouml;nnen wir arbeiten &#8211; die Daten liegen in einer Access-Importtabelle vor und wir k&ouml;nnten diese nun zu den eigentlichen Tabellen unserer Bestellverwaltung hinzuf&uuml;gen, um diese weiterzuverarbeiten.<\/p>\n<h2>Einlesen weiterer Bestellungen<\/h2>\n<p>Ein kleines Problem haben wir noch, wenn wir am n&auml;chsten Tag erneut die Bestellungen einlesen wollen. Die Prozedur w&uuml;rde nun versuchen, die bereits vorhandenen Datens&auml;tze erneut einzulesen, was zu Fehlern f&uuml;hrt, weil die IDs der bereits eingelesenen Datens&auml;tze schon in dem Feld <b>AirtableID <\/b>mit dem eindeutigen Index enthalten sind.<\/p>\n<p>Wir haben also zwei M&ouml;glichkeiten:<\/p>\n<ul>\n<li>Wir pr&uuml;fen bei jedem Hinzuf&uuml;gen, ob der Datensatz bereits vorhanden ist und f&uuml;gen diesen nur hinzu, wenn dies nicht der Fall ist.<\/li>\n<li>Oder wir passen den Abruf der Daten so an, dass nur die aktuellen Daten eingelesen werden.<\/li>\n<\/ul>\n<p>Den ersten Schritt sollten wir auf jeden Fall durchf&uuml;hren, allein weil wir so eventuellen Fehlermeldungen vorbeugen. <\/p>\n<p>Dazu fassen wir den kompletten Bereich von <b>rst.AddNew <\/b>bis <b>rst.Update <\/b>in die folgende <b>If&#8230;Then<\/b>-Bedingung ein:<\/p>\n<pre>If IsNull(DLookup(\"AirtableID\", _\r\n     \"tblBestellungenVonAirtable\", \"AirtableID = ''\" _\r\n     & objJSON.Item(\"records\").Item(i).Item(\"id\") & \"''\")) Then\r\n     ...\r\n<span style=\"color:blue;\">End If<\/span><\/pre>\n<p>Hier pr&uuml;fen wir mit <b>DLookup<\/b>, ob der ID-Wert des Airtable-Datensatzes bereits im Feld <b>AirtableID <\/b>vorhanden ist.<\/p>\n<p>Au&szlig;erdem wollen wir die Rest-API-Abfrage so anpassen, dass nur die neu hinzugef&uuml;gten Eintr&auml;ge erfasst werden.<\/p>\n<p>Dazu haben wir gegen&uuml;ber der bisherigen L&ouml;sung noch eine &Auml;nderung durchgef&uuml;hrt: Wir haben noch ein Feld namens <b>ErstelltAm <\/b>zur Tabelle hinzugef&uuml;gt, das wir mit dem Feld <b>createdTime <\/b>der Airtable-Datenbank gef&uuml;llt haben. Hier ermitteln wir den aktuellen Wert und verwenden diesen zur Anpassung des Aufrufs in der Form, dass wir nur Bestellungen nach diesem Zeitpunkt neu abfragen.<\/p>\n<p>Das erledigen wir mit den folgenden Zeilen, die wir vor dem Aufruf von <b>Airtable_CRUD<\/b> einf&uuml;gen. Wir holen das Datum des neuesten Eintrags und nutzen die Hilfsfunktion <b>GetAirtableDateFromDate<\/b>, um diese in ein Format wie <b>2025-10-09T19:22:45.000Z <\/b>zu &uuml;bersetzen:<\/p>\n<pre><span style=\"color:blue;\">Dim <\/span>datCreatedTime<span style=\"color:blue;\"> As Date<\/span>\r\n<span style=\"color:blue;\">Dim <\/span>strCreatedTime<span style=\"color:blue;\"> As String<\/span>\r\ndatCreatedTime = DMax(\"ErstelltAm\", _\r\n     \"tblBestellungenVonAirtable\")\r\nstrCreatedTime = GetAirtableDateFromDate(datCreatedTime)<\/pre>\n<p>Der neue Aufruf lautet nun:<\/p>\n<pre><span style=\"color:blue;\">If <\/span>Airtable_CRUD(\"Bestellungen\", \"GET\", strRequest, strID, strResponse, intErrorNumber, strErrorDescription, \"filterByFormula=IS_AFTER(CREATED_TIME(), \"\"\" & strCreatedTime & \"\"\")\") = <span style=\"color:blue;\">True<\/span><span style=\"color:blue;\"> Then<\/span><\/pre>\n<p>Wir f&uuml;gen also einen Parameter wie folgt f&uuml;r <b>varParameters<\/b> hinzu:<\/p>\n<pre>filterByFormula=IS_AFTER(CREATED_TIME(), \"2025-10-09T19:22:45.000Z\")<\/pre>\n<p>Damit erhalten wir alle Bestellungen, die nach dem Datum der letzten eingelesenen Bestellung hinzugef&uuml;gt wurden.<\/p>\n<h2>Datensatz zur Airtable-Tabelle hinzuf&uuml;gen<\/h2>\n<p>Das Hinzuf&uuml;gen eines neuen Datensatzes zur Airtable-Tabelle <b>Bestellungen <\/b>ist ebenfalls m&ouml;glich. Die Herausforderung hier lautet, dass wir nun nicht den JSON-Response auswerten m&uuml;ssen, was einfach ist, weil wir die Funktion <b>GetJSONDOM <\/b>haben und damit leicht die einzelnen Elemente identifizieren k&ouml;nnen. Stattdessen m&uuml;ssen wir einen JSON-Request zusammenstellen, der die Daten f&uuml;r den anzulegenden Datensatz enth&auml;lt. Das k&ouml;nnen wir durch String-Verkettung machen, aber wir nutzen einen eleganteren Weg.<\/p>\n<p>Erst einmal legen wir einen neuen Datensatz in der Tabelle <b>tblBestellungenVonAirtable <\/b>an, den wir in die Airtable-Tabelle <b>Bestellungen <\/b>hochladen wollen. Dieser enth&auml;lt noch keinen Wert in den Feldern <b>AirtableID <\/b>und <b>ErstelltAm<\/b>.<\/p>\n<p>Dann holen wir uns die Syntax f&uuml;r den JSON-Request, mit dem wir einen neuen Datensatz anlegen k&ouml;nnen (siehe Bild 12). Interessant ist hier der Bereich ab <b>&#8211;data<\/b>. Diesen m&uuml;ssen wir f&uuml;r unseren neuen Datensatz zusammenstellen.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2025_04\/pic_482_012.png\" alt=\"Anleitung zum Zusammenstellen des JSON-Requests zum Anlegen eines Datensatzes in der Tabelle Bestellungen in Airtable\" width=\"700\" height=\"453,7975\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 11: Anleitung zum Zusammenstellen des JSON-Requests zum Anlegen eines Datensatzes in der Tabelle Bestellungen in Airtable<\/span><\/b><\/p>\n<p>Den ersten Teil der Funktion <b>NeuenDatensatzInAirtableAnlegen <\/b>finden wir in Listing 6.<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>NeuenDatenatzInAirtableAnlegen(lngBestellungID<span style=\"color:blue;\"> As Long<\/span>)<span style=\"color:blue;\"> As Boolean<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>db<span style=\"color:blue;\"> As <\/span>DAO.Database\r\n     <span style=\"color:blue;\">Dim <\/span>rst<span style=\"color:blue;\"> As <\/span>DAO.Recordset\r\n     <span style=\"color:blue;\">Dim <\/span>strRequest<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>objJSON<span style=\"color:blue;\"> As <\/span>Scripting.Dictionary\r\n     <span style=\"color:blue;\">Dim <\/span>colRecords<span style=\"color:blue;\"> As <\/span>VBA.Collection\r\n     <span style=\"color:blue;\">Dim <\/span>dicFields<span style=\"color:blue;\"> As <\/span>Scripting.Dictionary\r\n     <span style=\"color:blue;\">Dim <\/span>dicField<span style=\"color:blue;\"> As <\/span>Scripting.Dictionary\r\n     <span style=\"color:blue;\">Dim <\/span>strID<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strResponse<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>intErrorNumber<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strErrorDescription<span style=\"color:blue;\"> As String<\/span>\r\n     \r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n     <span style=\"color:blue;\">Set<\/span> rst = db.OpenRecordset(\"SELECT * FROM tblBestellungenVonAirtable WHERE BestellungID = \" _\r\n         & lngBestellungID, dbOpenDynaset)\r\n     <span style=\"color:blue;\">Set<\/span> objJSON = <span style=\"color:blue;\">New<\/span> Dictionary\r\n     <span style=\"color:blue;\">Set<\/span> colRecords = <span style=\"color:blue;\">New<\/span> Collection\r\n     objJSON.Add \"records\", colRecords\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> rst.EOF<span style=\"color:blue;\"> Then<\/span>\r\n         <span style=\"color:blue;\">Set<\/span> dicFields = <span style=\"color:blue;\">New<\/span> Scripting.Dictionary\r\n         colRecords.Add dicFields\r\n         <span style=\"color:blue;\">Set<\/span> dicField = <span style=\"color:blue;\">New<\/span> Scripting.Dictionary\r\n         dicFields.Add \"fields\", dicField\r\n         dicField.Add \"Bestelldatum\", CStr(GetAirtableDateFromDate(rst!Bestelldatum))\r\n         dicField.Add \"Produkt\", CStr(rst!Produkt)\r\n         dicField.Add \"Firma\", CStr(rst!Firma)\r\n         dicField.Add \"Anrede\", CStr(rst!Anrede)\r\n         dicField.Add \"Vorname\", CStr(rst!Vorname)\r\n         dicField.Add \"Nachname\", CStr(rst!Nachname)\r\n         dicField.Add \"Stra&szlig;e\", CStr(rst!Strasse)\r\n         dicField.Add \"PLZ\", CStr(rst!PLZ)\r\n         dicField.Add \"Ort\", CStr(rst!Ort)\r\n         dicField.Add \"Land\", CStr(rst!Land)\r\n         dicField.Add \"E-Mail\", CStr(rst!EMail)\r\n         dicField.Add \"Bestellstatus\", CStr(rst!Bestellstatus)\r\n         \r\n         JsonOptions.AllowUnquotedKeys = <span style=\"color:blue;\">True<\/span>\r\n         strRequest = ConvertToJson(objJSON)\r\n         ...     <\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 6: Anlegen eines neuen Datenatzes (Teil 1)<\/span><\/b><\/p>\n<p>Die Funktion nimmt die <b>BestellungID <\/b>des zu &uuml;bertragenden Datensatzes entgegen und liefert einen <b>Boolean<\/b>-Wert zur&uuml;ck, der &uuml;ber Erfolg oder Misserfolg R&uuml;ckmeldung gibt.<\/p>\n<p>Wir erstellen eine Referenz zum aktuellen <b>Database<\/b>-Objekt und holen uns ein Recordset mit dem zu &uuml;bertragenden Datensatz. Die Aufgabe ist nun, die Daten des Datensatzes in folgende Struktur zu bringen:<\/p>\n<pre>{\r\n   \"records\": [\r\n     {\r\n       \"fields\": {\r\n         \"Bestelldatum\": \"2025-10-11T00:00:00.000Z\",\r\n         \"Produkt\": \"Access, SQL und Cloud Automation\",\r\n         \"Firma\": \"M&uuml;ller GbR\",\r\n         \"Anrede\": \"Herr\",\r\n         \"Vorname\": \"Dieter\",\r\n         \"Nachname\": \"M&uuml;ller\",\r\n         \"Stra&szlig;e\": \"Testweg 1\",\r\n         \"PLZ\": \"12321\",\r\n         \"Ort\": \"Berlin\",\r\n         \"Land\": \"Deutschland\",\r\n         \"E-Mail\": \"dieter@mueller.de\",\r\n         \"Bestellstatus\": \"Bezahlt\"\r\n       }\r\n     }\r\n   ]\r\n}<\/pre>\n<p>Dazu erstellen wir ein erstes <b>Dictionary<\/b>-Objekt, das die &auml;u&szlig;eren geschweiften Klammern des JSON-Requests enth&auml;lt. Diesem f&uuml;gen wir &uuml;ber die <b>Add<\/b>-Methode das <b>Collection<\/b>-Objekt <b>colRecords <\/b>mit dem Namen <b>records <\/b>hinzu. Merke: <b>Dictionary<\/b>-Objekte liefern immer Elemente in geschweiften Klammern, <b>Collection<\/b>-Objekte solche in eckigen Klammern.<\/p>\n<p>Wir haben erst eine geschweifte Klammer, die das komplette JSON-Dokument einfasst, dann eine eckige Klammer, die einen oder mehrere Datens&auml;tze aufnehmen soll.<\/p>\n<p>Diese kommen wiederum in geschweiften Klammern, also f&uuml;gen wir <b>colRecords <\/b>ein weiteres <b>Dictionary<\/b>-Objekt hinzu, diesmal namens <b>dicFields<\/b>. Jedes mit <b>fields <\/b>betitelte Element enth&auml;lt wieder eine geschweifte Klammer, die schlie&szlig;lich die Zuordnung der einzelnen Felder aufnimmt. Also weisen wir, um die Struktur nachzubilden, dem Dictionary <b>dicFields <\/b>noch ein weiteres Dictionary namens <b>dicField <\/b>hinzu.<\/p>\n<p>F&uuml;r diese k&ouml;nnen wir nun die Name-Wert-Paare f&uuml;r die Felder und die Feldinhalte hinzuf&uuml;gen.<\/p>\n<p><b>Wichtig: <\/b>Die Inhalte m&uuml;ssen, wenn sie im JSON-Dokument als Zeichenketten in Anf&uuml;hrungszeichen abgebildet werden sollen, unbedingt mit der Funktion <b>CStr<\/b> behandelt werden!<\/p>\n<p>F&uuml;r das Bestelldatum wandeln wir das Datum aus dem entsprechenden Feld noch mit der Funktion <b>GetAirtableDateFromDate <\/b>in das entsprechende Format um. Auf die gleiche Weise f&uuml;gen wir die Name-Wert-Paare f&uuml;r die &uuml;brigen Felder hinzu.<\/p>\n<p>Nun wollen wir aus unserem aus <b>Dictionary<\/b>&#8211; und <b>Collection<\/b>-Objekten bestehenden Konstrukt ein JSON-Dokument machen, was wir mit der Funktion <b>ConvertToJson <\/b>erledigen. Zuvor m&uuml;ssen wir allerdings noch die Eigenschaft <b>JsonOptions.AllowUnquotedKeys <\/b>auf <b>True <\/b>einstellen, damit Umlaute und andere Sonderzeichen nicht kodiert werden &#8211; das f&uuml;hrt in der Airtable-Rest-API zu Fehlern.<\/p>\n<p>Danach tragen wir das Ergebnis von <b>ConvertToJson <\/b>in die Variable <b>strRequest <\/b>ein.<\/p>\n<p>Im zweiten Teil folgt die &Uuml;bergabe an die Funktion <b>Airtable_CRUD <\/b>(siehe Listing 7).<\/p>\n<pre>         ...\r\n         If Airtable_CRUD(\"Bestellungen\", \"POST\", strRequest, strID, strResponse, intErrorNumber, strErrorDescription) _\r\n                 = <span style=\"color:blue;\">True<\/span> Then\r\n             <span style=\"color:blue;\">Set<\/span> objJSON = ParseJson(strResponse)\r\n             <span style=\"color:blue;\">Debug.Print<\/span> GetJSONDOM(strResponse, <span style=\"color:blue;\">True<\/span>)\r\n             rst.Edit\r\n             rst!AirtableID = objJSON.Item(\"records\").Item(1).Item(\"id\")\r\n             rst!ErstelltAm = mdlTools.GetDateFromAirtable(objJSON.Item(\"records\").Item(1).Item(\"createdTime\"))\r\n             rst.Update\r\n             NeuenDatenatzInAirtableAnlegen = <span style=\"color:blue;\">True<\/span>\r\n         <span style=\"color:blue;\">Else<\/span>\r\n             <span style=\"color:blue;\">Debug.Print<\/span> intErrorNumber, strErrorDescription\r\n             <span style=\"color:blue;\">Debug.Print<\/span> strResponse\r\n             NeuenDatenatzInAirtableAnlegen = <span style=\"color:blue;\">False<\/span>\r\n         <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 7: Anlegen eines neuen Datenatzes (Teil 2)<\/span><\/b><\/p>\n<p>Hier &uuml;bergeben wir als Parameter den Tabellennamen, als Methode <b>POST<\/b>, da wir Daten hinzuf&uuml;gen m&ouml;chten, den Request aus <b>strRequest <\/b>sowie die &uuml;blichen leeren Variablen. Die Variable <b>strID <\/b>wird diesmal genutzt, um die Airtable-ID des neuen Datensatzes zu erhalten.<\/p>\n<p>Sobald wir das Ergebnis zur&uuml;ckerhalten und dieses <b>True <\/b>lautet, erstellen wir ein neues <b>objJSON<\/b>-Objekt und f&uuml;llen es &uuml;ber die Funktion <b>ParseJson <\/b>mit dem Inhalt von <b>strResponse<\/b>.<\/p>\n<p>Um die R&uuml;ckgabe zu analysieren, geben wir mit <b>GetJSONDOM <\/b>die Ausdr&uuml;cke aus, mit denen wir auf die Inhalte von <b>objJSON <\/b>zugreifen k&ouml;nnen.<\/p>\n<p>Danach starten wir die Bearbeitung des &uuml;bertragenen Datensatzes, um das Feld <b>AirtableID <\/b>mit der von Airtable vergebenen ID und das Feld <b>ErstelltAm <\/b>mit dem Erstellungsdatum des Datensatzes in Airtable zu f&uuml;llen. Schlie&szlig;lich stellen wir den R&uuml;ckgabewert auf <b>True <\/b>ein.<\/p>\n<p>Falls der Aufruf nicht erfolgreich war, liefert die Funktion <b>Airtable_CRUD <\/b>einen Fehlercode und eine Fehlermeldung zur&uuml;ck, die wir im Direktbereich ausgeben.<\/p>\n<p>Der Aufruf dieser Funktion k&ouml;nnte beispielsweise wie folgt lauten:<\/p>\n<pre><span style=\"color:blue;\">Call<\/span> NeuenDatenatzInAirtableAnlegen(10)<\/pre>\n<h2>Airtable-Datens&auml;tze aktualisieren<\/h2>\n<p>Um einen Airtable-Datensatz zu aktualisieren, ben&ouml;tigen wir seine Airtable-ID.<\/p>\n<p>Da wir diese in der Access-Tabelle nach dem Einlesen und auch nach dem Hochladen von Datens&auml;tzen gespeichert haben, k&ouml;nnen wir also Daten in Access &auml;ndern und die ge&auml;nderten Daten zu Airtable &uuml;bertragen.<\/p>\n<p>Wie der Aufruf und die zu &uuml;bergebenden Daten aussehen, k&ouml;nnen wir uns wieder in der Dokumentation ansehen. Der Aufbau des ist &auml;hnlich dem, den wir zum Hochladen eines neuen Datensatzes ben&ouml;tigen.<\/p>\n<p>Der einzige Unterschied im zu &uuml;bergebenden JSON-Request ist, dass wir das Element <b>id <\/b>mit der Airtable-ID mit &uuml;bergeben m&uuml;ssen:<\/p>\n<pre>{\r\n   \"records\": [\r\n     {\r\n       \"id\": \"recElpIemkgW9bBQC\",\r\n       \"fields\": {\r\n         \"Bestelldatum\": \"2024-12-31T20:22:00.000Z\",\r\n         \"Produkt\": \"Access [basics]\",\r\n         \"Firma\": \"Test GmbH\",\r\n         \"Anrede\": \"Frau\",\r\n         \"Vorname\": \"Lisa\",\r\n         \"Nachname\": \"Schmitz\",\r\n         \"Stra&szlig;e\": \"Teststr. 2\",\r\n         \"PLZ\": \"80899\",\r\n         \"Ort\": \"M&uuml;nchen\",\r\n         \"Land\": \"Deutschland\",\r\n         \"E-Mail\": \"lisa@schmitz.de\",\r\n         \"Bestellstatus\": \"Eingegangen\"\r\n       }\r\n     }\r\n   ]\r\n}<\/pre>\n<p>Au&szlig;erdem verwenden wir hier nicht die <b>POST<\/b>-Methode, sondern die <b>PATCH<\/b>-Methode.<\/p>\n<p>Die Funktion zum Aktualisieren von Datens&auml;tzen hei&szlig;t <b>DatenatzInAirtableAktualisieren<\/b> und ist in Listing 8 zu finden.<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>DatenatzInAirtableAktualisieren(lngBestellungID<span style=\"color:blue;\"> As Long<\/span>)<span style=\"color:blue;\"> As Boolean<\/span>\r\n     '' Deklaration wie in NeuenDatenatzInAirtableAnlegen\r\n    \r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n     <span style=\"color:blue;\">Set<\/span> rst = db.OpenRecordset(\"SELECT * FROM tblBestellungenVonAirtable WHERE BestellungID = \" _\r\n         & lngBestellungID, dbOpenDynaset)\r\n     <span style=\"color:blue;\">Set<\/span> objJSON = <span style=\"color:blue;\">New<\/span> Dictionary\r\n     <span style=\"color:blue;\">Set<\/span> colRecords = <span style=\"color:blue;\">New<\/span> Collection\r\n     objJSON.Add \"records\", colRecords\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> rst.EOF<span style=\"color:blue;\"> Then<\/span>\r\n        <span style=\"color:blue;\">If <\/span>IsNull(rst!AirtableID)<span style=\"color:blue;\"> Then<\/span>\r\n             <span style=\"color:blue;\">MsgBox<\/span> \"Datensatz nicht in Airtable hinterlegt.\"\r\n             <span style=\"color:blue;\">Exit Function<\/span>\r\n         <span style=\"color:blue;\">End If<\/span>\r\n         <span style=\"color:blue;\">Set<\/span> dicFields = <span style=\"color:blue;\">New<\/span> Scripting.Dictionary\r\n         colRecords.Add dicFields\r\n         <span style=\"color:blue;\">Set<\/span> dicField = <span style=\"color:blue;\">New<\/span> Scripting.Dictionary\r\n         dicFields.Add \"id\", CStr(rst!AirtableID)\r\n         dicFields.Add \"fields\", dicField\r\n         dicField.Add \"Bestelldatum\", CStr(GetAirtableDateFromDate(rst!Bestelldatum))\r\n         dicField.Add \"Produkt\", CStr(rst!Produkt)\r\n         dicField.Add \"Firma\", CStr(rst!Firma)\r\n         dicField.Add \"Anrede\", CStr(rst!Anrede)\r\n         dicField.Add \"Vorname\", CStr(rst!Vorname)\r\n         dicField.Add \"Nachname\", CStr(rst!Nachname)\r\n         dicField.Add \"Stra&szlig;e\", CStr(rst!Strasse)\r\n         dicField.Add \"PLZ\", CStr(rst!PLZ)\r\n         dicField.Add \"Ort\", CStr(rst!Ort)\r\n         dicField.Add \"Land\", CStr(rst!Land)\r\n         dicField.Add \"E-Mail\", CStr(rst!EMail)\r\n         dicField.Add \"Bestellstatus\", CStr(rst!Bestellstatus)\r\n         \r\n         JsonOptions.AllowUnquotedKeys = <span style=\"color:blue;\">True<\/span>\r\n         strRequest = ConvertToJson(objJSON)\r\n         \r\n         If Airtable_CRUD(\"Bestellungen\", \"PATCH\", strRequest, strID, strResponse, intErrorNumber, _\r\n                 strErrorDescription) = <span style=\"color:blue;\">True<\/span> Then\r\n             DatenatzInAirtableAktualisieren = <span style=\"color:blue;\">True<\/span>\r\n         <span style=\"color:blue;\">Else<\/span>\r\n             <span style=\"color:blue;\">Debug.Print<\/span> intErrorNumber, strErrorDescription\r\n             <span style=\"color:blue;\">Debug.Print<\/span> strResponse\r\n             DatenatzInAirtableAktualisieren = <span style=\"color:blue;\">False<\/span>\r\n         <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Function<\/span>     <\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 8: Aktualisieren eines neuen Datenatzes<\/span><\/b><\/p>\n<p>Die Deklarationszeilen haben wir dort nicht abgebildet, da diese mit denen aus der vorherigen Funktion &uuml;bereinstimmen.<\/p>\n<p>Wir gehen analog zur Funktion <b>NeuenDatenatzInAirtableAnlegen <\/b>vor. Allerdings m&uuml;ssen wir hier pr&uuml;fen, ob im Feld <b>AirtableID<\/b> des zu aktualisierenden Datensatzes &uuml;berhaupt ein Wert steht. Falls nicht, wird die Funktion mit einer Meldung abgebrochen.<\/p>\n<p>Danach stellen wir das JSON-Dokument zusammen und f&uuml;gen im Objekt <b>dicField <\/b>gleich zu Beginn ein Element mit dem Namen <b>id <\/b>und dem Wert des Feldes <b>AirtableID <\/b>ein.<\/p>\n<p>Beim Aufruf der Funktion <b>Airtable_CRUD <\/b>&uuml;bergeben wir als zweiten Parameter den Wert <b>PATCH <\/b>als Methode.<\/p>\n<p>War der Aufruf erfolgreich, bekommen wir zwar den aktuellen Stand des Datensatzes zur&uuml;ck, jedoch liefert dieser keine neuen Informationen gegen&uuml;ber den gesendeten Daten. Daher legen wir in diesem Fall nur den R&uuml;ckgabewert auf <b>True <\/b>fest.<\/p>\n<p>Sollte das Aktualisieren fehlgeschlagen sein, schreiben wir zuvor die Fehlerinformationen in den Direktbereich des VBA-Editors.<\/p>\n<h2>Airtable-Datens&auml;tze l&ouml;schen<\/h2>\n<p>Fehlt noch das L&ouml;schen von Datens&auml;tzen in der Airtable-Tabelle. Hier ist der Ablauf ein wenig anders, denn wir m&uuml;ssen kein JSON-Dokument zusammenstellen, dass die zu l&ouml;schenden Datens&auml;tze enth&auml;lt. Stattdessen &uuml;bergeben wir den zu l&ouml;schenden Datensatz einfach per Parameter. Die Funktion <b>DatensatzAusAirtableLoeschen <\/b>aus Listing 9 erwartet wieder die ID des zu l&ouml;schenden Datensatzes als Parameter.<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>DatensatzAusAirtableLoeschen(lngBestellungID<span style=\"color:blue;\"> As Long<\/span>)\r\n     <span style=\"color:blue;\">Dim <\/span>db<span style=\"color:blue;\"> As <\/span>DAO.Database\r\n     <span style=\"color:blue;\">Dim <\/span>rst<span style=\"color:blue;\"> As <\/span>DAO.Recordset\r\n     <span style=\"color:blue;\">Dim <\/span>strRequest<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strID<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strResponse<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>intErrorNumber<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strErrorDescription<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strParameter<span style=\"color:blue;\"> As String<\/span>\r\n     \r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n     <span style=\"color:blue;\">Set<\/span> rst = db.OpenRecordset(\"SELECT * FROM tblBestellungenVonAirtable WHERE BestellungID = \" & lngBestellungID, _\r\n         dbOpenDynaset)\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> rst.EOF<span style=\"color:blue;\"> Then<\/span>\r\n         <span style=\"color:blue;\">If <\/span>IsNull(rst!AirtableID)<span style=\"color:blue;\"> Then<\/span>\r\n             <span style=\"color:blue;\">MsgBox<\/span> \"Datensatz nicht in Airtable hinterlegt.\"\r\n             <span style=\"color:blue;\">Exit Function<\/span>\r\n         <span style=\"color:blue;\">End If<\/span>\r\n         strParameter = \"records%5B%5D=\" & rst!AirtableID\r\n         If Airtable_CRUD(\"Bestellungen\", \"DELETE\", strRequest, strID, strResponse, intErrorNumber, _\r\n                 strErrorDescription, strParameter) = <span style=\"color:blue;\">True<\/span> Then\r\n             rst.Edit\r\n             rst!AirtableID = Null\r\n             rst.Update\r\n             DatensatzAusAirtableLoeschen = <span style=\"color:blue;\">True<\/span>\r\n         <span style=\"color:blue;\">Else<\/span>\r\n             <span style=\"color:blue;\">Debug.Print<\/span> intErrorNumber, strErrorDescription\r\n             <span style=\"color:blue;\">Debug.Print<\/span> strResponse\r\n             DatensatzAusAirtableLoeschen = <span style=\"color:blue;\">False<\/span>\r\n         <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Function<\/span>     <\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 9: L&ouml;schen eines Datenatzes aus der Airtable-Tabelle<\/span><\/b><\/p>\n<p>Danach &ouml;ffnen wir das Recordset mit der &uuml;bergebenen ID und pr&uuml;fen, ob dieses einen Datensatz enth&auml;lt.<\/p>\n<p>Danach untersuchen wir wieder, ob dieser einen Wert im Feld <b>Airtable <\/b>enth&auml;lt, denn sonst wissen wir nicht, auf welchen Datensatz in der Airtable wir die Operation anwenden sollen. Danach stellen wir den entscheidenden Parameter zusammen.<\/p>\n<p>Dieser sieht beispielsweise wie folgt aus:<\/p>\n<pre>records%5B%5D=recuZFO8i0rfUma2s<\/pre>\n<p>Die Zeichen <b>%5B <\/b>und <b>%5D <\/b>sind das bereits decodierte eckige Klammernpaar.<\/p>\n<p>Damit k&ouml;nnen wir nun die Funktion <b>Airtable_CRUD <\/b>aufrufen. Dabei &uuml;bergeben wir als Methode diesmal <b>DELETE<\/b>. Abh&auml;ngig vom Ergebnis geben wir den Wert <b>True <\/b>oder die &uuml;blichen Fehlerinformationen zur&uuml;ck.<\/p>\n<h2>Vorgehensweise zum Zusammenstellen des Codes f&uuml;r weitere Tabellen<\/h2>\n<p>Wenn wir die oben vorgestellten drei Funktionen zum Anlegen, Aktualisieren und L&ouml;schen von Tabellen auf andere Tabellen wie Bestellungen anwenden wollen, m&uuml;ssen wir lediglich den Tabellennamen in der URL anpassen und das JSON entsprechend zusammenstellen.<\/p>\n<p>Man kann einfach die Felder der zu bearbeitenden Tabellen durchgehen und den Code entsprechend anpassen. Aber wir sind schreibfaul und programmieren uns lieber eine Prozedur, die uns den entsprechenden Code zusammenstellt.<\/p>\n<p>Dazu m&uuml;ssen wir nur einen Weg finden, die Felder der Tabelle aus Airtable zu ermitteln und daraus den Code zusammenzusetzen.<\/p>\n<p>Dazu k&ouml;nnen wir die bereits zu Beginn verwendete Prozedur zum Auslesen der Tabellen nutzen, die nicht nur die Tabellen, sondern den vollst&auml;ndigen Aufbau der Airtable-Datenbank liefert. <\/p>\n<p>Diese kopieren wir und nennen diese nun <b>JSONCodeZusammenstellen<\/b>(siehe Listing 10). Der einzige Parameter dieser Prozedur nimmt den Namen der zu untersuchenden Tabelle entgegen.<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>JSONCodeZusammenstellen(strTabelle<span style=\"color:blue;\"> As String<\/span>)\r\n     <span style=\"color:blue;\">Dim <\/span>objXMLHTTP<span style=\"color:blue;\"> As <\/span>MSXML2.XMLHTTP60\r\n     <span style=\"color:blue;\">Dim <\/span>objJSON<span style=\"color:blue;\"> As Object<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strURL<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>i<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>j<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strFeldname<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strDatentyp<span style=\"color:blue;\"> As String<\/span>\r\n     \r\n     <span style=\"color:blue;\">Set<\/span> objXMLHTTP = <span style=\"color:blue;\">New<\/span> MSXML2.XMLHTTP60\r\n     strURL = cStrBaseURL & \"meta\/bases\/\" & cStrBaseID & \"\/tables\"\r\n     <span style=\"color:blue;\">Debug.Print<\/span> strURL\r\n     objXMLHTTP.Open \"GET\", strURL, <span style=\"color:blue;\">False<\/span>\r\n     objXMLHTTP.setRequestHeader \"Authorization\", \"Bearer \" & cStrAPIToken\r\n     objXMLHTTP.send\r\n     <span style=\"color:blue;\">Set<\/span> objJSON = ParseJson(objXMLHTTP.responseText)\r\n     \r\n     For i = 1 To objJSON.Item(\"tables\").Count\r\n         <span style=\"color:blue;\">If <\/span>objJSON.Item(\"tables\").Item(i).Item(\"name\") = strTabelle<span style=\"color:blue;\"> Then<\/span>\r\n             For j = 1 To objJSON.Item(\"tables\").Item(i).Item(\"fields\").Count\r\n                 strFeldname = objJSON.Item(\"tables\").Item(i).Item(\"fields\").Item(j).Item(\"name\")\r\n                 Select Case strFeldname\r\n                     <span style=\"color:blue;\">Case <\/span>\"Name\"\r\n                     <span style=\"color:blue;\">Case Else<\/span>\r\n                         strDatentyp = objJSON.Item(\"tables\").Item(i).Item(\"fields\").Item(j).Item(\"type\")\r\n                         Select Case strDatentyp\r\n                             <span style=\"color:blue;\">Case <\/span>\"dateTime\"\r\n                                 <span style=\"color:blue;\">Debug.Print<\/span> \"        dicField.Add \"\"\" & strFeldname & \"\"\", _\r\n                                     CStr(GetAirtableDateFromDate(rst!\" & strFeldname & \"))\"\r\n                             <span style=\"color:blue;\">Case <\/span>\"singleLineText\", \"singleSelect\", \"email\"\r\n                                 <span style=\"color:blue;\">Debug.Print<\/span> \"        dicField.Add \"\"\" & strFeldname & \"\"\", CStr(rst!\" _\r\n                                     & strFeldname & \")\"\r\n                             <span style=\"color:blue;\">Case Else<\/span>\r\n                                 <span style=\"color:blue;\">Debug.Print<\/span> strFeldname, strDatentyp\r\n                         <span style=\"color:blue;\">End Select<\/span>\r\n                 <span style=\"color:blue;\">End Select<\/span>\r\n             <span style=\"color:blue;\">Next<\/span> j\r\n         <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">Next<\/span> i\r\n<span style=\"color:blue;\">End Sub<\/span>   <\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 10: Zusammenstellen der Anweisungen f&uuml;r das F&uuml;llen des JSON-Requests f&uuml;r das Erstellen und Aktualisieren von Datens&auml;tzen<\/span><\/b><\/p>\n<p>Dann fragen wir den JSON-Response f&uuml;r den Aufbau der Datenbank ab. Wir durchlaufen alle <b>tables<\/b>-Elemente und pr&uuml;fen, ob die aktuelle Tabelle den Namen aus dem Parameter aufweist. Falls ja, durchlaufen wir eine weitere Schleife &uuml;ber alle enthaltenen <b>fields<\/b>-Elemente.<\/p>\n<p>Daraus holen wir uns den Feldnamen und den Datentyp. In einer <b>Select Case<\/b>-Bedingung untersuchen wir den Datentyp. Im Fall des Datentyps <b>formula <\/b>soll das Feld weder beim Anlegen noch beim Aktualisieren geschrieben werden. Einer Formel k&ouml;nnen wir keinen Wert zuweisen, da diese dynamisch aus anderen Feldinhalten oder mit sonstigen Funktionen ermittelt wird.<\/p>\n<p>In allen anderen F&auml;llen stellen wir die ben&ouml;tigte Zeile zum F&uuml;llen des jeweiligen Feldes zusammen. F&uuml;r Felder mit den Typen <b>singleLineText<\/b>, <b>singleSelect <\/b>oder <b>email <\/b>verwenden wir beispielsweise folgende Anweisung:<\/p>\n<pre>dicField.Add \"Produkt\", CStr(rst!Produkt)<\/pre>\n<p>F&uuml;r Datumsfelder erzeugen wir eine Anweisung wie:<\/p>\n<pre>dicField.Add \"Bestelldatum\", _\r\n     CStr(GetAirtableDateFromDate(rst!Bestelldatum))<\/pre>\n<p>Damit erhalten wir schlie&szlig;lich eine Liste aller Feldzuweisungen, wie wir sie bereits zum Anlegen oder Aktualisieren von Datens&auml;tzen in der Tabelle <b>Bestellungen <\/b>verwendet haben.<\/p>\n<p>Hiermit haben wir erst einmal nur die wesentlichen und bisher verwendeten Datentypen abgedeckt. Dies wird noch einmal etwas komplizierter, wenn wir verkn&uuml;pfte Daten &uuml;bertragen wollen. Dazu gehen wir in sp&auml;teren Artikeln ein.<\/p>\n<h2>Zusammenfassung und Ausblick<\/h2>\n<p>In diesem Artikel haben wir gezeigt, wie wir per Rest API und VBA von Access auf eine Airtable-Datenbank zugreifen k&ouml;nnen, um Datens&auml;tze von dort in eine Access-Datenbank einzulesen, neue Datens&auml;tze in der Airtable-Datenbank anzulegen und vorhandene Datens&auml;tze in Airtable zu aktualisieren oder zu l&ouml;schen.<\/p>\n<p>In weiteren Artikeln gehen wir weiter in die Tiefe und betrachten, wie wir eine Datenbank aus Access nach Airtable migrieren k&ouml;nnen &#8211; inklusive Verkn&uuml;pfungen und anderen Eigenschaften.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Das Datenbanksystem Airtable bietet eine Menge Funktionen an, aber offeriert leider keine direkte Schnittstelle, mit der man direkt etwa von Access auf die enthaltenen Daten zugreifen kann. Es gibt zwar kostenpflichtige ODBC-Schnittstellen von Drittanbietern, aber wir wollen den Zugriff selbst programmieren. Wie f&uuml;r moderne SaaS-Tools &uuml;blich, bietet auch Airtable eine Rest-API als Schnittstelle f&uuml;r den Zugriff auf die Daten an. Diese wollen wir im vorliegenden Artikel untersuchen und zeigen, wie wir auf die enthaltenen Daten zugreifen und Informationen aus einer lokalen Datenbank in eine Airtable-Datenbank schreiben k&ouml;nnen.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[662025,66042025,44000025],"tags":[],"yst_prominent_words":[],"class_list":["post-55000482","post","type-post","status-publish","format-standard","hentry","category-662025","category-66042025","category-VBAProgrammierung"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts\/55000482","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/comments?post=55000482"}],"version-history":[{"count":0,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts\/55000482\/revisions"}],"wp:attachment":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/media?parent=55000482"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/categories?post=55000482"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/tags?post=55000482"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/yst_prominent_words?post=55000482"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}