Tiếng việt và giải quyết vấn đề tìm kiếm có dấu - không dấu. 2007-11-29 12:48:04


Lang thang ở vài trang nhạc như nhạc số hay ngía thử cái code xtremedia của thằng em nó gửi cho. Tớ thấy có 1 vấn đề khá là vui. Hầu như mấy trang này chúng nó sử dụng công cụ tìm kiếm kiểu thế này: NhacSo thì nó dùng Javascript loại bỏ hết tất cả dấu tiếng việt trong chuỗi từ khóa mà khách nhập vào. Xtremedia dùng 1 hàm php xóa dấu chuỗi tìm kiếm khách nhập vào (và cả bắt khách tìm kiếm bằng từ khóa không dấu ^^) Tớ tìm thử ở nhạc số từ Dũng (để ra Quang Dũng hay Lê Dũng chẳng hạn) thì Nhạc số trả về có cả: Lê Dung, Thy Dung... Tớ thấy nó bị thừa nhiều quá... hoặc là gõ AC&M thì đố nhạc số trả về kết quả vì dấu cái dấu '&' hehe... Vì thế tớ nghĩ thế này. Viết 1 function trong đó sẽ phân tích chuỗi nhập vào của khách là có dấu hay không dấu. Nếu có dấu tớ sẽ tìm trong Data kiểu có dấu, nếu không dấu sẽ tìm kiểu không dấu. Tiếp nữa là nếu khách gõ không dấu như là "dem trang tinh yeu" thì quét vào data thì sẽ quét vào đâu? Tiếp nữa là giả dụ như trong data sử dụng utf-8. bạn viết tên ca sĩ Đàm Vĩnh Hưng chẳng hạn thì mã utf-8 trong data sẽ phân biệt chữ "Đ" khác chữ "đ", chữ "Ô" khác chữ "ô"... (túm lại là chữ Tiếng việt có dấu viết hoa khác chữ có dấu thường) vậy thì khách gõ "đàm vĩnh hưng" thì nếu quét theo kiểu bình thường (kể là LIKE hoặc MATCH) thì cũng k ra được vì trong data là "Đàm Vĩnh Hưng" cơ. vậy thì giải quyết thử nhé.
Đầu tiên là kiểm tra chuỗi nhập vào là tiếng việt có dấu hay là không dấu:
<?
function check_str($value) //kiem tra xem phai la khong dau hay khong?
{
for(
$i=0;$i<strlen($value);$i++)
{
$ord=ord($value{$i});
if((
$ord==32) || ($ord==35) || ($ord==37) || ($ord==38) || ($ord==39) || ($ord>=48 and $ord<=57) || ($ord>=65 and $ord<=90) || ($ord>=97 and $ord<=122))//0-9 a-z A - Z
{}else{return FALSE;}}return TRUE;}
?>
bạn chạy thử câu này:
<?
echo check_str('Chào anh em PHPBASIC');// co dau
?>
sau đó chạy câu này:
<?
echo check_str('Chao anh em PHPBASIC');// khong dau
?>
Bạn sẽ thấy: Nếu có dấu: Giá trị trả về cho check_str là NULL
Nếu là không dấu thì giá trị trả về là 1 =>Đã xong phần phân biệt chuỗi có dấu hay không dấu.
vậy thì nếu là chuỗi không dấu mình sẽ phải tìm kiếm trong data theo không dấu. Vậy vấn đề tiếp là trong Data bây giờ, giả sử đã có field name ta phải tạo thêm 1 field keyword chẳng hạn. Khi insert vào data thì ngoài đưa vào name là giá trị đúng chuỗi nhập vào (ví dụ: "Đàm Vĩnh Hưng") thì keyword sẽ là giá trị đã lược bỏ dấu (tức là "dam vinh hung") Về function chuyển từ 1 chuỗi có dấu sang không dấu thì đã quá đơn giản (trong PHPBasic đã có bài viết) vậy thì khi chuỗi tìm kiếm là k dấu thì chỉ việc quét vào field keyword là oke.
Tiếp theo là vấn đề chuỗi tìm kiếm có dấu nhưng lại phân biệt chữ có dấu viết hoa và viết thường (Đ và đ, Ây da và ây da) Nếu khách gõ "đàm" mà bạn cứ bê nguyên xi vào tìm trong field name thì còn lâu mới ra vì trong data là "Đàm" cơ. Vậy thì làm sao? Chắc bạn sẽ lại nghĩ là tạo tiếp 1 field keyword_co_dau chẳng hạn, viết 1 function chuyển chữ hoa thành chữ thường hết, lúc insert vào data thì chỉ cần replace chuỗi chữ hoa "Đàm Vĩnh Hưng" thành "đàm vĩnh hưng" rồi khi khách gõ từ khóa mình cũng sẽ replace như vậy, dù nó có gõ "ĐÀM Vĩnh Hưng" hay "đàm VĨNH HƯNG" cũng ra hết. ^^ khá chính xác đấy, nhưng như thế data của mình sẽ tốn gấp đôi (Tự nhiên thêm 1 field mà) Rất may là trường hợp này không cần phải làm như thế làm j cho khổ mà ta dùng câu Query SQL khá đặc biệt này ^^:
giả sử đặt $keyword là chuỗi tìm kiếm khách nhập vào: "ĐÀM vĩnh HưNG" câu tìm kiếm sẽ là:
<?
$query
="SELECT * FROM name WHERE LOWER(CONVERT(name USING utf8)) LIKE '%".mb_strtolower($keyword,'utf-8')."%'";
?>
vậy là xong :D hi vọng các bạn hiểu từ nãy giờ tớ nói gì
Đây là bài đầu tiên tớ đóng góp cùng PHPBasic. Tớ newbie nên các bạn đừng chê cười nha, mong các bạn giúp đỡ ^^ và nghĩ xem còn cách nào hay hay hơn không.
Chú ý nữa là: để tìm kiếm trong data các ký tự đặc biệt ví dụ
tìm AC&M, I don't know...
thì khi insert vào data bạn nên replace các ký tự &,' ... này thành 1 cái j đó bất kỳ, khi lấy từ khóa nhập vào bạn cũng replace các dấu này để tìm kiếm. ^^ (Gõ mỏi cả tay)

Tra loi 13 comment(s) GnuhNguyen 2007-11-29 12:48:04

GnuhNguyen 2007-11-29 12:52:18

Tớ xin nói thêm chút nữa là ở câu query sql chính xác hơn thì nên viết 1 function chuyển chữ tiếng việt có dấu viết hoa thành có dấu viết thường, sau đó thay vào mb_strtolower($keyword,'utf-8') thì sẽ chính xác hơn với mọi trường hợp và kiểu cách hoa - thường mà khách gõ vào. (TG ui, sao anh k làm chức năng edit à?

@GnuhNguyen

uoon 2007-11-29 10:00:38

Còn một cách khác, đơn giản hơn. Chỉ áp dụng cho những field dữ liệu nhỏ.
Cách làm như sau:
Tạo field keyword.
Dùng hàm rawurlencode mã hóa dữ liệu trước khi insert vào field này.
Khi search cũng vậy, mã hóa cái từ cần tìm.
Rồi dùng truy vấn dạng like bình thường.

Ưu điểm: Tìm kiếm khá chính xác.
Nhược điểm: Làm tăng dung lượng database.
Nhưng vấn đề này sẽ không ảnh hưởng mấy khi áp dụng cho các field nhỏ, như tên người, tên các danh mục....
Khi lấy dữ liệu ra phải decode mới dùng được. Nhưng nó cũng  có ưu điểm là loại bỏ được một số ký tự đặc biệt, không hợp
lệ, vì nó mã hóa hết rùi :).
Hi vọng còn các cách khác.

@uoon

lkn2 2007-11-30 12:52:31


Chỗ kiểm tra không dấu, dùng cái này nhanh hơn là dùng vòng lặp
<?php
if(preg_match('/[^a-zA-Z0-9[:space:]]/','PHP vo địch')) {
echo
'Có dấu';
} else {
echo
'Không dấu';
}
?>

@lkn2

GnuhNguyen 2007-11-30 04:00:24

Rất hay nhưng mã hoá sẽ chậm và data nặng hơn. tuy tìm chính xác nhg tốc độ k nhanh đc với data lớn

@GnuhNguyen

CuongPH 2008-08-15 09:49:37


Ý của GnuhNguyen cũng rất hay, nhưng mà phải thêm một trường dữ liệu cho nội dung thì phí quá, tôi có ý này.
Viết ra một function bỏ tiếng Việt đi
rồi search theo điều kiện OR của keyword
<?php
function stripVietName($str){
if(!
$str) return false;
$unicode = array(
'a'=>'á|à|ả|ã|ạ|ă|ắ|ặ|ằ|ẳ|ẵ|â|ấ|ầ|ẩ|ẫ|ậ',
'd'=>'đ',
'e'=>'é|è|ẻ|ẽ|ẹ|ê|ế|ề|ể|ễ|ệ',
'i'=>'í|ì|ỉ|ĩ|ị',
'o'=>'ó|ò|ỏ|õ|ọ|ô|ố|ồ|ổ|ỗ|ộ|ơ|ớ|ờ|ở|ỡ|ợ',
'u'=>'ú|ù|ủ|ũ|ụ|ư|ứ|ừ|ử|ữ|ự',
'y'=>'ý|ỳ|ỷ|ỹ|ỵ',
);
foreach(
$unicode as $nonUnicode=>$uni)
$str = preg_replace("/($uni)/i",$nonUnicode,$str);
return
$str;
}

$query="SELECT * FROM name WHERE LOWER(CONVERT(name USING utf8)) LIKE '%".mb_strtolower($keyword,'utf-8')."%' OR LOWER(CONVERT(name USING utf8)) LIKE '%".mb_strtolower(stripVietName($keyword),'utf-8')."%'";
?>
Đảm bào là giải quyết được 2 vấn đề trên
./.

@CuongPH

me@ducthuan.info 2008-08-15 03:21:52

$query="SELECT * FROM name WHERE LOWER(CONVERT(name USING utf8)) LIKE '%".mb_strtolower($keyword,'utf-8')."%' OR LOWER(CONVERT(name USING utf8)) LIKE '%".mb_strtolower(stripVietName($keyword),'utf-8')."%'"


Chạy câu query này đảm bảo sẽ rất nhanh cháy server ^^

@me@ducthuan.info

gaulucky92 2008-08-15 10:06:29

thôi tốt nhất là dùng song song 2 search engine :D 1 cái của mình làm thủ công (kiếm tương đối), 1 cái của google (khá chính xác) => good

@gaulucky92

dsfsdfsd 2011-07-09 15:29:01

ffsgfsdgsdgsdgfsdfsfsfsfsfs

@dsfsdfsd

Ngọc Bảo 2011-07-11 15:21:15

với những site lớn người ta kg làm như vậy đâu.
Người ta sẽ có một bộ Index dử liệu theo từ có phân cấp hết. ví dụ
tiêu đề được là 10 điểm
trong tag cho 15 điểm
trong nội dung nếu nằm trong phần in đậm 6 điểm
nếu chỉ là bình thường thì 1 điểm
nguyên tắc lưu từ là lưu từ gốc + kg dau + xếp abc...
ví dụ từ nhiều = nhiều | nhieu | eihnu
khi người dùng gõ một từ vào nó dò từ trong bảng từ gốc kg có dò tiếp bảng kg dấu nếu kg có nữa thì có nghĩa là sai lỗi chính tả dò trong xếp abc
nếu có kết quả của từ thì bắt đầu tìm trong bảng đánh giá điểm nhiều từ thì cộng điểm vào điểm càng cao thì nó nằm trên đầu.
đây là nguyên tắc chỉ mục dử liệu.

@Ngọc Bảo

N.M.H 2011-07-11 19:39:15

@Ng%E1%BB%8Dc%20B%E1%BA%A3o: Thanks bạn rất nhiều!

@N.M.H

Hangnt 2013-09-30 17:09:36

Mình thì search đc ko dấu, có dấu, hoa thường được r, Nhưng còn 1 vấn đề là nếu search chữ "d" không dấu thì ko được.
Ai biết thì hỗ trợ mình với. tks.

@Hangnt

PHAM TIEN THANH 2014-09-04 09:52:44

@GnuhNguyen: Mình dùng rồi nhưng Đàm vẫn không so sánh được.Có phải là mình dùng ut8_unicode_ci trong Sql?

@PHAM TIEN THANH

cc 2017-03-17 05:55:41

cdmm

@cc

Reply