I am building a simple live search based on this tutorial: http://blog.ninetofive.me/blog/build-a-live-search-with-ajax-php-and-mysql/
It works fine for queries like this with one word:
Or for queries with part of a word:
Or for queries with two words or parts of words where they occur together in the results being searched:
But if I search for two words that do not occur next to each other the query fails:
How would I modify my query so that I can search for any number of keywords that appear at any location in my results?
For example if my db has a record such as: The quick brown fox jumps over the lazy dog
I want a result to be returned if I search for: quick jumps dog or dog over not just quick brown or fox jumps or fox and so on.
I was thinking something along the lines of exploding my query if it has more than one word in it and then making my $search_string into an array of keywords for MySQL to query but I don't know if that is the best way to go about it.
Query:
// Get Search
$search_string = preg_replace("/[^A-Za-z0-9]+[.]/", " ", $_POST['query']);
$search_string = $tutorial_db->real_escape_string($search_string);
// Check Length More Than One Character
if (strlen($search_string) >= 1 && $search_string !== ' ') {
// Build Query
$query = 'SELECT * FROM search WHERE subsection LIKE "%'.$search_string.'%" OR def LIKE "%'.$search_string.'%" OR exception LIKE "%'.$search_string.'%" ORDER BY subsection ASC';
Full code:
<?php
/************************************************
The Search PHP File
************************************************/
/************************************************
MySQL Connect
************************************************/
// Credentials
$dbhost = "localhost";
$dbname = "livesearch";
$dbuser = "live";
$dbpass = "search";
// Connection
global $tutorial_db;
$tutorial_db = new mysqli();
$tutorial_db->connect($dbhost, $dbuser, $dbpass, $dbname);
$tutorial_db->set_charset("utf8");
// Check Connection
if ($tutorial_db->connect_errno) {
printf("Connect failed: %s\n", $tutorial_db->connect_error);
exit();
}
/************************************************
Search Functionality
************************************************/
// Define Output HTML Formating
$html = '';
$html .= '<li class="result">';
$html .= '<a target="_blank" href="urlString">';
$html .= '<h2><b> codeString - yearString - chapterString - sectionString - SUBHERE</b></h1>';
$html .= '<h3>defString</h3>';
//try to add exception string
$html .= '</br><h3>exceptionString</h3>';
$html .= '</a>';
$html .= '</li>';
// Get Search
$search_string = preg_replace("/[^A-Za-z0-9]+[.]/", " ", $_POST['query']);
$search_string = $tutorial_db->real_escape_string($search_string);
// Check Length More Than One Character
if (strlen($search_string) >= 1 && $search_string !== ' ') {
// Build Query
//$query = 'SELECT * FROM search WHERE function LIKE "%'.$search_string.'%" OR name LIKE "%'.$search_string.'%"';
$query = 'SELECT * FROM search WHERE subsection LIKE "%'.$search_string.'%" OR def LIKE "%'.$search_string.'%" OR exception LIKE "%'.$search_string.'%" ORDER BY subsection ASC';
// Do Search
$result = $tutorial_db->query($query);
while($results = $result->fetch_array()) {
$result_array[] = $results;
}
// Check If We Have Results
if (isset($result_array)) {
foreach ($result_array as $result) {
// Format Output Strings And Hightlight Matches
//$display_function = preg_replace("/".$search_string."/i", "<b class='highlight'>".$search_string."</b>", $result['function']);
//$display_name = preg_replace("/".$search_string."/i", "<b class='highlight'>".$search_string."</b>", $result['name']);
//$display_url = 'http://php.net/manual-lookup.php?pattern='.urlencode($result['function']).'&lang=en';
// Format Output Strings And Hightlight Matches
//Format code - ex IBC
$display_code = preg_replace("/".$search_string."/i", "<b class='highlight'>".$search_string."</b>", $result['code']);
//Format year - ex 2012
$display_year = preg_replace("/".$search_string."/i", "<b class='highlight'>".$search_string."</b>", $result['year']);
//Format Chapter - ex Means Of Egress
$display_chapter = preg_replace("/".$search_string."/i", "<b class='highlight'>".$search_string."</b>", $result['chapter']);
//Format Section - ex Stairs
$display_section = preg_replace("/".$search_string."/i", "<b class='highlight'>".$search_string."</b>", $result['section']);
//Format sub Section - ex 1009.4 width
$display_sub = preg_replace("/".$search_string."/i", "<b class='highlight'>".$search_string."</b>", $result['subsection']);
//$display_subsection = preg_replace("/".$search_string."/i", "<b class='highlight'>".$search_string."</b>", $result['subsection']);
$display_def = preg_replace("/".$search_string."/i", "<b class='highlight'>".$search_string."</b>", $result['def']);
$display_exception = preg_replace("/".$search_string."/i", "<b class='highlight'>".$search_string."</b>", $result['exception']);
$display_url = 'http://php.net/manual-lookup.php?pattern='.urlencode($result['code']).'&lang=en';
// Insert Name
$output = str_replace('nameString', $display_name, $html);
//Insert Code
$output = str_replace('codeString', $display_code, $output);
//Insert Year
$output = str_replace('yearString', $display_year, $output);
//Insert Chapter
$output = str_replace('chapterString', $display_chapter, $output);
// Insert Section
$output = str_replace('sectionString', $display_section, $output);
// Insert Sub Section
$output = str_replace('SUBHERE', $display_sub, $output);
// Insert Defenition
$output = str_replace('defString', $display_def, $output);
// Insert exceptions
$output = str_replace('exceptionString', $display_exception, $output);
// Insert URL
$output = str_replace('urlString', $display_url, $output);
// Output
echo($output);
}
}else{
// Format No Results Output
$output = str_replace('urlString', 'javascript:void(0);', $html);
$output = str_replace('nameString', '<b>No Results Found.</b>', $output);
$output = str_replace('functionString', 'Sorry :(', $output);
// Output
echo($output);
}
}
?>
As others are pointing out, you need to use a LIKE condition for each word in the search.
So, as a first approach to the problem, split your input like this:
$search_string = 'jump lazy dog';
$search_terms = explode(' ', $search_string);
$query = 'SELECT *
FROM search
WHERE ';
foreach($search_terms as $term) {
$query .= '(subsection LIKE "%'.$term.'%" OR def LIKE "%'.$term.'%" OR exception LIKE "%'.$term.'%") AND ';
}
$query = substr($query, 0, -4).'ORDER BY subsection ASC';
echo $query;
This will produce the query you need:
SELECT *
FROM search
WHERE (subsection LIKE "%jump%" OR def LIKE "%jump%" OR exception LIKE "%jump%")
AND (subsection LIKE "%lazy%" OR def LIKE "%lazy%" OR exception LIKE "%lazy%")
AND (subsection LIKE "%dog%" OR def LIKE "%dog%" OR exception LIKE "%dog%")
ORDER BY subsection ASC
(I added line breaks and indentation just to make this easier to read.)
There is a huge problem here, however: this is horrifically insecure, due to the risk of SQL injection. You need to use prepared statements to prevent this. See this help page for a variety of approaches to this.
Use parenthesis for more than 2 parameters for the WHERE statement.
$query = 'SELECT * FROM search WHERE (subsection LIKE "%'.$search_string.'%" OR def LIKE "%'.$search_string.'%" OR exception LIKE "%'.$search_string.'%") ORDER BY subsection ASC';
For each word have LIKE %word% OR LIKE %word2% etc constructs as #iamde_coder suggested
Use boolean search as #fred-ii suggested
Use specialized search-servers that split words based on language, get rid of stop-words etc. For example SOLR or sphinx
Related
I have a search engine that searches addresses. It works as designed but I've run across an issue that I can't figure out a work around for.
Currently if I search for "23 Main St" and let's say Main street is in the 87232 zip code. This search will display every address that's on Main street because the street number I'm searching for has "23" with is also in the 87232 zip code.
I'm wondering if there is a way to program it to accept the space behind the "23 " and that would rule out the zip code? Or maybe you guys have a more thought out solution?
Here is my query code. It uses ajax live search:
if($_POST['query'] != '') {
$pieces = explode(" ", $_POST['query']);
$index = 0;
$substring = "";
while ($index < count($pieces)) {
$substring .=" CONCAT(Name,Address,City,State,Zip) like '%" . $pieces[$index] . "%'" ;
if ($index !=count($pieces)-1){
$substring .= " and ";
}
$index++;
}
$query = "SELECT * FROM database where ";
$query .= $substring;
$query .= ' ORDER BY Name ASC, dt DESC ';
}
I create a simple search form. Firstly I explode words and make some query (for search multiple words at once). Also, I want to search only in Movie titles. Movie titles can be found in movie_data.
My database called movies have rows:
id and
movie_data - contains movie data in JSON. Example storaged data in json:
{"movie_title":"Forrest Gump","movie_cover":"http://1.fwcdn.pl/po/09/98/998/7314731.6.jpg","movie_name_en":"","movie_desc":"Historia życia Forresta, chłopca o niskim ilorazie inteligencji z niedowładem kończyn, który staje się miliarderem i bohaterem wojny w Wietnamie.","movie_year":"1994","movie_genres":"DramatKomedia"}
Here is my code. This code don't return any results.
$query = 'Forrest Gump';
$words = explode(' ', $query);
$i = 0;
$len = count($words);
$query_build = '';
foreach ($words as $item) {
if ($i == $len - 1) {
$query_build .= ' titlemov LIKE "%$item%"';
} else {
$query_build .= ' titlemov LIKE "%$item%" OR ';
}
// …
$i++;
}
$sql = "SELECT movie_data, JSON_EXTRACT(movie_data, '$.movie_title') AS titlemov
FROM movies WHERE $query_build";
This code don't result any items
UPDATE
I updated my code.
$query = 'Forrest Gump';
$words = explode(' ', $query);
$i = 0;
$len = count($words);
$query_build = '';
foreach ($words as $item) {
if ($i == $len - 1) {
$query_build .= " JSON_EXTRACT(movie_data, \'$.movie_title\') LIKE '%$item%'";
} else {
$query_build .= " JSON_EXTRACT(movie_data, \'$.movie_title\') LIKE '%$item%' OR ";
}
// …
$i++;
}
$sql = "SELECT * FROM movies WHERE $query_build";
Now i getting error:
Warning: mysqli::query(): (22032/3141): Invalid JSON text in argument 1 to function json_extract: "Missing a comma or '}' after an object member." at position 228. in /search.php on line 43
Line 43:
$result = $conn->query($sql);
var_dump of $sql var:
SELECT * FROM movies WHERE JSON_EXTRACT(movie_data, '$.movie_title') LIKE '%Forrest%' OR JSON_EXTRACT(movie_data, '$.movie_title') LIKE '%Gump%'
Hmm?
titlemov doesn't exist for your where clause to use. What you want to use inside your where clause (specified in the $query_build) is the JSON_SEARCH function. Here's what it looks like in general with wildcards for the movie name:
SELECT movie_data, movie_data->'$.movie_title' AS titlemov FROM movies
WHERE JSON_SEARCH(movie_data->'$.movie_title', 'one', "%orrest Gum%") IS NOT NULL;
You'll just need to modify your $query_build string. I also drop the needless JSON_EXTRACT function calls.
Try this:
$query_build = " JSON_SEARCH(movie_data->'$.movie_title', 'one', '%$item%') IS NOT NULL";
Final note, I'm assuming $item is not a user supplied string, otherwise this opens you up to SQL injection. If it is supplied by user, you'll want to use a prepared statement and bind this value.
I have concatenated three tables to one table that is edit all. Now i'm going to retrieve the all the 4 values that starts with the particular letter, But is displaying all values with starts with different letter.
include "config.php";
$term = strip_tags(substr($_POST['searchit'],0,100));
$term = mysql_real_escape_string($term);
if($term=="")
echo "Enter Something to search";
else{
$query = mysql_query("select distinct videoname,category,name,email from edit_all
where videoname like '$term%' or category like '$term%' or name like '$term%' or email like '$term%'limit 1") or die(mysql_error());
$string= '';
if (mysql_num_rows($query))
{
while($row = mysql_fetch_assoc($query)){
$string .= "<b><a href='#'>".$row['videoname']."</a></b> -<br> ";
$string .= $row['category']."-<br>";
$string .= $row['name']."-<br>";
$string .= $row['email']."-<br>";
$string .= "\n";
}
}
else{
$string = "No matches found!";
}
echo $string;
}
?>
I got a few queries built dynamically by my scripts. They usually fit the following template:
SELECT ...
FROM ...
JOIN ...
WHERE ... (lots of filters, search conditions, etc. here) ...
ORDER BY (optional) ...
LIMIT (optional) ... OFFSET (optional)
I want to remove the LIMIT and OFFSET parts from the query. I used
$sql_entitati = implode("LIMIT", explode("LIMIT", $sql_entitati, -1));
to do it but then it hit me: what if there's no LIMIT in the query and what if the only LIMIT is somewhere in the where clauses?
So my question to you is: How can I safely remove everything after the LIMIT key word, without screwing it up if there's no LIMIT and/or there's a "LIMIT" somewhere in the where clause? All this done in php.
A bit of an edit for clarity:
the algorithm i use:
$sql = implode("LIMIT", explode("LIMIT", $sql, -1));
Will work on 99% of the cases. The problem occurs when the "LIMIT" key word at the end is missing, AND there is "LIMIT" written somewhere in the conditions. for example:
SELECT * FROM table WHERE bla = 'SPEED LIMIT' ORDER BY table.a
this is the problem i need to tackle.
Solved using the following algorithm (Credit to techfoobar):
$p = strrpos($sql, "LIMIT");
if($p !== false) {
$q = strpos($sql, ")", $p);
$r = strpos($sql, "'", $p);
$s = strpos($sql, "\"", $p);
if($q === false && $r === false && $s === false)
$sql = substr($sql, 0, $p);
}
You should do something like:
Get the position of the last "LIMIT" - store this position in say, p
Ensure that you do not have a ")" character after p - if so, it is part of some inner query inside a condition etc..
Ensure that you do not have a "'" character after p - if so, it is part of some user input string
If steps 2 and 3 are passed, strip off everything after p
More hints:
For step 1 - use strrpos for the last occurrence of LIMIT
For steps 2 and 3, use strpos with p as the search start offset
For step4, use substr to strip off everything after p
Get the position of LIMIT, check it exists with !==FALSE then substring.
$str = "SELECT X FROM Y JOIN WHERE CONDITIONS ORDER BY ORDERS LIMIT OFFSET";
$pos = strrpos($str,"LIMIT");
if ($pos!==false)
{
$newStr = substr($str,0,$pos);
echo $newStr;
}
Try this aproach
$query = '';
$query .= 'SELECT...';
$query .= 'FROM ...';
$query .= 'JOIN ...';
$query .= 'WHERE...';
if ($limit)
{
$query .= " LIMIT $limit";
}
This may help you...
$str = " SELECT * FROM table WHERE bla = 'SPEED LIMIT' ORDER BY table.a";
$pos = strrpos($str,"LIMIT");
if ($pos!==false)
{
$ok = 1;
if(substr_count($str, '"', $pos) % 2 != 0)
{
$ok = 0;
}
if(substr_count($str, "'", $pos) % 2 != 0)
{
$ok = 0;
}
if(substr_count($str, ")", $pos) > 0)
{
$ok = 0;
}
if($ok == 1)
$str = substr($str,0,$pos);
}
echo $str;
I have the following PHP search script.
When I search for 1234567, it matches the exact phrase but I want it to match only initial 4 characters i.e. 1234.
Explanation:
I want it to count the initial 4 characters and display the result matching those initial characters. E.g. if someone search for 1234567 then the script should count the initial 4 characters i.e. 1234 and show the results. Similarly if someone search 456789 then the script should count the initial 4 characters i.e. 4567 and show the results.
///explode search term
$search_exploded = explode(" ",$search);
foreach($search_exploded as $search_each)
{
//construct query
$x++;
if ($x==1)
$construct .= "keywords LIKE '%$search_each%'";
else
$construct .= " OR keywords LIKE '%$search_each%'";
}
//echo out construct
$construct = "SELECT * FROM numbers WHERE $construct";
$run = mysql_query($construct);
$foundnum = mysql_num_rows($run);
if ($foundnum==0)
echo "No results found.";
{
echo "$foundnum results found.<p><hr size='1'>";
while ($runrows = mysql_fetch_assoc($run))
{
//get data
$title = $runrows['title'];
$desc = $runrows['description'];
$url = $runrows['url'];
echo "<b>$title</b>
<b>$desc</b>
<a href='$url'>$url</a><p>";
You can replace your foreach with:
foreach($search_exploded as $search_each)
{
$str = mysql_real_escape_string(substr($search_each, 0, 4));
//construct query
$x++;
if ($x==1) $construct .= "keywords LIKE '$str%'";
else $construct .= " OR keywords LIKE '$str%'";
}
To modify your existing code to only use the first 4 words, you just need to change your loop a bit:
Change this-
foreach($search_exploded as $search_each) {
To this -
for($i = 0; $i < min(count($search_exploded), 4); ++$i) {
$search_each = $search_exploded[$i];
Add the following code to the top of your foreach loop:
if(strlen($search_each) > 4) $search_each = substr($search_each, 0, 4);
Like this:
$search_exploded = explode(" ", $search);
foreach($search_exploded as $search_each)
{
if(strlen($search_each) > 4) $search_each = substr($search_each, 0, 4);
// your code
}
This code will search for first 4 characters in every word if that word is longer than 4.