none
Extrem langsame Excel-Berechnung RRS feed

  • Frage

  • Hallo,

    unter der Konstellation Microsoft Excel 2013 und Windows 10 tritt bei uns folgendes Problem auf:

    Es gibt eine Excel-Datei, welche Daten über ein SQL-Statement aus einer Datenbank abgreift.

    Diese Abfragen funktionieren auch problemlos, und solange ich die Berechnung für nur ein Feld ausführe, ist dies innerhalb von Sekundenbruchteilen durchgeführt.

    Nun möchte ich allerdings alle Felder des Blattes automatisiert berechnen lassen, hierbei handelt es sich um etwa 100 Felder.
    Diese Berechnung dauert nun allerdings bis zu 35 Minuten.

    Ist hierfür eine Problemlösung bekannt? Eine so lange Berechnungszeit kann ja nicht normal sein!

    Grüße,

    Freitag, 3. November 2017 14:56

Alle Antworten

  • Da kann man auch nur spekulieren, was du denn da so berechnest.
    Oder holst du innerhalb der Berechnung jeweils wieder neue SQL-Daten?
    Wenn eine Formel ggf. iterativ (also recursiv) berechnet wird und dabei jedes mal ein SQL läuft, dann summiert sich das halt.
    Aber, wie gesagt, alles nur Spekulation.

    Bei SQL-Abfragen selber kann es schon mal vorkommen, dass die Abfrage für 1 Satz anders optimiert wird als für viele Sätze. Hier hilft nur eine Prüfung des SQL und ggf. das Anlegen von Indizes auf den Tabellen.

    Freitag, 3. November 2017 17:49
  • Hallo,

    leider muss ich hierzu sagen, dass ich den Code nicht selbst geschrieben habe.

    Die SQL Abfragen selbst sind folgende:

    "select count(*) from abrechnungseinheit ae " _
          & "join ausgangsliste al on al.ausgangslistenr = ae.ausgangslistenr " _
          & "where cast(al.erstelldatetime as date) = ? and ae.zieladrlkz = ?"

    "select count(*) from ((select lieferungnr from lieferung " & _
                         "where speicherdatetime between ? and ?) as lf " & _
                         "join teillieferung tl on lf.lieferungnr = tl.lieferungnr " & _
                         "join versandeinheit ve on tl.versandeinheitnr = ve.versandeinheitnr " & _
                         "join abrechnungseinheit ae on ve.abrechnungseinheitnr = ae.abrechnungseinheitnr) " & _
                         "where zieladrlkz = ? and vestatus is null and labelname <> 'NeutralSpedition'"

    "select count(ve.barcodeinhalt) from ausgangsliste al " & _
          "join abrechnungseinheit ae on al.ausgangslistenr = ae.ausgangslistenr " & _
          "join versandeinheit ve on ae.abrechnungseinheitnr = ve.abrechnungseinheitnr " & _
          "join teillieferung tl on ve.versandeinheitnr = tl.versandeinheitnr " & _
          "join lieferung lf on tl.lieferungnr = lf.lieferungnr " & _
          "where ae.zieladrlkz = ? and cast(al.erstelldatetime as date) between ? and ? and " & _
          "lf.speicherdatetime between ? and ?"

    Grüße,

    Montag, 6. November 2017 07:11
  • Auch hier bleibt es bei der Spekulation meinerseits.
    Da du 3 Abfragen hast, werden diese auch alle bei deiner Berechnung eines einzelnen Feldes ausgeführt?
    Es kann hier durchaus sein, dass die Abfragen mit "cast... as date" ein Problem darstellen, da in solchen Fällen auf der Datenbank kein Index verwendet werden kann.
    Zu prüfen ist also, warum der Cast erforderlich ist bzw. warum der Parameter nicht dem Datenbankfeld entsprechend übergeben wird.
    Dann ließe sich ein Index erstellen, der die Performance u.U. drastisch erhöht.

    Wie werden denn die SQL's aufgerufen?
    Liegt da VBA-Code dahinter?
    Sind die "?"-Parameter ggf. Zellen zugeordnet?
    Auf welche Datenbank wird hier zugegriffen?

    Montag, 6. November 2017 08:49
  • Nein bei jedem Feld wird nur eine dieser Berechnungen ausgeführt.

    Dahinter liegt VBA-Code, am Ende dieser Antwort werde ich einmal den Code von einer der abfragen mitgeben, vielleicht hilft dies ja weiter.

    Ob die "?"-Parameter einer speziellen Zelle zugeordnet sind, kann ich leider nicht sagen, dies müsste ich selbst nachprüfen lassen.

    Zugegriffen wird auf eine Firebird-Datenbank.

    Hier der Code der zweiten SQL-Abfrage:

    Public Function NumRein(aAviseDate As Date, aZielland As String) As Integer
    
        ' Anzahl avisierter Sendungen nach Datum und Land
        '
        ' Abgegrenzt wird nach Tag und Uhrzeit, wobei für jedes Land ein anderer Zeitpunkt verwendet wird, so daß nach Tagesabschluß
        ' avisierte Sendungen zum folgenden Arbeitstag gezählt werden. Fällt der vorangehende Arbeitstag auf ein Wochenende, wird ab
        ' Freitag nach Tagesabschluß gezählt.
        '
        
        'declare ADO Connection, Command, Parameter and Recordset
        Dim cnDB As New ADODB.Connection
        Dim Cm As New ADODB.Command
        Dim Pm1, Pm2 As ADODB.Parameter
        Dim rsRecords As ADODB.Recordset
        
        'Open the ODBC Connection
        cnDB.Open ("HVS32 live")
         
        'Command for select
        Cm.ActiveConnection = cnDB
        
        Cm.CommandText = "select count(*) from ((select lieferungnr from lieferung " & _
                         "where speicherdatetime between ? and ?) as lf " & _
                         "join teillieferung tl on lf.lieferungnr = tl.lieferungnr " & _
                         "join versandeinheit ve on tl.versandeinheitnr = ve.versandeinheitnr " & _
                         "join abrechnungseinheit ae on ve.abrechnungseinheitnr = ae.abrechnungseinheitnr) " & _
                         "where zieladrlkz = ? and vestatus is null and labelname <> 'NeutralSpedition'"
        
        Cm.CommandType = adCmdText
         
        'use parameter objects to deal with data types
        
        Select Case aZielland
            Case Is = "DE"
                dtFrom = DateValue(WDay(aAviseDate, -1)) + TimeValue("16:00")
                dtUntil = DateValue(aAviseDate) + TimeValue("16:00")
            Case Is = "AT"
                dtFrom = DateValue(WDay(aAviseDate, -1)) + TimeValue("15:00")
                dtUntil = DateValue(aAviseDate) + TimeValue("15:00")
            Case Is = "CH"
                dtFrom = DateValue(WDay(aAviseDate, -1)) + TimeValue("14:00")
                dtUntil = DateValue(aAviseDate) + TimeValue("14:00")
            Case Is = "NL"
                dtFrom = DateValue(WDay(aAviseDate, -1)) + TimeValue("13:00")
                dtUntil = DateValue(aAviseDate) + TimeValue("13:00")
            Case Is = "FR"
                dtFrom = DateValue(WDay(aAviseDate, -1)) + TimeValue("12:00")
                dtUntil = DateValue(aAviseDate) + TimeValue("12:00")
            Case Is = "GB"
                dtFrom = DateValue(WDay(aAviseDate, -1)) + TimeValue("11:00")
                dtUntil = DateValue(aAviseDate) + TimeValue("11:00")
            Case Else
                dtFrom = DateValue(WDay(aAviseDate, -1)) + TimeValue("16:00")
                dtUntil = DateValue(aAviseDate) + TimeValue("16:00")
        End Select
        
        Cm.Parameters.Append Cm.CreateParameter("Avise", adDBTimeStamp, adParamInput, 0, dtFrom)
        Cm.Parameters.Append Cm.CreateParameter("Avise", adDBTimeStamp, adParamInput, 0, dtUntil)
        Cm.Parameters.Append Cm.CreateParameter("Land", adVarChar, adParamInput, 3, aZielland)
        
        'Open the command with the Recordset
        Set rsRecords = Cm.Execute
         
        'Temp - Result
        Dim Result As Integer
         
        'read field value
        If Not IsNull(rsRecords.Fields.Item(0)) Then
            Result = rsRecords.Fields.Item(0)
        Else
            Result = 0
        End If
     
        'close all
        rsRecords.Close
        cnDB.Close
         
        'Set the return value of the function (same name)
        NumRein = Result
        
        
    End Function


    • Bearbeitet ShinMaes Montag, 6. November 2017 09:09
    Montag, 6. November 2017 09:08
  • Dazu müsste man nun die Definition der betroffenen Tabellen der Firebird kennen um den SQL zu optimieren als auch um ggf. Indizes anzulegen als auch den VBA code anzupassen.
    Normalerweise bedarf es da allein schon keiner länderspezifischen Behandlung.

    Da ich nun VBA als auch Firebird schon sehr lange betreibe (Firebird ca. 12 Jahre, VB/VBA ca. 20 Jahre) könnte ich dir da auch helfen.
    Allerdings lebe ich auch von dieser Tätigkeit (€).
    Im Rahmen dieses Forums ist der Aufwand und eben auch die Raterei einfach zu groß um wirklich hier konkret helfen zu können.
    Bei Interesse: Finden kannst du mich mit "Baldur Fürchau";-).

    Montag, 6. November 2017 09:50
  • Eine letzte Sache ist mir dazu eingefallen.

    Bei einem Test auf einer Windows 7 Maschine (selbe Office-Version) ist diese Abfrage in weitaus kürzerer Zeit durchgelaufen.

    Sollte diese Information nicht weiterhelfen, lassen wir das Thema halt einfach so stehen.

    Mittwoch, 8. November 2017 13:25
  • Ist es auch dieselbe Datenbank oder hat jeder PC seine eigene Firebird?
    Ggf. mal den Sweeper (gfix) drüber laufen lassen, da vielleicht zuviele Satzversionen überlesen werden müssen. Auch dies geht zu Lasten der Performance.
    Mittwoch, 8. November 2017 13:43
  • Beide PCs greifen auf die selbe Datenbank zu.

    Den Sweeper über die Datenbank laufen zu lassen klingt ja sinnig, aber würde in einem hieraus resultierenden Fehlerfall nicht auf BEIDEN Maschine diese extrem lange Laufzeit auftreten? Es kann doch nicht sein, dass die Performance auf dem Windows 10 Client so viel schlechter ist.

    Donnerstag, 9. November 2017 07:57
  • Da gebe ich dir allerdings recht.
    Das kann dann nichts mehr mit den SQL's und der Datenbank zu tun haben.
    Es muss also irgendwo am Netzwerk liegen.

    Was allerdings noch sein könnte wäre die Einstellung des Connection-Pooling in ODBC.
    Wenn diese fehlt, wird jedesmal die Verbindung neu geöffnet und anschließend geschlossen.
    Ist das Connection-Pooling eingeschaltet, entfällt der reale Close und beim nächsten Open wird die Verbindung wieder verwendet. Dies könnte enorm Zeit sparen.

    Wie man dies ggf. einschaltet kannst du vielleicht hier entnehmen:
    https://docs.microsoft.com/en-us/sql/odbc/admin/setting-odbc-connection-pooling-options

    Vergleiche mal genau sämtliche Einstellungen der ODBC-Verbindung "HVS32 live", wichtig ODBC-Verwaltung 32-Bit.

    Donnerstag, 9. November 2017 08:47
  • Ein Fehler im Netzwerk kann ich mir auch nicht vorstellen, die beiden Clients hängen im selben Netz und wurden testweise auch am selben Switch etc. betrieben.

    Aber ich arbeite gerne einmal die Einstellungen des Links durch! Vielen Dank!

    Donnerstag, 9. November 2017 09:13
  • Wenn Sie sich ein wenig mit VBA auskennen, so können Sie eine kleine Performanceprüfung durchführen:

    dim StartTime as Double

    StartTime = Timer
    Aktion durchführen
    Debug.Print "Aktion: "; Timer-StartTime

    So könnte man die einzelnen Schritte genau prüfen und die Unterschiede zwischen den PC's ermitteln.

    Beispiel:

    StartTime = Timer
    cnDB.Open
    Debug.Print "Open: ";Timer-StartTime

    Die Ausgabe wird im Direktfenster gemacht (Ansicht->Direktfenster, bzw. ggf. STRG+G).

    Die Funktion Timer liefert die Uhrzeit in Sekunden seit Mitternacht zzgl. 2 Nachkommastellen, also hier ausreichende Genauigkeit.
    • Bearbeitet Der Suchende Donnerstag, 9. November 2017 09:35
    Donnerstag, 9. November 2017 09:34
  • Meine Erfahrung mit VBA ist leider doch sehr gering, aber ich versuche es gerne Mal.

    Allerdings ist die Verbindung zur Datenbank aktuell grundsätzlich gestört, daher muss dieser Testen wohl noch warten.

    Vielen Dank aber nochmal

    Donnerstag, 9. November 2017 10:11
  • Hallo nochmal,

    ich habe den angegebenen Code einmal eingefügt. Genauer gesagt habe ich die Zeit einmal vor dem cnDB.Open und einmal nach den Abfragen die Zeit ausgeben lassen.

    Dabei zeigte sich eine für mich doch recht interessante Begebenheit:

    Die Zeiten zum Ende lagen alle im bereich zwischen 0,5 und 3 Sekunden (ich vermute einfach mal, dass die Zeit in Sekunden angegeben wird).
    Weiterhin kam es mehrfach vor, dass die Ausgabe vom Anfang ausgegeben wird, dann geschieht einige Zeit gar nicht und die Zeit am Ende beträgt dann nur 0,xx Sekunden, das kann ja eigentlich schon nicht korrekt sein.
    Für mich sieht das so aus, als würde der komplette Timer einfach stehen bleiben.
    Worauf ich vorher gar nicht geachtet hatte, was mir aber jetzt auffiel:
    Sinn hinter den Abfragen ist es ja, die Felder einer Excel-Tabelle zu füllen. Allerdings werden diese Felder teilweise erst nach 3-4 Durchläufen der Berechnung gefüllt, davor passiert einfach nichts.
    Gestartet habe ich die Berechnungen über jede mir erdenkliche Methode, die Excel mir bietet.

    Für mich sieht es hier immer weniger nach einem Fehler mit der Datenbankabfrage aus, als viel mehr um eine Inkompatibilität von Excel mit irgendeinem Teil des Codes.

    Hilft es vielleicht bei der Lösungsfindung, dass die Datei schon etwas älter ist und frühestens unter Excel 2007 erstellt wurde, könnte sich hier einfach etwas an dem Verhalten von Excel geändert haben?

    Freitag, 10. November 2017 07:24
  • Hier übrigens ein Teil der Ergebnisse:

    NumRein Anfang:  0
    NumRein Ende:  0,40625
    NumRaus Anfang:  0
    NumRaus Ende:  27,564453125
    SDD Anfang:  0
    SDD Ende:  0,828125
    NumRein Anfang:  0
    NumRein Ende:  0,421875
    NumRaus Anfang:  0
    NumRaus Ende:  0,59375
    NumRaus Anfang:  0
    NumRaus Ende:  3,802734375
    SDD Anfang:  0
    SDD Ende:  0,36328125
    SDD Anfang:  0
    SDD Ende:  0,34375
    SDD Anfang:  0
    SDD Ende:  0,763671875
    SDD Anfang:  0
    SDD Ende:  0,37109375
    NumRein Anfang:  0
    NumRein Ende:  0,470703125
    SDD Anfang:  0
    SDD Ende:  0,31640625
    SDD Anfang:  0
    SDD Ende:  0,46484375
    NumRein Anfang:  0
    NumRein Ende:  0,392578125
    NumRaus Anfang:  0
    NumRaus Ende:  1,328125
    SDD Anfang:  0
    SDD Ende:  0,34375
    SDD Anfang:  0
    SDD Ende:  0,328125
    SDD Anfang:  0
    SDD Ende:  0,32421875
    SDD Anfang:  0
    SDD Ende:  0,880859375
    SDD Anfang:  0
    SDD Ende:  1,068359375

    Freitag, 10. November 2017 07:30
  • An der Kompatibilität liegt es nicht.
    VBA's, die ich mit Excel 97 entwickelt habe, laufen auch mit Excel 2016 ohne Veränderung!

    Interessant ist da schon, dass eine Aufgabe 27 Sekunden dauert.

    Da ich deinen Code nicht erraten kann, kann ich eben nur vermuten.
    Sicher ist auf jeden Fall, dass alle ADO-Aktionen synchron laufen und somit während dieser Zeit tatsächlich keine Reaktion von Excel möglich ist (ggf. Anzeige im Header "Anwendung reagiert nicht").

    Als nächstes enthält die Routine keine Fehlerbehandlung, so dass diese über zentrale Einstellungen ignoriert werden. Dies führt u.U. zu keinem Ergebnis, da die Funktion auf Grund Fehler abgebrochen wurde.

    Alles nur Spekulation. Da du ja nicht fit in VBA bist (wie du sagtest), solltest du dir da von einem Spezialisten helfen lassen. Es macht ansonsten keinen Sinn.

    "Nichts ist ömsönst".

    Freitag, 10. November 2017 08:22