online 1
gast (50)

/ Forum / Skripte(PHP,ASP,Perl...)

Skripte(PHP,ASP,Perl...)Skripte(PHP,ASP,Perl...)

Fragevon Uesch vom 08.03.2021, 15:34 Options

Lösung

Datensätze nach Ähnlichkeit sortieren

Hallo allerseits,

wie kann ich Daten aus einer MySQL-Tabelle so sortieren, dass das oberste Wort am ähnlichsten zum eingegebenen Wort ist und darunter die Ähnlichkeit immer mehr abnimmt? Momentan sortiert er nach dem Alphabet, aber ich möchte eine Sortierung nach der Ähnlichkeit mit dem eingegebenen Wort. Also zum Beispiel:

Eingegebenes Wort: Haus
1. Ergebnis = Haus
2. Ergebnis = Rathaus
3. Ergebnis = durchaus

Ich habe versucht, es mit levenshtein zu realisieren:
SELECT * FROM ".$table 
." WHERE irgendwas like '%".$search."%' or irgendwas2 like '%".$search."%' order by ".levenshtein(???)." 


Muss ich zwei Abfragen machen? Die erste fragt nach der Ähnlichkeit ab und die zweite selected dann die Datensätze und sortiert sie nach dem levenshtein-Wert? Oder kann ich das ganze mit was anderem als levenshtein machen?

Vielen Dank


Antwort schreiben

Antwort 1 von son_quatsch vom 09.03.2021, 08:20 Options

Levenshtein ist in MySQL nicht implementiert. Du kannst SOUNDEX() verwenden, was natürlich nur mit einer Spalte klappt:
$sSql= "SELECT * 
  FROM $sTable
  WHERE spalte LIKE '%$sSearch%'
  ORDER BY soundex(spalte)";


Eine Nutzung von levenshtein() unter PHP erfordert etwas mehr Aufwand, siehe auch sämtliche Kommentare zu http://de.php.net/levenshtein

Antwort 2 von Uesch vom 09.03.2021, 16:40 Options

Mit SOUNDEX ist merkwürdigerweise gar kein Unterschied zu der von mir oben angegebenen MySQL-Suche zu erkennen.
Aber möglicherweise könnte eine Kombination von levenshtein und SOUNDEX zum Ziel führen.

Bei deinem Link gibt es jemanden vom 07-Mar-2005 05:01, der folgendermaßen seine Suchergebnisse sortiert:

<?php
// PHP CODE INCLUDING DB LOOKUPS HERE
    usort($searchresults, "finallevenshteinsortfunction");

function finallevenshteinsortfunction($a, $b)
{
    if(($a['levenshtein'] > $b['levenshtein']) || ( $a['levenshtein'] == $b['levenshtein'] && strnatcasecmp( $a['Last_Name'], $b['Last_Name']) >= 1) ){ return $a['levenshtein'];} // Ok... The levenstein is greater OR with the same levenshtein, the last name is alphanumerically first
    elseif($a['levenshtein'] == $b['levenshtein']){ return '0';} // The levenstein matches 
    elseif($a['levenshtein'] < $b['levenshtein']){ return -$a['levenshtein'];}
    else{die("<!-- a horrable death -->");}
}
?>


Jedoch frage ich mich nun wie man nun mithilfe der Funktion die ähnlichsten Datensätze auswählt.
Ich dachte vielleicht mit:

while $search_array = mysql_fetch_array($searchresults) {
$reihenfolge = finallevenshteinsortfunction($sucheingabe, $search_array["irgendwas"]);
SELECT ... ORDER BY ".$reihenfolge."...

Aber wie mir gerade beim Schreiben auffällt, ist das ziemlich falsch. Man müsste die Abfrage mit ORDER BY außerhalb der WHILE-Schleife durchführen.


Vielleicht kann mir jemand weiterhelfen

Antwort 3 von son_quatsch vom 10.03.2021, 08:33 Options

Wie gesagt, es ist nicht das einfachste. Das Beispiel hab ich ebenfalls gesehen, allerdings nicht nachvollziehen können, da levenshtein() immer einen Wert für zwei Strings ermittelt - somit kann man einem alleine keinen Wert zuordnen und dann einfach nach diesen sortieren. Die Funktion im Beispiel ist übrigens speziell eine für usort().

Und nochmal: zielführend ist es, zunächst alle Ergebnisse der Abfrage in einem Feld zu speichern. Danach erfolgt die eigentliche Sortierarbeit innerhalb von PHP (und nicht mehr unter MySQL, wie du es dauernd mit ORDER BY machen willst).

Vielleicht ist das die Lösung:
http://codingforums.com/showthread.php?p=612198
Der Aufruf wäre dann in etwa:
$aSortiert= getSimilar( $sSuchwort, $aAbfrageErgebnisse );

Antwort 4 von Uesch vom 10.03.2021, 17:42 Options

Danke für den Link. Was mache ich aber jetzt? Ich habe jetzt den Array, der die Zahlen enthält, welche die jeweilige Ähnlichkeit angeben. Wenn ich das $aSortiert mit foreach ausgebe erhalte ich ungefähr 100342111. Erstens frage ich mich, warum das Script nicht alle Datensätze, die selected wurden, berücksichtigt und zweitens was man jetzt mit dem $aSortiert macht.

Muss man jetzt noch eine weitere Abfrage durchführen, in der man nach $aSortiert ordnet?

Vielen Dank.

Antwort 5 von son_quatsch vom 11.03.2021, 08:38 Options

Zunächst ein vollständiges, voll funktionales Beispiel:
<?php

	function getSimilar( $sSuch, $aErg ) { 
		$ar= array();
		foreach( $aErg as $v1 ) {
			$iSim= similar_text( $sSuch, $v1 );
			$iLev= levenshtein( $sSuch, $v1 );
			if ( $iSim!= 0 ) {
				$iLevError= intval( $iLev/ strlen( $v1 )* 100 );
				$iPerSim= intval( $iSim/ strlen( $v1 ) )* 100;
				$iTotal= $iPerSim- $iLevError;
			} else {
				$iTotal= 0; 
			} 
			$ar[$v1]= $iTotal;
		} 
		arsort( $ar );
		return $ar;
	}

	$aAbfrageErgebnisse= array( 'jany', 'hotel', 'jane', 'jaine', 'gun', 'jaimy', 'june', 'rain', 'house' );
	$sSuchwort= 'jane';

	$aSortiert= getSimilar( $sSuchwort, $aAbfrageErgebnisse );
	print_r( $aSortiert );

?>

Zitat:
Wenn ich das $aSortiert mit foreach ausgebe erhalte ich ungefähr 100342111.
Dann sag einfach, wie dein $aSortiert überhaupt aussah (Ergebnis von print_r($aAbfrageErgebnisse) hier posten)

Zitat:
Erstens frage ich mich, warum das Script nicht alle Datensätze, die selected wurden, berücksichtigt
Es berücksichtigt alle - vermutlich baust du dein Array falsch auf?

Zitat:
und zweitens was man jetzt mit dem $aSortiert macht. Muss man jetzt noch eine weitere Abfrage durchführen, in der man nach $aSortiert ordnet?
Wie oft denn noch? Du hast doch schon längst alle Ergebnisse der Abfrage, wieso willst du sie immer wieder nochmal abfragen?? $aSortiert enthält die Wörter geordnet nach ihrer Relevanz. Und falls du mehr als die Wörter ausgeben willst, assoziierst du diese einfach mit deinen Abfrageergebnissen:
foreach( $aSortiert as $k1=> $v1) {
  foreach( $aAbfrageErgebnisse as $k2=> $v2 ) {
    if ( $v2['spalte']== $k1 ) {
      print_r( $v2 );
      break;
    }
  }
}

Antwort 6 von Uesch vom 11.03.2021, 14:45 Options

Vielen Dank.
Bei print_r($aSortiert) wird folgendes ausgegeben:
Array ( [jane] => 100 [jaine] => -20 [june] => -25 [jany] => -25 [jaimy] => -60 [rain] => -75 [house] => -80 [hotel] => -80 [gun] => -100 )


Das
foreach( $aSortiert as $k1=> $v1
bewirkt in diesem Beispiel nichts.

Ich lese nun also die Daten aus der Datenbank aus:

$query = "SELECT * FROM words WHERE deutsch like '%".$search."%'

$resultID = @mysql_query($query);


Die Variable $search enthält das Wort "Haus". Ich mache nun alle Ergebnisse in ein Array:
$results = mysql_fetch_array($resultID);


Anschließend verwende ich die Funktion:
$aSortiert= getSimilar( $search, $results );


Mit
foreach( $aSortiert as $k1=> $v1) {
  foreach( $results as $k2=> $v2 ) {
    if ( $v2['deutsch']== $k1 ) {
      print_r( $v2 );
      break;
    }
  }
}
erhalte ich als Ausgabe: 4Ärmliches Haus7

Antwort 7 von son_quatsch vom 11.03.2021, 16:07 Options

Ich weiß doch selber, was ich rausbekomme - du sollst jetzt konkret die Ausgabe von deinem
print_r( $results );
vor dem Aufruf an getSimilar..() posten.

Und du weißt hoffentlich, dass mysql_fetch_array() lediglich einen Datensatz zurückgibt..?

Antwort 8 von Uesch vom 12.03.2021, 11:14 Options

Würdest du denn alle Datensätze mit
 $num = mysql_num_rows($result);
 for ($i=0; $i<$num; $i++)
   {
      $de = mysql_result($res, $i, "deutsch");
usw.
}
auswählen?

Antwort 9 von Uesch vom 12.03.2021, 11:15 Options

Statt
$de = mysql_result($res, $i, "deutsch");
meine ich
$de = mysql_result($result, $i, "deutsch");

Antwort 10 von Uesch vom 12.03.2021, 11:19 Options

Ach und mit print_r erhalte ich:
Array ( [0] => 7 [id] => 7 [1] => Ärmliches Haus [deutsch] => Ärmliches Haus [2] => &#966;&#964;&#969;&#967;&#953;&#954;&#972; [griechisch] => &#966;&#964;&#969;&#967;&#953;&#954;&#972; [3] => [zusatz] => [4] => Unbekannt [artikel] => Unbekannt [5] => Substantiv [wortart] => Substantiv [6] => 4 [genitiv] => 4 [7] => [gen_ersatz] => [8] => 0 [plural] => 0 )


Also alle Daten der Spalten eines einzelnen Datensatzes.
Wichtig ist, dass diese Daten trotz der Sortierung erhalten bleiben.

Antwort 11 von son_quatsch vom 13.03.2021, 08:57 Options

Zitat:
Würdest du denn alle Datensätze mit ... auswählen?
Nein, ich würde es so machen:
$res1= mysql_query( $sSql ) or die( 'Fehler: '. $sSql. ' - '. mysql_error() );

$aAbfrageErgebnisse= array();
while ($r1= mysql_fetch_array( $res1 ) ) {
  $aAbfrageErgebnisse[]= $r1;
}
mysql_free_result( $res1 );
Damit sind wir bei den Grundlagen angekommen. Bitte so mit einflechten, dann die Ergebnisse (print_r()) nochmal hier posten - und möglichst nachdem du im Browser Rechtsklick -> Quelltext anzeigen gewählt hast, dann ist es besser lesbar. Und ja - deine Reihenfolge bleibt erhalten - schließlich und endlich können wir uns ja beliebig viele Kopien des Feldes erzeugen...

Antwort 12 von Uesch vom 14.03.2021, 18:52 Options

Es wird angezeigt:
Array
(
    [0] => Array
        (
            [0] => 5868
            [id] => 5868
            [1] => Haus
            [deutsch] => Haus
            [2] => &#954;&#945;&#964;&#959;&#953;&#954;&#943;&#945;
            [griechisch] => &#954;&#945;&#964;&#959;&#953;&#954;&#943;&#945;
            [3] => 
            [zusatz] => 
            [4] => Unbekannt
            [artikel] => Unbekannt
            [5] => Unbekannt
            [wortart] => Unbekannt
            [6] => 3
            [genitiv] => 3
            [7] => 
            [gen_ersatz] => 
            [8] => 0
            [plural] => 0
        ))


Das ganze für jeden Datensatz, der die Übersetzung "Haus" enthält.

Antwort 13 von son_quatsch vom 16.03.2021, 08:16 OptionsLösung

Lösung
Und nach was gucken wir? Ich hab mich jetzt an Schlüssel [1] orientiert:

	function getSimilar( $sSuch, $aErg ) { 
		$ar= array();
		foreach( $aErg as $k1=> $v1 ) {	// jeden Ergebnisdatensatz durchgehen
			$iSim= similar_text( $sSuch, $v1[1] ); // wir vergleichen nur Schlüssel [1] von jedem Datensatz
			$iLev= levenshtein( $sSuch, $v1[1] );
			if ( $iSim!= 0 ) {
				$iLevError= intval( $iLev/ strlen( $v1[1] )* 100 );
				$iPerSim= intval( $iSim/ strlen( $v1[1] ) )* 100;
				$iTotal= $iPerSim- $iLevError;
			} else {
				$iTotal= 0; 
			} 
			$ar[$k1]= $iTotal; // Schlüssel des Datensatzfeldes merken
		} 
		arsort( $ar ); // nach errechnetem Wert sortieren
		return $ar;
	}


	// Abfrage definieren und ausführen
	$sSuchwort= 'jane';
	$sSql= "SELECT * 
		FROM tabelle
		WHERE spalte LIKE '%$sSuchwort%'";
  $res1= mysql_query( $sSql ) or die( 'Fehler: '. $sSql. ' - '. mysql_error() );


	// Datensätze sammeln
	$aAbfrageErgebnisse= array();
	while ( $r1= mysql_fetch_array( $res1 ) ) {
		$aAbfrageErgebnisse[]= $r1;
	}
	mysql_free_result( $res1 );


	// Ähnlichkeitssortierung ermitteln
	$aSortiert= getSimilar( $sSuchwort, $aAbfrageErgebnisse );


	// Datensätze entsprechend ihrer Ähnlichkeit ausgeben
	foreach ( $aSortiert as $k1=> $v1 ) {
		print_r( $aAbfrageErgebnisse[$k1] );
	}

Antwort 14 von Uesch vom 16.03.2021, 22:38 Options

Vielen Dank!
Das hat mir sehr geholfen.

Antwort 15 von Uesch vom 17.03.2021, 18:31 Options

Doch noch eine kurze Frage. Um das ganze mit Seitenzahlen zu bewerkstelligen, muss ich mit LIMIT arbeiten. Wenn ich allerdings folgendes mache:
WHERE spalte LIKE '%$sSuchwort%' LIMIT 0,10
erhalte ich zwar 10 Datensätze, allerdings fängt das Ganze nicht bei dem Wort an, bei dem es ohne LIMIT anfängt. Also zum Beispiel bei Eingabe von "Hand" erhalte ich nicht als erstes "Hand", sondern "Handy".

Könnt Ihr euch erklären, warum?

Viele Grüße von Üsch

Antwort 16 von Uesch vom 19.03.2021, 11:15 Options

Ach, das liegt natürlich daran, dass nur noch innerhalb der 10 Datensätze nach Ähnlichkeiten gesucht wird.

Wie würdet Ihr denn dann die Beiträge limitieren? Also dass zum Beispiel auf Seite 1 die 10 ähnlichsten Datensätze angezeigt werden, auf Seite 2 dann die Datensätze von 10 bis 20 usw.

Ich hoffe, das liest überhaupt noch jemand, da ich bereits angegeben habe, dass "Diese Antwort das Problem gelöst hat!".

Schöne Grüße von Üsch!

Antwort 17 von son_quatsch vom 19.03.2021, 11:42 Options

Da gibt es mehrere Herangehensweisen. Die simpelste davon ist, dass du jedesmal die komplette Abfrage durchführst und dann bei der Ausgabe entsprechend limitierst.

	// Datensätze entsprechend ihrer Ähnlichkeit ausgeben
	$i1= 0;
	foreach ( $aSortiert as $k1=> $v1 ) {
		if ( $i1>= $iStart&& $i1< $iStart+ $iAnzahl ) print_r( $aAbfrageErgebnisse[$k1] );
		$i1++;
	}
...wobei $iStart und $iAnzahl in dieser Reihenfolge dieselbe Funktion übernehmen wie die Angaben bei LIMIT unter MySQL.

Antwort 18 von Uesch vom 19.03.2021, 12:09 Options

Danke. Wenn $iStart = 0 und $iAnzahl = 10 ist, dann funktioniert es. Es werden 10 Datensätze angezeigt. Wenn jetzt aber $iStart = 10 und $iAnzahl = 20 ist, wird nichts angezeigt.

Ist in diesem Fall nicht $i1 gleich Null? Und deshalb kann
$i1>= $iStart
nicht true zurückliefern?

Es wird allerdings auch kein Ergebnis zurückgeliefert, wenn ich $i1 gleich 10 setze.

Antwort 19 von son_quatsch vom 19.03.2021, 12:37 Options

Mit $i1 zählen wir lediglich, wie oft wir die Schleife durchlaufen - das scheint wohl alles andere als offensichtlich. Folglich wollen wir bei einem $iStart größer als 0 ein paar der ersten Ergebnisse "wegschmeißen" und dann erst welche anzeigen.

Ich wette du hast einfach noch das LIMIT in deiner MySQL-Abfrage drin. Nimm das raus.

Antwort 20 von Uesch vom 19.03.2021, 12:54 Options

Am Anfang definierst du doch
$i1= 0;

Und das
$i1++;
steht innerhalb der if-Klausel. Das heißt, dass das $i1 (=0)überhaupt nicht verändert wird, weil ja $i1 auf jeden Fall kleiner ist als $iStart.
Wenn ich jetzt das
$i1++;
außerhalb der if-Klausel innerhalb des foreach einfüge, dann werden mir die korrekten Datensätze angezeigt, nur dass es mit jeder Seite jeweils 10 mehr werden (weil eben keine Datensätze "weggeschmissen" werden.

PS: Limit war nicht mehr in der Abfrage.

Ähnliche Themen

Sortieren von LinkedList
UZUZ  19.02.2008 - 59 Hits - 3 Antworten

Musik in mp3 sortieren.
Cooper3210  30.09.2008 - 2009 Hits -

Datensätze mit bestimmter Endung
Uesch  18.11.2008 - 43 Hits - 2 Antworten

Zeige Datensätze 0 - 7 (8 insgesamt)
Uesch  07.02.2009 - 133 Hits - 2 Antworten

Hinweis

Diese Frage ist schon etwas älter, Sie können daher nicht mehr auf sie antworten. Sollte Ihre Frage noch nicht gelöst sein, stellen Sie einfach eine neue Frage im Forum..

Neue Einträge

Version: supportware 1.9.150 / 10.06.2022, Startzeit:Thu Jan 8 21:07:44 2026