Indexer für mein Forum basteln
Andreas-Lindig
- programmiertechnik
0 Vinzenz0 Rolf Rost
Hallo Forum,
nachdem ich nun einiges hin- und herüberlegt habe, die Volltextsuche in mySQL zu überlisten, habe ich die Schnauze voll. Ich will mir nun einen eigenen Index anlegen, der auch die zwei- und mehr-Byte-Zeichen meines utf-8 Datensatzes ordentlich indiziert, sowie Wörter kleiner als 4 Zeichen zuläßt und mir nicht eine Stopwordliste aufzwingt. Nun stellen sich mir zwei Fragen:
1.
Wie die Wörter indizieren? Motivation: ich will auch _Teile_ von Wörtern ohne "Like '%wort%'" finden. Das geht in diesem Forum ja auch. Z.B. habe ich "sapperlot" gesucht und gefunden. Ebenso habe ich "apperlot" und "sapperlo" gesucht und die gleichen Postings gefunden. Heißt das jetzt, daß die folgenden Wörter indiziert sind?:
Und wie kommen die in den Index? Ich würde es jetzt so machen, daß ich ein Posting komplett an den Leerzeichen aufsplitte und jedes Wort folgendermaßen indiziere:
Ausgangswort: sapperlot
zähle Zeichen: 9
indiziere ganzes Wort: sapperlot
indiziere ab Zeichen 2: apperlot
indiziere ab Zeichen 3: pperlot
.
.
.
Schleife bis zum vorletzten Zeichen (z.B.) - also mindest-Wortlänge = 2 Zeichen
.
.
.
indiziere ab Zeichen 8: ot
nächste Schleife zieht immer ein Zeichen von hinten ab:
indiziere bis Zeichen 8: sapperlo
indiziere bis Zeichen 7: sapperl
...
Im Grunde könnte man jetzt auch noch Wortteile in der Mitte indizieren, aber meine Frage ist: macht man das so? sieht ja erstmal viel aus, aber mit der Zeit ist wohl das meiste indiziert, sodaß nur noch neue Postingnummern dazu gepackt werden müßten.
2.
Wie die Tabellen und Abfrage anlegen? Motivation: die Suche soll natürlich schnell sein. Macht es einen Unterschied ob ich:
a)
nur eine Tabelle (INDEX) anlege:
IDs | suchwort
---------------------------
12; 135; 452 | Sapperlot
---------------------------
13; 24; 32; 98 | Gewitter
---------------------------
...
Hier ist die Spalte 'Suchwort' indiziert, Spalte IDs enthält auf einen Schlag alle gefunden Postings
Abfrage: SELECT IDs
from INDEX
WHERE suchwort = 'Sapperlot'
ODER ob ich
b)
zwei Tabellen (INDEX und VERKNUEPFUNG) anlege:
id | suchwort id_suchwort | id_posting
-------------- ------------------------
1 | Sapperlot 1 | 12
-------------- ------------------------
2 | Gewitter 1 | 135
-------------- ------------------------
... 1 | 452
------------------------
2 | 13
------------------------
2 | 24
------------------------
2 | 32
------------------------
2 | 98
Hier sind alle Spalten indiziert.
Abfrage: SELECT INDEX.Suchwort, INDEX.id, VERKNUEPFUNG.id_suchwort, VERKNUEPFUNG.id_posting
FROM INDEX, VERKNUEPFUNG
WHERE INDEX.id = VERKNUEPFUNG.id_suchwort AND INDEX.Suchwort = 'Sapperlot'
c)
Ich kann ja auch mit LIKE 'suchwort%' abfragen. Ist es ein Unterschied, ob ich, wie oben beschrieben jeden Furz-Wort-Teil indiziere und "apperlo" mit "= 'apperlo'" suche oder ob ich nur die Wörter aus der ersten Schleife indiziere, also:
sapperlot
apperlot
pperlot
und dann mit LIKE 'apperlo%' suche? Also sind '=' und LIKE 'wort%' gleich schnell (mal angenommen, sie würden gleich viele Ergebnisse bringen)?
Ich hoffe, das war jetzt nicht zuviel. Nebenbei möchte ich mich noch bei Sven Rautenberg für seine Ausführungen bedanken, durch die ich auf diese Probleme mit dem Zeichensatz gestoßen bin. Der Thread ist leider schon weg, also muß ich hier weitermachen.
Gruß, Andreas
Hallo Andreas,
Im Grunde könnte man jetzt auch noch Wortteile in der Mitte indizieren, aber meine Frage ist: macht man das so? sieht ja erstmal viel aus, aber mit der Zeit ist wohl das meiste indiziert, sodaß nur noch neue Postingnummern dazu gepackt werden müßten.
Hierzu nur die Anmerkung, dass ich nicht glaube, dass mit der Zeit das meiste indiziert ist, oder der Index ist unüberschaubar groß ...
Ich konzentriere mich auf Punkt 2
Wie die Tabellen und Abfrage anlegen? Motivation: die Suche soll natürlich schnell sein. Macht es einen Unterschied ob ich:
a)
nur eine Tabelle (INDEX) anlege:IDs | suchwort
12; 135; 452 | Sapperlot
13; 24; 32; 98 | Gewitter
...
Hier ist die Spalte 'Suchwort' indiziert, Spalte IDs enthält auf einen Schlag alle gefunden Postings
Das ist ein eklatanter Verstoß gegen die Normalformen der Datenbanken. Erstes Gesetz: Daten liegen atomar vor.
An a) solltest Du gar nicht denken.
b)
zwei Tabellen (INDEX und VERKNUEPFUNG) anlege:id | suchwort id_suchwort | id_posting
-------------- ------------------------
1 | Sapperlot 1 | 12
-------------- ------------------------
2 | Gewitter 1 | 135
-------------- ------------------------
... 1 | 452
------------------------
2 | 13
------------------------
2 | 24
------------------------
2 | 32
------------------------
2 | 98Hier sind alle Spalten indiziert.
Mach es so :-)
c)
...
und dann mit LIKE 'apperlo%' suche? Also sind '=' und LIKE 'wort%' gleich schnell (mal angenommen, sie würden gleich viele Ergebnisse bringen)?
Befrage dazu explain http://dev.mysql.com/doc/mysql/de/EXPLAIN.html
Freundliche Grüsse,
Vinzenz
Hallo Vinzenz,
a)
nur eine Tabelle (INDEX) anlege:IDs | suchwort
12; 135; 452 | Sapperlot
13; 24; 32; 98 | Gewitter
...
Hier ist die Spalte 'Suchwort' indiziert, Spalte IDs enthält auf einen Schlag alle gefunden Postings
Das ist ein eklatanter Verstoß gegen die Normalformen der Datenbanken. Erstes Gesetz: Daten liegen atomar vor.
das ist in diesem Falle doch wohl schnuppe. Ich hoffe, niemand denkt, daß mit Deiner lapidaren Antwort das Thema hinreichend erschöpft sei.
Ich suche nach wie vor Hilfe bei der Vorgehensweise, einen Index zu erstellen :)
Gruß, Andreas
Hallo Andreas-Lindig,
IDs | suchwort
12; 135; 452 | Sapperlot
13; 24; 32; 98 | Gewitter
...
Das ist ein eklatanter Verstoß gegen die Normalformen der Datenbanken. Erstes Gesetz: Daten liegen atomar vor.
das ist in diesem Falle doch wohl schnuppe. Ich hoffe, niemand denkt, daß mit Deiner lapidaren Antwort das Thema hinreichend erschöpft sei.
Nein, das ist nicht schnuppe. Ich bin der Ansicht, dass ein Verstoß gegen die _erste_ Normalform einer Datenbank beim Einsatz eines RDBMS ein grundsätzlicher Fehler ist. Diesen Fehler muss man unbedingt vermeiden. Deswegen bleibe ich hier bei einem lapidaren *Nein* zu dieser Idee. Lege sie zu den Akten. Du kannst gern ein klein wenig in dieser Richtung suchen, z.B. http://www.google.de/search?q=Normalform+Datenbank+erste&ie=UTF-8&hl=de&btnG=Google-Suche&meta=lr%3Dlang_de und Dir ein paar Treffer durchlesen, z.B. http://www.sql-und-xml.de/sql-tutorial/erste-normalform-datentypen.html.
Ich suche nach wie vor Hilfe bei der Vorgehensweise, einen Index zu erstellen :)
Einen Lösungsansatz frühzeitig auszuschließen, ist meiner Meinung nach eine Hilfestellung ;-) Konzentriere Dich auf Deine anderen Ideen.
Freundliche Grüsse,
Vinzenz
Einen Lösungsansatz frühzeitig auszuschließen, ist meiner Meinung nach eine Hilfestellung ;-) Konzentriere Dich auf Deine anderen Ideen.
Es ist m.E. kein wirklicher Verstoß, weil das Feld in dem jeweiligen Datensatz als _Ganzes_ ausgelesen wird. Es stellt für die DB keine Mehrzahl an Informationen dar. Eine Ansammlung von IDs wird aus "12; 13; 14..." erst in PHP. Da ist die DB schon längst nicht mehr beteiligt. Ich würde Deinen Einwand gelten lassen, wenn ich sowas konstruiert hätte:
ID1 | ID2 | ID3 | ID4 | suchwort
---------------------------------
12 | 135 | | | Sapperlot
---------------------------------
13 | 24 | 32 | 98 | Gewitter
---------------------------------
hab ich aber nicht :-)
Außerdem hab ich nicht wirklich Lust jetzt über DB-design zu dikutieren. Ich kenne die Einwände und auch die Normalisierungsregeln. Aber es geht mir hier um etwas ganz anderes. Um die Logik, die hinter einem Index steht.
Gruß, Andreas
Hallo Andreas,
Einen Lösungsansatz frühzeitig auszuschließen, ist meiner Meinung nach eine Hilfestellung ;-) Konzentriere Dich auf Deine anderen Ideen.
Es ist m.E. kein wirklicher Verstoß, weil das Feld in dem jeweiligen Datensatz als _Ganzes_ ausgelesen wird.
Es ist ein Verstoß, weil Du nicht auf das Datum im Feld als _Ganzes_ zugreifst, sondern jede in diesem Feld als Zeichenkette enthaltene Ziffernfolge als ID betrachtest. Somit greifst Du sehr wohl auf jeden Bestandteil des Feldinhalts zu.
Es stellt für die DB keine Mehrzahl an Informationen dar. Eine Ansammlung von IDs wird aus "12; 13; 14..." erst in PHP.
Das ist irrelevant. Du sagst es hier selbst: Du willst auf die IDs als einzelne Werte zugreifen. Dann speichere sie doch, wie Du Dir das selbst bereits zu Anfang überlegt hattest.
Da ist die DB schon längst nicht mehr beteiligt. Ich würde Deinen Einwand gelten lassen, wenn ich sowas konstruiert hätte:
Andreas, hast Du Dir überlegt, welchen Datentyp Deine ID-Spalte erhalten soll, MEDIUMBLOB oder LONGBLOB, siehe http://dev.mysql.com/doc/mysql/en/Storage_requirements.html, alles andere wäre viel zu knapp bemessen. Performanceprobleme handelst Du Dir auch ein, lies mal folgenden Thread
</archiv/2004/7/84465/#m495197>.
Dein Indexer soll doch performant sein, oder?
ID1 | ID2 | ID3 | ID4 | suchwort
12 | 135 | | | Sapperlot
13 | 24 | 32 | 98 | Gewitter
hab ich aber nicht :-)
Nö, das wäre ein anderer Designfehler, der noch viel früher scheitert.
Außerdem hab ich nicht wirklich Lust jetzt über DB-design zu dikutieren. Ich kenne die Einwände und auch die Normalisierungsregeln. Aber es geht mir hier um etwas ganz anderes. Um die Logik, die hinter einem Index steht.
Aber selbstverständlich musst Du Dir Dein DB-Design genau überlegen. Und Du bestehst derzeit völlig überflüssigerweise auf einem Modell, das fehlerhaft ist, das sehr schnell an seine Grenzen stösst. Warum? Du hast doch auch andere, bessere Ideen.
Es ist ganz bestimmt ein Fehler, eine Folge ganzer Zahlen als ellenlange Zeichenfolge zu speichern. Kannst Du Dir z.B. vorstellen, in wieviel Beiträgen z.B. die Zeichenfolge "un" vorkommen kann. Du willst ja alle Vorkommen speichern. Jedes Vorkommen bedeutet eine weitere Zeichenkette anzuhängen. Fällt Dir nicht auf, dass diese Lösung nicht skaliert?
Bitte stecke mehr Energie in Deine besseren Ideen, statt sie an einer untauglichen Idee zu verschwenden, nur weil ich diese Idee von vornherein als untauglich abgetan habe. Ich habe doch einfach ein Sackgassenschild an eine der Verzweigungen gestellt. Lauf doch nicht rein ;-) Du hast vielversprechendere Wege selbst aufgezeigt. Geh' dort entlang!
Freundliche Grüsse,
Vinzenz
Aber selbstverständlich musst Du Dir Dein DB-Design genau überlegen.
ja sicher.
Und Du bestehst derzeit völlig überflüssigerweise auf einem Modell, das fehlerhaft ist, das sehr schnell an seine Grenzen stösst. Warum?
Weil es mich genervt hat, daß Du auf meine eigentliche Frage, die ich lang und breit beschrieben habe, nicht eingegangen bist. Das hat mich geärgert - gut so? Mir geht es gar nicht um dieses DB-Modell, sondern um das richtige Indizieren. Dieses Beispiel habe ich gelesen und dachte, es sei möglicherweise zumindest im Zugriff sehr schnell. Daß das im Grunde kein ordentliches DB-Design ist ist mir schon klar. Spätestens beim updaten des Index wird man viel Arbeit haben.
Gruß, Andreas
Hallo Andreas,
Aber selbstverständlich musst Du Dir Dein DB-Design genau überlegen.
ja sicher.
Das ist gut so :-)
Und Du bestehst derzeit völlig überflüssigerweise auf einem Modell, das fehlerhaft ist, das sehr schnell an seine Grenzen stösst. Warum?
Weil es mich genervt hat, daß Du auf meine eigentliche Frage, die ich lang und breit beschrieben habe, nicht eingegangen bist. Das hat mich geärgert - gut so?
Nein, das ist nicht gut so. Denn ich bin auf Deine _Fragen_ (beachte den Plural) sehr wohl eingegangen. Ich habe Deine erste Frage (war mit 1. gekennzeichnet) behandelt, siehe dazu auch die Antwort von Mathias Bigge [pref:t=85026&m=499533].
Etwas ausführlicher: Meine Tastatur liefert mir ca. 100 druckbare Zeichen. Du bist durchaus an einem noch größeren Zeichenvorrat interessiert. Für die folgende Abschätzung arbeite ich mit der Zahl 100.
Damit hast Du
100^2 = 10.000 Möglichkeiten für zwei aufeinanderfolgende Zeichen
100^3 = 1.000.000 bei drei aufeinanderfolgenden Zeichen
100^4 = 100.000.000 bei vier Zeichen
100^5 = 10.000.000.000 bei fünf Zeichen
100^6 = 1.000.000.000.000 bei sechs Zeichen
...
Eine Anmerkung zu den zwei aufeinanderfolgenden Zeichen. Wie relevant sind diese. Zeichenfolgen, die in fast jedem Posting aufzufinden sind, sind _nicht_ relevant für die Suche. In der deutschen Sprache sind Wörter wie
das, der, die, und, als, ...
für eine Suche völlig irrelevant. Sie kommen in fast jedem Artikel vor.
Natürlich werden nicht alle vorkommen, aber es werden immer wieder neue hinzukommen, z.B. durch Rechtschreibefehler oder Postings wie [pref:t=84990&m=499167] und Antwort.
Das ist ganz schön viel. Dein Ausgangsposting umfasste ca. 500 Wörter mit fast 3000 Zeichen (ohne Leerzeichen). Hast Du Deine Analyse mal mit typischen Postings, und zwar nicht nur ein paar - hier hast Du ja eine genügende Auswahl :-) - durchgetestet und ausgewertet: Wieviele Neueinträge gibt es? Wieviele Zuordnungen gibt es zu einem Posting? Damit kannst Du Dir ein Gefühl dafür verschaffen, wie aufwendig es wird.
Ich bin auf die drei Unterpunkte Deines Punktes 2) eingegangen:
2 a) Ablehnung mit Begründung
2 b) Zustimmung
2 c) Hinweis auf ein Werkzeug zur Beantwortung Deiner Frage
OK, meine Antworten sind im Vergleich zu Deinem ausführlichen Posting knapp ausgefallen. Das gebe ich zu. Das ist aber kein Qualitätsmerkmal.
Mir geht es gar nicht um dieses DB-Modell, sondern um das richtige Indizieren. Dieses Beispiel habe ich gelesen und dachte, es sei möglicherweise zumindest im Zugriff sehr schnell.
Es hat Dich geblendet, deswegen wolltest Du keine Kritik daran annehmen. Ich hätte es mir denken sollen, es war Deine Wahl a), Deine erste Wahl, Dein Lieblingskind. Ich habe es gemeuchelt, kein Wunder, dass Deine Reaktion darauf nicht rational ausfiel, sondern emotional :-)
Daß das im Grunde kein ordentliches DB-Design ist ist mir schon klar.
Dann lass es auch sein.
Spätestens beim updaten des Index wird man viel Arbeit haben.
... und schnell an die selbst auferlegten Grenzen stossen.
Freundliche Grüsse,
Vinzenz
Hi Andreas
Außerdem hab ich nicht wirklich Lust jetzt über DB-design zu dikutieren. Ich kenne die Einwände und auch die Normalisierungsregeln. Aber es geht mir hier um etwas ganz anderes. Um die Logik, die hinter einem Index steht.
Sobald du nach mehr als einem Wort suchst, wirst du mit du mit der Variante 1;2;3;n Probleme haben. Du musst dann die einzelnen Listen von Hand mergen.
Gruss Daniela
Hi Vinzenz,
Im Grunde könnte man jetzt auch noch Wortteile in der Mitte indizieren, aber meine Frage ist: macht man das so? sieht ja erstmal viel aus, aber mit der Zeit ist wohl das meiste indiziert, sodaß nur noch neue Postingnummern dazu gepackt werden müßten.
Hierzu nur die Anmerkung, dass ich nicht glaube, dass mit der Zeit das meiste indiziert ist, oder der Index ist unüberschaubar groß ...
So ein paar GB sind es schon. Noch nicht einmal Google ermöglicht die Suche mir vorne weggelassenen Buchstaben, nur der Feature-geile Selfraum *g*. Genaueres dazu können Dir Michael Schröpl für die alte und Daniela Koller für die neue Suche sagen, vielleicht melden die sich hier ja mal zu Wort.
Hier ist die Spalte 'Suchwort' indiziert, Spalte IDs enthält auf einen Schlag alle gefunden Postings
Das ist viellleicht die tabellarische Darstellung eines Suchergebnisses, nicht aber ein Konzept für eine Tabelle.
Das ist ein eklatanter Verstoß gegen die Normalformen der Datenbanken. Erstes Gesetz: Daten liegen atomar vor.
Richtig.
Viele Grüße
Mathias Bigge
Hi Mattes
So ein paar GB sind es schon. Noch nicht einmal Google ermöglicht die Suche mir vorne weggelassenen Buchstaben, nur der Feature-geile Selfraum *g*. Genaueres dazu können Dir Michael Schröpl für die alte und Daniela Koller für die neue Suche sagen, vielleicht melden die sich hier ja mal zu Wort.
Morgen kann ich ja mal was dazu schreiben wenn es gewünscht ist. Das DB-Modell und den Indexer kann ich aktuell nicht einfach verlinken da das Webinterface fürs CVS Probleme macht. Vor Morgen hab ich keine Zeit etwas dazu zu schreiben da es kaum so schnell geht. Interessant sind dazu aber im Archiv die Diskusionen zwischen Michael Schröpl, Andreas Korthaus und mir. Bin gerade zu Müde zum Suchen und muss morgen um 6 raus.
Das Ergebnis könnte Andreas sich aber auch ansehen wenn sich endlich jemand erbarmt und den Newstext schreibt, die .htaccess hab ich schon weggemacht.
Gruss Daniela
Hallo Daniela,
Morgen kann ich ja mal was dazu schreiben wenn es gewünscht ist.
ja bitte, es interessiert mich brennend. Ich will ein anständiges Forum - nicht sone Kindergartensuche mit LIKE '%wort%' :-)
Besonders mußt Du genau erklären, wie Du das indizierst, und wie man so Wortteile mittendrin findet.
Gruß, Andreas
Hi Andreas
Mein Datenbanklayout besteht aus mehreren Tabellen:
Sektion: (Feature Artikel, Tutorials, Selfhtml, Forum...)
Dokument: Wie der Name schon sagt, eine Datei
Item: Eine findbare Einheit, z.B. ein Posting im Forum oder ein Abschnitt in Selfhtml
Keywd_Item: Die Verknüpfung der einzelnen Stichwörter zu einem Item.
|---------------------------------|
| Keywd | Itemid | Rating | Range |
|---------------------------------|
| Blub | 1 | 10 | 1 |
| Test | 1 | 5 | 2 |
| Blub | 2 | 10 | 1 |
...
|---------------------------------|
Schlüssel über diese Tabelle sind Keywd, Itemid und Range zusammen. Ich habe die Schlüsselwörter absichtlich nicht ausgelagert da ich eigentlich nur Nachteile darin sehe. Ich brauche einen zusätzlichen Join. Einen Index brauch ich sowieso über das Schlüsselwort, den kann ich aber auch direkt in die Tabelle legen. Ein Schlüsselwort kann in verschiedenen Zusammenhängen auftauchen nach denen auch einzeln gesucht werden kann (Titel, Text, Autor, Kategorie), das ist in der Spalte Range abgebildet. Die Spalte Rating ist eine Bewertung der Relevanz eines einzelnen Schlüsselworts in einem Item und hängt davon ab wo und wie oft es vorkommt.
Part_Keywd: Hier sind alle möglichen Teilwörter für ein Schlüsselwort gespeichert die eine bestimmte Mindestlänge haben.
|----------------------|
| PartKeywd | Keywd |
|----------------------|
| ndreas | Andreas |
| dreas | Andreas |
| reas | Andreas |
...
|----------------------|
Ich speichere nur Wörter bei denen der Anfang fehlt, Wörter deren Ende unbekannt ist, suche ich mit like 'xxx%'. Hierfür kann ein Index über die Spalte Keywd benutzt werden. Vorsicht falls du Postgres benutzt, evtl ist ein Index vom Typ varchar_pattern_ops notwending.
Eine Besonderheit ist die Tabelle common_words. Sie dient dazu, besonders häufige Wörter zu verwalten die aber dennoch inhaltliche Relevanz haben (Perl, Javascript...). Sie dient dazu bei einer Suche nach mehreren Wörtern die Suchbegriffe und die einzelnen Joins so anzuordnen, dass dies möglichst Ideal funktioniert. Diese Tabelle wird nicht laufend verändert sondern nur ab und zu neu erstellt wenn grössere Änderungen passiert sind, die das Verhältnis der einzelnen Stichwörter zueinander beinflussen.
|-------------------|
| Wort | Häufigkeit |
|-------------------|
Für Wörter ohne inhaltliche Relevanz gibt es eine Stoppwortliste. Diese ist nicht mit den übrigen Tabellen gespeichert sondern in einer DB4-Tabelle. Wörter die in dieser Liste stehen, werden nicht in den Index aufgenommen. Nach dem Indizieren können keine Wörter mehr entfernt werden da dann ein erneutes indizieren notwendig wäre.
Dem ganzen ist noch eine Volltextsuche nachgelagert um auch Phrasen finden zu können. Dazu besitzt jede Sektion ein File indem die einzelnen Einträge zu finden sind. Jedes Item hat eine Startposition in diesem File sowie eine Länge die beide in der Tabelle Item gespeichert sind. Wenn jetzt eine Phrase in ihre Einzelwörter zerlegt auf ein Item zutrifft, wird dieser Text ausgelesen und mit der gesammten Phrase verglichen.
Gruss Daniela
P.S: Ich empfehle einen Blick in die News
Hallo Daniela,
das waren sehr aufschlußreiche Ausführungen. Dankeschön.
Ich habe die Schlüsselwörter absichtlich nicht ausgelagert da ich eigentlich nur Nachteile darin sehe. Ich brauche einen zusätzlichen Join.
Die Idee, Suchwort und Verknüpfung in eine Tabelle zu legen finde ich gut, mach ich auch :-)
Ein paar Einzelheiten habe ich aber nicht verstanden:
Part_Keywd: Hier sind alle möglichen Teilwörter für ein Schlüsselwort gespeichert die eine bestimmte Mindestlänge haben.
|----------------------|
| PartKeywd | Keywd |
|----------------------|
| ndreas | Andreas |
| dreas | Andreas |
| reas | Andreas |
...
|----------------------|
Ich speichere nur Wörter bei denen der Anfang fehlt, Wörter deren Ende unbekannt ist, suche ich mit like 'xxx%'.
Man kann bei Dir ja 'eigenständige Worte' ankreuzen. Wird dann nur in 'Keywd_Item' gesucht? Und angenommen ich suche nach 'ndreas' ohne 'eigenständige Worte' anzuklicken. Suchst Du dann erst in 'Part_Keywd' und das zugehörige Keywd dann in 'Keywd_Item'? Also etwa so?:
SELECT Itemid
From Keywd_Item, Part_Keywd
WHERE Keywd_Item.Keywd = Part_Keywd.Keywd
AND Part_Keywd.PartKeywd = 'ndreas'
Aber was machst Du dann (falls es denn so ist), wenn 'Andreas' ohne 'eigenständige Worte' gesucht wird. Das kommt ja in PartKeywd gar nicht vor. Woher weißt Du, ob es als Wortanfang (WHERE Keywd_Item.Keywd LIKE 'Andreas%') oder als Wortende gesucht werden muß (WHERE Part_Keywd.PartKeywd = 'Andreas')?
Nach dem Indizieren können keine Wörter mehr entfernt werden da dann ein erneutes indizieren notwendig wäre.
was machst Du denn, wenn ein Posting gelöscht wird und es als einziges ein indiziertes Wort enthielt?
Hier leider gar nichts verstehn:
Dem ganzen ist noch eine Volltextsuche nachgelagert um auch Phrasen finden zu können. Dazu besitzt jede Sektion ein File indem die einzelnen Einträge zu finden sind.
Was für Einträge stehen da drin?
Jedes Item hat eine Startposition in diesem File sowie eine Länge die beide in der Tabelle Item gespeichert sind.
Wenn jetzt eine Phrase in ihre Einzelwörter zerlegt auf ein Item zutrifft,...
also, Du suchst alle Wörter als ganze Wörter und wenn alle gefunden werden _und_ alle Treffer auf mindestens ein gemeinsames Item verweisen (Itemid)
...wird dieser Text ausgelesen und mit der gesammten Phrase verglichen.
der Text der gefunden Itemid. Ist das so richtig?
Aber was für Einträge stehen in diesem File der Sektion?
Gruß, Andreas
P.S: Ich empfehle einen Blick in die News
ja, ich war auf der neuen. Also 'apperlot' (als Teil von 'Sapperlot') kann man jetzt nicht mehr finden. Ist das nun Absicht?
Hi Andreas
Man kann bei Dir ja 'eigenständige Worte' ankreuzen. Wird dann nur in 'Keywd_Item' gesucht? Und angenommen ich suche nach 'ndreas' ohne 'eigenständige Worte' anzuklicken. Suchst Du dann erst in 'Part_Keywd' und das zugehörige Keywd dann in 'Keywd_Item'? Also etwa so?:
Nein, dann such ich immernoch nur in Keywd_Item. Part_Keywd kommt erst ins Spiel, wenn man vor dem zu suchenden Teilwort ein * angibt.
SELECT Itemid
From Keywd_Item, Part_Keywd
WHERE Keywd_Item.Keywd = Part_Keywd.Keywd
AND Part_Keywd.PartKeywd = 'ndreas'
Nicht ganz. Eigentlich ist das ganze eine uncorrelated Subquery, es muss also nur einmal die Liste aller möglichen Wörter ermittelt werden und nicht pro Item. So artet das bei Postgres in einen Nested Loop aus. Ich hole mir in einer separaten Query alle möglichen Wörter und generiere daraus eine Bedingung die ich dann mit IN benutze. Möglich wäre auch direkt eine Subquery zu benutzen aber das artet bei Postgres ebenfalls in einem Nested-Loop aus und MySQL beherrscht sowas nur in der Alphaversion.
Allgemein würde ich explizite Joins empfehlen da du dann selber bestimmen kannst, in welcher Reihenfolge die ausgeführt werden.
Aber was machst Du dann (falls es denn so ist), wenn 'Andreas' ohne 'eigenständige Worte' gesucht wird. Das kommt ja in PartKeywd gar nicht vor. Woher weißt Du, ob es als Wortanfang (WHERE Keywd_Item.Keywd LIKE 'Andreas%') oder als Wortende gesucht werden muß (WHERE Part_Keywd.PartKeywd = 'Andreas')?
Wenn ein * angegeben wird, muss vorher etwas kommen. Wenn nicht, ist das kein Treffer. Alternativ könnte man einfach als mögliches Teilwort auch das ganze Wort aufnehmen.
Nach dem Indizieren können keine Wörter mehr entfernt werden da dann ein erneutes indizieren notwendig wäre.
was machst Du denn, wenn ein Posting gelöscht wird und es als einziges ein indiziertes Wort enthielt?
Ehm, hä? Das betrifft nur die Stoppwortliste. Wenn etwas indiziertes geändert wird, wozu auch löschen gehört, muss das im Index natürlich nachgeführt werden.
Hier leider gar nichts verstehn:
Dem ganzen ist noch eine Volltextsuche nachgelagert um auch Phrasen finden zu können. Dazu besitzt jede Sektion ein File indem die einzelnen Einträge zu finden sind.
Was für Einträge stehen da drin?
Titel | volltext
Jedes Item hat eine Startposition in diesem File sowie eine Länge die beide in der Tabelle Item gespeichert sind.
Wenn jetzt eine Phrase in ihre Einzelwörter zerlegt auf ein Item zutrifft,...
also, Du suchst alle Wörter als ganze Wörter und wenn alle gefunden werden _und_ alle Treffer auf mindestens ein gemeinsames Item verweisen (Itemid)
Ehm, hä? Ich suche alle Items die prinzipiell in Frage kommen, also eine Verbindung zu sämtlichen Wörter innerhalb einer Phrase haben.
...wird dieser Text ausgelesen und mit der gesammten Phrase verglichen.
der Text der gefunden Itemid. Ist das so richtig?
Aber was für Einträge stehen in diesem File der Sektion?
Titel und Volltext eines jeden Items das zu dieser Sektion gehört.
ja, ich war auf der neuen. Also 'apperlot' (als Teil von 'Sapperlot') kann man jetzt nicht mehr finden. Ist das nun Absicht?
Solange du kein * vorne hin machst, ja. Die Anleitung für die Suche ist leider noch nicht ganz fertig. Hier mal die EBNF für die Suchesyntax:
searchfield = clause.
clause = {word | expression}.
expression = ["-"] (type ":" ( ["*"] word | """{word}""" )) | ["*"] word.
type = "author" | "category" | "body" | "title".
word = "a" | ... | "z" | "A" | ... | "Z".
Gruss Daniela
Hallo Daniela, mal wieder vielen Dank für deine nützlichen Ausführungen.
Nein, dann such ich immernoch nur in Keywd_Item. Part_Keywd kommt erst ins Spiel, wenn man vor dem zu suchenden Teilwort ein * angibt.
na, _das_ muß einem ja auch gesagt werden ;-)
Nicht ganz. Eigentlich ist das ganze eine uncorrelated Subquery, es muss also nur einmal die Liste aller möglichen Wörter ermittelt werden und nicht pro Item. So artet das bei Postgres in einen Nested Loop aus.
fürchte, das ist mir zu hoch, aber ich brauche es ja auch nicht so komplex wie Du, nur eine Frage ist noch übrig:
Allgemein würde ich explizite Joins empfehlen da du dann selber bestimmen kannst, in welcher Reihenfolge die ausgeführt werden.
Wie muß denn eine 'gute' Reihenfolge sein? Und wie bestimme ich die? Mein Beispiel:
SELECT Itemid
From Keywd_Item, Part_Keywd
WHERE Keywd_Item.Keywd = Part_Keywd.Keywd
AND Part_Keywd.PartKeywd = 'ndreas'
braucht ja nicht wirklich eine Join-Angabe, um überhaupt die richtigen Ergebnisse zu liefern. Das ist doch automatisch ein inner-Join gell? Ich bin so in den Tiefen eines DB-Systems nicht bewandert, daß ich jetzt wüßte, was besser, schneller, wasauchimmer ausgeführt wird.
Was für Einträge stehen da drin?
Titel | volltext
äh, ja. Das konnte ich halt nicht glauben, deshalb habe ich mal nachgefragt. Also in _einer_ Datei für die Sektion Forum z.B. stehen _alle_ Volltexte des Archivs oder eines Ausschnitts davon?
also, Du suchst alle Wörter als ganze Wörter und wenn alle gefunden werden _und_ alle Treffer auf mindestens ein gemeinsames Item verweisen (Itemid)
Ehm, hä? Ich suche alle Items die prinzipiell in Frage kommen, also eine Verbindung zu sämtlichen Wörter innerhalb einer Phrase haben.
Ich vermute, da haben wir das gleiche gemeint. Also erst Suche nach _allen_ Wörtern der Eingabe und die gefunden Postings dann auslesen und die tatsächliche Phrase vergleichen ja?
Gruß, Andreas
Hi Andreas
Nein, dann such ich immernoch nur in Keywd_Item. Part_Keywd kommt erst ins Spiel, wenn man vor dem zu suchenden Teilwort ein * angibt.
na, _das_ muß einem ja auch gesagt werden ;-)
Es kommt noch eine Anleitungsseite oder ein Kasten. Der ist nur noch nicht fertig.
Nicht ganz. Eigentlich ist das ganze eine uncorrelated Subquery, es muss also nur einmal die Liste aller möglichen Wörter ermittelt werden und nicht pro Item. So artet das bei Postgres in einen Nested Loop aus.
fürchte, das ist mir zu hoch, aber ich brauche es ja auch nicht so komplex wie Du, nur eine Frage ist noch übrig:
Es ist eigentlich ganz einfach: Die verschiedenen möglichen Wörter die für ein Teilwort möglich sind, unterscheiden sich bei den einzelnen Ergebnissen nicht. Beim Dokument 1 sind sie identisch mit denen von Dokument n. Sie sind unabhängig vom Rest der Query. Das nennt man eine "uncorrelated" Subquery. Nur hab ich es nicht hingekriegt das Postgres begreiflich zu machen, dass er bitte nicht für jeden Möglichkeit des äusseren Joins (oder in meinem Fall auch Query da ich es sowohl als Subquery als auch über Joins probiert hatte), nochmal die Tabelle Part_Keywd anschauen soll, sondern dass ein einziges Mal für die ganze Query reicht.
Wie muß denn eine 'gute' Reihenfolge sein? Und wie bestimme ich die? Mein Beispiel:
SELECT Itemid
From Keywd_Item, Part_Keywd
WHERE Keywd_Item.Keywd = Part_Keywd.Keywd
AND Part_Keywd.PartKeywd = 'ndreas'braucht ja nicht wirklich eine Join-Angabe, um überhaupt die richtigen Ergebnisse zu liefern. Das ist doch automatisch ein inner-Join gell? Ich bin so in den Tiefen eines DB-Systems nicht bewandert, daß ich jetzt wüßte, was besser, schneller, wasauchimmer ausgeführt wird.
Nein, das ganze spielt erst bei mehreren verschiedenen Schlüsselwörtern einer Rolle. Bei grossen Zwischenergebnissen sagen wir einmal 10000 Treffer für ein einzelnes Wort wird ein Merge mit einem anderen, ebenfalls sehr grossen, Zwischenergebnis ziemlich teuer. Ich sortiere die Joins deswegen in einer Reihenfolge die, nach Möglichkeit, erstmal eine kleine Menge mit einer grossen Menge zusammenmergt und so in jedem Fall sämtliche Zwischenresultate kleiner oder maximal gleich Gross wie die kleinste Menge sind.
Ein Beispiel:
Eine Suche nach Perl, Javascript und noch einem seltenen Wort. Perl und Javascript sind hier sehr häufige Wörter da sie gleichzeitig Kategorien darstellen. Wenn jetzt zuerst die beiden Ergebnismengen von Perl und Javascript zusammengefügt werden, müssen zwei sehr grosse Mengen zusammengeführt werden. Wenn jetzt aber erst eine der Mengen mit den sagen wir mal 100 Treffern des seltenen Wortes zusammengefügt werden, geht das viel schneller.
Das kannst du recht gut an den Explains der Query sehen. Ich weis nicht ob MySQl die Möglichkeit auch bietet, aber mit Postgres kann man sich die effektiv benötigte Zeit von jedem Zwischenschritt mit explain analyze ansehen. Wenn da Joins egal welcher Art sehr lange dauern, kann es daran liegen. Durch eine bessere Sortierung der Reihenfolge der Wörter und damit der Joins etwas verbessert werden. Teilweise war es bei mir von 30s auf 0.5s, das waren allerdings die Extreme.
Die Reihenfolge vorgeben kann man allerdings, zumindest bei Postgres, nur mit expliziter geklammerter Joins:
FROM x
INNER JOIN (y INNER JOIN z on(y.asdf = z.asdf)) on(x.asdf = y.asdf)
äh, ja. Das konnte ich halt nicht glauben, deshalb habe ich mal nachgefragt. Also in _einer_ Datei für die Sektion Forum z.B. stehen _alle_ Volltexte des Archivs oder eines Ausschnitts davon?
Fast, das Forum ist unterteilt in verschiedene Sektionen. Die Daten sind so aufgeteilt, wie du sie auswählen kannst in der Maske. Es steht nicht nur ein Ausschnitt drin, sondern alles (das was auch der User sieht, also keine Tags und sowas).
Ich vermute, da haben wir das gleiche gemeint. Also erst Suche nach _allen_ Wörtern der Eingabe und die gefunden Postings dann auslesen und die tatsächliche Phrase vergleichen ja?
Jap.
Gruss Daniela
Ich will Dich nicht langweilen, das ist wie im richtigen Forscherleben: es tauchen immer neue Fragen auf ;-)
Nicht ganz. Eigentlich ist das ganze eine uncorrelated Subquery, es muss also nur einmal die Liste aller möglichen Wörter ermittelt werden und nicht pro Item. So artet das bei Postgres in einen Nested Loop aus.
fürchte, das ist mir zu hoch, aber ich brauche es ja auch nicht so komplex wie Du, nur eine Frage ist noch übrig:
Es ist eigentlich ganz einfach: Die verschiedenen möglichen Wörter die für ein Teilwort möglich sind, unterscheiden sich bei den einzelnen Ergebnissen nicht. Beim Dokument 1 sind sie identisch mit denen von Dokument n.
also, '*lot' findet 'Sapperlot', 'Echolot' und 'Silberlot', egal, ob diese drei Wörter oder einzelne davon in Posting 1 oder in Posting n stehen.
Sie sind unabhängig vom Rest der Query. Das nennt man eine "uncorrelated" Subquery. Nur hab ich es nicht hingekriegt das Postgres begreiflich zu machen, dass er bitte nicht für jeden Möglichkeit des äusseren Joins (oder in meinem Fall auch Query da ich es sowohl als Subquery als auch über Joins probiert hatte), nochmal die Tabelle Part_Keywd anschauen soll, sondern dass ein einziges Mal für die ganze Query reicht.
Aaah..., bei meiner Beispiel-Abfrage werden die Schlüsselwörter zu einem gegebenen Teilwort für jede Verknüpfung zwischen der Tabelle 'Part_Keywd' und der Tabelle 'Keywd_Item' (WHERE Keywd_Item.Keywd = Part_Keywd.Keywd) immer wieder neu gesucht? Wie umständlich.
angenommen das eingegebene Wort war '*lot' und eine Abfrage
SELECT Keywd
FROM Part_Keywd
WHERE PartKeywd = 'lot'
würde als Ergebnis bringen:
'Sapperlot'
und wenn dieses Wort in drei Postings in der Tabelle 'Keywd_Item' gefunden werden kann, dann wird die oben beschriebene Teilabfrage intern auch dreimal ausgeführt? Ne, das hab ich wieder falsch verstanden :-)...
Nein, das ganze spielt erst bei mehreren verschiedenen Schlüsselwörtern einer Rolle. Bei grossen Zwischenergebnissen sagen wir einmal 10000 Treffer für ein einzelnes Wort wird ein Merge mit einem anderen, ebenfalls sehr grossen, Zwischenergebnis ziemlich teuer. Ich sortiere die Joins deswegen in einer Reihenfolge die, nach Möglichkeit, erstmal eine kleine Menge mit einer grossen Menge zusammenmergt und so in jedem Fall sämtliche Zwischenresultate kleiner oder maximal gleich Gross wie die kleinste Menge sind.
Also, wenn ich das mit Joins in mySQL nicht hinkriege: im Zweifel erst die Schlüsselwörter suchen, dann die Posting-IDs für jedes Schlüsselwort einzeln suchen und dann sagen wir mal, die größte Teilmenge mit der kleinsten schneiden, dann die nächstgrößere mit dem Ergebnis von der vorigen Durchschnittsoperation usw...
Ein Beispiel:
[...] Ich weis nicht ob MySQl die Möglichkeit auch bietet, aber mit Postgres kann man sich die effektiv benötigte Zeit von jedem Zwischenschritt mit explain analyze ansehen.
nö, Teilzeiten scheinen da nicht angezeigt zu werden. Aber mit diesem EXPLAIN muß ich mich erstmal beschäftigen - ist nicht so einfach zu interpretieren.
Gruß, Andreas
Hi Andreas
Aaah..., bei meiner Beispiel-Abfrage werden die Schlüsselwörter zu einem gegebenen Teilwort für jede Verknüpfung zwischen der Tabelle 'Part_Keywd' und der Tabelle 'Keywd_Item' (WHERE Keywd_Item.Keywd = Part_Keywd.Keywd) immer wieder neu gesucht? Wie umständlich.
Evtl werden sie das. Das kommt darauf an wie gut der Planer (das Teil das festlegt, wie die Query ausgeführt wird) gerade drauf ist. Ich würde einfach da sehr genau drauf schauen wie er das macht. Ich habe einfach meine Zweifel, dass der Planer merkt, dass es nicht nötig ist.
und wenn dieses Wort in drei Postings in der Tabelle 'Keywd_Item' gefunden werden kann, dann wird die oben beschriebene Teilabfrage intern auch dreimal ausgeführt? Ne, das hab ich wieder falsch verstanden :-)...
Darauf wollte ich hinaus, doch. Nicht garantiert, aber wenn du Pech hast.
Also, wenn ich das mit Joins in mySQL nicht hinkriege: im Zweifel erst die Schlüsselwörter suchen, dann die Posting-IDs für jedes Schlüsselwort einzeln suchen und dann sagen wir mal, die größte Teilmenge mit der kleinsten schneiden, dann die nächstgrößere mit dem Ergebnis von der vorigen Durchschnittsoperation usw...
Würde ich nicht empfehlen, selbst wenn die Query ungünstig ausgeführt wird, dürfte es noch ähnlich schnell sein wie wenn du es selber machst. Es müssen, wenn du es selber machst, viel mehr Daten zwischen deinem Programm und der Datenbank ausgetauscht werden. Dazu kommt, das die Mergingalgorithmen der Datenbanken gut sind da sie das quasi dauernd machen muss.
Zudem kannst du ihm mit Klammersetzung doch eigentlich immer klar machen, wie das sinnvoll geht. Das einzige was _evtl_ sinnvoll ist, wäre die Liste der möglichen ganzen Wörter zuerst zu holen und dann per IN einzusetzen:
SELECT keywd
FROM part_keywd
WHERE ...;
SELECT ....
FROM item
INNER JOIN keywd_item ON(...)
WHERE keywd_item.keywd IN(Liste der Wörter aus der
ersten Query)
Du wirst ausprobieren müssen ob es nötig ist.
nö, Teilzeiten scheinen da nicht angezeigt zu werden. Aber mit diesem EXPLAIN muß ich mich erstmal beschäftigen - ist nicht so einfach zu interpretieren.
Ja, die von Postgresql sind wesentlich einfacher zu lesen (ist auf meinem Rechner, nicht auf dem Server):
suche=# explain select * from keywd_item where keywd in (select keywd from part_keywd where keywdpart like 'n dreas');
QUERY PLAN
-------------------------------------------------------------------------------------------------------------- -----
Nested Loop (cost=104.84..11309.51 rows=73 width=19)
-> HashAggregate (cost=104.84..104.84 rows=6 width=18)
-> Index Scan using i_keywdpart_keywdpart_keywd_like on part_keywd (cost=0.00..104.78 rows=26 width =18)
Index Cond: ((keywdpart)::text ~=~ 'ndreas'::character varying)
Filter: ((keywdpart)::text ~~ 'ndreas'::text)
-> Index Scan using i_keywditem_keywd_itemid on keywd_item (cost=0.00..1859.92 rows=602 width=19)
Index Cond: ((keywd_item.keywd)::text = ("outer".keywd)::text)
(7 Zeilen)
Da siehst du btw auch den Nestedloop von dem ich gesprochen habe.
Achja, noch ein Tipp: Regelmässige die Tabellen neu analysieren lassen nicht vergessen damit der Planer nicht von veralteten Statistiken ausgeht und völligen Mist macht.
Gruss Daniela
Hi Daniela,
Also, wenn ich das mit Joins in mySQL nicht hinkriege: im Zweifel erst die Schlüsselwörter suchen, dann die Posting-IDs für jedes Schlüsselwort einzeln suchen und dann sagen wir mal, die größte Teilmenge mit der kleinsten schneiden, dann die nächstgrößere mit dem Ergebnis von der vorigen Durchschnittsoperation usw...
Würde ich nicht empfehlen, selbst wenn die Query ungünstig ausgeführt wird, dürfte es noch ähnlich schnell sein wie wenn du es selber machst.
Ich meinte schon mit temporären Tabellen, nicht erst alles in PHP einlesen und dort filtern. Etwa so:
CREATE TEMPORARY TABLE ganzeWoerter
SELECT keywd
FROM part_keywd
WHERE ...;
SELECT item
FROM ganzeWoerter, keywd_item
WHERE ganzeWoerter.keywd = keywd_item.keywd
oder eben mit explizitem Join:
SELECT item
FROM ganzeWoerter INNER JOIN keywd_item
weiß gar nicht, ob das ohne ON geht hehe ;-)
da hätte ich statt der IN-Prüfung, die Du weiter unten beschrieben hast, den inner-Join zu der Temporären Liste. Na egal jetzt, es geht in mySQL wohl auch mit verschachtelten Joins. Mich hat die Klammerei der Joins nur irritiert - die gibts in mySQL nämlich nicht, aber das gleiche Prinzip mit anderer Syntax geht schon. Heute erstmal 'n neues Buch besorgt ;-)
Achja, noch ein Tipp: Regelmässige die Tabellen neu analysieren lassen nicht vergessen damit der Planer nicht von veralteten Statistiken ausgeht und völligen Mist macht.
guter Tip. Eine DB-Anwendung schnell zu machen ist für mich auch erstmal Neuland.
Möchte mich mal für Deine Geduld und kompetente Beratung bedanken. Ich fühle mich jetzt vollkommen gerüstet, die Suche so zu schreiben, wie ich mir das vorgestellt habe.
Gruß, Andreas
Hallo Mathias,
Hierzu nur die Anmerkung, dass ich nicht glaube, dass mit der Zeit das meiste indiziert ist, oder der Index ist unüberschaubar groß ...
So ein paar GB sind es schon. Noch nicht einmal Google ermöglicht die Suche mir vorne weggelassenen Buchstaben, nur der Feature-geile Selfraum *g*. Genaueres dazu können Dir Michael Schröpl für die alte und Daniela Koller für die neue Suche sagen, vielleicht melden die sich hier ja mal zu Wort.
Die neue Suche macht richtig Spaß. Vielen Dank an Daniela!
Freundliche Grüsse,
Vinzenz
Hi Vinzenz
Die neue Suche macht richtig Spaß. Vielen Dank an Daniela!
Ich frage mich gerade, woher du die URL weist und ob du die nicht einfach mit der alten Suche auf den neuen Rechnern verwechselst.
Gruss Daniela
Hallo Daniela,
Ich frage mich gerade, woher du die URL weist und ob du die nicht einfach mit der alten Suche auf den neuen Rechnern verwechselst.
Das werde ich wohl tun, macht aber trotzdem bereits Spaß. Und weckt Vorfreude auf die neue. Danke im Voraus :-)
Freundliche Grüsse,
Vinzenz
hi Andreas,
Text::Query (PERL Modul) ist Dein Freund.
Hab auf
http://perlbase.xwolf.de/
Einiges dazu geschrieben.
Gruss, Rolf
Falls PERL...