Hallo zusammen,
ich bin auf der Suche nach einer Möglichkeit zur Optimierung einer Dokumentensuchfunktion in einer Anwendung, die wir seit ca. 10 Jahren einsetzen. Die Anwendung wurde nicht von uns entwickelt sondern extern zugekauft, allerdings mit offenem Design, so dass ich Anpassungen vornehmen kann.
Hintergrund ist der, dass sich unsere Anwender in letzter Zeit darüber beschweren, dass die Anlage von neuen Einträgen in der Anwendung extrem lange dauert (früher: ca. 10 Sekunden, jetzt ca. 90 Sekunden). Die aktuellen Zeiten kann ich bestätigen, bei den 'früher' Angaben verlasse ich mich darauf, dass sie kurz genug waren, dass die Anwender sie als akzeptabel betrachtet haben.
Die Anwendung ist ein Verzeichnis für die Ablage von Offlineakten (-> Papier) und umfasst inzwischen ca. 50.000 Dokumente. Pro Jahrgang kommen aktuell ca. 2.500 dazu.
In den Dokumenten wird das gewünschte Archivierungsjahr aus einer Dropdownliste gewählt. Beim Speichern wird die zu vergebende fortlaufende Nummer automatisch über eine Ansicht ermittelt. In der Ansicht sind alle Dokumente aufteigend nach dem gewählten Jahr sortiert, so dass bei neuen Dokumenten des aktuellen Jahrs sehr schnell die nächste laufende Nummer ermittelt werden kann (->view.GetLastDocument). Die Nummern sind immer 5-stellig mit führenden Nullen und werden aus diesem Grund als Text im Dokument gespeichert.
Die Anwendung ruft im QuerySave eine Prüffunktion auf, die sicherstellen soll, dass keine doppelten Nummern vergeben werden. Dazu wird die laufende Nummer des aktuellen Dokuments über besagte Ansicht mit dem Bestand abgeglichen. Dieser Vorgang dauert extrem lange, da alle Dokumente in der Ansicht mit dem aktuellen Dokument abgeglichen werden. Dadurch ergibt sich ein neues Problem, denn wärend des Abgleichs könnte ein anderer Mitarbeiter ein weiteres Dokument mit der selben Nummer für das Jahr erzeugen.
Ich suche also nach einer Möglichkeit, die Prüfung zu beschleunigen, um den Kollegen wieder eine bessere Performance bieten zu können und gleichzeitig die Gefahr der doppelten Nummernvergabe deutlich zu reduzieren.
Hier mal der Code der Prüfroutine:
Function NummerSuchen(jahr As String, nummer As String, refdoc As NotesDocument) As Variant
Dim s As New NotesSession
Dim db As NotesDatabase
Dim v As NotesView
Dim doc As NotesDocument
Set db = s.CurrentDatabase
Set v = db.GetView("(LUNummern)")
Set doc = v.GetFirstDocument
While Not doc Is Nothing
If Cint(doc.Ablagenummer_2 (0)) = Cint(nummer) Then
If Cint(jahr) = Cint(doc.Ablagenummer_1 (0)) Then
'Wenn das Dokument nicht neu ist
If Not refdoc Is Nothing Then
If Cstr(doc.UniversalID) <> Cstr(refdoc.UniversalID) Then
Msgbox "Die Nummer wurde schon vergeben.",16,"Hinweis"
NummerSuchen = True
Exit Function
End If
Else
Msgbox "Die Nummer wurde schon vergeben.",16,"Hinweis"
NummerSuchen = True
Exit Function
End If
End If
End If
Set doc = v.GetNextDocument(doc)
Wend
End Function
Mein bisheriger Ansatz wäre, für das aktuelle und das Vorjahr eigene Ansichten zu generieren, so dass hier nur jeweils bis zu ~2.500 Prüfungen nötig wären. Für alle älteren Jahrgänge sollte eine gemeinsame Ansicht reichen, da laut Aussage der Fachabteilung nur sehr selten ältere Vorgänge nachgetragen werden.
Oder gibt es eine Alternative, die noch deutlich flexibler und dennoch performant wäre?
Gruß
Dirk
Hallo Dirk,
wie Mitch bereits erwähnt hat, ist es nicht notwendig alle Dokumente zu durchlaufen.
Es ist wesentlich performanter, explizit danach in einer sortierten Ansicht zu suchen.
So wie ich das verstanden habe, darf eine Nummer pro Jahr nur einmal vergeben werden,
d.h. die Nummer "00023" darf jeweils einmal für 2012, 2013 und 2014 existieren. Ist das so korrekt?
Ich habe auf die schnelle deinen Code etwas angepasst (Nicht getestet -> Dient nur als Ansatz)
Function NummerSuchen(jahr As String, nummer As String, refdoc As NotesDocument) As Variant
Dim s As New NotesSession
Dim db As NotesDatabase
Dim v As NotesView
Dim doc As NotesDocument
Dim asKeys(0 To 1)
Set db = s.CurrentDatabase
Set v = db.GetView("(LUNummern)") '// Eine View, dessen 1. Spalte Ablagenummer_1 und 2. Spalte Ablagenummer_2 ist (Beide sortiert)
asKeys(0) = jahr
asKeys(1) = nummer
Set doc = v.GetDocumentByKey(asKeys, True)
If doc Is Nothing Then
'//Es wurde nichts gefunden, die mitgegebene Nummer existiert also nicht für das mitgegebene Jahr
Else
If Not refdoc Is Nothing Then
If StrCompare(doc.Universalid, refdoc.Universalid, 5) <> 0 Then '// StrCompare ist meines Wissens nach performanter, als Strings direkt miteinander zu vergleich
MsgBox "Die Nummer wurde schon vergeben.",16,"Hinweis"
NummerSuchen = True
End If
Else
MsgBox "Die Nummer wurde schon vergeben.",16,"Hinweis"
NummerSuchen = True
End If
End If
End Function
Wenn in der Ansicht mit den angegebenen Keys nichts gefunden wird, so ist die Nummer noch
nicht vergeben.
Falls jedoch was gefunden wird, wird noch geprüft, ob es sich beim gefundenen Dokument um
das mitgegebene Dokument handelt.
Diese Stelle habe ich mit StrCompare ersetzt, da nach meinem Wissensstand StrCompare
performanter ist. -> Holt zwar nicht viel raus, da es nur noch einmal aufgerufen wird, aber egal :-P
Ich hoffe, ich konnte dir hiermit ein wenig unter die Arme greifen. ;-)
Gruß,
Rich
Ich habe die Funktion auf Basis von Mitchs Tip angepasst und bisher sieht das von der Geschwindigkeit sehr gut aus (Prüfung deutlich unter 10 Sekunden). Die View war schon passend aufgebaut (1. Spalte = Jahr; 2. Spalte = laufende Nr.; beide aufsteigend sortiert)
Function NummerSuchen(jahr As String, nummer As String, refdoc As NotesDocument, v As NotesView) As Variant
Dim s As New NotesSession
Dim db As NotesDatabase
Dim dc As NotesDocumentCollection
Dim keys( 1 To 2 ) As Integer
Dim doc As NotesDocument
Set db = s.CurrentDatabase
keys(1) = Cint(jahr)
keys(2) = Cint(nummer)
Set dc = v.GetAllDocumentsByKey(keys, True)
NummerSuchen = False
If dc.Count = 0 Then
Exit Function
Else
Set doc = dc.GetFirstDocument ()
While Not doc Is Nothing
If Cint(doc.Ablagenummer_2 (0)) = Cint(nummer) Then
If Cint(jahr) = Cint(doc.Ablagenummer_1 (0)) Then
'Wenn das Dokument nicht neu ist
If Not refdoc Is Nothing Then
If Cstr(doc.UniversalID) <> Cstr(refdoc.UniversalID) Then
Msgbox "Die Nummer wurde schon vergeben.",16,"Hinweis"
NummerSuchen = True
Exit Function
End If
Else
Msgbox "Die Nummer wurde schon vergeben.",16,"Hinweis"
NummerSuchen = True
Exit Function
End If
End If
End If
Set doc = dc.GetNextDocument(doc)
Wend
End If
End Function
@Rich
Den Tip mit dem StrCompare werde ich auch mal testen, mehr Performance kann nicht schaden.
Hallo Dirk,
die Abfragerei unten...
If CInt(doc.Ablagenummer_2 (0)) = CInt(nummer) Then
If CInt(jahr) = CInt(doc.Ablagenummer_1 (0)) Then
'...
End If
End If
... ist nicht mehr notwendig, da diese nur zur identifizierung des Dokuments diente.
Diese Abfragen werden nun IMMER True liefern, da die Dokumente mit denen du prüfst,
anhand dieser Kriterien gefunden worden sind.
Dann zu deinen Keys,...
Dim keys( 1 To 2 ) As Integer
keys(1) = CInt(jahr)
keys(2) = CInt(nummer)
Aufgrund folgender Aussage von dir...
Die Nummern sind immer 5-stellig mit führenden Nullen und werden aus diesem Grund als Text im Dokument gespeichert.
... gehe ich davon aus, dass deine NotesDocumentCollection immer leer bleibt. ;)
Du musst in diesem Fall dein Array als String deklarieren und dementsprechend mit den Strings füllen.
Edit: Mitch war schneller... :(
Gruß,
Rich
Hallo Dirk,
da ich gerade nichts zu tun habe,... hier die fertige Funktion. ;D
Getestet und Einsatzbereit. :)
~Kann 1:1 so übernommen werden ;)
- Suche über GetAllDocumentsByKey
- Prüfung ob Nummern bereits doppelt vergeben sind
- Prüfung ob es sich beim gefundenen Dokument um das aktuelle Dokument handelt, welches gespeichert werden soll.
Function NummerSuchen(jahr As String, nummer As String, refdoc As NotesDocument) As Boolean
Dim s As New NotesSession
Dim db As NotesDatabase
Dim v As NotesView
Dim doc As NotesDocument
Dim ndc As NotesDocumentCollection
Dim asKeys(0 To 1) as String
Set db = s.CurrentDatabase
Set v = db.GetView("(LUNummern)") '// Eine View, dessen 1. Spalte Ablagenummer_1 und 2. Spalte Ablagenummer_2 ist (Beide sortiert)
asKeys(0) = jahr
asKeys(1) = nummer
Set ndc = v.GetAllDocumentsByKey(asKeys, True)
If ndc.Count <> 1 Then
'// Es wurden keine, oder mehrere Dokumente gefunden
If ndc.Count > 1 Then
'// Achtung! Nummer ist bereits doppelt vergeben!
MsgBox "Die Nummer ist bereits doppelt vergeben. Bitte informieren Sie einen Administrator",16,"Hinweis"
NummerSuchen = True
End If
Else
'// Es wurde genau 1 Dokument gefunden
Set doc = ndc.GetFirstDocument()
If refdoc Is Nothing Then '// If Not ... ist ebenfalls unperformanter, als If ... (Macht auch nur paar ms aus)
MsgBox "Die Nummer wurde schon vergeben.",16,"Hinweis"
NummerSuchen = True
Else
'// Prüfung, ob es sich beim gefundenen Dokument um sich selber handelt
If StrCompare(doc.Universalid, refdoc.Universalid, 5) <> 0 Then '// StrCompare ist meines Wissens nach performanter, als Strings direkt miteinander zu vergleich
MsgBox "Die Nummer wurde schon vergeben.",16,"Hinweis"
NummerSuchen = True
End If
End If
End If
End Function
Gruß,
Rich
Danke für die weiteren Tips und die fertige Funktion :knuddel:
Das mit dem Cint() bei den Keys ist schon korrekt, ich hatte sie beim ersten Versuch als String definiert und mich gewundert, warum ich keine Treffer hatte. Ein genauer Blick auf die Ansicht ergabe dann, dass die Spalten in der LU-Ansicht per @TextToNumer(Feld) in Zahlen gewandelt werden.
Die Anpassung im Script erschien mir daher sinnvoller, als ggf. eine neue Baustelle durch Anpassung der Ansicht zu eröffnen. Ich soll die Anwendug nicht 'komplett überarbeiten', auch wenn ich das gerne würde.
Eine weitere Frage hätte ich allerdings noch:
Gibt es eine Möglichkeit eine DocumentCollection zu sortieren?
Bin in der Anwendung gerade über eine 2. Funktion gestolpert, die für die Vergabe der Nummern für neue Dokumente zuständig ist.
Für das aktuelle Jahr holt sie sich aus der LU-Ansicht das letzte Dokument und rechnet +1 auf die laufende Nummer, füllt mit führenden Nullen auf und trägt die Nummer in das neue Dokument ein.
Lege ich jedoch ein Dokument für das Vorjahr (oder älter) an, geht die Funktion wieder die Dokumente in der Ansicht durch, um das letzte Dokument des ausgewählten Jahres zu finden und dann wieder auf die Nummer +1 zu addieren. Dauert natürlich auch wieder ewig....
Auf Grund eurer Tips dachte ich mir, dass ich hier ggf. auch mit einer DocumentCollection arbeiten kann, aus der ich mir dann das letzte Dokument rauspicke.
If Cint(jahr_uidoc) < jahr) Then
'Gibt es schon ein Dokument für das Jahr ?
Set dc = v.GetAllDocumentsByKey(Cint(jahr_uidoc),True)
If dc.Count = 0 Then
Call Source.FieldSetText("Ablagenummer_2","00001")
Else
Set doc = dc.GetLastDocument
nummer = Cint(doc.Ablagenummer_2 (0))
nummer = nummer + 1
Call Source.FieldSetText("Ablagenummer_2",_
Right("00000" + Cstr(nummer),5))
Exit Function
End If
End If
Bei einigen Jahrgängen funktioniert das wie gewünscht, das letzte Dokument der Collection ist das mit der höchsten Nummer, bei anderen Jahren ist das letzte Dokument der Collection zwar in der Nähe der höchsten Nummer, aber es gibt jeweils noch 2-3 Dokumente, die eine höhere Nummer haben.
Und nochmal herzlichen Dank an Mitch.
Die Anwendung schnurrt jetzt auch bei der Anlage von neuen Dokumenten - egal für welches Jahr - wie ein Kätzchen ;D
jahr_uidoc = Source.FieldGetText("Ablagenummer_1")
Set vc = v.GetAllEntriesByKey(Cint(jahr_uidoc),True)
If vc.Count = 0 Then
Call Source.FieldSetText("Ablagenummer_2","00001")
Else
Set entry = vc.GetLastEntry
Set doc = entry.Document
nummer = Cint(doc.Ablagenummer_2 (0))
nummer = nummer + 1
Call Source.FieldSetText("Ablagenummer_2",_
Right("00000" + Cstr(nummer),5))
End If