2008. február 20., szerda

Keresés MySQL adatbázisban - ékezetek és html tag-ek, mint zavarótényezők..

Az alap probléma az, hogy a CMS részeként, a megrendelő adminisztrációs felületen, bármilyen tartalmat feltölthet, mivel már ott tart a webes technika ugye, hogy a laikusok kezébe adjuk a wysiwyg editort1, amivel mintha csak word-ben kalimpálna, klikkelgetős módszerrel formázhatja a szöveget, szúrhat be képet stb. stb...

Ezenkívül magyarok vagyunk, használunk sok szép ékezetet, amiket a MySQL még mindig nem tud túl jól megkülönböztetni. Ezen okokból kifolyólag, mikor az alap keresési módszert használnánk (SELECT content FROM table WHERE content LIKE '%valami%'), akkor számolnunk kell azzal, hogy a "tárogat" és a "tartalom" egyaránt meg fog jelenni, ha pl. a felhasználó arra keresett rá, hogy "tár".

Ez még mindig nem minden, hiszen a wysiwyg editornak köszönhetően, a content tele lesz html kóddal is, így aztán a találatok között lehet olyan content is, melyben nem szerepel ugyan valós tartalomként a "tár", sem pedig a "tar", de lehet, hogy egy link tartalmaz "target" definíciót is. Ez aztán végképp nem kell nekünk mint találat.

1 pl.: tinyMCE


Erre a két problémára van itt egy-egy megoldás, amit lehet, hogy szebben is meg lehet oldani, nekem így sikerült. Ha valaki mégis tudna jobbat ajánlani, várom szeretettel a kommentet :)

1.) Ékezetek megkülönböztetése.

Legyen egy news táblánk a következők szerint:

CREATE TABLE IF NOT EXISTS `news` (
    `id` MEDIUMINT NOT NULL AUTO_INCREMENT ,
    `title` VARCHAR(255) NOT NULL,
    `lead` TEXT NOT NULL,
    `content` TEXT NOT NULL,
    `published` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
) ENGINE=MYISAM DEFAULT CHARSET=utf8;

Jelen esetben egy olyan hírt fogunk keresni, melyben szerepel az a szó, hogy "tar". Ennek megfelelően, felveszünk három bejegyzést:
Most keressünk rá a szokásos módon a "tar" szóra, és nézzük meg mi az eredmény:
Eredményként megkapjuk mind a három bejegyzést egymás után. Ebből kitűnik, hogy a MySQL utf8 general (sőt utf8 hungarian) kódolás mellett sem tesz különbséget az ékezetes és nem ékezetes karakterek között. A következő két karaktersorozat számára teljesen egyenlő: öüóőúéáű = ouooueau

Adott viszont egy kódolás, ami megkülönbözteti ezeket a karaktereket, egyetlen baj vele csak az, hogy a kis és nagybetűk sem egyformák, ennyire viszont nem szeretnénk szigorúak lenni. Kis és nagybetű legyen ugyanolyan, de az ékezetes és ékezetmentesek ne. Ő lenne az utf8_bin.

Módosítva a lekérdezést:
Máris kiszűrtük a tárogatós hírt a találatokból! Egyszerűen annyit mondtunk a MySQL-nek, hogy a content mező tartalmát alakítsa kisbetűssé lower(), és keresse meg azt a rekordot, amiben íly módon szerepel a tar szó bármilyen környezetben, de mindezt úgy, hogy használja az utf8-bin karakterkódolást.

Azért használtuk a lower() beépített függvényt, mert fentebb említettem, hogy az utf8_bin különbséget tesz a kis és nagybetűk között. Ebben az esetben viszont ha a szövegben a tar a következők valamelyikeként szerepelne csak: Tar, tAr, taR, TAR stb., akkor nem találná meg a bejegyzést, hiszen tAr nem egyenlő tar. Ebből kifolyólag mikor PHP segítségével mondjuk meg, hogy mire szeretnénk keresni, a behelyettesítés során is használjuk az strtolower() függvényt.

$_GET["search"] = "Tar";
$sql = "SELECT `title` FROM `news` WHERE lower(`content`) LIKE '%" . strtolower($_GET["search"]); . "%' COLLATE utf8_bin";

 2.) HTML tag-ek kiszűrése

A fenti megoldásban észrevehettük, hogy még mindig szerepelt olyan találat a listában, ami nem kéne, hogy ott legyen. Az "Olyan hír címe, amiben..." című cikk mégis ott van, és ennek oka a content-ben elhelyezett link, aminek meg van adva, hogy új ablakban nyissa meg a böngésző (target). Ennek a problémának a megoldására használhatunk egy függvényt, ami annyit tesz, hogy a HTML tag-eket egyszerűen kivágja a tartalomból, így mindössze a nyers szöveg marad meg.:
A fenti függvény mindössze annyit csinál, hogy megkeresi az összes "<" jelet, és törli azt, illetve az utána szereplő összes karaktert mindaddig, míg nem találja meg a párját, azaz a ">" karaktert. Hiba nem lehet a dologban, ugyanis a szöveg nem tartalmazhat ilyet, maximum htmlentitites formában, ami természetesen teljesen másképp néz ki.

Ha ez megvan, több lehetőségünk van. Az egyik, hogy úgy hozzuk létre a "news" táblát, hogy felveszünk egy plusz mezőt, pl.: "plain_content", amibe a tartalmat a strip_tags függvény meghívásával írjuk be:
Így a plain_content tartalma ez lesz:

"Viszont szerepel benne egy link, aminek van egy olyan része, amit nem kéne találatként kezelni: valami.hu"

Lehet használni még például nézeteket (view) triggereket stb, de most a legegyszerűbb módon kezeljük a problémát, a lower() függvényt a strip_tags függvénnyel megspékeljük:
Máris láthatjuk, hogy a találat mindössze azt a rekordot tartalmazza, amire valóban szükségünk van. Nem zavarnak be az ékezetek, sem a HTML kódok, azt kaptuk eredményül, amit kerestünk.

Azt írták a MySQL fejlesztői, hogy majd foglalkoznak az abc problémánkkal, ha kapnak egy komplett leírást arról, hogy mik a nyelvünk szabályai, mit és hogyan kell kezelni. Majd akkor megérjük azt is, hogy a kis és nagy betűk nem fognak bezavarni a sorba rendezésnél, és nem kell trükköznünk a kereséseknél. Addig pedig marad a szép magyar hagyomány, gányolunk és trükközünk össze-vissza, hogy érvényesülni tudjunk :D

Jó étvágyat.

3 megjegyzés:

  1. Az utolso kodban legyszi az utf8_bin collation nevet javitsd, most utf8?bin.

    VálaszTörlés
    Válaszok
    1. Ja, tudom mar mi ez, a syntax highlighter plugin sugojanag/nevjegyenek trigger gombja. Mindegy.

      Törlés