MySQL-DB durchsuchen
andreas
- php
Moin!
Ich hatte noch nie wirklich drüber nach gedacht, da eine SQL-DB mit SELECT...WHERE feld == $suchwort ja ziemlich einfach durchsucht werden kann. Jetzt habe ich aber doch ein Problem, wie kann ich es machen, das nicht der kpl. Feldinhalt gleich dem Suchwort sein muß, sondern das das Feld das Suchwort enthalten muß?
Ich weiß dass ich mit substr() genau sowas finde, aber wie soll ich das denn in die Abfrage einbinden, das geht doch nicht!
Gibt es vielleicht ein Zeichen anstelle von "==", welches eine Logik der Art "enthält" ergibt?
Wenn nicht wie mache ich das sonst?
Viele Grüße
Andreas
Hi andreas
Ich hatte noch nie wirklich drüber nach gedacht, da eine SQL-DB mit SELECT...WHERE feld == $suchwort ja ziemlich einfach durchsucht werden kann. Jetzt habe ich aber doch ein Problem, wie kann ich es machen, das nicht der kpl. Feldinhalt gleich dem Suchwort sein muß, sondern das das Feld das Suchwort enthalten muß?
Gibt es vielleicht ein Zeichen anstelle von "==", welches eine Logik der Art "enthält" ergibt?
Was du suchst heisst like, die Platzhalter (Wildcards) sind _ für ein Zeichen und % für
beliebig viele Zeichen, ausserdem bin ich fast sicher, für normales Vergleichen ist es =
und nicht ==.
Nachteile der Platzhalter und like: wenn sie am Anfang des Strings stehen, können
Indizes nicht mehr benutzt werden und die Suche wird entsprechend langsamer und
aufwändiger.
Gruss Daniela
Hi Daniela!
Was du suchst heisst like, die Platzhalter (Wildcards) sind _ für ein Zeichen und % für
beliebig viele Zeichen, ausserdem bin ich fast sicher, für normales Vergleichen ist es =
und nicht ==.
ups, hast Recht :-)
Nachteile der Platzhalter und like: wenn sie am Anfang des Strings stehen, können
Indizes nicht mehr benutzt werden und die Suche wird entsprechend langsamer und
aufwändiger.
Verstehe ich nicht. Ich benutze doch keine Indizes?! Das sieht dann wohl so aus:
SELECT...WHERE feld1 LIKE '%$suchwort%' OR feld2 LIKE '%$suchwort%';
Richtig? Klar das das langsamer ist als wenn ich nur suche ob Feld = $suchwort, oder was meintest Du? Aber wenn das Suchwort irgendwo im Text vorkommen kann, muß ich das wohl so machen, oder geht das auch anders?
Grüße
Andreas
Hi Andreas,
Verstehe ich nicht. Ich benutze doch keine Indizes?!
Solltest Du aber.
Das sieht dann wohl so aus:
SELECT...WHERE feld1 LIKE '%$suchwort%' OR feld2 LIKE '%$suchwort%';
Richtig?
Ja.
Klar das das langsamer ist als wenn ich nur suche ob Feld = $suchwort,
oder was meintest Du?
Stell Dir vor, Du hast eine Million Datensätze. Jede Deiner beiden Suchanfragen muß dann also eine Million Vergleiche durchführen. Das ist nicht wirklich beliebig schnell.
Und jetzt denke Dir zum Vergleich einen Index hinzu, also einen binären Baum, welcher sämtliche Werte des zu vergleichenden Feldes enthält. Wie findest Du (genauer gesagt: die SQL engine) darin einen Wert? Durch fortgesetzte Halbierung der Datenmenge, d. h. durch Absteigen in diesem Baum. Wieviele Schritte sind dafür notwendig? So viele, wie der Baum 'hoch' ist - im idealen Fall also Logarithmus zur Basis 2 von einer Million, also etwa 20. Und daß 20 Vergleiche schneller sind als eine Million, darüber sind wir uns doch einig, ja?
Genau diese Baum-Halbierungs-Methode funkioniert aber nur dann, wenn Du Suchbegriff auf den Anfang der gespeicherten Worte anwenden kannst - deshalb darf die wildcard nicht am Anfang vorliegen, sonst geht wiederum nur der lineare Vergleich aller Datensätze.
Aber wenn das Suchwort irgendwo im Text vorkommen kann, muß ich das
wohl so machen, oder geht das auch anders?
Das kommt darauf an, wie Du Deinen Text gespeichert hast.
Wenn Dein gesamter Text ein Feld der Tabelle ist _und_ Du nichts anderes verwenden willst als Dein obiges Statement, dann droht Dir die "eine-Million-Vergleiche-Strafe".
Zu diesem Modell gibt es zwei naheliegende Alternativen:
1. Du speicherst in einer weiteren Tabelle sämtliche Worte Deines Textes, je ein Wort pro Zeile, und als zweite Spalte den Primärschlüssel aus der ersten Tabelle. (Den Worte-Zerleger mußt Du Dir natürlich selbst schreiben.)
Nun kannst Du in der zweiten Tabelle Worte suchen und zu jedem Treffer aus der ersten Tabelle das zugehörige Dokument finden.
Und solange Du Dich auf Worte beschränkst (im Gegensatz zu Phrasen, also beliebigen Text-Teilen inklusive Wort-Trennern), kommst Du mit diesem Modell prima aus - und für eine exakte Übereinstimmung zwischen Deinem Suchbegriff und einem einzelnen Wort kannst Du wiederum den Index über die Worte-Spalte der zweiten Tabelle nutzen (das macht die Datenbank von alleine, Du mußt den Indexe einfach nur anlegen, gefüllt wird er beim Einfügen Deiner Worte in die Tabelle).
2. Du überläßt es mySQL, sich mit dem Problem zu befassen.
mySQL hat seit Version 3.23.23 eine eingebaute Volltextsuche! Lies Dir mal das Kapitel über FULLTEXT-Indexe durch ...
Viele Grüße
Michael
(der am Freitag versuchsweise die Konstante für die Wort-Mindestlänge für FULLTEXT im mySQL-Source von 4 auf 3 Zeichen geändert hat, was zu einer Zunahme von gerade mal 15% der Indexbaumgröße geführt hat ... Daniela, willst Du das wirklich immer noch selbst implementieren für die neue Self-Suche? Eine Stopwortliste gibt es auch schon im Quelltext ... bisher eine englische, aber diese editieren und mySQL neu übersetzen ist kein Problem.)
Hi Michael!
Erstmal vielen Dank für die Erklährung und auch für die weiter unten, war noch nicht dazu gekommen zu antworten.
Verstehe ich nicht. Ich benutze doch keine Indizes?!
Solltest Du aber.
Also einfach in PHPmyAdmin "index" anklicken, bei den Felder die nacher durchsicht werden sollen? Aber bei der Suche(select...) muß ich da doch nichts ändern, oder durchsucht man mi indices anders, außer das man das Wort am Anfang stehen haben sollte?
Stell Dir vor, Du hast eine Million Datensätze. Jede Deiner beiden Suchanfragen muß dann also eine Million Vergleiche durchführen. Das ist nicht wirklich beliebig schnell.
Von einer Mio bin ich aber weit entfernt, sind bis jetzt vielleich 1000!
Und jetzt denke Dir zum Vergleich einen Index hinzu, also einen binären Baum, welcher sämtliche Werte des zu vergleichenden Feldes enthält. Wie findest Du (genauer gesagt: die SQL engine) darin einen Wert? Durch fortgesetzte Halbierung der Datenmenge, d. h. durch Absteigen in diesem Baum. Wieviele Schritte sind dafür notwendig? So viele, wie der Baum 'hoch' ist - im idealen Fall also Logarithmus zur Basis 2 von einer Million, also etwa 20. Und daß 20 Vergleiche schneller sind als eine Million, darüber sind wir uns doch einig, ja?
Denke das kann ich nur schwer widerlegen:-)
Genau diese Baum-Halbierungs-Methode funkioniert aber nur dann, wenn Du Suchbegriff auf den Anfang der gespeicherten Worte anwenden kannst - deshalb darf die wildcard nicht am Anfang vorliegen, sonst geht wiederum nur der lineare Vergleich aller Datensätze.
Warum 'Halbierung', hörte sich gerade wie 1.000.000/x = 20 ?
Aber wenn das Suchwort irgendwo im Text vorkommen kann, muß ich das
wohl so machen, oder geht das auch anders?
Das kommt darauf an, wie Du Deinen Text gespeichert hast.
Wenn Dein gesamter Text ein Feld der Tabelle ist _und_ Du nichts anderes verwenden willst als Dein obiges Statement, dann droht Dir die "eine-Million-Vergleiche-Strafe".
Mit wieviel Jahren kann man denn bei 1 Mio so rechnen(wenn möglich auf Sekunden umgerechnet:)?
- Du speicherst in einer weiteren Tabelle sämtliche Worte Deines Textes, je ein Wort pro Zeile, und als zweite Spalte den Primärschlüssel aus der ersten Tabelle. (Den Worte-Zerleger mußt Du Dir natürlich selbst schreiben.)
Nun kannst Du in der zweiten Tabelle Worte suchen und zu jedem Treffer aus der ersten Tabelle das zugehörige Dokument finden.
Und solange Du Dich auf Worte beschränkst (im Gegensatz zu Phrasen, also beliebigen Text-Teilen inklusive Wort-Trennern), kommst Du mit diesem Modell prima aus - und für eine exakte Übereinstimmung zwischen Deinem Suchbegriff und einem einzelnen Wort kannst Du wiederum den Index über die Worte-Spalte der zweiten Tabelle nutzen (das macht die Datenbank von alleine, Du mußt den Indexe einfach nur anlegen, gefüllt wird er beim Einfügen Deiner Worte in die Tabelle).
Nun ja, denke das würde ich hinbekommen, aber was wird das dann für eine krasse Tabelle, wenn die erste 1 Mio Datensätze hat, wird das hier ja langsam ziemlich viel! Außerdem werden die Datensätze je oft geändert und neue hinzugefügt, das müßte ich dann jedesmal mit ändern!
- Du überläßt es mySQL, sich mit dem Problem zu befassen.
mySQL hat seit Version 3.23.23 eine eingebaute Volltextsuche! Lies Dir mal das Kapitel über FULLTEXT-Indexe durch ...
Das wäre natürlich das beste! Wie ist das mit der Performance?
Grüße
Andreas
Hört sich wirklich gut an mit FULLTEXT, hab das direkt mal probiert:
In phpmyadmin (:-) einfach den FULLTEXT-Index angelegt, bekommt automatisch den Namen der Spalte, in meinem Versuch 'Bilddatei'.
Jetzt starte ich folgende Abfrage:
SELECT * FROM s_objekte WHERE MATCH Bilddatei AGAINST 'Master'
Da erhalte ich die Fehlermeldung ein Error in der Nähe von 'Master'?!
Folgende Suche funktioniert dagegen:
SELECT * FROM s_objekte WHERE Bilddatei like '%Master%'
Und den FULLTEXT Index mit Namen Bilddatei gibt es auch. Was mache ich da falsch?
Grüße
Andreas
Hi Andreas,
SELECT * FROM s_objekte WHERE MATCH Bilddatei AGAINST 'Master'
Da erhalte ich die Fehlermeldung ein Error in der Nähe von 'Master'?!
spendier doch mal ein Paar runder Klammern um den Suchbegriff ...
Viele Grüße
Michael
Nin ja, ich dachte das hätte ich probiert, jetzt kommt schonmal keine Fehlermeldung mehr! Dafür hat er aber 0 Reultate, hab das jetz wie folgt:
SELECT * FROM s_objekte WHERE MATCH Bilddatei AGAINST ('Master');
Wie gesagt bei der gleichen Suche mit WHERE Bilddatei LIKE '%Master%' bekomme ich 200 Resultate.
Master kommt nicht als erstes vor, sondern in jedem Dateipfad(Bilddatei), nach "//".
Was kann das jetzt sein? Der Index hat wie gesagt auch den Namen 'Bilddatei'!
Noch ne Idee?
Grüße
Andreas
Hi!
Danke nochmal für den Tipp, funktioniert wirklich super! Problem war, dass 'Master' in jedem Datensatz vorkam, und da gibt es ja diese 50% Regel....!
Wirklich cool, daher auch das mit der Relevanz bei so vielen Suchen :-)
So lernt man immer dazu!
Das einzige was mich noch stört ist das man nicht nach Wortteilen suchen kann, z.B. nach 'data' und man findet auch 'database'. Geht wohl erst in 4.0 mit *, oder?
Grüße
Andreas
Hi,
SELECT * FROM s_objekte WHERE MATCH Bilddatei AGAINST ('Master');
Das ist eine Suche nach dem _Wort_ "Master".
Wie gesagt bei der gleichen Suche mit WHERE Bilddatei LIKE '%Master%' bekomme ich 200 Resultate.
Das ist eine Suche nach der _Zeichenkette_ "Master".
Master kommt nicht als erstes vor, sondern in jedem Dateipfad(Bilddatei), nach "//".
Eben. Und damit ist es wahrscheinlich kein 'Wort'.
Aber wie gesagt - es gibt Quelltexte zu mySQL, und dort ist auch definiert, wie der FULLTEXT-Indexer den Inhalt des Textes in Worte zerschlägt. Das kann man ggf. auch ändern.
Das, was Du eigentlich willst, nämlich auch innerhalb von Worten matchen zu können, würde - wie bereits erwähnt - mal wieder die Verwendung des Indexes abschalten. Aber dennoch hat mySQL angekündigt, so etwas in Version 4.0 können zu wollen.
Viele Grüße
Michael