{"id":55001056,"date":"2016-10-01T00:00:00","date_gmt":"2020-05-22T15:25:07","guid":{"rendered":"http:\/\/access-im-unternehmen.aix-dev.de\/aiu\/?p=1056"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-30T00:00:00","slug":"Daten_von_Access_zum_SQL_Server_kopieren","status":"publish","type":"post","link":"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/","title":{"rendered":"Daten von Access zum SQL Server kopieren"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/vg09.met.vgwort.de\/na\/c24045bed6dc4062abaadf2b7400acf6\" width=\"1\" height=\"1\" alt=\"\"><\/p>\n<p><b>Manchmal ben&ouml;tigt man eine 1:1-Kopie der Daten aus einer Access-Tabelle in einer anderen Tabelle &#8211; vielleicht in der gleichen, vielleicht aber auch in einer anderen Datenbank. Das ist mit entsprechenden Einf&uuml;geabfragen kein Problem, solange kein anderes Datenbanksystem wie etwa der SQL Server als Ziel infrage kommt &#8211; und Sie gleichzeitig die Daten nicht nur einfach einf&uuml;gen, sondern auch noch die Inhalte der Autowertfelder beibehalten wollen. Dieser Beitrag zeigt praktische Erfahrungen und L&ouml;sungsweisen f&uuml;r spezielle Migrationsvorhaben auf.<\/b><\/p>\n<h2>Warum von Tabelle zu Tabelle kopieren<\/h2>\n<p>Bei mir ist die oben erw&auml;hnte Aufgabe aufgetreten, als ich die Daten einer lokalen Suedsturm-Datenbank in das neu erstellte Datenmodell einer LocalDB-SQL Server-Datenbank kopieren wollte. Ich habe die Tabellen der LocalDB-Datenbank dann in die aktuelle Access-Datenbank eingebunden, sodass der komplette Satz der Tabellen jeweils zweimal vorlag &#8211; einmal unter den richtigen Namen als lokale Tabellen und einmal mit dem Pr&auml;fix <b>1 <\/b>als eingebundene Tabellen (also etwa <b>tblArtikel <\/b>und <b>tblArtikel1<\/b>).<\/p>\n<h2>Kopieren per Drag and Drop<\/h2>\n<p>Der erste Ansatz, die Tabelleninhalte von A nach B zu kopieren, war der per Drag and Drop. Also die Quelltabelle &ouml;ffnen, mit <b>Strg + A <\/b>alle Datens&auml;tze markieren und mit <b>Strg + C <\/b>kopieren und somit in die Zwischenablage einf&uuml;gen. Dann die Zieltabelle &ouml;ffnen und versuchen, die Daten einzuf&uuml;gen. Der erste Versuch: einfach wild ein Feld markiert und <b>Strg + V<\/b> zum Einf&uuml;gen bet&auml;tigt. Das gelang nat&uuml;rlich nicht, weil die Struktur des markierten Bereichs nicht mit dem einzuf&uuml;genden Inhalt &uuml;bereinstimmt. Wenn Sie dann den kompletten neuen, leeren Datensatz in der Zieltabelle markieren und erneut <b>Strg + V <\/b>bet&auml;tigen, klappt es &#8211; die Daten landen in der Zieltabelle (s. Bild 1).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_05\/pic_1056_001.png\" alt=\"Kopieren der Inhalte einer Tabelle in eine baugleiche Tabelle\" width=\"424,6255\" height=\"259,1611\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 1: Kopieren der Inhalte einer Tabelle in eine baugleiche Tabelle<\/span><\/b><\/p>\n<h2>Schon fertig<\/h2>\n<p>Es klappt also allein mit Bordmitteln &#8211; das wird ja ein kurzer Beitrag! Doch leider war das nicht alles, denn wir haben hier den Idealfall erwischt. Wenn Sie nun feststellen, dass irgendetwas beim Kopieren nicht wie gew&uuml;nscht funktioniert hat, etwa weil eines der Zielfelder den falschen Datentyp hat oder weil Sie vielleicht noch mal neu beginnen wollen, weil die Daten der per Fremdschl&uuml;sselfeld verkn&uuml;pften Daten noch gar nicht kopiert wurden, treten die ersten Probleme auf.<\/p>\n<p>Wenn Sie beispielhaft die verkn&uuml;pfte Tabelle <b>tblAnreden<\/b> leeren und die Daten erneut kopieren, erhalten Sie beispielsweise das Ergebnis aus Bild 2. Das sieht nun gar nicht mehr so gut aus, denn die Werte des Prim&auml;rschl&uuml;sselfeldes <b>AnredeID <\/b>entsprechen nicht mehr denen aus der Originaltabelle. Dies wird zu Problemen f&uuml;hren, wenn Sie die Datens&auml;tze der Tabelle <b>tblKunden <\/b>kopieren, deren Fremdschl&uuml;sselfeld <b>AnredeID <\/b>ja die Werte der urspr&uuml;nglichen Tabelle enth&auml;lt. Hier wird dann also mit den Datens&auml;tzen der Tabelle <b>tblAnreden <\/b>verkn&uuml;pft, welche die Werte <b>1 <\/b>und <b>2 <\/b>enthalten, aber nicht die mit den Werten <b>5 <\/b>und <b>6<\/b>. Wir m&uuml;ssten also im schlimmsten Fall auch noch die Fremdschl&uuml;sselfelder anpassen und auf die neuen Werte mappen.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_05\/pic_1056_002.png\" alt=\"Erneutes Kopieren liefert andere Prim&auml;rschl&uuml;sselwerte.\" width=\"424,6255\" height=\"255,3907\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 2: Erneutes Kopieren liefert andere Prim&auml;rschl&uuml;sselwerte.<\/span><\/b><\/p>\n<p>Sie sehen also: Wir haben nur zuf&auml;llig genau die gleichen Daten wie in der Ausgangstabelle erhalten, weil die Autowert-Funktion der Zieltabelle gerade auf den gleichen Startwert wie die einzuf&uuml;genden Datens&auml;tze eingestellt war.<\/p>\n<p>Die Autowert-Funktion der Zieltabelle wird uns noch weitere Probleme bescheren: Sie hat ja &uuml;blicherweise die Aufgabe, vom zuletzt eingef&uuml;gten Wert ausgehend die n&auml;chste Ganzzahl als neuen Wert f&uuml;r das Prim&auml;rschl&uuml;sselfeld zu ermitteln. In der Regel werden die Datens&auml;tze also von 1 bis n durchnummeriert. Sollten wir also irgendwann einmal einen Datensatz gel&ouml;scht haben, folgt das n&auml;chste Dilemma: Die Datens&auml;tze werden nun ab diesem Loch wiederum anders nummeriert als in der Originaltabelle.<\/p>\n<h2>Die L&ouml;sung: INSERT INTO<\/h2>\n<p>Es gibt leider keine M&ouml;glichkeit, Access mitzuteilen, dass es die Werte eines mit Autowert-Funktion ausgestatteten Prim&auml;rschl&uuml;sselfeldes beim Kopieren &uuml;ber die Benutzeroberfl&auml;che beibehalten soll. Das Einf&uuml;gen von Datens&auml;tzen auf diese Art und Weise wird Access immer als manuelles Einf&uuml;gen einzelner Datens&auml;tze interpretieren, und somit werden die Autowerte vom System vergeben (in diesem Fall &uuml;brigens von der LocalDB-Instanz &#8211; siehe Beitrag <b>Access und LocalDB<\/b>, <b>www.access-im-unternehmen.de\/1057<\/b>). Zum Gl&uuml;ck gibt es aber ja auch noch codegesteuerte M&ouml;glichkeiten, Datens&auml;tze zu kopieren. Also probieren wir es aus!<\/p>\n<p>Also leeren wir die Tabelle <b>tblAnreden1 <\/b>wieder, um es per VBA zu probieren. Diesmal wollen wir es mit einer <b>INSERT INTO<\/b>-Abfrage probieren, die einfach nur alle Felder der Quelltabelle in die entsprechenden Felder der Zieltabelle kopiert. Dazu brauchen wir noch nicht einmal alle Felder anzugeben, sondern k&ouml;nnen das Sternchen (*) als Platzhalter f&uuml;r alle Felder verwenden. Die Abfrage zum Kopieren der Inhalte der Tabelle <b>tblAnreden<\/b> sieht wie folgt aus:<\/p>\n<pre>INSERT INTO tblAnreden1 SELECT * FROM tblAnreden<\/pre>\n<p>Der hintere Teil legt fest, welche Daten eingef&uuml;gt werden sollen, der vordere Teil gibt das Ziel an. Wir erstellen noch eine kleine VBA-Prozedur, welche uns das Ausf&uuml;hren dieser SQL-Anweisung abnimmt und zus&auml;tzlich eine Erfolgsmeldung liefert:<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>AnredenKopieren()\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>strSQL<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>lngAnzahl<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n     strSQL = \"INSERT INTO tblAnreden1  SELECT * FROM tblAnreden\"\r\n     db.Execute strSQL, dbFailOnError\r\n     lngAnzahl = db.RecordsAffected\r\n     <span style=\"color:blue;\">MsgBox<\/span> \"Hinzugef&uuml;gte Datens&auml;tze: \" & lngAnzahl\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Die Abfrage speichern wir in der Variablen <b>strSQL<\/b>. Diese wiederum &uuml;bergeben wir als ersten Parameter f&uuml;r die Methode <b>Execute <\/b>des <b>Database<\/b>-Objekts f&uuml;r die aktuelle Datenbank. Damit Fehler an die Benutzeroberfl&auml;che gemeldet werden, geben wir als zweiten Parameter den Wert <b>dbFailOnError <\/b>mit. Die Eigenschaft <b>RecordsAffected <\/b>liefert immer die Anzahl der von der letzten Aktionsabfrage betroffenen Datens&auml;tze.<\/p>\n<p>Und es gelingt &#8211; wir erhalten eine Erfolgsmeldung &uuml;ber zwei angef&uuml;gte Datens&auml;tze und die Tabelle <b>tblAnreden1<\/b> zeigt die neuen Datens&auml;tze mit den Original-Prim&auml;rschl&uuml;sselwerten an (s. Bild 3).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_05\/pic_1056_003.png\" alt=\"Mit der INSERT INTO-Anweisung gelingt der Kopiervorgang.\" width=\"424,6255\" height=\"259,0605\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 3: Mit der INSERT INTO-Anweisung gelingt der Kopiervorgang.<\/span><\/b><\/p>\n<p>Nun bekommen wir nur noch ein Problem, wenn bereits Datens&auml;tze mit den Prim&auml;rschl&uuml;sselwerten der einzuf&uuml;genden Datens&auml;tze vorhanden sind.<\/p>\n<p>Sollten wir also nun beispielsweise erneut die beiden Anreden kopieren, sind ja bereits zwei Datens&auml;tze mit den Prim&auml;rschl&uuml;sselwerten <b>1 <\/b>und <b>2 <\/b>vorhanden &#8211; und da das Prim&auml;rschl&uuml;sselfeld immer nur eindeutige Werte enthalten darf, sollte beim Einf&uuml;gen ein Fehler ausgel&ouml;st werden.<\/p>\n<p>Wenn wir dies mit einer lokalen Tabelle, also einer Access-Tabelle, als Ziel durchf&uuml;hren (wir haben diese <b>tblAnreden2<\/b> genannt), l&ouml;st dies den Fehler aus Bild 4 aus.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_05\/pic_1056_004.png\" alt=\"Fehler beim Einf&uuml;gen eines Datensatzes mit einem bereits vorhandenen Prim&auml;rschl&uuml;sselwert in eine lokale Tabelle\" width=\"424,6255\" height=\"241,0291\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 4: Fehler beim Einf&uuml;gen eines Datensatzes mit einem bereits vorhandenen Prim&auml;rschl&uuml;sselwert in eine lokale Tabelle<\/span><\/b><\/p>\n<p>Handelt es sich bei der Zieltabelle um eine per ODBC verkn&uuml;pfte Tabelle, wie es bei der Tabelle <b>tblAnreden1 <\/b>der Fall ist, erscheint die Meldung aus Bild 5. W&auml;hrend wir mit der Meldung f&uuml;r die lokale Tabelle noch etwas anfangen k&ouml;nnen, liefert diese Fehlermeldung nur wenig wirkliche Informationen. Allerdings k&ouml;nnen wir noch weitere Hinweise erhalten, wenn wir die VBA-Prozedur, die den Fehler ausgel&ouml;st hat, erweitern.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_05\/pic_1056_005.png\" alt=\"Fehler beim Einf&uuml;gen eines Datensatzes mit einem bereits vorhandenen Prim&auml;rschl&uuml;sselwert in eine per ODBC verkn&uuml;pfte Tabelle\" width=\"424,6255\" height=\"241,0291\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 5: Fehler beim Einf&uuml;gen eines Datensatzes mit einem bereits vorhandenen Prim&auml;rschl&uuml;sselwert in eine per ODBC verkn&uuml;pfte Tabelle<\/span><\/b><\/p>\n<h2>Fehlermeldungen aufbohren<\/h2>\n<p>Wenn ein Fehler durch einen Zugriff per ODBC auf eine verkn&uuml;pfte Tabelle entsteht, liefert Access n&auml;mlich immer nur die Standardfehlermeldung mit der Nummer <b>3146<\/b>. Weitere Informationen liefert dann die <b>Errors<\/b>-Auflistung des <b>DBEngine<\/b>-Objekts. Diese enth&auml;lt auf jeden Fall den auch schon von VBA gemeldeten Fehler <b>3146<\/b>, gegebenenfalls aber auch noch weitere Fehler. Im Falle des Anlegens von Daten mit bereits vorhandenem Prim&auml;rschl&uuml;sselwert finden wir beispielsweise mit folgender Anweisung w&auml;hrend des Debuggens des Fehlers die Anzahl <b>3<\/b>:<\/p>\n<pre><span style=\"color:blue;\">Debug.Print<\/span> DBEngine.Errors.Count<\/pre>\n<p>Wir k&ouml;nnen dann &uuml;ber die <b>Errors<\/b>-Auflistung direkt auf die Fehlermeldungen der &uuml;brigen Fehler zugreifen.<\/p>\n<pre><span style=\"color:blue;\">Debug.Print<\/span> DBEngine.Errors(1).Description<\/pre>\n<p>liefert dann beispielsweise einen weiteren Fehler mit dem folgenden Text:<\/p>\n<pre>[Microsoft][ODBC Driver 11 for SQL Server][SQL Server]The statement has been terminated.<\/pre>\n<p>Und der Fehler mit dem Index <b>2 <\/b>lautet so:<\/p>\n<pre>[Microsoft][ODBC Driver 11 for SQL Server][SQL Server]Violation of \r\nPRIMARY KEY constraint ''''PK_tblAnreden''''. Cannot insert duplicate key \r\nin object ''''dbo.tblAnreden''''. The duplicate key value is (1).<\/pre>\n<p>Dies nutzen wir aus, indem wir die Prozedur zum Kopieren um eine ausf&uuml;hrlichere Fehlerbehandlung erg&auml;nzen (s. Listing 1).<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>AnredenKopieren()\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>strSQL<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>lngAnzahl<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strFehler<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;\">Set<\/span> db = CurrentDb\r\n     strSQL = \"INSERT INTO tblAnreden1 SELECT * FROM tblAnreden\"\r\n     On Error Resume <span style=\"color:blue;\">Next<\/span>\r\n     db.Execute strSQL, dbFailOnError\r\n     Select Case Err.Number\r\n         <span style=\"color:blue;\">Case <\/span>3022\r\n             <span style=\"color:blue;\">MsgBox<\/span> Err.Description\r\n         <span style=\"color:blue;\">Case <\/span>3146\r\n             For i = DBEngine.Errors.Count - 1 To 0 Step -1\r\n                 strFehler = strFehler & DBEngine.Errors(i).Description & <span style=\"color:blue;\">vbCrLf<\/span> & <span style=\"color:blue;\">vbCrLf<\/span>\r\n             <span style=\"color:blue;\">Next<\/span> i\r\n             <span style=\"color:blue;\">MsgBox<\/span> strFehler\r\n         <span style=\"color:blue;\">Case <\/span>0\r\n             lngAnzahl = db.RecordsAffected\r\n             <span style=\"color:blue;\">MsgBox<\/span> \"Hinzugef&uuml;gte Datens&auml;tze: \" & lngAnzahl\r\n         <span style=\"color:blue;\">Case Else<\/span>\r\n             <span style=\"color:blue;\">MsgBox<\/span> Err.Number & \" \" & Err.Description\r\n     <span style=\"color:blue;\">End Select<\/span>\r\n     <span style=\"color:blue;\">On Error GoTo<\/span> 0\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 1: Kopieren von Tabellen mit ausf&uuml;hrlicher Fehlerbehandlung<\/span><\/b><\/p>\n<p>Hier ben&ouml;tigen wir zun&auml;chst zwei weitere Variablen namens <b>strFehler <\/b>und <b>i <\/b>als Laufvariable.<\/p>\n<p>Vor die <b>Execute<\/b>-Methode setzen wir die Zeile <b>On Error Resume Next<\/b>, damit eventuell auftretende Fehler nicht &uuml;ber die eingebaute Fehlerbehandlung abgehandelt werden. Danach fragen wir dann in einer <b>Select Case<\/b>-Bedingung den Wert der Eigenschaft <b>Err.Number <\/b>ab. Lautet dieser <b>3146<\/b>, handelt es sich um einen ODBC-Fehler und wir wollen weitere Informationen ermitteln. Dazu hinterlegen wir eine <b>For&#8230;Next<\/b>-Schleife mit der Laufvariablen <b>i<\/b>, die alle Elemente der <b>Errors<\/b>-Auflistung von <b>DBEngine <\/b>durchl&auml;uft und diese nacheinander an die <b>String<\/b>-Variable <b>strFehler <\/b>anh&auml;ngt. Dies liefert dann f&uuml;r den Fehler durch die bereits vorhandenen Prim&auml;rschl&uuml;sselwerte den Fehler aus Bild 6.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_05\/pic_1056_006.png\" alt=\"ODBC-Fehler mit Hintergrundinformationen\" width=\"424,6255\" height=\"236,0988\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 6: ODBC-Fehler mit Hintergrundinformationen<\/span><\/b><\/p>\n<p>An dieser Stelle muss man sich dann dar&uuml;ber im Klaren sein, welche Datens&auml;tze man &uuml;berschreibt &#8211; ob es sich um bereits vorhandene Datens&auml;tze handelt, die gesch&uuml;tzt werden m&uuml;ssen oder ob die Datens&auml;tze &uuml;berschrieben werden k&ouml;nnen.<\/p>\n<p>F&uuml;r den Fall, dass Sie die Daten aus den Tabellen zweier Datenbanken zusammenf&uuml;hren m&uuml;ssen, w&auml;re eine andere Strategie angezeigt als die in diesem Beitrag beschriebene &#8211; hier wollen wir nur die kompletten Daten einer Tabelle in eine andere, m&ouml;glichst leere Tabelle &uuml;bertragen. F&uuml;r das Zusammenf&uuml;hren von Daten m&uuml;ssten Sie das Zielsystem die jeweiligen Autowerte bestimmen lassen und die Fremdschl&uuml;sselwerte der damit verkn&uuml;pften Tabellen entsprechend aktualisieren. Dies soll aber nicht Thema dieses Beitrags sein.<\/p>\n<h2>Reihenfolge beachten!<\/h2>\n<p>Wenn Sie mehrere Tabellen auf die oben beschriebene Weise kopieren m&ouml;chten, k&ouml;nnen Sie das nicht in beliebiger Reihenfolge erledigen. Wenn Sie beispielsweise die Daten der Tabellen <b>tblArtikel <\/b>und der Tabelle <b>tblKategorien <\/b>und <b>tblLieferanten <\/b>kopieren (siehe Prozedur <b>ArtikelKopieren <\/b>im Modul <b>mdlBeispiele_DatenKopieren<\/b>), und zwar in dieser Reihenfolge, erhalten Sie den Fehler aus Bild 7.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_05\/pic_1056_007.png\" alt=\"Fehler durch das F&uuml;llen von Fremdschl&uuml;sselfeldern ohne Vorhandensein der Datens&auml;tze mit den entsprechenden Prim&auml;rschl&uuml;sselfeldern\" width=\"424,6255\" height=\"256,2239\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 7: Fehler durch das F&uuml;llen von Fremdschl&uuml;sselfeldern ohne Vorhandensein der Datens&auml;tze mit den entsprechenden Prim&auml;rschl&uuml;sselfeldern<\/span><\/b><\/p>\n<p>Die Originaltabelle <b>tblArtikel <\/b>enth&auml;lt im ersten Datensatz beispielsweise den Wert <b>1 <\/b>im Fremdschl&uuml;sselfeld <b>LieferantenID<\/b>. In der verkn&uuml;pften Tabelle ist aber noch gar kein Datensatz vorhanden, und somit auch keiner mit dem Wert <b>1 <\/b>im Prim&auml;rschl&uuml;sselfeld. In der Zieldatenbank ist f&uuml;r dieses Fremdschl&uuml;sselfeld jedoch referenzielle Integrit&auml;t definiert, was bedeutet, dass das Feld <b>LieferantID <\/b>der Tabelle <b>tblArtikel <\/b>nur Werte aufnehmen darf, die bereits in der Tabelle <b>tblLieferanten <\/b>vorhanden sind.<\/p>\n<p>Was tun wir in diesem Fall Ganz einfach: Wir kopieren nat&uuml;rlich zuerst die Tabellen, welche nur einen Prim&auml;rschl&uuml;ssel, aber keinen Fremdschl&uuml;ssel f&uuml;r die Verkn&uuml;pfung mit anderen Tabellen enthalten. In diesem Fall kopieren wir also zuerst die beiden Tabellen <b>tblLieferanten <\/b>und <b>tblKategorien<\/b>, bevor wir die Tabelle <b>tblArtikel <\/b>kopieren. <\/p>\n<h2>Parametrisiertes Kopieren<\/h2>\n<p>Und bevor wir nun f&uuml;r jede Tabelle eine neue Kopie der Prozedur <b>AnredenKopieren <\/b>erstellen und dort nur die Zeile mit der Zuweisung der SQL-Anweisung an die Variable <b>strSQL <\/b>&auml;ndern, &uuml;bergeben wir die variablen Daten doch lieber per Parameter. Wir verwenden also die beiden Parameter <b>strQuelle <\/b>und <b>strZiel <\/b>zur Angabe der jeweiligen Quell- und Zieltabelle. In der Variablen <b>strSQL <\/b>setzen wir dann die ben&ouml;tigte SQL-Anweisung zusammen. Der aktuelle Stand sieht nun wie in Listing 2 aus.<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>TabelleKopieren(strQuelle<span style=\"color:blue;\"> As String<\/span>, strZiel<span style=\"color:blue;\"> As String<\/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>lngAnzahl<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strFehler<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>strSQL<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n     On Error Resume <span style=\"color:blue;\">Next<\/span>\r\n     strSQL = \"INSERT INTO \" & strZiel & \" SELECT * FROM \" & strQuelle\r\n     db.Execute strSQL, dbFailOnError\r\n     Select Case Err.Number\r\n         <span style=\"color:blue;\">Case <\/span>3022\r\n             <span style=\"color:blue;\">MsgBox<\/span> Err.Description\r\n         <span style=\"color:blue;\">Case <\/span>3146\r\n             For i = DBEngine.Errors.Count - 1 To 0 Step -1\r\n                 strFehler = strFehler & DBEngine.Errors(i).Description & <span style=\"color:blue;\">vbCrLf<\/span> & <span style=\"color:blue;\">vbCrLf<\/span>\r\n             <span style=\"color:blue;\">Next<\/span> i\r\n             <span style=\"color:blue;\">MsgBox<\/span> strFehler\r\n         <span style=\"color:blue;\">Case <\/span>0\r\n             lngAnzahl = db.RecordsAffected\r\n             <span style=\"color:blue;\">MsgBox<\/span> \"Hinzugef&uuml;gte Datens&auml;tze: \" & lngAnzahl\r\n         <span style=\"color:blue;\">Case Else<\/span>\r\n             <span style=\"color:blue;\">MsgBox<\/span> Err.Number & \" \" & Err.Description\r\n     <span style=\"color:blue;\">End Select<\/span>\r\n     <span style=\"color:blue;\">On Error GoTo<\/span> 0\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><!--30percent--><\/p>\n<p><b><span style=\"color:darkgrey;\">Listing 2: Kopieren von Tabellen mit Parametern f&uuml;r die SQL-Anweisung<\/span><\/b><\/p>\n<p>Zum Kopieren etwa der drei Tabellen <b>tblArtikel<\/b>, <b>tblLieferanten <\/b>und <b>tblKategorien <\/b>w&uuml;rden wir folgende Anweisungen aufrufen:<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>Beispiel_TabellenKopieren()\r\n     <span style=\"color:blue;\">Dim <\/span>db<span style=\"color:blue;\"> As <\/span>DAO.Database\r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n     db.Execute \"DELETE FROM tblArtikel\", dbFailOnError\r\n     db.Execute \"DELETE FROM tblKategorien\", dbFailOnError\r\n     db.Execute \"DELETE FROM tblLieferanten\", dbFailOnError\r\n     TabelleKopieren \"tblKategorien\", \"tblKategorien1\"\r\n     TabelleKopieren \"tblLieferanten\", \"tblLieferanten1\"\r\n     TabelleKopieren \"tblArtikel\", \"tblArtikel1\"\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Da wir diese Prozedur sicher des &ouml;fteren testweise aufrufen, ist es sinnvoll, das L&ouml;schen der vorhandenen Datens&auml;tze in der Zieltabelle zu Beginn durchzuf&uuml;hren. Danach rufen wir die Methode <b>TabelleKopieren <\/b>f&uuml;r die drei Tabellen auf.<\/p>\n<p>Das sieht dann wie in Listing 3 aus &#8211; die zu f&uuml;llenden Tabellen leeren wir vorab noch, damit der Fehler wegen bereits vorhandener Identit&auml;tswerte nicht auftritt.<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>Beispiel_TabellenKopieren()\r\n     <span style=\"color:blue;\">Dim <\/span>db<span style=\"color:blue;\"> As <\/span>DAO.Database\r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n     db.Execute \"DELETE FROM tblArtikel1\", dbFailOnError\r\n     db.Execute \"DELETE FROM tblKategorien1\", dbFailOnError + dbSeeChanges\r\n     db.Execute \"DELETE FROM tblLieferanten1\", dbFailOnError\r\n     TabelleKopieren \"tblKategorien\", \"tblKategorien1\"\r\n     TabelleKopieren \"tblLieferanten\", \"tblLieferanten1\"\r\n     TabelleKopieren \"tblArtikel\", \"tblArtikel1\"\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 3: Aufruf der Prozedur zum Kopieren von Tabellen<\/span><\/b><\/p>\n<p>Allerdings erhalten wir nach dem Start f&uuml;r den dritten Aufruf der Prozedur <b>TabelleKopieren<\/b> den Fehler aus Bild 8. Was hei&szlig;t das nun wieder<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_05\/pic_1056_008.png\" alt=\"Fehler durch die Eigenschaft IDENTITY_INSERT\" width=\"424,6255\" height=\"182,8615\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 8: Fehler durch die Eigenschaft IDENTITY_INSERT<\/span><\/b><\/p>\n<pre>Cannot insert explicit value for identity column in table ''''tblArtikel'''' \r\nwhen IDENTITY_INSERT is set to OFF.<\/pre>\n<p>Eine kleine Recherche nach dem Schl&uuml;sselwort <b>IDENTITY_INSERT <\/b>ergibt, dass es normalerweise nicht vorgesehen ist, dass man die Identit&auml;tswerte eines Datensatzes selbst vergibt. Diese SQL Server-Eigenschaft hat normalerweise f&uuml;r alle Tabellen den Wert <b>OFF<\/b>. Sie kann nur f&uuml;r eine Tabelle gleichzeitig den Wert <b>ON <\/b>annehmen.<\/p>\n<p>Wenn sie den Wert <b>ON <\/b>besitzt, muss man den Wert der Identit&auml;tsspalte mit angeben. Das h&ouml;rt sich logisch an &#8211; man kann so per T-SQL tempor&auml;r daf&uuml;r sorgen, dass man individuelle Identit&auml;tswerte eintragen kann, wenn diese sonst automatisch vom System vergeben werden. Allerdings sind unsere Tests nicht so eindeutig. Wenn Sie beispielsweise die Prozedur <b>Beispiel_TabelleKopieren <\/b>ausf&uuml;hren, werden die ersten beiden Tabellen mit den Daten gef&uuml;llt, ohne dass wir an irgendeiner Stelle <b>IDENTITY_INSERT <\/b>auf <b>ON <\/b>stellen. Erst bei der dritten Tabelle, hier <b>tblArtikel<\/b>, tritt der Fehler auf. <\/p>\n<p>Noch verwirrender wird es, wenn wir noch einen Kopiervorgang f&uuml;r die Tabelle <b>tblAnreden <\/b>voranstellen:<\/p>\n<pre>TabelleKopieren \"tblAnreden\", \"tblAnreden1\"\r\nTabelleKopieren \"tblKategorien\", \"tblKategorien1\"\r\nTabelleKopieren \"tblLieferanten\", \"tblLieferanten1\"\r\nTabelleKopieren \"tblArtikel\", \"tblArtikel1\"<\/pre>\n<p>Dann gelingt das Einf&uuml;gen in die Tabelle <b>tblAnreden1<\/b>, bei <b>tblKategorien1 <\/b>erscheint der obige Fehler, das Einf&uuml;gen in <b>tblLieferanten1 <\/b>gelingt wieder und bei <b>tblArtikel1 <\/b>gibt es wieder den Fehler.<\/p>\n<p>Wenn wir die Datenbank schlie&szlig;en und wieder &ouml;ffnen, sieht das Ergebnis mitunter noch anders aus. Wie k&ouml;nnen wir also sicherstellen, dass wir die Daten inklusive Identit&auml;tswerten in die SQL Server-Tabellen &uuml;bertragen<\/p>\n<h2>IDENTITY_INSERT im SQL Server Management Studio<\/h2>\n<p>Schauen wir uns an, ob es an Access liegt, und testen ein paar SQL-Anweisungen direkt im SQL Server Management Studio. Wenn wir die folgende Anweisung absetzen, erhalten wir den gleichen Fehler wie unter Access:<\/p>\n<pre>INSERT INTO tblAnreden(AnredeID, Anrede) VALUES(3,''''bla'''');<\/pre>\n<p>Wir m&uuml;ssen zun&auml;chst angeben, dass f&uuml;r diese Tabelle Identit&auml;tswerte individuell geschrieben werden sollen, und zwar mit folgender Anweisung:<\/p>\n<pre>SET IDENTITY_INSERT dbo.tblAnreden ON;<\/pre>\n<p>Danach k&ouml;nnen wir dann mit der zuvor verwendeten Anweisung einen neuen Datensatz zur Tabelle <b>tblAnreden <\/b>hinzuf&uuml;gen.<\/p>\n<p>Hier ist das Bild also etwas eindeutiger: Wenn wir nicht zuvor <b>IDENTITY_INSERT <\/b>f&uuml;r die betroffene Tabelle auf <b>ON <\/b>einstellen, k&ouml;nnen wir keine individuellen Identit&auml;tswerte festlegen. Unter Access scheint dies also in manchen F&auml;llen etwas aufgeweicht zu werden, wobei wir nicht wissen, in welchen F&auml;llen.<\/p>\n<p>Auch beeinflusst das Setzen von <b>IDENTITY_INSERT <\/b>vom SQL Server Management Studio aus nicht das Verhalten beim Schreiben von Daten unter Access &#8211; es scheint also sitzungsbezogen zu sein.<\/p>\n<p>Wenn wir also im SQL Server Management Studio <b>IDENTITY_INSERT <\/b>f&uuml;r die Tabelle <b>tblKategorien <\/b>auf <b>ON <\/b>setzen, k&ouml;nnen wir noch lange keine Datens&auml;tze mit individuellen Identit&auml;tswerten per ODBC von Access aus hinzuf&uuml;gen.<\/p>\n<p>Also versuchen wir, einen Weg zu finden, von Access aus zuvor anzugeben, dass die jeweilige Tabelle mit individuellen Identit&auml;tswerten gef&uuml;llt werden soll. Leider gelingt dies nicht wie gew&uuml;nscht: Wir k&ouml;nnen zwar eine Pass-Through-Abfrage der folgenden Art absetzen, was erfolgreich verl&auml;uft:<\/p>\n<pre>SET IDENTITY_INSERT tblAnreden ON\r\nINSERT INTO tblAnreden(AnredeID, Anrede) VALUES(101, ''''Test'''')\r\nSET IDENTITY_INSERT tblAnreden OFF<\/pre>\n<p>Allerdings legen wir damit nur einen Datensatz an, dessen Werte wir direkt in der Abfrage explizit festlegen. Wir wollen aber die Daten der lokalen Tabelle in die LocalDB-Tabelle schreiben. Auf die lokalen Tabellen erhalten wir aber in einer Pass-Through-Abfrage keinen Zugriff.<\/p>\n<h2>Experimente<\/h2>\n<p>Danach folgten zahlreiche Experimente, wie wir die Daten aus den sieben Access-Tabellen in die entsprechenden Tabellen des SQL Servers kopieren k&ouml;nnen &#8211; unter Beibehaltung des Identit&auml;tswertes wohlgemerkt, denn das ist ja das gro&szlig;e Problem dabei. Wenn Sie einfach nur die Daten kopieren wollten, ohne diese Werte beizubehalten, w&auml;re das zwar grunds&auml;tzlich einfacher m&ouml;glich, allerdings m&uuml;ssten Sie dann wiederum beim Kopieren von Tabellen mit Fremdschl&uuml;sselwerten zun&auml;chst herausfinden, wie sich die Prim&auml;rschl&uuml;sselwerte der verkn&uuml;pften Tabellen beim Anf&uuml;gen ge&auml;ndert haben.<\/p>\n<p>Das Ergebnis dieser Experimente ist folgende Vorgehensweise (s. Bild 9): Wir erstellen f&uuml;r jeden einzelnen Datensatz einen Satz von SQL-Anweisungen, der etwa wie folgt aussieht:<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2016_05\/pic_1056_009.png\" alt=\"SQL-Anweisungen, um einen Datensatz unter Beibehaltung des Identit&auml;tswertes zu kopieren\" width=\"424,6255\" height=\"281,0533\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 9: SQL-Anweisungen, um einen Datensatz unter Beibehaltung des Identit&auml;tswertes zu kopieren<\/span><\/b><\/p>\n<pre>SET IDENTITY_INSERT tblAnreden ON\r\nSET NOCOUNT ON\r\nINSERT INTO tblAnreden(AnredeID, Anrede) VALUES(1, ''''Herr'''')\r\nSELECT @@ROWCOUNT AS AnzahlEingefuegt\r\nSET IDENTITY_INSERT tblAnreden OFF<\/pre>\n<p>Diesen senden wir dann per ODBC an den SQL Server beziehungsweise LocalDB und lassen diesen ausf&uuml;hren. Warum f&uuml;r jeden Datensatz einzeln Das hat sich so entwickelt: Zuerst durchlief die Routine, die dies erledigt, eine Schleife, die alle <b>INSERT INTO<\/b>-Aufrufe f&uuml;r eine Tabelle zusammenstellt und diese dann im Anschluss an den SQL Server schickte. Allerdings traten dabei sehr viele Fehler aus unterschiedlichen Gr&uuml;nden auf &#8211; mal war ein Hochkomma zu viel, dann war das Datum falsch formatiert oder ein Feld, das keine Nullwerte annehmen durfte, wurde mit einem Nullwert beschickt. Also haben wir, um die Fehler nach und nach auszumerzen, nicht alle <b>INSERT INTO<\/b>-Anweisungen f&uuml;r eine Tabelle in einem Rutsch an den Server geschickt sondern nur jeweils eine. Auf diese Weise lie&szlig;en sich die Fehlermeldungen einfacher auswerten. Solange die Datenmenge in akzeptabler Ausf&uuml;hrungszeit kopiert werden kann, ist dieser Weg in Ordnung; wenn es zu langsam wird, kann man die Schleife allerdings auch wieder umgestalten &#8211; also so, dass in der Schleife nur die <b>INSERT INTO<\/b>-Elemente zusammengestellt werden.<\/p>\n<h2>Kopieren per ODBC<\/h2>\n<p>Die Prozedur <b>TabelleKopieren_IDENTITY_INSERT <\/b>erwartet Parameter (s. Listing 4):<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>TabelleKopieren_IDENTITY_INSERT(strQuelle<span style=\"color:blue;\"> As String<\/span>, strZiel<span style=\"color:blue;\"> As String<\/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>lngAnzahl<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strFehler<span style=\"color:blue;\"> As String<\/span>, strSQL<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>qdf<span style=\"color:blue;\"> As <\/span>DAO.QueryDef\r\n     <span style=\"color:blue;\">Dim <\/span>rst<span style=\"color:blue;\"> As <\/span>DAO.Recordset, rstDatensaetze<span style=\"color:blue;\"> As <\/span>DAO.Recordset\r\n     <span style=\"color:blue;\">Dim <\/span>strFelder<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strWerte<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strInsert<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n     db.QueryDefs.Delete \"qryTemp\"\r\n     <span style=\"color:blue;\">On Error GoTo<\/span> 0\r\n     <span style=\"color:blue;\">Set<\/span> qdf = db.CreateQueryDef(\"qryTemp\")\r\n     qdf.Connect = DLookup(\"Verbindungszeichenfolge\", \"tblVerbindungszeichenfolgen\", _\r\n         \"VerbindungszeichenfolgeID = 9\")\r\n     <span style=\"color:blue;\">Set<\/span> rstDatensaetze = db.OpenRecordset(strQuelle, dbOpenDynaset)\r\n     strFelder = FelderErmitteln(db, strQuelle)\r\n     <span style=\"color:blue;\">Do While<\/span> <span style=\"color:blue;\">Not<\/span> rstDatensaetze.EOF\r\n         strSQL = \"SET IDENTITY_INSERT \" & strZiel & \" ON\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n         strWerte = WerteErmitteln(rstDatensaetze)\r\n         strInsert = \"INSERT INTO \" & strZiel & \"(\" & strFelder & \") VALUES(\" & strWerte & \")\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n         strSQL = strSQL & \"SET NOCOUNT ON\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n         strSQL = strSQL & strInsert\r\n         strSQL = strSQL & \"SELECT @@ROWCOUNT AS AnzahlEingefuegt\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n         strSQL = strSQL & \"SET IDENTITY_INSERT \" & strZiel & \" OFF\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n         qdf.SQL = strSQL\r\n         qdf.ReturnsRecords = <span style=\"color:blue;\">True<\/span>\r\n         On Error Resume <span style=\"color:blue;\">Next<\/span>\r\n         <span style=\"color:blue;\">Set<\/span> rst = qdf.OpenRecordset\r\n         Select Case Err.Number\r\n             <span style=\"color:blue;\">Case <\/span>3146\r\n                 For i = DBEngine.Errors.Count - 1 To 0 Step -1\r\n                     strFehler = strFehler & DBEngine.Errors(i).Description & <span style=\"color:blue;\">vbCrLf<\/span> & <span style=\"color:blue;\">vbCrLf<\/span>\r\n                 <span style=\"color:blue;\">Next<\/span> i\r\n                 <span style=\"color:blue;\">Debug.Print<\/span> \"Tabelle: \" & strQuelle\r\n                 <span style=\"color:blue;\">Debug.Print<\/span> strFehler\r\n             <span style=\"color:blue;\">Case <\/span>0\r\n                 lngAnzahl = lngAnzahl + rst.Fields(0)\r\n             <span style=\"color:blue;\">Case Else<\/span>\r\n                 <span style=\"color:blue;\">MsgBox<\/span> Err.Number & \" \" & Err.Description\r\n         <span style=\"color:blue;\">End Select<\/span>\r\n         <span style=\"color:blue;\">On Error GoTo<\/span> 0\r\n         rstDatensaetze.Move<span style=\"color:blue;\">Next<\/span>\r\n     <span style=\"color:blue;\">Loop<\/span>\r\n     <span style=\"color:blue;\">Debug.Print<\/span> \"Hinzugef&uuml;gte Datens&auml;tze in Tabelle ''''\" & strZiel & \"'''': \" & lngAnzahl\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 4: Prozedur zum Zusammenstellen von INSERT INTO-Anweisungen<\/span><\/b><\/p>\n<ul>\n<li><b>strQuelle <\/b>erwartet den Namen der Tabelle oder Abfrage der zu kopierenden Daten<\/li>\n<li><b>strZiel <\/b>erwartet den Namen der Zieltabelle<\/li>\n<\/ul>\n<p>Die Prozedur arbeitet mit einer tempor&auml;ren Pass-Through-Abfrage, die mit der Variablen <b>qdf <\/b>referenziert wird. Sie erstellt eine neue Abfrage namens <b>qryTemp<\/b>, die zuvor, soweit vorhanden, zun&auml;chst gel&ouml;scht wird.<\/p>\n<p>Dieses <b>QueryDef<\/b>-Objekt wird erst durch das Zuweisen einer Verbindungszeichenfolge zur Eigenschaft <b>Connect <\/b>zu einer Pass-Through-Abfrage, die direkt an den SQL Server\/LocalDB &uuml;bermittelt wird. Diese sieht in unserem Beispiel etwa so aus:<\/p>\n<pre>ODBC;DRIVER={ODBC Driver 11 for SQL Server};SERVER=(localdb)\\MSSQLLocalDB;DATABASE=Suedsturm;\r\nTrusted_Connection=Yes;OPTION=3;LOG_QUERY=1;<\/pre>\n<p>Im Beispiel holen wir diese Verbindungszeichenfolge aus einer Tabelle namens <b>tblVerbindungszeichenfolgen<\/b>. Mehr dazu im Beitrag <b>RDBMS-Zugriff per VBA: Verbindungen <\/b>(<b>www.access-im-unternehmen.de\/1054<\/b>). Anschlie&szlig;end &ouml;ffnet die Prozedur ein Recordset namens <b>rstDatensaetze <\/b>auf Basis der als Quelle angegebenen Tabelle oder Abfrage.<\/p>\n<p>Warum &uuml;berhaupt eine Tabelle oder Abfrage, wenn wir komplette Tabellen kopieren wollen Wir wollen uns zumindest theoretisch die M&ouml;glichkeit offenhalten, einmal nur einen Teil der Felder einer Tabelle zum SQL Server zu kopieren. Eine gute Gelegenheit dazu lernen Sie weiter unten kennen.<\/p>\n<p>Nun kommt die Funktion <b>FelderErmitteln <\/b>zum Einsatz (s. Listing 5). Diese soll eine Liste aller Felder der Datenquelle zusammenstellen, die sp&auml;ter als Teil der <b>INSERT INTO<\/b>-Anweisung dient.<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>FelderErmitteln(db<span style=\"color:blue;\"> As <\/span>DAO.Database, strTabelle<span style=\"color:blue;\"> As String<\/span>)<span style=\"color:blue;\"> As String<\/span>\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>fld<span style=\"color:blue;\"> As <\/span>DAO.Field\r\n     <span style=\"color:blue;\">Dim <\/span>strFelder<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> <span style=\"color:blue;\">Left<\/span>(strTabelle, 6) = \"SELECT\"<span style=\"color:blue;\"> Then<\/span>\r\n         strTabelle = \"SELECT * FROM \" & strTabelle\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">Set<\/span> rst = db.OpenRecordset(strTabelle & \" WHERE 1=2\")\r\n     For Each fld In rst.Fields\r\n         Select Case fld.Type\r\n             <span style=\"color:blue;\">Case <\/span>dbLongBinary\r\n             <span style=\"color:blue;\">Case Else<\/span>\r\n                 strFelder = strFelder & \", \" & fld.Name\r\n         <span style=\"color:blue;\">End Select<\/span>\r\n     <span style=\"color:blue;\">Next<\/span> fld\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Len<\/span>(strFelder) &gt; 0<span style=\"color:blue;\"> Then<\/span>\r\n         strFelder = <span style=\"color:blue;\">Mid<\/span>(strFelder, 3)\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     FelderErmitteln = strFelder\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 5: Diese Funktion ermittelt die Felder einer Datenquelle<\/span><\/b><\/p>\n<p>Diese Funktion erwartet einen Verweis auf das aktuelle <b>Database<\/b>-Objekt (<b>db<\/b>) sowie den Namen der Tabelle beziehungsweise die Abfrage als Parameter (<b>strTabelle<\/b>). Sie pr&uuml;ft, ob die ersten sechs Zeichen der Variablen <b>strTabelle <\/b>den Wert <b>SELECT <\/b>enthalten. Falls ja, handelt es sich schon um eine Abfrage, falls nicht, wird der Name der Tabelle so erg&auml;nzt, dass eine Abfrage mit allen Feldern daraus entsteht (wenn <b>tblAnreden <\/b>&uuml;bergeben wird, also etwa <b>SELECT * FROM tblAnreden<\/b>).<\/p>\n<p>Der Hintergrund ist, dass wir nachfolgend ein Recordset basierend auf dieser Abfrage erstellen und der Abfrage noch die Bedingung <b>WHERE 1=2 <\/b>zuweisen. Wir ben&ouml;tigen hier n&auml;mlich gar nicht die Inhalte der Tabelle, sondern nur die Feldnamen, und bei gro&szlig;en Tabellen sparen wir so etwas Zeit. Nachdem die Funktion das Recordset ge&ouml;ffnet hat, durchl&auml;uft sie dessen Felder &uuml;ber die <b>Fields<\/b>-Auflistung in einer <b>For Each<\/b>-Schleife und schreibt diese in der Variablen <b>strFelder <\/b>jeweils mit f&uuml;hrendem Komma zusammen.<\/p>\n<p>Nachdem das erste Komma entfernt wurde, geht der Ausdruck als Funktionswert zur&uuml;ck an die Prozedur <b>TabelleKopieren_IDENTITY_INSERT<\/b>, also beispielsweise <b>AnredeID, Anrede<\/b>. Die Funktion <b>FelderErmitteln <\/b>spart hier die Felder mit dem Datentyp <b>dbLongBinary <\/b>aus, also OLE-Felder. Die Behandlung von OLE-Feldern und Anlage-Feldern besprechen wir in einem sp&auml;teren Artikel.<\/p>\n<p>Weiter geht es in der Prozedur <b>TabelleKopieren_IDENTITY_INSERT<\/b>. Hier durchl&auml;uft die Prozedur nun das Recordset mit den Datens&auml;tzen der Datenquelle. Die erste Anweisung f&uuml;gt den Ausdruck <b>SET IDENTITY_INSERT <Zieltabelle> ON <\/b>zur Variablen <b>strSQL <\/b>hinzu. Danach folgt der Aufruf einer weiteren Hilfsfunktion namens <b>WerteErmitteln<\/b>. Diese arbeitet prinzipiell wie die oben schon beschriebene Funktion <b>FelderErmitteln<\/b>, nur dass sie nicht die Feldnamen, sondern die Inhalte des aktuellen Datensatzes einliest. Diese Funktion erwartet nur das <b>Recordset<\/b>-Objekt mit dem Zeiger auf dem zu untersuchenden aktuellen Datensatz als Parameter (s. Listing 6).<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>WerteErmitteln(rst<span style=\"color:blue;\"> As <\/span>DAO.Recordset)<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strWerte<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>fld<span style=\"color:blue;\"> As <\/span>DAO.Field\r\n     For Each fld In rst.Fields\r\n         Select Case fld.Type\r\n             <span style=\"color:blue;\">Case <\/span>dbLongBinary\r\n             <span style=\"color:blue;\">Case <\/span>dbText, dbMemo\r\n                 <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> IsNull(fld.Value)<span style=\"color:blue;\"> Then<\/span>\r\n                     strWerte = strWerte & \", ''''\" & <span style=\"color:blue;\">Replace<\/span>(fld.Value, \"''''\", \"''''''''\") & \"''''\"\r\n                 <span style=\"color:blue;\">Else<\/span>\r\n                     strWerte = strWerte & \", NULL\"\r\n                 <span style=\"color:blue;\">End If<\/span>\r\n             <span style=\"color:blue;\">Case <\/span>dbDate\r\n                 <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> IsNull(fld.Value)<span style=\"color:blue;\"> Then<\/span>\r\n                     strWerte = strWerte & \", ''''\" & SQLServerDatum(fld.Value) & \"''''\"\r\n                 <span style=\"color:blue;\">Else<\/span>\r\n                     strWerte = strWerte & \", NULL\"\r\n                 <span style=\"color:blue;\">End If<\/span>\r\n             <span style=\"color:blue;\">Case <\/span>dbBoolean\r\n                 strWerte = strWerte & \", \" & IIf(fld.Value, -1, 0)\r\n             <span style=\"color:blue;\">Case Else<\/span>\r\n                 <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> IsNull(fld.Value)<span style=\"color:blue;\"> Then<\/span>\r\n                     strWerte = strWerte & \", \" & <span style=\"color:blue;\">Replace<\/span>(fld.Value, \",\", \".\")\r\n                 <span style=\"color:blue;\">Else<\/span>\r\n                     strWerte = strWerte & \", NULL\"\r\n                 <span style=\"color:blue;\">End If<\/span>\r\n         <span style=\"color:blue;\">End Select<\/span>\r\n     <span style=\"color:blue;\">Next<\/span> fld\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Len<\/span>(strWerte) &gt; 0<span style=\"color:blue;\"> Then<\/span>\r\n         strWerte = <span style=\"color:blue;\">Mid<\/span>(strWerte, 3)\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     WerteErmitteln = strWerte\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 6: Diese Funktion ermittelt die Wertliste eines Datensatzes.<\/span><\/b><\/p>\n<p>Es durchl&auml;uft ebenfalls per <b>For Each<\/b>-Schleife alle Felder und untersucht per <b>Select Case<\/b>-Bedingung den Datentyp des aktuellen, mit <b>fld <\/b>referenzierten Feldes. Das wir den Datentyp <b>dbLongBinary <\/b>auslassen, haben wir oben schon erl&auml;utert. F&uuml;r Datentypen wie <b>dbText <\/b>(Text) oder <b>dbMemo <\/b>(Memo) haben wir einen <b>Case<\/b>-Zweig, der zun&auml;chst pr&uuml;ft, ob der Inhalt des Feldes nicht <b>Null <\/b>ist. Ist das der Fall, f&uuml;gt die Funktion den Inhalt des Feldes zur <b>String<\/b>-Variablen <b>strWerte <\/b>hinzu, allerdings erst nachdem die enthaltenen Hochkommata durch doppelte Hochkommata ersetzt wurden. Der Hintergrund ist, dass die Texte in der <b>INSERT INTO<\/b>-Anweisung schon in Hochkommata eingefasst werden m&uuml;ssen. Einzelne Hochkommata wie etwa in <b>Tom&#8220;&#8220;s Diner <\/b>werden dann f&auml;lschlicherweise als Ende der Zeichenkette interpretiert, wodurch die folgenden Zeichen dann meist zu Fehlern f&uuml;hren. Aus <b>Tom&#8220;&#8220;s Diner <\/b>wird so <b>Tom&#8220;&#8220;&#8220;&#8220;s Diner<\/b>. Sollte das Feld hingegen den Wert <b>Null <\/b>enthalten, wird als Wert lediglich <b>, NULL <\/b>an <b>strWerte <\/b>angeh&auml;ngt.<\/p>\n<p>Beim Datum (<b>dbDate<\/b>) sieht es &auml;hnlich aus, was die Verarbeitung von Null angeht. Davon abgesehen muss das Datum in einem speziellen Format angegeben werden, zum Beispiel als <b>yyyymmdd hh:nn:ss<\/b>. Die Funktion <b>SQLServerDatum <\/b>formt die in der Tabelle gespeicherten Datumsangaben entsprechend um:<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>SQLServerDatum(varDate<span style=\"color:blue;\"> As Variant<\/span>)\r\n     SQLServerDatum = Format(varDate, _\r\n          \"yyyymmdd hh\\:nn\\:ss\")\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p>Boolean-Werte (<b>dbBoolean<\/b>) werten wir mit der <b>IIf<\/b>-Funktion aus: Hat das Feld den Wert <b>Wahr<\/b>, liefert diese <b>1 <\/b>zur&uuml;ck, sonst <b>0<\/b> (im SQL Server entspricht <b>1 <\/b>dem Wert <b>True<\/b>).<\/p>\n<p>Fehlen noch die &uuml;brigen Datentypen, wobei wir uns hier auf Zahlendatentypen beziehen. Hier kann es zu Fehlern kommen, wenn die L&auml;ndereinstellungen das Komma als Dezimaltrennzeichen vorgeben. Wird etwa der Wert <b>1,5 <\/b>dann genau so als Feldwert einer <b>INSERT INTO<\/b>-Abfrage eingetragen, interpretiert der SQL Server dies als zwei Feldwerte, die durch ein Komma getrennt wurden. Also ersetzen wir eventuell auftretende Kommata noch durch den Dezimalpunkt. Auch hier pr&uuml;fen wir auf den Wert Null, f&uuml;r den wir gegebenenfalls die Zeichenfolge <b>NULL <\/b>als Feldwert angeben.<\/p>\n<p>Damit ist unsere Werteliste perfekt, zum Beispiel:<\/p>\n<pre>1, ''''Chai'''', 1, 6, ''''10 Kartons x 20 Beutel'''', 9, 39, 1, 10, -1, ''''20160101 00:00:00''''<\/pre>\n<p>Hiermit geht es in der Prozedur <b>TabelleKopieren_IDENTITY_INSERT <\/b>weiter. Diese f&uuml;gt die Felder und die Werte aus <b>strFelder <\/b>und <b>strWerte <\/b>nun mit ein paar weiteren Zeichenfolgen zu einer vollst&auml;ndigen <b>INSERT INTO<\/b>-Anweisung zusammen und speichert diese in der Variablen <b>strInsert<\/b>. Die SQL-Anweisungen werden nun nach und nach um die Zeilen <b>SET NOCOUNT ON<\/b>, die <b>INSERT INTO<\/b>-Anweisung, <b>SELECT @@ROWCOUNT AS Anzahl-Eingefuegt <\/b>und <b>SET IDENTITY_INSERT <Zieltabelle> OFF <\/b>erg&auml;nzt. Die komplette Anweisung aus <b>strSQL <\/b>landet dann in der Eigenschaft <b>SQL <\/b>des <b>QueryDef<\/b>-Objekts. Da unsere Reihe von Anweisungen auch eine <b>SELECT<\/b>-Anweisung enth&auml;lt, welche mit der <b>@@ROWCOUNT<\/b>-Funktion die Anzahl der betroffenen Datens&auml;tze zur&uuml;ckliefern soll, stellen wir die Eigenschaft <b>ReturnsRecords <\/b>der Abfrage auf <b>True <\/b>ein.<\/p>\n<p>Mit der <b>OpenRecordset<\/b>-Methode f&uuml;hren wir die Pass-Through-Abfrage nun aus und referenzieren das Ergebnis mit einer weiteren <b>Recordset<\/b>-Objektvariablen (<b>rst<\/b>).<\/p>\n<p>Falls auf der SQL Server-Seite etwas schiefgeht, liefert dies den bekannten Fehler mit der Nummer <b>3146 <\/b>und der bekanntlicherweise nicht besonders vielsagenden Meldung <b>ODBC-Aufruf fehlgeschlagen<\/b>. Das ist aber kein Problem, da ja die <b>Errors<\/b>-Auflistung des <b>DBEngine<\/b>-Objekts meist weitere Informationen leifert, die wir in der Variablen <b>strFehler <\/b>zusammenfassen und im Direktfenster ausgeben (Merke: Das ist keine L&ouml;sung f&uuml;r den Endanwender, sondern f&uuml;r den Entwickler, der sich mit der Migration von Daten einer Access-Tabelle zum SQL Server abm&uuml;ht).<\/p>\n<p>Tritt kein Fehler auf, addiert die Prozedur den Wert des ersten Feldes des zur&uuml;ckgegebenen Recordsets zur Variablen <b>lngAnzahl <\/b>hinzu. Dies d&uuml;rfte immer der Wert <b>1<\/b> sein, da wir ja nur einen Datensatz pro Aufruf hinzuf&uuml;gen. Tritt ein anderer Fehler als <b>3146 <\/b>auf, wird dieser per <b>MsgBox <\/b>gemeldet.<\/p>\n<p>Auf diese Weise durchl&auml;uft die Prozedur alle Datens&auml;tze der zu kopierenden Tabelle. Nach dem Verlassen der <b>Do While<\/b>-Schleife gibt die Prozedur noch die Anzahl der eingef&uuml;gten Datens&auml;tze im Direktfenster aus.<\/p>\n<h2>Aufruf der Methode<\/h2>\n<p>Wir ben&ouml;tigen f&uuml;r jede Tabelle einen eigenen Aufruf der Methode <b>TabelleKopieren_IDENTITY_INSERT<\/b>. Au&szlig;erdem sollten wir zuvor noch jeweils die bereits vorhandenen Datens&auml;tze aus den Zieltabellen l&ouml;schen &#8211; zumindest, wenn dort keine anderen Datens&auml;tze vorhanden sind, die erhalten werden m&uuml;ssen. In diesem Fall wird das Unterfangen ohnehin sehr interessant, da dann ja gegebenenfalls schon Datens&auml;tze mit entsprechenden Prim&auml;rschl&uuml;sselwerten vorhanden sind.<\/p>\n<p>Das L&ouml;schen und Kopieren der Daten erledigen wir mit der Prozedur <b>TabellenLoeschenUndKopieren<\/b> (s. Listing 7). Nach dem L&ouml;schen der Daten folgen dann die Aufrufe der Methode <b>TabelleKopieren_IDENTITY_INSERT<\/b>. In fast allen F&auml;llen &uuml;bergeben wir nur den Namen der als Zieltabelle zu verwendenden Tabelle. Die Ausnahme bildet die Tabelle <b>tblPersonal<\/b>. Hier taucht beim Kopieren das Problem auf, dass die Tabelle ein Fremdschl&uuml;sselfeld mit einem Verweis auf das eigene Prim&auml;rschl&uuml;sselfeld aufweist. Damit soll die Hierarchie der Mitarbeiter festgelegt werden. Leider gibt es keine sinnvolle Abfrage, mit der man die Daten dieser Tabelle so sortieren kann, dass sie so angelegt werden, dass die als Vorgesetzter angegebenen Mitarbeiter jeweils bereits vorhanden sind. Dies ist also ein Beispiel daf&uuml;r, warum man reflexive Verkn&uuml;pfungen besser in eine eigene Tabelle auslagert, statt das Fremdschl&uuml;sselfeld direkt in der gleichen Tabelle unterzubringen. Wie auch immer: Wir haben zur Vereinfachung einfach nur die Felder ohne das Fremdschl&uuml;sselfeld <b>VorgesetzterID<\/b> als Quelle angegeben, damit wir die Daten problemlos kopieren konnten.<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>TabellenLoeschenUndKopieren()\r\n     <span style=\"color:blue;\">Dim <\/span>db<span style=\"color:blue;\"> As <\/span>DAO.Database\r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n     db.Execute \"DELETE FROM tblBestelldetails1\", dbFailOnError\r\n     db.Execute \"DELETE FROM tblBestellungen1\", dbFailOnError + dbSeeChanges\r\n     db.Execute \"DELETE FROM tblPersonal1\", dbFailOnError + dbSeeChanges\r\n     db.Execute \"DELETE FROM tblArtikel1\", dbFailOnError\r\n     db.Execute \"DELETE FROM tblKategorien1\", dbFailOnError + dbSeeChanges\r\n     db.Execute \"DELETE FROM tblLieferanten1\", dbFailOnError\r\n     db.Execute \"DELETE FROM tblKunden1\", dbFailOnError + dbSeeChanges\r\n     db.Execute \"DELETE FROM tblAnreden1\", dbFailOnError + dbSeeChanges\r\n     TabelleKopieren_IDENTITY_INSERT \"tblAnreden\", \"tblAnreden\"\r\n     TabelleKopieren_IDENTITY_INSERT \"tblKunden\", \"tblKunden\"\r\n     TabelleKopieren_IDENTITY_INSERT \"tblLieferanten\", \"tblLieferanten\"\r\n     TabelleKopieren_IDENTITY_INSERT \"tblKategorien\", \"tblKategorien\"\r\n     TabelleKopieren_IDENTITY_INSERT \"tblArtikel\", \"tblArtikel\"\r\n     TabelleKopieren_IDENTITY_INSERT \"SELECT PersonalID, AnredeID, Nachname, &quot; _\r\n         & &quot;Vorname, Position, Geburtsdatum, \" _\r\n         & \"Einstellung, Strasse, Ort, Region, PLZ, Land, TelefonPrivat, &quot;_\r\n         & &quot;DurchwahlBuero, Foto, Bemerkungen \" _\r\n         & \"FROM tblPersonal\", \"tblPersonal\"\r\n     TabelleKopieren_IDENTITY_INSERT \"tblBestellungen\", \"tblBestellungen\"\r\n     TabelleKopieren_IDENTITY_INSERT \"tblBestelldetails\", \"tblBestelldetails\"\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 7: Leeren der Zieltabellen und Aufrufen der Methode TabelleKopieren_IDENTITY_INSERT f&uuml;r alle betroffenen Tabellen<\/span><\/b><\/p>\n<p>Damit landen nun alle Daten der Suedsturm-Datenbank von Access in der SQL Server-Datenbank. Die Ausgabe sieht so aus:<\/p>\n<pre>Hinzugef&uuml;gte Datens&auml;tze in Tabelle ''''tblAnreden'''': 2\r\nHinzugef&uuml;gte Datens&auml;tze in Tabelle ''''tblKunden'''': 91\r\nHinzugef&uuml;gte Datens&auml;tze in Tabelle ''''tblLieferanten'''': 29\r\nHinzugef&uuml;gte Datens&auml;tze in Tabelle ''''tblKategorien'''': 8\r\nHinzugef&uuml;gte Datens&auml;tze in Tabelle ''''tblArtikel'''': 77\r\nHinzugef&uuml;gte Datens&auml;tze in Tabelle ''''tblPersonal'''': 9\r\nHinzugef&uuml;gte Datens&auml;tze in Tabelle ''''tblBestellungen'''': 830\r\nHinzugef&uuml;gte Datens&auml;tze in Tabelle ''''tblBestelldetails'''': 2155<\/pre>\n<p>Die Messung der f&uuml;r diesen Vorgang ben&ouml;tigten Zeit lieferte &uuml;brigens eine Dauer von rund 19 Sekunden. In der Prozedur <b>TabelleKopieren_IDENTITY_INSERT_Kompakt <\/b>haben wir nun alle Befehle au&szlig;er die f&uuml;r die Zusammenstellung der <b>INSERT INTO<\/b>-Anweisungen aus der <b>Do While<\/b>-Schleife herausgenommen, um zu pr&uuml;fen, wieviel schneller diese Variante sein w&uuml;rde (siehe Beispieldatenbank, Modul <b>mdlBeispiele_DatenKopieren<\/b>).<\/p>\n<p>Das Ergebnis &uuml;berrascht, denn die Prozedur konnte gar nicht bis zum Ende ausgef&uuml;hrt werden. Der Grund: Die Zeichenkette mit einer L&auml;nge von rund 302.000 Zeichen konnte gar nicht der SQL-Eigenschaft des <b>QueryDef<\/b>-Objekts zugewiesen werden &#8211; der Fehler <b>3035<\/b>, <b>Nicht gen&uuml;gend System-Ressourcen<\/b>, wurde ausgel&ouml;st. Die SQL-Eigenschaft erfasst nur maximal 64 KB. Also haben wir noch eine kleine Schleife um die Erstellung und die Ausf&uuml;hrung der Pass-Through-Abfrage eingebaut und innerhalb der Schleife, welche die Variable <b>strInsert <\/b>f&uuml;llt, noch eine Abbruchbedingung hinzugef&uuml;gt, die nach dem Erreichen des Limits von 64.000 Zeichen abbricht und die zusammengestellten <b>INSERT INTO<\/b>-Anweisungen erstmal abschickt. Die gek&uuml;rzte Fassung dieser Prozedur finden Sie in Listing 8.<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>TabelleKopieren_IDENTITY_INSERT_Kompakt(strQuelle<span style=\"color:blue;\"> As String<\/span>, strZiel<span style=\"color:blue;\"> As String<\/span>)\r\n     ...\r\n     strFelder = FelderErmitteln(db, strQuelle)\r\n     <span style=\"color:blue;\">Do While<\/span> <span style=\"color:blue;\">Not<\/span> rstDatensaetze.EOF\r\n         strSQL = \"SET IDENTITY_INSERT \" & strZiel & \" ON\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n         strSQL = strSQL & \"SET NOCOUNT ON\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n         <span style=\"color:blue;\">Do While<\/span> <span style=\"color:blue;\">Not<\/span> rstDatensaetze.EOF\r\n             strWerte = WerteErmitteln(rstDatensaetze)\r\n             strInsert = \"INSERT INTO \" & strZiel & \"(\" & strFelder & \") VALUES(\" & strWerte & \")\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n             strSQL = strSQL & strInsert\r\n             rstDatensaetze.Move<span style=\"color:blue;\">Next<\/span>\r\n             <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Len<\/span>(strSQL) &gt; 64000<span style=\"color:blue;\"> Then<\/span>\r\n                 <span style=\"color:blue;\">Exit Do<\/span>\r\n             <span style=\"color:blue;\">End If<\/span>\r\n         <span style=\"color:blue;\">Loop<\/span>\r\n         strSQL = strSQL & \"SELECT @@ROWCOUNT AS AnzahlEingefuegt\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     ...\r\n     <span style=\"color:blue;\">Loop<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 8: Viel schnellere Variante der Daten&uuml;bertragung als mit je einem INSERT INTO-Aufruf pro Pass-Through-Abfrage<\/span><\/b><\/p>\n<p>Der Performance-Gewinn ist drastisch: Die Daten werden nun in ca. einer Sekunde von Access zum SQL Server kopiert.<\/p>\n<h2>Zusammenfassung und Ausblick<\/h2>\n<p>Wenn Sie einmal einen Export von Access-Daten zum SQL Server durchf&uuml;hren wollen und widrige Bedingungen herrschen &#8211; wie zum Beispiel die Anforderung, dass die Prim&auml;rschl&uuml;sselwerte erhalten bleiben &#8211; finden Sie hier ein passendes Beispiel, das einmal beleuchtet, wie der Weg zu einer performanten L&ouml;sung aussehen kann. Nat&uuml;rlich h&auml;tte man auch einfach die Autowert-Funktion auf Seiten des SQL Server ausstellen k&ouml;nnen, jedoch w&auml;re dies ein Eingriff in den Entwurf des Datenmodells, den man nur zu Beginn eines Migrationsvorhabens durchf&uuml;hren sollte. Wenn Daten auf &auml;hnliche Weise regelm&auml;&szlig;ig einem System zugef&uuml;hrt werden sollen, ist es sinnvoller, keine &auml;nderungen mehr am Entwurf des Datenmodells durchzuf&uuml;hren.<\/p>\n<h3>Downloads zu diesem Beitrag<\/h3>\n<p>Enthaltene Beispieldateien:<\/p>\n<p>DatenKopieren.accdb<\/p>\n<p><a href=\"..\/fileadmin\/beispiele\/8695BABD-C166-46B5-86B4-942C34F24E6F\/aiu_1056.zip\">Download<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Manchmal ben&ouml;tigt man eine 1:1-Kopie der Daten aus einer Access-Tabelle in einer anderen Tabelle &#8211; vielleicht in der gleichen, vielleicht aber auch in einer anderen Datenbank. Das ist mit entsprechenden Einf&uuml;geabfragen kein Problem, solange kein anderes Datenbanksystem wie etwa der SQL Server als Ziel in Frage kommt &#8211; und Sie gleichzeitig die Daten nicht nur einfach einf&uuml;gen, sondern auch noch die Inhalte der Autowertfelder beibehalten wollen. Dieser Beitrag zeigt praktische Erfahrungen und L&ouml;sungsweise f&uuml;r spezielle Migrationsvorhaben auf.<\/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":[662016,66052016,44000022],"tags":[],"class_list":["post-55001056","post","type-post","status-publish","format-standard","hentry","category-662016","category-66052016","category-SQL_Server_und_Co"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v20.9 (Yoast SEO v27.3) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Daten von Access zum SQL Server kopieren - Access im Unternehmen<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Daten von Access zum SQL Server kopieren\" \/>\n<meta property=\"og:description\" content=\"Manchmal ben&ouml;tigt man eine 1:1-Kopie der Daten aus einer Access-Tabelle in einer anderen Tabelle - vielleicht in der gleichen, vielleicht aber auch in einer anderen Datenbank. Das ist mit entsprechenden Einf&uuml;geabfragen kein Problem, solange kein anderes Datenbanksystem wie etwa der SQL Server als Ziel in Frage kommt - und Sie gleichzeitig die Daten nicht nur einfach einf&uuml;gen, sondern auch noch die Inhalte der Autowertfelder beibehalten wollen. Dieser Beitrag zeigt praktische Erfahrungen und L&ouml;sungsweise f&uuml;r spezielle Migrationsvorhaben auf.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/\" \/>\n<meta property=\"og:site_name\" content=\"Access im Unternehmen\" \/>\n<meta property=\"article:published_time\" content=\"2020-05-22T15:25:07+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/vg09.met.vgwort.de\/na\/c24045bed6dc4062abaadf2b7400acf6\" \/>\n<meta name=\"author\" content=\"Andr\u00e9 Minhorst\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Andr\u00e9 Minhorst\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"30\u00a0Minuten\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Daten_von_Access_zum_SQL_Server_kopieren\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Daten_von_Access_zum_SQL_Server_kopieren\\\/\"},\"author\":{\"name\":\"Andr\u00e9 Minhorst\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/person\\\/13395c4bcd7d7963efe33be9c584d93f\"},\"headline\":\"Daten von Access zum SQL Server kopieren\",\"datePublished\":\"2020-05-22T15:25:07+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Daten_von_Access_zum_SQL_Server_kopieren\\\/\"},\"wordCount\":4805,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Daten_von_Access_zum_SQL_Server_kopieren\\\/#primaryimage\"},\"thumbnailUrl\":\"http:\\\/\\\/vg09.met.vgwort.de\\\/na\\\/c24045bed6dc4062abaadf2b7400acf6\",\"articleSection\":[\"2016\",\"5\\\/2016\",\"SQL Server und Co.\"],\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/access-im-unternehmen.de\\\/Daten_von_Access_zum_SQL_Server_kopieren\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Daten_von_Access_zum_SQL_Server_kopieren\\\/\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Daten_von_Access_zum_SQL_Server_kopieren\\\/\",\"name\":\"Daten von Access zum SQL Server kopieren - Access im Unternehmen\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Daten_von_Access_zum_SQL_Server_kopieren\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Daten_von_Access_zum_SQL_Server_kopieren\\\/#primaryimage\"},\"thumbnailUrl\":\"http:\\\/\\\/vg09.met.vgwort.de\\\/na\\\/c24045bed6dc4062abaadf2b7400acf6\",\"datePublished\":\"2020-05-22T15:25:07+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Daten_von_Access_zum_SQL_Server_kopieren\\\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/access-im-unternehmen.de\\\/Daten_von_Access_zum_SQL_Server_kopieren\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Daten_von_Access_zum_SQL_Server_kopieren\\\/#primaryimage\",\"url\":\"http:\\\/\\\/vg09.met.vgwort.de\\\/na\\\/c24045bed6dc4062abaadf2b7400acf6\",\"contentUrl\":\"http:\\\/\\\/vg09.met.vgwort.de\\\/na\\\/c24045bed6dc4062abaadf2b7400acf6\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Daten_von_Access_zum_SQL_Server_kopieren\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/access-im-unternehmen.de\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Daten von Access zum SQL Server kopieren\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#website\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/\",\"name\":\"Access im Unternehmen\",\"description\":\"Das Magazin f\u00fcr Datenbankentwickler auf Basis von Microsoft Access\",\"publisher\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/access-im-unternehmen.de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#organization\",\"name\":\"Andr\u00e9 Minhorst Verlag\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/wp-content\\\/uploads\\\/2019\\\/09\\\/aiu_wp.png\",\"contentUrl\":\"https:\\\/\\\/access-im-unternehmen.de\\\/wp-content\\\/uploads\\\/2019\\\/09\\\/aiu_wp.png\",\"width\":370,\"height\":111,\"caption\":\"Andr\u00e9 Minhorst Verlag\"},\"image\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/person\\\/13395c4bcd7d7963efe33be9c584d93f\",\"name\":\"Andr\u00e9 Minhorst\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"caption\":\"Andr\u00e9 Minhorst\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Daten von Access zum SQL Server kopieren - Access im Unternehmen","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/","og_locale":"de_DE","og_type":"article","og_title":"Daten von Access zum SQL Server kopieren","og_description":"Manchmal ben&ouml;tigt man eine 1:1-Kopie der Daten aus einer Access-Tabelle in einer anderen Tabelle - vielleicht in der gleichen, vielleicht aber auch in einer anderen Datenbank. Das ist mit entsprechenden Einf&uuml;geabfragen kein Problem, solange kein anderes Datenbanksystem wie etwa der SQL Server als Ziel in Frage kommt - und Sie gleichzeitig die Daten nicht nur einfach einf&uuml;gen, sondern auch noch die Inhalte der Autowertfelder beibehalten wollen. Dieser Beitrag zeigt praktische Erfahrungen und L&ouml;sungsweise f&uuml;r spezielle Migrationsvorhaben auf.","og_url":"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/","og_site_name":"Access im Unternehmen","article_published_time":"2020-05-22T15:25:07+00:00","og_image":[{"url":"http:\/\/vg09.met.vgwort.de\/na\/c24045bed6dc4062abaadf2b7400acf6","type":"","width":"","height":""}],"author":"Andr\u00e9 Minhorst","twitter_card":"summary_large_image","twitter_misc":{"Verfasst von":"Andr\u00e9 Minhorst","Gesch\u00e4tzte Lesezeit":"30\u00a0Minuten"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/#article","isPartOf":{"@id":"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/"},"author":{"name":"Andr\u00e9 Minhorst","@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/person\/13395c4bcd7d7963efe33be9c584d93f"},"headline":"Daten von Access zum SQL Server kopieren","datePublished":"2020-05-22T15:25:07+00:00","mainEntityOfPage":{"@id":"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/"},"wordCount":4805,"commentCount":0,"publisher":{"@id":"https:\/\/access-im-unternehmen.de\/#organization"},"image":{"@id":"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/#primaryimage"},"thumbnailUrl":"http:\/\/vg09.met.vgwort.de\/na\/c24045bed6dc4062abaadf2b7400acf6","articleSection":["2016","5\/2016","SQL Server und Co."],"inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/","url":"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/","name":"Daten von Access zum SQL Server kopieren - Access im Unternehmen","isPartOf":{"@id":"https:\/\/access-im-unternehmen.de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/#primaryimage"},"image":{"@id":"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/#primaryimage"},"thumbnailUrl":"http:\/\/vg09.met.vgwort.de\/na\/c24045bed6dc4062abaadf2b7400acf6","datePublished":"2020-05-22T15:25:07+00:00","breadcrumb":{"@id":"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/#primaryimage","url":"http:\/\/vg09.met.vgwort.de\/na\/c24045bed6dc4062abaadf2b7400acf6","contentUrl":"http:\/\/vg09.met.vgwort.de\/na\/c24045bed6dc4062abaadf2b7400acf6"},{"@type":"BreadcrumbList","@id":"https:\/\/access-im-unternehmen.de\/Daten_von_Access_zum_SQL_Server_kopieren\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/access-im-unternehmen.de\/"},{"@type":"ListItem","position":2,"name":"Daten von Access zum SQL Server kopieren"}]},{"@type":"WebSite","@id":"https:\/\/access-im-unternehmen.de\/#website","url":"https:\/\/access-im-unternehmen.de\/","name":"Access im Unternehmen","description":"Das Magazin f\u00fcr Datenbankentwickler auf Basis von Microsoft Access","publisher":{"@id":"https:\/\/access-im-unternehmen.de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/access-im-unternehmen.de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/access-im-unternehmen.de\/#organization","name":"Andr\u00e9 Minhorst Verlag","url":"https:\/\/access-im-unternehmen.de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/logo\/image\/","url":"https:\/\/access-im-unternehmen.de\/wp-content\/uploads\/2019\/09\/aiu_wp.png","contentUrl":"https:\/\/access-im-unternehmen.de\/wp-content\/uploads\/2019\/09\/aiu_wp.png","width":370,"height":111,"caption":"Andr\u00e9 Minhorst Verlag"},"image":{"@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/person\/13395c4bcd7d7963efe33be9c584d93f","name":"Andr\u00e9 Minhorst","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","caption":"Andr\u00e9 Minhorst"}}]}},"_links":{"self":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/posts\/55001056","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/comments?post=55001056"}],"version-history":[{"count":0,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/posts\/55001056\/revisions"}],"wp:attachment":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/media?parent=55001056"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/categories?post=55001056"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/tags?post=55001056"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}