{"id":55000451,"date":"2024-12-01T00:00:00","date_gmt":"2025-03-10T20:19:18","guid":{"rendered":"http:\/\/access-im-unternehmen.aix-dev.de\/aiu\/?p=451"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-30T00:00:00","slug":"SQL_Server_Fehlerbehandlung_in_DAO_und_ADODB","status":"publish","type":"post","link":"https:\/\/vbentwickler.de\/SQL_Server_Fehlerbehandlung_in_DAO_und_ADODB\/","title":{"rendered":"SQL Server: Fehlerbehandlung in DAO und ADODB"},"content":{"rendered":"<p><b>Die Fehlerbehandlung in VBA ist relativ einfach zu handhaben, sobald man einmal die grundlegenden Techniken kennt. Nicht viel anders sollte es eigentlich sein, wenn man die Daten nicht mehr aus Access-Tabellen bezieht, sondern aus SQL Server-Tabellen. Es gibt jedoch einige Unterschiede, die man kennen sollte. In diesem Artikel zeigen wir, welche Besonderheiten auftreten, wenn man nicht mehr auf Access-Tabellen zugreift, sondern auf verkn&uuml;pfte SQL Server-Tabellen und dabei die Datenzugriffstechniken DAO- und ADODB nutzt. Im letzteren Fall gibt es sogar noch Unterschiede bez&uuml;glich der verwendeten Treiber\/Provider.<\/b><\/p>\n<p>Wenn wir mit VBA programmieren und es wird ein Laufzeitfehler ausgel&ouml;st, liefert dieser immer genau ein Fehlerobjekt namens <b>Err <\/b>zur&uuml;ck, das uns mit Eigenschaften wie <b>Number <\/b>oder <b>Descriptionen <\/b>Informationen &uuml;ber den Fehler liefert.<\/p>\n<p>Wenn wir mit DAO auf die Daten eines SQL Servers oder einer anderen per ODBC-Tabellenverkn&uuml;pfung eingebundenen Datenbanksystems zugreifen, erhalten wir ebenfalls eine Fehlermeldung mit <b>Err<\/b>.<\/p>\n<p>Allerdings lautet diese immer gleich &#8211; n&auml;mlich den Fehler <b>ODBC-Aufruf fehlgeschlagen <\/b>mit der Nummer <b>3146<\/b>.<\/p>\n<p>Damit kann man eigentlich nichts anfangen &#8211; da dieser Fehler immer in Zusammenhang mit dem Datenzugriff auftritt, wissen wir zwar grob, worum es geht, aber nicht genau, was der eigentliche Fehler ist.<\/p>\n<p>Man kann zwar nun die ausl&ouml;sende SQL-Anweisung kopieren, sie im SQL Server Management Studio ausf&uuml;hren und erh&auml;lt dann dort die eigentliche Fehlermeldung. Das hilft uns allerdings nicht, wenn wir im VBA-Code darauf reagieren wollen.<\/p>\n<p>In diesem Artikel stellen wir Dir eine M&ouml;glichkeit vor, wie Du dennoch auf die Fehlerinformationen zugreifen kannst. Dazu gibt es in DAO und ADODB verschiedene M&ouml;glichkeiten.<\/p>\n<h2>Unterscheidung der verschiedenen Fehler-Klassen<\/h2>\n<p>Zuerst jedoch ein kleiner Blick auf das &#8220;normale&#8221; <b>Err<\/b>-Objekt. Dieses hat den Typ <b>ErrObject <\/b>und bietet die folgenden Eigenschaften und Methoden.<\/p>\n<p>Hier zun&auml;chst die Eigenschaften:<\/p>\n<ul>\n<li><b>Description<\/b>: Enth&auml;lt die Fehlerbeschreibung als String.<\/li>\n<li><b>HelpContext<\/b>: Gibt die ID des Hilfethemas zur&uuml;ck, das mit dem Fehler verbunden ist.<\/li>\n<li><b>HelpFile<\/b>: Gibt den Pfad zur zugeh&ouml;rigen Hilfedatei zur&uuml;ck (falls vorhanden).<\/li>\n<li><b>LastDLLError<\/b>: Gibt den letzten Windows-API-Fehlercode zur&uuml;ck (nur bei API-Aufrufen relevant).<\/li>\n<li><b>Number<\/b>: Gibt die Fehlernummer zur&uuml;ck. <b>0<\/b> bedeutet, dass kein Fehler aufgetreten ist. Bei eingebauten Elementen sind die Fehlernummern positiv und ein- bis f&uuml;nfstellig. Es gibt aber auch Fehlermeldungen mit gro&szlig;en negativen Fehlernummern.<\/li>\n<li><b>Source<\/b>: Gibt die Quelle des Fehlers zur&uuml;ck (zum Beispiel <b>VBAProject<\/b>).<\/li>\n<\/ul>\n<p>Und hier sind die beiden Methoden:<\/p>\n<ul>\n<li><b>Clear<\/b>: Setzt alle Fehlerinformationen zur&uuml;ck (Number = 0, Description = &#8220;&#8221;, et cetera).<\/li>\n<li><b>Raise<\/b>: Erzeugt einen benutzerdefinierten Fehler mit einer bestimmten Fehlernummer.<\/li>\n<\/ul>\n<p>Unter DAO gibt es dagegen wesentlich weniger Elemente. Die Fehlerklasse hei&szlig;t hier auch nicht <b>ErrObject<\/b>, sondern <b>Error<\/b>. Hier gibt es nur (Beschreibung wie bei VBA):<\/p>\n<ul>\n<li><b>Description<\/b><\/li>\n<li><b>Number<\/b><\/li>\n<li><b>Source<\/b><\/li>\n<\/ul>\n<p>Unter ADO finden wir wieder fast die gleichen Eigenschaften wie unter VBA, n&auml;mlich <b>Description<\/b>, <b>HelpContext<\/b>, <b>HelpFile<\/b>, <b>Number <\/b>und <b>Source<\/b>. Wir finden aber auch zwei weitere Eigenschaften:<\/p>\n<ul>\n<li><b>NativeError<\/b>: Liefert uns die Server-spezifische Fehlernummer. So erhalten wir beispielsweise f&uuml;r den gleichen Fehler im VBA-Error die Nummer <b>-2147217873 <\/b>und im ADODB-Error die Nummer <b>547<\/b>.<\/li>\n<li><b>SQLState<\/b>: Gibt den SQL-Fehlercode im standardisierten 5-stelligen Zeichenformat zur&uuml;ck. Diese Codes folgen dem SQL-92-Standard und sind von der Datenbank unabh&auml;ngig.<\/li>\n<\/ul>\n<h2>Fehler beim Zugriff mit DAO auswerten<\/h2>\n<p>Wenn beim Zugriff auf eine SQL Server-Datenbank &uuml;ber eine Tabellenverkn&uuml;pfung oder andere Zugriffe per ODBC Fehler auftreten, k&ouml;nnen wir diese mit der herk&ouml;mmlichen <b>Err<\/b>-Klasse nur bedingt analysieren.<\/p>\n<p>Der Grund ist, dass wir mit der <b>Err<\/b>-Klasse nur Informationen zum letzten aufgetretenen Fehler erhalten. Das ist auch so, wenn wir per Tabellenverkn&uuml;pfung auf eine per ODBC verkn&uuml;pfte Datenbank zugreifen. Hier k&ouml;nnen wir dann jedoch die <b>Errors<\/b>-Auflistung von DAO hinzuziehen. Hier finden wir jedoch noch ein weiteres Problem vor, n&auml;mlich dass die <b>Err<\/b>-Klasse immer nur eine generische Meldung liefert, die keinen Hinweis auf den tats&auml;chlichen Fehler enth&auml;lt. Die folgenden Anweisungen sollten beispielsweise einen Fehler ausl&ouml;sen, weil wir dem Feld <b>AnlageID <\/b>keinen Wert zuweisen k&ouml;nnen:<\/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\ndb.Execute \"INSERT INTO tblAnlagen(AnlageID) VALUES(1)\", dbFailOnError + dbSeeChanges<\/pre>\n<p>Wir erhalten daf&uuml;r jedoch lediglich die Fehlermeldung aus Bild 1.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_06\/pic_451_006.png\" alt=\"Fehler beim Ausf&uuml;hren einer SQL Server-Abfrage\" width=\"499,6267\" height=\"283,6019\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 1: Fehler beim Ausf&uuml;hren einer SQL Server-Abfrage<\/span><\/b><\/p>\n<h2>Fehler mit DAO.Errors auswerten<\/h2>\n<p>Es gibt jedoch nicht nur das <b>Err<\/b>-Objekt, um Fehler auszuwerten. Wir k&ouml;nnen auch die <b>DAO.Errors<\/b>-Auflistung bem&uuml;hen, um den tats&auml;chlich vom SQL Server gelieferten Fehler zu erhalten. Diese liefert uns Elemente des gleichen Typs wie unser bekanntes <b>Error<\/b>-Objekt. Diese k&ouml;nnen wir in einer Schleife &uuml;ber die <b>Errors<\/b>-Auflistung der DAO-Klasse durchlaufen:<\/p>\n<pre>For i = 0 To DAO.Errors.Count\r\n     <span style=\"color:blue;\">Debug.Print<\/span> i, DAO.Errors(i).Description\r\n<span style=\"color:blue;\">Next<\/span> i<\/pre>\n<p>Wir schauen uns diese Codezeilen einmal im Zusammenhang mit einer Prozedur an, die einen entsprechenden Fehler ausl&ouml;st (siehe Listing 1). Hier erhalten wir die Fehlermeldungen, wie sie auch im SQL Server angezeigt werden:<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>DAOFehlerAnalysieren()\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>i<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n     On Error Resume <span style=\"color:blue;\">Next<\/span>\r\n     db.Execute \"INSERT INTO tblAnlagen(AnlageID) VALUES(1)\", dbFailOnError + dbSeeChanges\r\n     <span style=\"color:blue;\">Debug.Print<\/span> db.RecordsAffected, Err.Number\r\n     <span style=\"color:blue;\">Debug.Print<\/span> \"DAO.Errors:\"\r\n     For i = 0 To DAO.Errors.Count\r\n         <span style=\"color:blue;\">Debug.Print<\/span> i, DAO.Errors(i).Description\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: Analysieren von Fehlern beim DAO-Zugriff auf ODBC-Verbindungen<\/span><\/b><\/p>\n<pre>[Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Verletzung der PRIMARY KEY-Einschr&auml;nkung \"tblAnlagen$PrimaryKey\". Ein doppelter Schl&uuml;ssel kann in das dbo.tblAnlagen-Objekt nicht eingef&uuml;gt werden. Der doppelte Schl&uuml;sselwert ist (1).\r\n[Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Die Anweisung wurde beendet.\r\nODBC-Aufruf fehlgeschlagen.<\/pre>\n<h2>Fehlerbehandlung unter ADODB<\/h2>\n<p>Das ist bei Fehlern bei Datenbankzugriffen mit ADODB bereits etwas besser gel&ouml;st &#8211; hier erhalten wir eine aussagekr&auml;ftige Fehlermeldung. Wir l&ouml;sen in Listing 2 den gleichen Fehler aus wie in der DAO-Version, nur diesmal &uuml;ber die <b>Execute<\/b>-Methode der <b>Connection<\/b>-Klasse.<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>FehlerAnalysieren()\r\n     <span style=\"color:blue;\">Dim <\/span>cnn<span style=\"color:blue;\"> As <\/span>ADODB.Connection\r\n     <span style=\"color:blue;\">Dim <\/span>strConnection<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strSQL<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>objError<span style=\"color:blue;\"> As <\/span>ADODB.Error\r\n     <span style=\"color:blue;\">Set<\/span> cnn = <span style=\"color:blue;\">New<\/span> ADODB.Connection\r\n     strConnection = \"Provider=MSOLEDBSQL;Server=amvDesktop2023;Trusted_Connection=yes;Database=Anlagen\"\r\n     strSQL = \"INSERT INTO tblAnlagen(AnlageID) VALUES(1)\"\r\n     cnn.ConnectionString = strConnection\r\n     cnn.Open\r\n     On Error Resume <span style=\"color:blue;\">Next<\/span>\r\n     cnn.Execute strSQL\r\n     <span style=\"color:blue;\">Debug.Print<\/span> \"Err.Number: \" & Err.Number\r\n     <span style=\"color:blue;\">Debug.Print<\/span> \"Err.Description: \" & Err.Description\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> Err.Number = 0<span style=\"color:blue;\"> Then<\/span>\r\n         For Each objError In cnn.Errors\r\n             <span style=\"color:blue;\">With<\/span> objError\r\n                 <span style=\"color:blue;\">Debug.Print<\/span> \"Number:          \" & .Number\r\n                 <span style=\"color:blue;\">Debug.Print<\/span> \"Description:     \" & .Description\r\n                 <span style=\"color:blue;\">Debug.Print<\/span> \"NativeError:     \" & .NativeError\r\n                 <span style=\"color:blue;\">Debug.Print<\/span> \"Source:          \" & .Source\r\n                 <span style=\"color:blue;\">Debug.Print<\/span> \"SQLState:        \" & .SQLState\r\n             End <span style=\"color:blue;\">With<\/span>\r\n         <span style=\"color:blue;\">Next<\/span> objError\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     cnn.Close\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 2: Beispiel f&uuml;r einen SQL Server-Fehler<\/span><\/b><\/p>\n<p>Bereits f&uuml;r <b>Err.Number <\/b>und <b>Err.Description <\/b>erhalten wir diese Meldung:<\/p>\n<pre>Err.Number: -2147217873\r\nErr.Description: Ein expliziter Wert f&uuml;r die Identit&auml;tsspalte kann nicht in der tblAnlagen-Tabelle eingef&uuml;gt werden, wenn IDENTITY_INSERT auf OFF festgelegt ist.<\/pre>\n<p>Die Ausgabe des Fehlers der <b>Errors<\/b>-Auflistung des <b>Connection<\/b>-Objekts liefert folgende Informationen:<\/p>\n<pre>Number:          -2147217873\r\nDescription:     Ein expliziter Wert f&uuml;r die Identit&auml;tsspalte kann nicht in der tblAnlagen-Tabelle eingef&uuml;gt werden, wenn IDENTITY_INSERT auf OFF festgelegt ist.\r\nNativeError:     544\r\nSource:          Microsoft OLE DB Driver for SQL Server\r\nSQLState:        23000<\/pre>\n<p>Unter <b>Number <\/b>und <b>Description <\/b>erhalten wir die gleichen Informationen wir &uuml;ber das <b>Err<\/b>-Objekt.<\/p>\n<p>Mit <b>NativeError <\/b>erhalten wir die SQL Server-interne Fehlernummer. Alle Fehlernummern und Fehlerbeschreibungen des SQL Servers in deutscher Sprache k&ouml;nnen wir uns &uuml;brigens mit der folgenden Anweisung, abgesetzt im SQL Server Management Studio, anzeigen lassen:<\/p>\n<pre>SELECT * FROM sys.messages WHERE language_id = 1031;<\/pre>\n<p><b>Source <\/b>liefert die Fehlerquelle, in diesem Fall den Treiber, der hinter dem Provider <b>MSOLEDBSQL <\/b>steckt. Und <b>SQLState <\/b>liefert einen Zahlenwert, der wiederum einen ODBC-Fehlercode enth&auml;lt. Diese Fehlercodes k&ouml;nnen wir beispielsweise auf der folgende Seite nachlesen:<\/p>\n<pre>https:\/\/learn.microsoft.com\/en-us\/sql\/odbc\/reference\/appendixes\/appendix-a-odbc-error-codes?view=sql-server-ver15<\/pre>\n<p>Anderenfalls sollten diese unter dem Schl&uuml;sselwort <b>ODBC Error Codes <\/b>zu finden sein.<\/p>\n<p>F&uuml;r den Zahlencode <b>23000 <\/b>finden wir hier beispielsweise die Information <b>Integrity constraint violation<\/b>.<\/p>\n<h2>Benutzerdefinierte Fehler in gespeicherten Prozeduren<\/h2>\n<p>Um das n&auml;chste Beispiel sowohl mit DAO als auch mit ADODB nachvollziehen zu k&ouml;nnen, legen wir eine gespeicherte Prozedur in einer SQL Server-Datenbank an, die wie in Listing 3 aussieht.<\/p>\n<pre>CREATE PROCEDURE Beispielfehlerprozedur_EinFehler\r\nAS  \r\nBEGIN  \r\n     SET NOCOUNT ON;  \r\n     BEGIN TRY  \r\n         -- Ein Fehler wird absichtlich erzeugt (Division durch Null)\r\n         DECLARE @x INT = 1 \/ 0;  \r\n     END TRY  \r\n     BEGIN CATCH  \r\n         -- Fehlerinformationen abrufen\r\n         DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();  \r\n         DECLARE @ErrorSeverity INT = ERROR_SEVERITY();  \r\n         DECLARE @ErrorState INT = ERROR_STATE();  \r\n         -- Fehler zur&uuml;ckgeben\r\n         RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState);  \r\n     END CATCH  \r\nEND;<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 3: Erzeugen eines Fehlers in einer gespeicherten Prozedur<\/span><\/b><\/p>\n<p>Die <b>CREATE PROCEDURE<\/b>-Anweisung verwendet die <b>TRY&#8230;CATCH<\/b>-Struktur von T-SQL. Im <b>TRY<\/b>-Block, der mit <b>BEGIN TRY <\/b>eingeleitet wird, weisen wir einer Variablen das Ergebnis einer Berechnung zu, die durch das Teilen von <b>1 <\/b>durch <b>0 <\/b>einen Fehler ausl&ouml;st.<\/p>\n<p>Im <b>CATCH<\/b>-Block deklarieren wir die drei Variablen <b>@ErrorMessage<\/b>, <b>@ErrorSeverity <\/b>und <b>@ErrorState<\/b> und weisen diesen die Ergebnisse der Funktionen <b>ERROR_MESSAGE()<\/b>, <b>ERROR_SEVERITY() <\/b>und <b>ERROR_STATE() <\/b>zu.<\/p>\n<p>Schlie&szlig;lich rufen wir die <b>RAISEERROR<\/b>-Methode auf und &uuml;bergeben dieser die drei zuvor festgelegten Variablen als Parameter. Durch das Ausf&uuml;hren der <b>CREATE PROCEDURE<\/b>-Anweisung wird die gespeicherte Prozedur erstellt.<\/p>\n<p>Dann rufen wir diese gespeicherte Prozedur mit der folgenden Anweisung auf:<\/p>\n<pre>EXEC Beispielfehlerprozedur_EinFehler<\/pre>\n<p>Damit erhalten wir eine Fehlermeldung, die wie eine herk&ouml;mmlich ausgel&ouml;ste Fehlermeldung aussieht (siehe Bild 2).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_06\/pic_451_001.png\" alt=\"Ausf&uuml;hren einer SQL Server-Abfrage im SQL Server Management Studio\" width=\"700\" height=\"226,0187\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 2: Ausf&uuml;hren einer SQL Server-Abfrage im SQL Server Management Studio<\/span><\/b><\/p>\n<h2>Fehler aus gespeicherter Prozedur mit VBA\/DAO auswerten<\/h2>\n<p>Nun wollen wir die gespeicherte Prozedur per DAO &uuml;ber VBA aufrufen und den Fehler ausl&ouml;sen und behandeln (siehe Listing 4). Dazu deaktivierten wir zun&auml;chst die eingebaute Fehlerbehandlung mit <b>On Error Resume Next<\/b>. Dann referenzieren wir das aktuelle <b>Database<\/b>-Objekt mit <b>db <\/b>und erstellen mit der <b>CreateQueryDef<\/b>-Methode eine neue tempor&auml;re Abfrage.<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>DAO_TestError()\r\n     On Error Resume <span style=\"color:blue;\">Next<\/span> '' Fehlerbehandlung aktivieren\r\n     \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>qdf<span style=\"color:blue;\"> As <\/span>DAO.QueryDef\r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb()\r\n     \r\n     '' Gespeicherte Prozedur ausf&uuml;hren\r\n     <span style=\"color:blue;\">Set<\/span> qdf = db.CreateQueryDef(\"\")\r\n     qdf.Connect = \"ODBC;DRIVER={ODBC Driver 17 for SQL Server};SERVER=amvDesktop2023;DATABASE=Anlagen;\" _\r\n         & \"Trusted_Connection=Yes;\"\r\n     qdf.SQL = \"EXEC Beispielfehlerprozedur_EinFehler\" '' Prozedur aufrufen\r\n     qdf.ReturnsRecords = <span style=\"color:blue;\">False<\/span>\r\n     qdf.Execute\r\n     \r\n     '' Fehler abfangen\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> Err.Number = 0<span style=\"color:blue;\"> Then<\/span>\r\n         <span style=\"color:blue;\">MsgBox<\/span> \"DAO-Fehler: \" & Err.Number & <span style=\"color:blue;\">vbCrLf<\/span> & Err.Description, <span style=\"color:blue;\">vbCr<\/span>itical, \"Fehler abgefangen\"\r\n         Err.Clear\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">Set<\/span> qdf = Nothing\r\n     <span style=\"color:blue;\">Set<\/span> db = Nothing\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 4: Testen der gespeicherten Prozedur<\/span><\/b><\/p>\n<p>Der <b>Connect<\/b>-Eigenschaft des <b>QueryDef<\/b>-Objekts f&uuml;llen wir mit einer Verbindungszeichenfolge f&uuml;r unsere Beispieldatenbank. Dann weisen wir der SQL-Eigenschaft den Aufruf unserer gespeicherten Prozedur zu. Die Eigenschaft <b>ReturnsRecords <\/b>stellen wir auf <b>False <\/b>ein und f&uuml;hren die Abfrage schlie&szlig;lich mit dem <b>Execute<\/b>-Befehl aus. Wenn <b>Err.Number <\/b>danach nicht den Wert <b>0 <\/b>enth&auml;lt, also ein Fehler aufgetreten ist, wollen wir <b>Err.Number <\/b>und <b>Err.Description <\/b>in einer Meldung ausgeben. Hier erhalten wir nach dem Ausf&uuml;hren der Prozedur den Fehler mit der Nummer <b>3146 <\/b>und der Beschreibung <b>ODBC-Aufruf fehlgeschlagen<\/b>.<\/p>\n<p>Hier sehen wir also erst einmal nicht die Meldung, die wir eigentlich vom SQL Server aus &uuml;bergeben wollten. Das liegt, wie weiter oben erl&auml;utert, daran, dass immer nur die Standardmeldung ausgegeben wird. Wir ersetzen nun den Teil, der den Fehler ausgibt, durch die folgenden Anweisungen:<\/p>\n<pre><span style=\"color:blue;\">If <\/span>Err.Number &lt;&gt; 0<span style=\"color:blue;\"> Then<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>intFehler<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>objError<span style=\"color:blue;\"> As <\/span>DAO.Error\r\n     For intFehler = 0 To DAO.Errors.Count - 1\r\n         <span style=\"color:blue;\">Set<\/span> objError = DAO.Errors(intFehler)\r\n         <span style=\"color:blue;\">Debug.Print<\/span> intFehler, objError.Number, _\r\n             objError.Description, objError.Source\r\n     <span style=\"color:blue;\">Next<\/span> intFehler\r\n<span style=\"color:blue;\">End If<\/span><\/pre>\n<p>F&uuml;hren wir die Prozedur nun erneut aus, erhalten wir das Ergebnis aus Bild 3 im Direktbereich. Hier sehen wir nun den Fehler, dessen Parameter wir im T-SQL-Skript festgelegt haben.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_06\/pic_451_002.png\" alt=\"Alle Fehler im Direktbereich\" width=\"700\" height=\"109,35\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 3: Alle Fehler im Direktbereich<\/span><\/b><\/p>\n<h2>Fehler aus gespeicherter Prozedur mit VBA\/ADODB auswerten<\/h2>\n<p>Nun schauen wir uns an, welche Fehlerinformationen die gleiche Prozedur unter Verwendung von ADODB hervorbringt. Dazu verwenden wir die Prozedur aus Listing 5.<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>ADO_TestError()\r\n     On Error Resume <span style=\"color:blue;\">Next<\/span> '' Fehlerbehandlung aktivieren\r\n     <span style=\"color:blue;\">Dim <\/span>cnn<span style=\"color:blue;\"> As <\/span>ADODB.Connection\r\n     <span style=\"color:blue;\">Dim <\/span>cmd<span style=\"color:blue;\"> As <\/span>ADODB.Command\r\n     <span style=\"color:blue;\">Dim <\/span>objError<span style=\"color:blue;\"> As <\/span>ADODB.Error\r\n     <span style=\"color:blue;\">Dim <\/span>intFehler<span style=\"color:blue;\"> As Integer<\/span>\r\n     '' Verbindung zu SQL Server herstellen\r\n     <span style=\"color:blue;\">Set<\/span> cnn = CreateObject(\"ADODB.Connection\")\r\n     cnn.ConnectionString = \"Provider=MSOLEDBSQL;Data Source=amvDesktop2023;Initial Catalog=Anlagen;\" _\r\n         & \"Integrated Security=SSPI;\"\r\n     cnn.Open\r\n     \r\n     '' SQL-Befehl vorbereiten\r\n     <span style=\"color:blue;\">Set<\/span> cmd = CreateObject(\"ADODB.Command\")\r\n     <span style=\"color:blue;\">Set<\/span> cmd.ActiveConnection = cnn\r\n     cmd.CommandText = \"EXEC Beispielfehlerprozedur_EinFehler\" '' Prozedur aufrufen\r\n     cmd.CommandType = adCmdText '' (Optional, verbessert Performance)\r\n     \r\n     '' Prozedur ausf&uuml;hren\r\n     cmd.Execute\r\n     '' Fehlerbehandlung\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> Err.Number = 0<span style=\"color:blue;\"> Then<\/span>\r\n         <span style=\"color:blue;\">Debug.Print<\/span> Err.Number, Err.Description, Err.Source\r\n         For intFehler = 0 To cnn.Errors.Count - 1\r\n             <span style=\"color:blue;\">Set<\/span> objError = cnn.Errors(intFehler)\r\n             <span style=\"color:blue;\">Debug.Print<\/span> intFehler, objError.Number, objError.Description, objError.Source, objError.NativeError, _\r\n                 objError.SQLState\r\n         <span style=\"color:blue;\">Next<\/span> intFehler\r\n         cnn.Errors.Clear\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     '' Aufr&auml;umen\r\n     cnn.Close\r\n     <span style=\"color:blue;\">Set<\/span> cmd = Nothing\r\n     <span style=\"color:blue;\">Set<\/span> cnn = Nothing\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 5: Fehler ausgeben mit ADODB<\/span><\/b><\/p>\n<p>Hier definieren wir drei Variablen f&uuml;r das <b>Connection<\/b>&#8211; und das <b>Command<\/b>-Objekt sowie das <b>Error<\/b>-Objekt. Wir erstellen eine Verbindung zur Datenbank und &ouml;ffnen diese mit der <b>Open<\/b>-Methode.<\/p>\n<p>Dann erstellen wir ein <b>Command<\/b>-Objekt und weisen diesem die Verbindung aus <b>cnn <\/b>zu. Als <b>CommandText <\/b>legen wir den Aufruf der gespeicherten Prozedur fest. Als <b>CommandType <\/b>nutzen wir die Konstante <b>acCmdText<\/b>. Schlie&szlig;lich f&uuml;hren wir die gespeicherten Prozedur mit der <b>Execute<\/b>-Methode des <b>Command<\/b>-Objekts aus.<\/p>\n<p>Auch hier pr&uuml;fen wir wieder, ob <b>Err.Number <\/b>einen Wert ungleich <b>0 <\/b>aufweist. Dann geben wir als Erstes den Fehler aus, wie wir es auch im ersten Beispiel erledigt haben:<\/p>\n<pre><span style=\"color:blue;\">Debug.Print<\/span> Err.Number, Err.Description, Err.Source<\/pre>\n<p>Dies liefert:<\/p>\n<pre>-2147217900 Fehler aufgrund einer Division durch Null.              Microsoft OLE DB Driver for SQL Server<\/pre>\n<p>Nun geben wir noch die Fehler aus, die in der <b>Errors<\/b>-Auflistung von ADODB aufgelaufen sind.<\/p>\n<p>Hier erhalten wir:<\/p>\n<pre>0            \r\n-2147217900   \r\nFehler aufgrund einer Division durch Null.\r\nMicrosoft OLE DB Driver for SQL Server\r\n50000\r\n42000<\/pre>\n<p>Die ersten Daten entsprechen denen, die uns auch das <b>Err<\/b>-Objekt liefert. Die letzten beiden sind die Werte f&uuml;r <b>NativeError <\/b>und <b>SQLState<\/b>, die wir wiederum individuell auswerten k&ouml;nnen. Die wichtigste Erkenntnis ist aber: W&auml;hrend uns die <b>Errors<\/b>-Auflistung der DAO-Klasse und in diesem Fall zwei Fehler liefert, n&auml;mlich den generischen VBA-Fehler und den speziellen SQL Server-Fehler, erhalten wir bei ADODB nicht den VBA-Fehler, sondern nur den SQL Server-Fehler.<\/p>\n<h2>Nutzen der Errors-Auflistungen bei mehreren Fehlern<\/h2>\n<p>Aber ben&ouml;tigen wir dann die <b>Errors<\/b>-Auflistung der ADODB-Klasse &uuml;berhaupt? Wenn wir ohnehin nur einen Fehler erhalten?<\/p>\n<p>Das untersuchen wir nun. Zumindest, wenn die gespeicherte Prozedur auch einmal mehr als einen Fehler ausl&ouml;sen kann, sollten wir diese Fehler sowohl mit DAO als auch mit ADODB abrufen k&ouml;nnen. Wir haben in eine solche Prozedur nachgestellt (siehe Listing 6). Hier verwenden wir als ersten Fehler die bereits von oben bekannte Division durch 0. Danach nutzen wir eine weitere Anweisung, die einen Fehler ausl&ouml;st &#8211; in diesem Fall durch den versuchten Zugriff auf eine Tabelle, die im Beispiel nicht existiert.<\/p>\n<pre>CREATE PROCEDURE Beispielfehlerprozedur_ZweiFehler  \r\nAS  \r\nBEGIN  \r\n     SET NOCOUNT ON;  \r\n     BEGIN TRY  \r\n         -- 1. Fehler: Division durch Null\r\n         DECLARE @x INT = 1 \/ 0;  \r\n     END TRY  \r\n     BEGIN CATCH  \r\n         -- Fehlerdetails erfassen\r\n         DECLARE @ErrorMessage1 NVARCHAR(4000) = ERROR_MESSAGE();  \r\n         DECLARE @ErrorSeverity1 INT = ERROR_SEVERITY();  \r\n         DECLARE @ErrorState1 INT = ERROR_STATE();  \r\n         -- Fehler mit RAISEERROR weitergeben\r\n         RAISERROR(@ErrorMessage1, @ErrorSeverity1, @ErrorState1);  \r\n     END CATCH  \r\n     BEGIN TRY  \r\n         -- 2. Fehler: Tabelle existiert nicht\r\n         INSERT INTO NichtExistiert (ID) VALUES (1);  \r\n     END TRY  \r\n     BEGIN CATCH  \r\n         -- Fehlerdetails erfassen\r\n         DECLARE @ErrorMessage2 NVARCHAR(4000) = ERROR_MESSAGE();  \r\n         DECLARE @ErrorSeverity2 INT = ERROR_SEVERITY();  \r\n         DECLARE @ErrorState2 INT = ERROR_STATE();  \r\n         -- Fehler mit RAISEERROR weitergeben\r\n         RAISERROR(@ErrorMessage2, @ErrorSeverity2, @ErrorState2);  \r\n     END CATCH  \r\nEND;<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 6: Gespeicherte Prozedur mit zwei Fehlern<\/span><\/b><\/p>\n<p>Wenn wir diese gespeicherte Prozedur im SQL Server Management Studio ausl&ouml;sen, erhalten wir die beiden Meldungen aus Bild 4.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_06\/pic_451_003.png\" alt=\"Zwei Fehler in einer gespeicherten Prozedur im SQL Server\" width=\"700\" height=\"239,9163\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 4: Zwei Fehler in einer gespeicherten Prozedur im SQL Server<\/span><\/b><\/p>\n<p>Auch beim Aufrufen dieser gespeicherten Prozedur per DAO von VBA aus erhalten wir alle erwarteten Fehlermeldungen (siehe Bild 5).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_06\/pic_451_004.png\" alt=\"Drei Fehler beim Aufruf der gespeicherten Prozedur per DAO\" width=\"700\" height=\"115,0495\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 5: Drei Fehler beim Aufruf der gespeicherten Prozedur per DAO<\/span><\/b><\/p>\n<p>Danach passen wir unsere ADODB-Beispielprozedur auf die neue gespeicherte Prozedur an. Das Ergebnis &uuml;berrascht ein wenig: Wir erhalten nur die erste Fehlermeldung zur&uuml;ck (siehe Bild 6).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_06\/pic_451_005.png\" alt=\"Drei Fehler beim Aufruf der gespeicherten Prozedur per ADODB\" width=\"700\" height=\"92,66954\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 6: Drei Fehler beim Aufruf der gespeicherten Prozedur per ADODB<\/span><\/b><\/p>\n<p>Wozu dient dann aber die <b>Errors<\/b>-Auflistung? Diese funktioniert schlicht in diesem Fall nicht, wo wir &uuml;ber einen Provider auf die Datenbank zugreifen und nicht per ODBC. Damit wir alle Fehler zur&uuml;ckerhalten, m&uuml;ssen wir also die Verbindungszeichenfolge &auml;ndern:<\/p>\n<p>conn.ConnectionString = &#8220;DRIVER={ODBC Driver 17 for SQL Server};SERVER=amvDesktop2023;DATABASE=Anlagen;Trusted_Connection=Yes;&#8221;<\/p>\n<p>Damit erhalten wir nun alle Fehlermeldungen. Wenn man also alle Fehlermeldungen erhalten m&ouml;chte, muss man ODBC verwenden. Das geht in einigen F&auml;llen zu Lasten der Performance.<\/p>\n<h2>Zusammenfassung und Ausblick<\/h2>\n<p>Wenn man per VBA und DAO oder ADODB auf die Daten eines SQL Servers zugreifen m&ouml;chte, kann es zu verschiedenen Problemen kommen.<\/p>\n<p>Dieser Artikel zeigt, wie man die Fehlerinformationen vom SQL Server holt und auswerten kann.<\/p>\n<h2>Downloads zu diesem Beitrag<\/h2>\n<p>Enthaltene Beispieldateien:<\/p>\n<p>FehlerbehandlungFuerSQLServerInDAOUndADODB.accdb<\/p>\n<p><a href=\"..\/fileadmin\/beispiele\/22C26D15-E264-4B1F-9AD7-99C9DC362179\/vbe_451.zip\">Download<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Die Fehlerbehandlung in VBA ist relativ einfach zu handhaben, sobald man einmal die grundlegenden Techniken kennt. Nicht viel anders sollte es eigentlich sein, wenn man die Daten nicht mehr aus Access-Tabellen bezieht, sondern aus SQL Server-Tabellen. Es gibt jedoch einige Unterschiede, die man kennen sollte. In diesem Artikel zeigen wir, welche Besonderheiten auftreten, wenn man nicht mehr auf Access-Tabellen zugreift, sondern auf verkn&uuml;pfte SQL Server-Tabellen und dabei die Datenzugriffstechniken DAO- und ADODB nutzt. Im letzteren Fall gibt es sogar noch Unterschiede bez&uuml;glich der verwendeten Treiber\/Provider.<\/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":[662024,66062024,44000021,44000006],"tags":[],"yst_prominent_words":[],"class_list":["post-55000451","post","type-post","status-publish","format-standard","hentry","category-662024","category-66062024","category-Entity_Framework","category-SQL_Server_und_Co"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts\/55000451","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=55000451"}],"version-history":[{"count":0,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts\/55000451\/revisions"}],"wp:attachment":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/media?parent=55000451"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/categories?post=55000451"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/tags?post=55000451"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/yst_prominent_words?post=55000451"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}