[MySQL] Ordnungskriterien bzw. Where-Kriterien spezifizieren

Hatachy

Erfahrenes Mitglied
Hi

gibt es eine Möglichkeit die ORDER BY oder WHERE Klausel so zu spezifizieren, dass er das Resulat, auf welches das Kriterium am "schnellsten" passt ausgibt ?

d.h.:
Habe eine Tabelle mit verschiedenen Sprachversionen von Texten,:
- z.B. 3 Versionen: de, en, fr
#Dann kommt deutscher Nutzer: es soll deutsch ausgewählt werden
#Dann kommt dänischer Nutzer: es sollte dänisch ausgewählt werden -> nicht vorhanden -> weiter zur nächsten Kriterium -> es soll englisch ausgewählt werden.

Sodass ich eine "Sprachreihenfolge" aufstellen kann, wonach das bestmöglichste Resultat ausgewählt wird. Mit normalen Order befehlen komm ich ja leider net weit, da die Reihenfolge, selbst wenn man statt den Sprachabkürzungen zahlen verwenden würde, bei jedem Nutzer anders ist.

Ich hoffe es versteht jemand mein Problem, und kennt vielleicht sogar eine Antwort ;)
 
Hallo Hatachy,

ist denn englisch immer die "Standard-Sprache", wann immer ein Text in der eigentlich gewünschten Sprache nicht existiert?

Dann könnte man das ja vielleicht so machen:
Code:
WHERE sprache = '$wunschsprache' OR sprache = 'en'
ORDER BY IF(sprache = '$wunschsprache', 0, 999)
LIMIT 1
So würden also entweder zwei Sätze gefunden (Wunschsprache und englisch) oder nur einer (nur englisch). Die IF-Formel bei ORDER BY liefert 0 beim Satz in der Wunschsprache und 999 beim anderen Satz, womit der Satz mit der Wunschsprache - so es ihn gibt - immer der erste Satz wäre. Durch LIMIT 1 kommt dann auch nur dieser erste Satz zurück.

Das Prinzip könnte man auch ausbauen, falls es eine Art Sprach-Hierarchie gibt, bspw. "Wenn nicht Wunschsprache dann englisch, wenn nicht englisch dann eben deutsch und wenn auch das nicht, dann von mir aus suaheli".
Code:
WHERE sprache IN ('$wunschsprache','en','de','sh')
ORDER BY FIND_IN_SET(sprache, '$wunschsprache,en,de,sh')
LIMIT 1

Hilft Dir das weiter?

Grüße,
Martin

Edit:
Um das mit der Sprach-Hierarchie weiterzuspinnen: Wenn man als letzte Möglichkeit, falls es keine der 4 Sprachen gibt, die erstbeste andere Sprache ausgeben will, dann kann man sich auch das WHERE sparen und das FIND_IN_SET "umdrehen":
Code:
ORDER BY 10 - FIND_IN_SET(sprache, 'sh,de,en,$wunschsprache')
LIMIT 1
So liefert die FIND_IN_SET-Funktion 4 für die Wunschsprache, 3 für englisch, 2 für deutsch, 1 für suaheli und 0 für jede andere Sprache. Da der Wert von 10 abgezogen wird, ergibt sich eine Sortier-Reihenfolge 6, 7, 8, 9 für die vier Sprachen der Hierarchie und 10 für alle anderen. Sollte ein Text jetzt also in keiner der 4 Sprachen vorlegen, dann kommt er zumindest irgend einer anderen Sprache (falls es ihn überhaupt gibt). So, genug gesponnen.;)
Schönes Wochenende.

Edit II:
Jetzt fällt mir doch noch eine Spinnerei ein: Richtig ausgeklügelt wäre das natürlich, wenn für verschiedene Wunschsprachen unterschiedliche Sprach-Hierarchien vorliegen würden. Die Dänen bevorzugen ja wohl als Alternativ-Fremdsprache eher englisch wobei ein Holländer vielleicht mit deutsch mehr anfangen kann, wenn er's schon nicht in holländisch haben kann.
Als Lösungsansatz kommt mir da ein CASE SELECT-Konstrukt für das ORDER BY in den Sinn. Aber das führe ich jetzt nicht mehr weiter aus...
 
Zuletzt bearbeitet:
vielen Dank erstmal.

leider - wie sollte es auch anders sein - bekomm ichs net hin:

Inhalt meiner News-Einträge Tabelle derzeit:
1 1 News 1 (in deutsch) de
2 2 News 2 (in franze) fr
3 1 News 1 (in english) en
4 1 News 1 (in franze) fr
5 2 News 2 (in english) en

(Unordnung is aus Testzwecken ;))

Mein Query:

Code:
SELECT chq_News.*, chq_News_ct.*, chq_News_cn.*, chq_users.users_nick, chq_users.users_id 
FROM chq_News 
    LEFT JOIN chq_News_ct ON(chq_News_ct.News_ct_node_id=chq_News.News_cat)
    LEFT JOIN chq_News_cn ON(chq_News_cn.News_cn_nid=chq_News_ct.News_ct_node_id)
    LEFT JOIN chq_users  ON(chq_users.users_id=chq_News_cn.News_cn_author) 
WHERE News_cn_lang IN ('de', 'en', 'fr') 
ORDER BY FIND_IN_SET(News_cn_lang, 'de, en,  fr') LIMIT 1

[In der News-Tabelle stehen die News-Ids; In der News_cn-Tabelle die verschiedenen Sprachversionen]

Resultat bei 3 EInträgen (deutsch, englisch, französisch) ist "News 1 (in english)"

Habe keine Ahnung, wie das Ergebnis zustande kommt :S

und dazu ne weitere Frage: das hier ist jetzt am Beispiel der News. Ist aber ja eher für Artikel geeignet in dieser Form (mit Limit 1). Wie kann ich auf diese Weise mehrere Einträge auslesen. Als ich die gleiche Abfrage mit Limit 0,2 ausführte bekam ich:
"News 1 (in english)", "News 1 (in franze)"
 
Auf den ersten Blick würde ich sagen: Der Fehler liegt in der FIND_IN_SET-Funktion. Die Funktion liefert die Position eines gesuchten Eintrags in einer komma-getrennten Liste (das "Set"). Das Set wird als ein String angegeben, also innerhalb von einem Satz Hochkommas (im Gegensatz zu den Elementen beim IN-Operator, wo jedes Element selbst in Hochkommas steht.
Du schreibst FIND_IN_SET(News_cn_lang, 'de, en, fr'), wobei Du vor en und fr jeweils ein Leerzeichen hast. Deshalb wird beispielsweise "en" nicht im Set gefunden, denn im Set steht ja nur " en". Für den en-Satz kommt also 0 raus, genauso für den fr-Satz. Nur "de" wird im Set gefunden, es kommt 1 raus. 0 ist kleiner als 1, ergo: der deutsche Datensatz steht am Ende der Sortierung, alle anderen in willkürlicher Reigenfolge davor.

Das war jetzt etwas viel Erklärung, hätte auch einfach schreiben können: "Lass die Leerzeichen weg, dann geht's." ;)

Zu Deiner 2. Frage: Ich verstehe das so, dass Du eine Liste von News haben willst, jede jeweils in der Wunschsprache oder einer Alternativsprache gemäß Sprach-Hierarchie. Das ist etwas verzwickter. Werd' mal kurz drüber nachgrübeln...
 
jo danke, so gehts schonmal ;)
deinen Hinweis mit dem 'minus' werd ich auch noch machen, hatte das gelesen, keine angst, aber wollt erstmal das "simple" zum laufen bekommen. Mit 'minus' ist ja noch um einiges besser, falls keine "richtige" Sprache vorliegt.

ich probiere derzeit auch etwas rum, aber so überragend sind meine SQL Kenntnisse nun auch net :rolleyes:

Wie gesagt, schon mal vielen Dank, und vielleicht fällt dir ja noch ne Lösung ein, wäre super ;)

PS: richtig verstanden
 
Prima, freut mich. Wenn Du die "Minus-Technik" anwendest, vergiss nicht, dass das Set umgedreht werden muss, also die Top-Sprache ans Ende.

Für Deine 2. Frage habe ich ein bischen rumprobiert. Wenn man stur auf einer reinen SQL-Lösung besteht, dann wird alles was mir einfällt super kompliziert und hat immer mit SubSelects bzw. temporären Tabellen zu tun. Und ich bezweifle, ob das bei dieser Anwendung wirklich sinnvoll wäre.

Stattdessen würde ich das wohl bei der Ausgabe lösen. Schätze mal, Du machst die Ausgabe mit PHP. Dann würde ich die SQL-Abfrage zuerst nach der News_Id und danach erst nach 10 - FIND_IN_SET(News_cn_lang, 'fr,en,de') sortieren. So erhältst Du eine Liste aller News, wobei alle Sätze mit gleicher News_id nacheinender kommen. Innerhalb der Sätze mit gleicher News_id steht der Satz mit der jeweils passendsten Sprache oben.

Bei der Ausgabe der News-Liste per PHP gibst Du dann nur die Zeilen aus, bei denen sich die News_id gegenüber der vorherigen Zeile geändert hat.

So wird das Result-Set zwar unnötig groß, weil Sätze zurückgegeben werden, die man gar nicht braucht, aber dafür braucht man nur eine Abfrage anstatt mindestens 2 (temp. Tabelle oder SubSelect). Für mich wäre das die favorisierte Lösung.

Hier noch meine Beispiel-Tabelle:
Code:
+----+---------+---------------+---------+
| id | news_id | news          | sprache |
+----+---------+---------------+---------+
|  1 |       1 | Hallo Welt    | de      |
|  2 |       1 | Hello world   | en      |
|  3 |       1 | Bonjour monde | fr      |
|  4 |       2 | How are you   | en      |
|  5 |       2 | Wie geht's    | de      |
|  6 |       1 | Hola mundo    | es      |
|  7 |       1 | Ciao mondo    | it      |
|  8 |       2 | Como sao voce | pt      |
+----+---------+---------------+---------+
Angenommen, meine Wunschsprache wäre nun französisch (das ist nur ein Beispiel, nicht das wirkliche Leben!), alternativ ginge noch englisch, deutsch oder italienisch (in dieser Reihenfolge). Dann sähe mein Select so aus:
Code:
SELECT
    news_id,
    news
FROM
    news 
ORDER BY
    news_id,
    10 - FIND_IN_SET(sprache, 'it,de,en,fr')
Das Ergebnis wäre dieses:
Code:
+---------+---------------+
| news_id | news          |
+---------+---------------+
|       1 | Bonjour monde |
|       1 | Hello world   |
|       1 | Hallo Welt    |
|       1 | Ciao mondo    |
|       1 | Hola mundo    |
|       2 | How are you   |
|       2 | Wie geht's    |
|       2 | Como sao voce |
+---------+---------------+
Für die Ausgabe dann ein PHP-Skript ähnlich diesem:
PHP:
	$rst = mysql_query($sql, $con);
	$lastNewsId = 0;
	while ($row = mysql_fetch_assoc($rst)) {
		if ($row["news_id"] == $lastNewsId) {
			echo "<p>";
			echo $row["news]";
			echo "</p>";
			$lastNewsId = $row["news_id"];
		}
	}
	mysql_free_result($rst);
und das Ergebnis müsste in etwa so aussehen:

Bonjour monde
How are you


Wenn Du schon MySQL 4.1 einsetzt (ich leider noch nicht), dann kannst Du SubSelects einsetzten, das müsste etwa so gehen:
Code:
SELECT
	*
FROM
	(
		SELECT
			news_id,
			news
		FROM
			news 
		ORDER BY
			news_id,
			10 - FIND_IN_SET(sprache, 'it,de,en,fr')
	)
GROUP BY
	news_id
Das kann ich leider nicht selbst ausprobieren. Ohne SubSelect geht's mit temporärer Tabelle:
Code:
DROP TABLE IF EXISTS tmp_news
;
CREATE TEMPORARY TABLE tmp_news TYPE=HEAP
	SELECT
		news_id,
		news
	FROM
		news 
	ORDER BY
		news_id,
		10 - FIND_IN_SET(sprache, 'it,de,en,fr')
;
SELECT
	*
FROM
	tmp_news
GROUP BY
	news_id
;
DROP TABLE IF EXISTS tmp_news
Wobei natürlich vor dem letzten DROP TABLE die Ausgabe des Ergebnisses erfolgen sollte.

Grüße,
Martin
 
4.1 hab ich auch noch net, sorry

und bei ner PHP-Lösung (ja, ich verwende PHP) taucht ein weiteres Problem dann auf, nämlich, dass ich die komplette tabelle auslesen müsste, jedesmal, denn wenn z.B. "page = 2" übergeben wird, konnte ich bisher ja einfach das Limit erhöhen, bzw. das Startlimit. Da nun weder eindeutig ist, wieviele Datensätze davor liegen, noch wieviele die Seite 2 umfasst müsste ich zig bis hunderte Datensätze (je anchdem wieviele Sprachen möglich sind) zuviel auslesen.

Das einfachste wäre an dieser Stelle wahrscheinlich 2 Queries, wobei der erste die News-IDs ausliest und dann im zweiten query noch die where-Klausel, dass es einer der folgenden IDs sein muss, ergänzt wird.

EDIT: oh man, nun bekomm ich jedesmal den Eintrag der ersten News, ist ja auch logisch, denn die bedingung, dass es eine der News-IDs sein muss, ist ja bei der ersten immer erfüllt ... so wird det nix ...:rolleyes:

EDIT2:

Also wenn mich net alles täuscht es jetzt ;)
Code:
SELECT News_id 
FROM chq_News

Affected Rows: 2

Code:
SELECT chq_News.*, chq_News_ct.*, chq_News_cn.*, chq_users.users_nick, chq_users.users_id 
FROM chq_News 
LEFT JOIN chq_News_ct ON(chq_News_ct.News_ct_node_id=chq_News.News_cat)
LEFT JOIN chq_News_cn ON(chq_News_cn.News_cn_nid=chq_News.News_id)
LEFT JOIN chq_users ON(chq_users.users_id=chq_News_cn.News_cn_author) 
WHERE News_id IN ('1','2') AND News_cn_lang IN ('de', 'en', 'fr') 
ORDER BY News_id, 10 - FIND_IN_SET(News_cn_lang, 'fr,en,de')

Affected Rows: 5

Resultat nach PHP "auslesung"
News 1 in deutsch
News 2 in english

Resultat: Tausend Dank für alles ;)
 
Zuletzt bearbeitet:
Wenn Du's in mehreren Seiten ausgeben willst, dann würde stark ich zur temporären Tabelle raten. Das hochsetzen des LIMIT bezieht sich dann auf die Abfrage über die temp. Tabelle. Natürlich wird dadurch bei jedem Seitenwechsel erneut eine Tabelle angelegt, ausgegeben und wieder gelöscht. MySQL macht das bei HEAP-Tabellen eigentlich ganz flott, weiß nur nicht wie der Server es verkraftet, wenn gleichzeitig tausende von Usern in Deinen News-Seiten blättern...

Übrigens hab' ich mir heute mittag auf meinem Privatrechner MySQL 4.1 installiert (SubSelects sind doch sehr verlockend), hab das SubSelect-SQL-Statement von heute vormittag ausprobiert und: Fehler. Da muss ein "AS tmp_news" hinter die Klammer die den SubSelect umschließt. Ich schreib's nur der Ordnung halber...

Ciao,
Martin
 
danke ;)

tausende von Usern ... HaHa :D

naja, "erstmal" lass ichs so, nun es ja erstmal, über optimierungen kann man immer noch diskutieren ;)
 

Neue Beiträge

Zurück