{"id":1167,"date":"2013-05-28T22:22:40","date_gmt":"2013-05-29T05:22:40","guid":{"rendered":"http:\/\/www.designersgate.com\/blogs\/?p=1167"},"modified":"2013-05-28T22:22:40","modified_gmt":"2013-05-29T05:22:40","slug":"handling-keyword-search-phpmysql","status":"publish","type":"post","link":"https:\/\/designersgate.com\/blog\/handling-keyword-search-phpmysql\/","title":{"rendered":"Handling a Keyword Search with PHP\/MySQL (MATCH\/AGAINST MySQL Function)"},"content":{"rendered":"<p>For the longest time I used the MySQL &#8216;LIKE&#8217; statement style for searching by keywords, which it is probably the most reliable and fastest option. But today I&#8217;m going to talk about another option you have to create a more effective keyword search and get the result from MySQL order by keywords relevance.<\/p>\n<p>For this tutorial you will require (unless you work directly in your web hosting):<\/p>\n<ol>\n<li><a title=\"XAMPP - Apache\/MySQL\/PHP for Windows and more.\" href=\"http:\/\/www.apachefriends.org\/en\/xampp.html\">xampp<\/a> or any other Web Server installed in your system. (Apache\/MySQL\/PHP).<\/li>\n<li><a title=\"PHPMyAdmin - Bringing MySQL to the web\" href=\"http:\/\/www.phpmyadmin.net\/home_page\/index.php\" target=\"_blank\" rel=\"noopener noreferrer\">PHPMyAdmin<\/a> &#8211; Browser Based PHP MySQL Administrator.<\/li>\n<li>Some knowledge in PHP and MySQL is a plus but not a most.<\/li>\n<li>Variable: $input (variable that holds the user request), $string ($input exploded in an array), $sql (the SQL request string variable).<\/li>\n<\/ol>\n<p>Alright let&#8217;s first create a table in MySQL to test the code:<\/p>\n<p><a href=\"http:\/\/www.designersgate.com\/blogs\/wp-content\/uploads\/2013\/05\/match000.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1175\" alt=\"Creating Table MySQL\" src=\"http:\/\/www.designersgate.com\/blogs\/wp-content\/uploads\/2013\/05\/match000.jpg\" width=\"426\" height=\"326\" srcset=\"https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match000.jpg 426w, https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match000-300x230.jpg 300w\" sizes=\"auto, (max-width: 426px) 100vw, 426px\" \/><\/a><\/p>\n<p><code> CREATE TABLE IF NOT EXISTS `cds` (<br \/>\n`id` INT NOT NULL AUTO_INCREMENT ,<br \/>\n`title` VARCHAR(45) NULL ,<br \/>\n`year` VARCHAR(45) NULL ,<br \/>\n`tags` VARCHAR(255) NULL ,<br \/>\nPRIMARY KEY (`id`),<br \/>\nFULLTEXT (tags(255))<br \/>\n)<br \/>\nENGINE = MyISAM;<br \/>\n<\/code><\/p>\n<p><a href=\"http:\/\/www.designersgate.com\/blogs\/wp-content\/uploads\/2013\/05\/match003.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1179\" alt=\"Final Structure Result\" src=\"http:\/\/www.designersgate.com\/blogs\/wp-content\/uploads\/2013\/05\/match003.jpg\" width=\"734\" height=\"811\" srcset=\"https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match003.jpg 734w, https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match003-600x663.jpg 600w, https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match003-272x300.jpg 272w\" sizes=\"auto, (max-width: 734px) 100vw, 734px\" \/><\/a><\/p>\n<p>Notice that I created an <code>FULLTEXT<\/code> Index using the `tags` column. This is going to be our search indexed column.<\/p>\n<p><a href=\"http:\/\/www.designersgate.com\/blogs\/wp-content\/uploads\/2013\/05\/match004.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1180\" alt=\"Final CREATE Result Hightlights\" src=\"http:\/\/www.designersgate.com\/blogs\/wp-content\/uploads\/2013\/05\/match004.jpg\" width=\"734\" height=\"811\" srcset=\"https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match004.jpg 734w, https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match004-600x663.jpg 600w, https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match004-272x300.jpg 272w\" sizes=\"auto, (max-width: 734px) 100vw, 734px\" \/><\/a><\/p>\n<p>Now let&#8217;s populate the table `cds` with some information:<\/p>\n<p><a href=\"http:\/\/www.designersgate.com\/blogs\/wp-content\/uploads\/2013\/05\/match000a.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1176\" alt=\"Inserting Information MySQL\" src=\"http:\/\/www.designersgate.com\/blogs\/wp-content\/uploads\/2013\/05\/match000a.jpg\" width=\"540\" height=\"370\" srcset=\"https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match000a.jpg 540w, https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match000a-300x206.jpg 300w\" sizes=\"auto, (max-width: 540px) 100vw, 540px\" \/><\/a><\/p>\n<p><code> INSERT INTO `cds` (`id`, `title`, `year`, `tags`)<br \/>\nVALUES (NULL, 'What about Now', '1996', 'Christian,The Kry,Rock,Alternative,1996'),<br \/>\n(NULL, 'The Singles', '2000', 'Pop,80''s,Rock,Hard Rock,Corey Hart,2000'),<br \/>\n(NULL, '18 ''til I die', '1996', 'Bryan Adams,Pop,1996,Hard Rock'),<br \/>\n(NULL, 'Almas Unidas', '2002', 'Cristiano,Perucho,Christian,Rock,Pop,Ballad'),<br \/>\n(NULL, 'Falta Amor', '1992', 'Pop,Man\u00e1,Latino,Rock');<\/code><\/p>\n<p>With PHPMyAdmin installed you only need to select and Schema then select SQL to run both this codes. You can run it both at once all each by itself. If you run both at once then make sure CREATE is the first code and there&#8217;s a semicolon (;) after line <code>ENGINE = MyISAM<\/code>.<\/p>\n<p>Previously, when I made a search with a list of keywords I had to explode a string line in an array then run a dynamic list of <code>LIKE<\/code> statements, for example:<\/p>\n<p><code>&lt;?php<br \/>\n\/\/exploding user input into an array<br \/>\n$list = explode(' ',$input);<br \/>\n$sql = \"SELECT * FROM `cds` WHERE\";<br \/>\n$sqlOption = array();<br \/>\n\/\/--<br \/>\n\/\/creating tags keywords search<br \/>\nforeach($list as \u00a0$keyword){<br \/>\n$sqlOption[] = \"tags LIKE '%\".addslashes($keyword).\"%'\";<br \/>\n}<br \/>\n\/\/--<br \/>\n\/\/ joining the array to look something like<br \/>\n\/\/ tags LIKE 'keyword1' OR tags LIKE 'keyword2' etc...<br \/>\n$sql .= \" \".join(' OR ',$sqlOption).\";\";<br \/>\n?&gt;<\/code><\/p>\n<p>If the user input would&#8217;ve be something like <code>\"1996 80's Rock\"<\/code> the previous SQL would looks like:<\/p>\n<p><code>SELECT * FROM cds WHERE tags LIKE '%1996%' OR tags LIKE '%80\\'s%' OR tags LIKE '%Rock%';<\/code><\/p>\n<p>Even though this way is faster in some cases it doesn&#8217;t give you the option to order the results by relevance of the keywords entered. Now you might ask, how can I do that? Well, you can make a couple lines of codes with PHP to do this, all you can just use the <code>MATCH\/AGAINST<\/code> functionality included with MySQL. Let see the code:<\/p>\n<p><code>&lt;?php<br \/>\n\/\/join the previous array $list this time<br \/>\n\/\/using the + sign that means that tags must include<br \/>\n\/\/and * that's like % in the LIKE statement<br \/>\n$string = join('* +',$list);<br \/>\n\/\/--<br \/>\n\/\/We are using now the MATCH AGAINST function from MySQL<br \/>\n$sql = \"SELECT *,MATCH(tags) AGAINST(+$string*) AS `relevance` FROM `cds` WHERE MATCH(tags) AGAINST('+$string*' IN BOOLEAN MODE) ORDER BY relevance DESC;\";<br \/>\n?&gt;<\/code><\/p>\n<p>As you see this code is a little bit cleaner, but this is not why I prefer it. Look closer the SQL statement, I created a column called <code>relevance<\/code> holding the result from the <code>MATCH\/AGAINST<\/code> statement, then run it again as the filter for the search but this time using <code>IN BOOLEAN MODE<\/code> which will determine a value from 0 to 1 in relevance, the most precise results will be closer to 1 and the less relevant will be closer to 0, meaning the most relevant will be in top because we set the <code>ORDER BY<\/code> as <code>DESC<\/code> (descendant).<\/p>\n<p><a href=\"http:\/\/www.designersgate.com\/blogs\/wp-content\/uploads\/2013\/05\/match001.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1177\" alt=\"MATCH\/AGAINST SQL Statement\" src=\"http:\/\/www.designersgate.com\/blogs\/wp-content\/uploads\/2013\/05\/match001.jpg\" width=\"1172\" height=\"334\" srcset=\"https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match001.jpg 1172w, https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match001-600x171.jpg 600w, https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match001-300x85.jpg 300w, https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match001-1024x292.jpg 1024w, https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match001-768x219.jpg 768w\" sizes=\"auto, (max-width: 1172px) 100vw, 1172px\" \/><\/a><\/p>\n<p><a href=\"http:\/\/www.designersgate.com\/blogs\/wp-content\/uploads\/2013\/05\/match002.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1178\" alt=\"MATCH\/AGAINST Results\" src=\"http:\/\/www.designersgate.com\/blogs\/wp-content\/uploads\/2013\/05\/match002.jpg\" width=\"546\" height=\"344\" srcset=\"https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match002.jpg 546w, https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/match002-300x189.jpg 300w\" sizes=\"auto, (max-width: 546px) 100vw, 546px\" \/><\/a><\/p>\n<p>The downside is that multiple columns search tend to be a hassle, even just for the slightest mistake won&#8217;t work, sometimes throw INDEX errors even though there are FULLTEXT indexes set by each column. Remember, any column that you would like to search need to have a <code>FULLTEXT<\/code> index in order the <code>MATCH\/AGAINST<\/code> statement to work. Meaning that might slow down performance or increase bandwidth. The recommended way to do it is to create a column or an indexed table to which you run the code against.<\/p>\n<p>By the way, this is just basic functionality, remember to always secure your code with whatever extension you use, PDO, MySQLi, etc&#8230;<\/p>\n<p>If you would like more information about this functionality from MySQL you can visit:<\/p>\n<ol>\n<li><a title=\"MySQL Match\/Against Order by relevance\" href=\"http:\/\/stackoverflow.com\/questions\/6259647\/mysql-match-against-order-by-relevance-and-column\" target=\"_blank\" rel=\"noopener noreferrer\">MySQL Match\/Against Order by relevance<\/a><\/li>\n<li><a title=\"MySQL - FULLTEXT BOOLEAN\" href=\"http:\/\/dev.mysql.com\/doc\/refman\/4.1\/en\/fulltext-boolean.html\" target=\"_blank\" rel=\"noopener noreferrer\">MySQL &#8211; FULLTEXT BOOLEAN<\/a><\/li>\n<li><a title=\"MySQL Indexing and Searching\" href=\"http:\/\/www.petefreitag.com\/item\/477.cfm\" target=\"_blank\" rel=\"noopener noreferrer\">MySQL Indexing and Searching<\/a><\/li>\n<\/ol>\n<p>Well, I hope this would help anyone&#8230; HAPPY CODING!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>For the longest time I used the MySQL &#8216;LIKE&#8217; statement style for searching by keywords, which it is probably the most reliable and fastest option. But today I&#8217;m going to talk about another option you have to create a more effective keyword search and get the result from MySQL order by keywords relevance. For this [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1185,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[13,7],"tags":[136,38,137,8,66,75,138,139,140,141,45,142,143,78,29,106,19,144,145,146,53,6,14],"class_list":["post-1167","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tips","category-tutorials","tag-against","tag-apache","tag-bandwidth","tag-browsers","tag-code","tag-coding","tag-create","tag-fast","tag-fulltext-index","tag-insert","tag-internet","tag-keywords","tag-match","tag-mysql","tag-php","tag-phpmyadmin","tag-practice","tag-relevance","tag-search","tag-set","tag-sql","tag-tutorial","tag-users"],"acf":[],"jetpack_featured_media_url":"https:\/\/designersgate.com\/blog\/wp-content\/uploads\/2013\/05\/DESIGNERS-GATE-PHP-MATCH-AGAINST.jpg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/designersgate.com\/blog\/wp-json\/wp\/v2\/posts\/1167","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/designersgate.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/designersgate.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/designersgate.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/designersgate.com\/blog\/wp-json\/wp\/v2\/comments?post=1167"}],"version-history":[{"count":0,"href":"https:\/\/designersgate.com\/blog\/wp-json\/wp\/v2\/posts\/1167\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/designersgate.com\/blog\/wp-json\/wp\/v2\/media\/1185"}],"wp:attachment":[{"href":"https:\/\/designersgate.com\/blog\/wp-json\/wp\/v2\/media?parent=1167"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/designersgate.com\/blog\/wp-json\/wp\/v2\/categories?post=1167"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/designersgate.com\/blog\/wp-json\/wp\/v2\/tags?post=1167"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}