Autor Thema: Optimierung einer Suchfunktion  (Gelesen 3310 mal)

Offline Keydins

  • Aktives Mitglied
  • ***
  • Beiträge: 163
  • Geschlecht: Männlich
Optimierung einer Suchfunktion
« am: 21.08.14 - 11:41:34 »
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:
Code
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
 
« Letzte Änderung: 21.08.14 - 17:32:14 von Keydins »
Gruß Dirk

Aktuelle Notesumgebung
~800 BasicClients 9.0.1 FP9 SHF55
  10 FullClients 9.0.1 FP7 SHF143
    7 DominoServer 9.0.1 FP9 HF 139 / 64 Bit

klaussal

  • Gast
Re: Optimierung einer Suchfunktion
« Antwort #1 am: 21.08.14 - 11:58:20 »
Das ist mit der lfd. Nummer ist hier bestimmt schon 100te Male diskutiert worden.
Ergebnis: es geht nicht.

In der Applikation HELP ist ein Algorithmus für eine Nummernvergabe bestehend aus User-Name, Datum und Zeit.
Vielleicht wäre das die Lösung.

Offline Keydins

  • Aktives Mitglied
  • ***
  • Beiträge: 163
  • Geschlecht: Männlich
Re: Optimierung einer Suchfunktion
« Antwort #2 am: 21.08.14 - 12:29:41 »
Der Aufbau der Numern (Jahr / lfdNr) kann nicht verändert werden, da wie beschrieben schon ca. 50.000 Akten im Keller gelagert sind und niemand hingehen wird, um diese 50.000 Ordner neu zu beschriften.

Mir und auch der Fachabteilung ist durchaus bewußt, dass es hier keine 100% Lösung gibt, um die Vergabe von doppelten Nummen zu verhindern. Da im Schnitt ca. 10 Einträge pro Arbeitstag erfolgen, würde eine performatere Prüfroutine jedoch die Wahrscheinlichkeit der doppelten Vergabe deutlich reduzieren.

Für den Fall, dass tatsächlich eine Nummer doppelt vergeben wurde, gibt es eine Korrekturmöglichkeit und die Kollegen der Fachabteilung prüfen zur Sicherheit nach der Anlage neuer Dokumente auch die jeweilige Ansicht auf doppelte Vergabe.

Es geht mir nur darum, die Prüfung zeitlich zu optimieren und nicht darum, ein wasserdichtes Vergabeverfahren zu implementieren.
Gruß Dirk

Aktuelle Notesumgebung
~800 BasicClients 9.0.1 FP9 SHF55
  10 FullClients 9.0.1 FP7 SHF143
    7 DominoServer 9.0.1 FP9 HF 139 / 64 Bit

Mitch

  • Gast
Re: Optimierung einer Suchfunktion
« Antwort #3 am: 21.08.14 - 12:51:05 »
Huhu Dirk,

warum läufst du denn jedes Dokument zur Prüfung einzeln durch? Mach doch lieber eine Ansicht in der die relevanten Werte in der/den ersten Spalte(n) sind und zieh dir potentielle Dublikate via view.getAllDocumentsByKey.

Zur eindeutigen Nummer: Wenn man bei neuen Dokumenten nicht unbedingt sofort eine Nummer beim Anlegen haben muss, dann kann man auch erstmal eine temporäre Nummer nutzen (z.B. die UniversalID oder über @Unique) und die richtige Nummer von einem geschedulten Agenten vergeben lassen. Wenn man dann die letzte Nummer nicht aus einer Ansicht "errechnet", sondern sie in einen (Profil-)Dokument hinterlegt, dann sind doppelte Vergaben ohnehin Geschichte.

Gruß,

Mitch

Offline Keydins

  • Aktives Mitglied
  • ***
  • Beiträge: 163
  • Geschlecht: Männlich
Re: Optimierung einer Suchfunktion
« Antwort #4 am: 21.08.14 - 13:45:02 »
Hallo Mitch,

wie geschrieben, die Anwendung ist nicht von mir. Der Code ist 10 Jahre alt und ich soll 'auf die Schnelle' eine Lösung für das Performanceproblem finden. Mehr als ein paar Stunden Entwicklungsarbeit darf ich also nicht 'verbraten', somit entfallen größere Umbaumaßnahmen.

Die Idee mit dem getAllDocumentsByKey klingt auf jeden Fall nach einer Alternative, die mal testen sollte.
Gruß Dirk

Aktuelle Notesumgebung
~800 BasicClients 9.0.1 FP9 SHF55
  10 FullClients 9.0.1 FP7 SHF143
    7 DominoServer 9.0.1 FP9 HF 139 / 64 Bit

Offline Valron

  • Frischling
  • *
  • Beiträge: 4
  • Geschlecht: Männlich
Re: Optimierung einer Suchfunktion
« Antwort #5 am: 21.08.14 - 14:05:32 »
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)

Code
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

Offline Keydins

  • Aktives Mitglied
  • ***
  • Beiträge: 163
  • Geschlecht: Männlich
Re: Optimierung einer Suchfunktion
« Antwort #6 am: 21.08.14 - 14:29:57 »
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)

Code
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.
Gruß Dirk

Aktuelle Notesumgebung
~800 BasicClients 9.0.1 FP9 SHF55
  10 FullClients 9.0.1 FP7 SHF143
    7 DominoServer 9.0.1 FP9 HF 139 / 64 Bit

Offline Andrew Harder

  • Senior Mitglied
  • ****
  • Beiträge: 295
  • Geschlecht: Männlich
Re: Optimierung einer Suchfunktion
« Antwort #7 am: 21.08.14 - 14:42:12 »
Hast Du schon in der Lookup-Ansicht alle Dokumente ausgeschlossen, die älter als 2 Jahre sind?
Beim Jahreswechsel wird man evtl. auch den Lookup auf das alte Jahr benötigen.

Noch ältere sollten für einen Lookup aber nicht notwendig sein?
Das sollte die Ansicht schneller aufbauen, beim ersten mal wirds etwas länger dauern, aber dann gehts los....
Der Hauptgeschwindigkeitsgewinn wird aber auf jeden Fall vom Lookup kommen ;)
« Letzte Änderung: 21.08.14 - 14:44:48 von Andrew Harder »
Andy

Mitch

  • Gast
Re: Optimierung einer Suchfunktion
« Antwort #8 am: 21.08.14 - 14:50:15 »
Huhu Dirk,

das sieht doch schon gut aus.

Da du ja die potentiellen Dublikate mit einem exakten Schlüssel ziehst, kannst du dir im weiteren Verlauf die Prüfung auf die Items "Jahr" und "Ablagennummer_2" noch sparen.

Eigentlich reicht die Prüfung auf "Nichts gefunden -> Nummer frei", "Mehr als 1 gefunden -> Ohoh, schnell dem Anwendungsverantwortlichen mailen", "Genau eins gefunden -> Prüfen ob ich es selbst bin".

Eine entsprechende Anpassung hier wird zwar vermutlich keinen fühlbaren Performance-Unterschied machen, aber du hast ein paar Zeilen weniger in denen Fehler auftreten können. Apropos Fehler: Ein Errorhandling ist immer eine gute Idee.  ;) In den Best Practices gibt es Anregungen dazu: http://atnotes.de/index.php/topic,11980.0.html

Gruß,

Mitch

Offline Valron

  • Frischling
  • *
  • Beiträge: 4
  • Geschlecht: Männlich
Re: Optimierung einer Suchfunktion
« Antwort #9 am: 21.08.14 - 14:53:04 »
Hallo Dirk,

die Abfragerei unten...

Code
	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,...

Code
	Dim keys( 1 To 2 ) As Integer
	keys(1) = CInt(jahr)
	keys(2) = CInt(nummer)	

Aufgrund folgender Aussage von dir...
Zitat
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

Mitch

  • Gast
Re: Optimierung einer Suchfunktion
« Antwort #10 am: 21.08.14 - 14:57:39 »
Aufgrund folgender Aussage von dir...
Zitat
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.

Ohja, sehr wichtig!  :)

Offline Valron

  • Frischling
  • *
  • Beiträge: 4
  • Geschlecht: Männlich
Re: Optimierung einer Suchfunktion
« Antwort #11 am: 21.08.14 - 15:31:57 »
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.

Code
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
« Letzte Änderung: 21.08.14 - 15:35:05 von Valron »

Offline Keydins

  • Aktives Mitglied
  • ***
  • Beiträge: 163
  • Geschlecht: Männlich
Re: Optimierung einer Suchfunktion
« Antwort #12 am: 21.08.14 - 16:36:13 »
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.

Code
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.
Gruß Dirk

Aktuelle Notesumgebung
~800 BasicClients 9.0.1 FP9 SHF55
  10 FullClients 9.0.1 FP7 SHF143
    7 DominoServer 9.0.1 FP9 HF 139 / 64 Bit

Mitch

  • Gast
Re:
« Antwort #13 am: 21.08.14 - 16:40:27 »
Jain. :) Die NotesDocumentCollection ist unsortiert. Aber du solltest mit einer NotesViewEntryCollection arbeiten können, die ist wie die Ansicht sortiert.

Gruß,

Mitch

Gesendet von meinem Nexus 7 mit Tapatalk

Offline Keydins

  • Aktives Mitglied
  • ***
  • Beiträge: 163
  • Geschlecht: Männlich
Re: Optimierung einer Suchfunktion
« Antwort #14 am: 21.08.14 - 17:31:59 »
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

Code
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
Gruß Dirk

Aktuelle Notesumgebung
~800 BasicClients 9.0.1 FP9 SHF55
  10 FullClients 9.0.1 FP7 SHF143
    7 DominoServer 9.0.1 FP9 HF 139 / 64 Bit

 

Impressum Atnotes.de  -  Powered by Syslords Solutions  -  Datenschutz