none
ADO Recordset in EXCEL RRS feed

  • Frage

  • Hallo zusammen,
    ich bin gerade dabei, eine Excel 2010 automatisierung mittels VBA zu realisieren. Nun habe ich leider ein (hoffentlich) kleines Problem.

    Für den Datenbankzugriff auf einen SQL Server 2008 Standard verwende die Microsoft ActiveX Data Objects 6.1 Library. Ich verwende folgenden Code:

    Dim connection As New ADODB.Connection
    Dim recordset as New ADODB.Recordset
    
    connection.Open <connectionstring>
    recordset.Open <sqlQuery>, connection
    
    Worksheets("Tabelle2").Range("A2").CopyFromRecordset recordset

    Zunächst möchte ich betonen, dass der SQL Server gefunden, und die Query die abgeschickt wird, im SQL Server Management Studio einwandfrei durchläuft. Der Fehler sollte als nicht am ConnectionString oder am SQL liegen.

    Das Programm läuft bis zur letzten Zeile durch. Dort erhalte ich die Fehlermeldung "Der Vorgang für ein geschlossenes Objekt nicht zugelassen" (Laufzeitfehler '3704'). Und tatsächlich, das Recordset ist noch geschlossen...die Frage die mich plagt ist...WARUM?

    Vielen Dank für eure Hilfe :)


    Viele Grüße Holger M. Rößler

    Dienstag, 5. Januar 2016 15:24

Antworten

  • Hallo Holger,

    das ist keine Abfrage/View. Diese kannst Du so nicht sinnvoll aus ADO heraus verwenden bzw. wird das erheblich schwieriger und macht Probleme.

    Kapsele das, was Du schreibst, in eine Funktion oder Stored Procedure und ruf die dann aus ADO heraus auf, so dass nur noch:

    SELECT ... FROM ... WHERE ...

    in deinem Programm vorkommt.

    Dass das im SQL Server Management Studio funktioniert, hat nichts zu bedeuten. Dein Recordset ist dafür da, Datenlisten abzubilden und nicht, Code auszuführen, Variablen zu definieren, usw.


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community


    Freitag, 8. Januar 2016 09:43
    Moderator

Alle Antworten

  • Hallo Holger,

    poste doch mal bitte dein SQL Statement.

    Dann prüf bitte, ob die Connection geöffnet ist. Setz einfach einen Breakpoint auf die Zeile, gehe dann zeilenweise durch und schau, welchen Status die einzelnen Objekte haben.

    Falls eigentlich alles passt, probier mal, bei recordset.Open noch zwei weitere Parameter anzugeben. Bspw. so:

    recordset.Open <sqlQuery>, connection, 3, 1

    Siehe dazu auch:

      https://msdn.microsoft.com/de-de/library/ms675544.aspx

      https://msdn.microsoft.com/de-de/library/ms681771.aspx

      https://msdn.microsoft.com/de-de/library/ms680855.aspx

    Das sollte zwar nichts damit zu tun haben aber falls alles andere nicht hilft, ... :)

    Ansonsten fällt mir eigentlich nur ein, dass Du es mal so probieren kannst:

    Dim connection As ADODB.Connection
    Dim recordset As ADODB.Recordset
    
    Set connection = New ADODB.Connection()
    Set recordset  = New ADODB.Recordset()
    
        connection.Open <connectionstring>
        recordset.Open <sqlQuery>, connection
    
        Worksheets("Tabelle2").Range("A2").CopyFromRecordset recordset
    
        recordset.Close
    Set recordset = Nothing
    
        connection.Close
    Set connection = Nothing

    Ob "Set" in VBA notwendig ist, weiß ich aber grade nicht, dafür mache ich damit zu wenig.




    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community

    Dienstag, 5. Januar 2016 15:50
    Moderator
  • Hallo Holger,
    sind da Memofelder oder überlange Strings in der Tabelle?
    Zu viele Zeilen im Result?
    Ist das eine Stored Procedure und Set NOCOUNT ON fehlt?
    Hilft es  ConnectionTimeout höher zu setzen?
    HTH
    Grüße Alexander
    Dienstag, 5. Januar 2016 17:53
  • Hallo Stefan,
    vielen Danke für deine Antwort.

    Die Query ist recht komplex. Ich bin mir gar nicht sicher, ob ich diese überhaupt posten kann. Aber wie bereits erwähnt tritt während des Abrufs dieser Daten kein Fehler auf, und im Management Studio kommen die erwarteten Daten auch zurück.

    Das habe ich alles schon probiert. Ich habe auch schon das Command Objekt verwendet, die Query über die Connection verschickt etc.

    Leider immer gleiches Ergebnis :-/


    Viele Grüße Holger M. Rößler

    Donnerstag, 7. Januar 2016 06:46
  • Hallo Alexander,
    auch an dich erst mal vielen Dank, dass du dich meines Problems annimmst.

    Nein, es sind alles kurze Zeichenketten, also nichts mir Überlänge o.ä.

    Die Query liefert 1500 Datensätze. Es ist keine Prozedur, sondern eine Query.
    Set NOCOUNT ist nicht in der Query. Query läuft nicht in einen Timeout.


    Viele Grüße Holger M. Rößler

    Donnerstag, 7. Januar 2016 06:50
  • Hallo Holger,

    um den Fehler besser eingrenzen zu können, könntest Du zunächst mal das übertragen in das Worksheet, Zeile für Zeile in Einzelschritten vornehmen, das hat den Vorteil, dass man im Debugger besser sehen kann wo der Fehler auftritt.

    Hier mal der VBA-Code dazu:

    Dim connection As New ADODB.connection
    Dim recordset As New ADODB.recordset
    Dim Feld As ADODB.Field
    Dim Zeile As Integer
    Dim Spalte As Integer
    Dim Zelle As Range
    Dim ws As Worksheet
    
    connection.Open "..."
    
    
    recordset.Open "...", connection
    Set ws = Worksheets("Tabelle2")
    
    recordset.MoveFirst
    Zeile = 1
    Do
        Spalte = 1
        For Each Feld In recordset.Fields
            Spalte = Spalte + 1
            Set Zelle = ws.Cells(Zeile, Spalte)
            Zelle.Value = Feld.Value
        Next Feld
        Zeile = Zeile + 1
        recordset.MoveNext
    Loop Until recordset.EOF
    

    Grüße

    Roland

    Donnerstag, 7. Januar 2016 09:34
  • Hallo Roland,
    vielen Dank für deinen Tipp.

    Leider bekomme ich den gleichen Fehler bei recordset.MoveFirst. Ich habe nun mal eine einfache Query ablaufen lassen (Einfaches select from <table>). Das funktioniert.

    Wahrscheinlich ist die Query einfach zu komplex.


    Viele Grüße Holger M. Rößler

    Donnerstag, 7. Januar 2016 14:02
  • Hallo Holger,

    Du verwendest nicht zufällig ODBC für die Connection?

    GrüßeRoland

    Donnerstag, 7. Januar 2016 14:24
  • Hallo Holger,

    dann poste doch mal bitte dein SQL Statement so, wie es letztendlich an die Datenbank gesendet werden würde. (Also den vollständigen String, falls Platzhaltern/Parameter vorkommen, diese mit den richtigen Werten ersetzen)


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community

    Donnerstag, 7. Januar 2016 14:40
    Moderator
  • Hallo Holger,

    verwende fürs Recordset als CursorType adOpenStatic (+ adLockReadOnly). Denn serverseitige Cursor können nunmal kein MoveFirst.

    Das mag auch dazu führen, dass CopyFromRecordset funktioniert, zumindest sollte man die Daten sehen.

    Gruß Elmar

    Donnerstag, 7. Januar 2016 16:45
    Beantworter
  • Hallo Roland,
    nein, ist OLEDB.


    Viele Grüße Holger M. Rößler

    Freitag, 8. Januar 2016 08:36
  • Hi Stefan,

    SET DATEFORMAT YMD
    
    DECLARE @YEAR INT
    SET @YEAR = CONVERT(INT,YEAR(GETDATE()))
    print CONVERT(INT,YEAR(GETDATE()))
    
    DECLARE @MONTH DATETIME
    SET @MONTH = MONTH(GETDATE())
    
    DECLARE @FIRSTOFMONTH DATETIME
    SET @FIRSTOFMONTH = GETDATE()
    print CONVERT(NVARCHAR(8),@FIRSTOFMONTH,112)
    DECLARE @LASTOFMONTH DATETIME
    SET @LASTOFMONTH = DATEADD(DAY, -(DAY(DATEADD(MONTH,1, GETDATE()))), DATEADD(MONTH,1, GETDATE()))
    print  CONVERT(NVARCHAR(8),@LASTOFMONTH,112)
    
    DECLARE @FIRSTNEXTMONTH DATETIME
    SET @FIRSTNEXTMONTH = DATEADD(day,1,@LASTOFMONTH)
    print  CONVERT(NVARCHAR(8),@FIRSTNEXTMONTH,112)
    
    DECLARE @LASTNEXTMONTH DATETIME
    SET @LASTNEXTMONTH = DATEADD(DAY, -(DAY(DATEADD(MONTH,1, @FIRSTNEXTMONTH))), DATEADD(MONTH,1, @FIRSTNEXTMONTH))
    print CONVERT(NVARCHAR(8),@LASTNEXTMONTH,112)
    
    DECLARE @FIRSTINTWOMONTH DATETIME
    SET @FIRSTINTWOMONTH = DATEADD(day, 1, @LASTNEXTMONTH)
    print CONVERT(NVARCHAR(8),@FIRSTINTWOMONTH,112)
    
    DECLARE @LASTOFYEAR DATETIME
    SET @LASTOFYEAR = CONVERT(NVARCHAR(4),YEAR(GETDATE()))+ '1231'
    print CONVERT(NVARCHAR(8),@LASTOFYEAR,112)
    
    declare @tmpUrlaub table (KSTNR int, Nachname nvarchar(50), Vorname nvarchar(50), PNR int, GLZANF decimal(10,2), GLZAKT decimal(10,2), GLZDIFF decimal(10,2),URLANS int, URLVOR int, URLSUM int, URLGEN int, URLPLAN1 int, URLPLAN2 int, URLPLANREST int, URLREST int, URLUNK int, PGNR int)
    
    USE tisoware_production
    
    insert into @tmpUrlaub
          SELECT  p.KSTNR, p.PSNACHNA AS Nachname, p.PSVORNA AS Vorname,  p.PSPERSNR AS PNR, 
          -- SubSELECT GLZ-Stand 01.01.2012
          'GLZANF' = 
          ISNULL((SELECT CONVERT(DECIMAL(10,2),(Zeitkont.ZKGLEVVM)/60.0) 
                FROM ZEITKONT
                      WHERE 
                            ZEITKONT.JAHR = CONVERT(NVARCHAR(4),YEAR(GETDATE())) 
                      AND 
                            ZEITKONT.MONAT = 1 
                      AND 
                            p.PSNR = ZEITKONT.PSNR),0),
          --Ausgabe des aktuellen GLZ-Standes
          CONVERT(DECIMAL(10,2),((Zeitkont.ZKGLEVVM + Zeitkont.ZKGLEUMB + Zeitkont.ZKGLELFM)/60.0)) AS 'GLZAKT',
          0.0 as GLZDIFF,
          URLKON.VLFDTAG AS 'URLANS', 
          URLKON.VVORTAG - URLKON.VEVORTAG AS 'URLVOR',
          URLKON.VLFDTAG +(URLKON.VVORTAG - URLKON.VEVORTAG) AS 'URLSUM', 
          URLKON.GLFDTAG + URLKON.GVORTAG AS 'URLGEN', 
          -- SubSELECT Urlaubstage im 1. Monat
          URLPLAN1 = 
          (SELECT COUNT(PSNR)-COUNT(kfeidate)  
                FROM ABWKAL
                      LEFT JOIN 
                            KALFEIER ON KFEIDATE = ABWDATE 
                      AND 
                            KALFEIER.LANR = 'BW'
                            WHERE 
                                 ABWDATE BETWEEN CONVERT(NVARCHAR(8),@FIRSTOFMONTH,112) 
                            AND 
                                 CONVERT(NVARCHAR(8),@LASTOFMONTH,112) 
                            AND 
                                 LOANR = 'URL' 
                            AND 
                                 abwkal.ABWSOLL >0 
                            AND 
                                 abwkal.PSNR  = p.PSNR),
          -- SubSELECT Urlaubstage im 2. Monat
          URLPLAN2 = 
          (SELECT COUNT(PSNR)-COUNT(kfeidate)  
                FROM ABWKAL
                      LEFT JOIN  
                            KALFEIER ON KFEIDATE = ABWDATE 
                      AND 
                            KALFEIER.LANR = 'BW'
                            WHERE 
                                 ABWDATE BETWEEN CONVERT(NVARCHAR(8),@FIRSTNEXTMONTH,112) 
                            AND 
                                 CONVERT(NVARCHAR(8),@LASTNEXTMONTH,112) 
                            AND 
                                 LOANR = 'URL' 
                            AND 
                                 abwkal.ABWSOLL >0 
                            AND 
                                 abwkal.PSNR  = p.PSNR),
          -- SubSELECT Urlaubstage des restlichen Jahres
          URLPLANREST = (SELECT COUNT(PSNR)-COUNT(kfeidate)  
                FROM ABWKAL
                      LEFT JOIN  
                            KALFEIER ON KFEIDATE = ABWDATE 
                      AND 
                            KALFEIER.LANR = 'BW'
                            WHERE 
                                       ABWDATE BETWEEN CONVERT(NVARCHAR(8),@FIRSTINTWOMONTH,112) 
                                 AND 
                                       CONVERT(NVARCHAR(8),@LASTOFYEAR,112) 
                                 AND 
                                       LOANR = 'URL' 
                                 AND 
                                       abwkal.ABWSOLL >0 
                                 AND 
                                       abwkal.PSNR  = p.PSNR),
          -- Resturlaub Start
          (URLKON.VLFDTAG +(URLKON.VVORTAG - URLKON.VEVORTAG)) -
          (URLKON.GLFDTAG + URLKON.GVORTAG) - ((SELECT COUNT(PSNR)-COUNT(kfeidate)  
                FROM ABWKAL
                      LEFT JOIN  
                            KALFEIER ON KFEIDATE = ABWDATE 
                      AND 
                            KALFEIER.LANR = 'BW'
                            WHERE 
                                 ABWDATE BETWEEN CONVERT(NVARCHAR(8),@FIRSTOFMONTH,112) 
                            AND 
                                 CONVERT(NVARCHAR(8),@LASTOFMONTH,112) 
                            AND 
                                 LOANR = 'URL' 
                            AND 
                                 abwkal.ABWSOLL >0 
                            AND 
                                 abwkal.PSNR  = p.PSNR))
          -
          (SELECT COUNT(PSNR)-COUNT(kfeidate)  
                FROM ABWKAL
                LEFT JOIN  
                            KALFEIER ON KFEIDATE = ABWDATE 
                AND 
                            KALFEIER.LANR = 'BW'
                            WHERE 
                                 ABWDATE BETWEEN CONVERT(NVARCHAR(8),@FIRSTNEXTMONTH,112) 
                            AND 
                                 CONVERT(NVARCHAR(8),@LASTNEXTMONTH,112) 
                            AND 
                                 LOANR = 'URL' 
                            AND 
                                 abwkal.ABWSOLL >0 
                            AND 
                                 abwkal.PSNR  = p.PSNR)  
                                 AS 'URLREST',0.0 as URLUNK
          -- Resturlaub Ende
          , p.PGNR
          FROM PERSTAMM AS p
                INNER JOIN 
                      URLKON ON p.PSNR = URLKON.PSNR
                INNER JOIN 
                      ZEITKONT ON p.PSNR = Zeitkont.PSNR
          -- PSAUSDAT muss immer aktuelles Datum haben
                WHERE 
                            URLKON.JAHR = @YEAR  
                      AND 
                      -- Nur Personengruppe Gewerbliche, Angestellte + OFK & Personal
                            p.PGNR in (1,2,7) 
                      AND 
                            p.PSAUSDAT > CONVERT(NVARCHAR(8),@FIRSTOFMONTH,112) 
                      AND  
                      --Ausgrenzung der Vorstände
                            p.PSPERSNR not in(2012,2019,2016)
                      AND
                            ZEITKONT.JAHR = CONVERT(NVARCHAR(4),YEAR(GETDATE())) 
                      AND 
                            ZEITKONT.MONAT = MONTH(GETDATE())
                            ORDER BY KSTNR, PSNACHNA
    Danke!

    Viele Grüße Holger M. Rößler

    Freitag, 8. Januar 2016 08:39
  • Hallo Elmar,
    vielen Dank für den Tipp,  führt aber leider zum gleichen Fehler.


    Viele Grüße Holger M. Rößler

    Freitag, 8. Januar 2016 08:41
  • Hallo Holger,

    das ist keine Abfrage/View. Diese kannst Du so nicht sinnvoll aus ADO heraus verwenden bzw. wird das erheblich schwieriger und macht Probleme.

    Kapsele das, was Du schreibst, in eine Funktion oder Stored Procedure und ruf die dann aus ADO heraus auf, so dass nur noch:

    SELECT ... FROM ... WHERE ...

    in deinem Programm vorkommt.

    Dass das im SQL Server Management Studio funktioniert, hat nichts zu bedeuten. Dein Recordset ist dafür da, Datenlisten abzubilden und nicht, Code auszuführen, Variablen zu definieren, usw.


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community


    Freitag, 8. Januar 2016 09:43
    Moderator
  • Hallo Stefan,
    das war es tatsächlich.

    Ich bin verwirrt. Die Ausführung geschieht doch durch den SQL Server. Ob das Result jetzt durch eine Query, einer View oder einer SP ermittelt wird, sollte doch ADO egal sein, bzw. es muss es doch gar nicht wissen???!!!???

    Trotzdem vielen Dank an dich.


    Viele Grüße Holger M. Rößler

    Freitag, 8. Januar 2016 10:40
  • Hallo Holger,

    nö, das ist ADO erst einmal nicht egal. Letztendlich werden die Befehle einzeln ausgeführt und das würde wahrscheinlich mehrere Resultsets ergeben. Die kann man zwar mittels MARS verarbeiten, bringt aber oftmals Probleme mit sich. Siehe dazu u.a.:

      http://stackoverflow.com/questions/374444/disadvantages-of-mars-multiple-active-result-sets

    Wenn Du ein ADO Recordset füllen willst, steht in deinem SQL Statement sinnvollerweise _nur_

      SELECT ... FROM ... WHERE ... ...

    und nichts anderes. Eine tiefere Erklärung dazu kann ich dir grad nicht liefern, ggfs. solltest Du dazu aber mal das hier lesen:

      https://support.microsoft.com/en-us/kb/245179


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community

    Freitag, 8. Januar 2016 11:43
    Moderator