I was trying to make a search engine so far I had done with it and the feature I lack in it was, when user queries a long sentence or two or three words with on not in line-wise in my DB is not going to show and it shows 0 results.
So far it is working fine with the one-word search it is doing a great job with that.
public function getResultsHtml($page, $pageSize, $term){
$fromLimit = ($page - 1) * $pageSize;
//page 1: (1-1) * 20
//page 2:(2-1) * 20
$query = $this->con->prepare("SELECT *
FROM sites WHERE title LIKE :term
OR url LIKE :term
OR keywords LIKE :term
OR description LIKE :term
ORDER BY clicks DESC
LIMIT :fromLimit,:pageSize");
$searchTerm = "%" . $term ."%";
$query->bindParam(":term",$searchTerm);
$query->bindParam(":fromLimit",$fromLimit,PDO::PARAM_INT);
$query->bindParam(":pageSize",$pageSize,PDO::PARAM_INT);
$query->execute();
$resultsHtml ="<div class='siteResults'>";
while($row = $query->fetch(PDO::FETCH_ASSOC)){
$id = $row["id"];
$url = $row["url"];
$title = $row["title"];
$description = $row["description"];
$title = $this->trimField($title,55);
$description = $this->trimField($description,230);
$resultsHtml .="<div class='resultContainer'>
<h3 class='title'>
<a class='result' href='$url' data-linkId='$id'>
$title
</a>
</h3>
<span class='url'>$url</span>
<span class='description'>$description</span>
</div>";
}
$resultsHtml .= "</div>";
return $resultsHtml;
}
So when user searches apple it is retrieving the data and we can see the search result in "apple store search" in the first image.But in second image when we search "apple search" it should be able to show us the "apple store search".
first image
Second image
First you need to breakdown your $term into separated words. With European languages, you can simply use explode:
<?php
$terms = explode(' ', $term);
// lets say your $term is 'apple orange lemon banana'
// $terms would be ['apple', 'orange', 'lemon', 'banana']
Prepare Terms List for Binding
Then, I'd build a key-value array for the terms binding:
<?php
// build a term list with term key (:term{number}) and associated term
// for binding
$term_params = [];
foreach ($terms as $key => $term) {
$term_params[":term{$key}"] = "%{$term}%";
}
// $term_params would be:
// [
// ':term0' => '%apple%',
// ':term1' => '%orange%',
// ':term2' => '%lemon%',
// ':term3' => '%banana%',
// ]
Prepare the Where Clause in SQL for Binding
Now, supposed the logics is all the terms need to show up in either of the target fields:
<?php
// a list of fields to search from
$fields = ['title', 'url', 'keywords', 'description'];
// build a where clause SQL based on the terms and fields
$or_where_clauses = [];
foreach (array_keys($term_params) as $term_key) {
$or_where_clauses[] = implode(' OR ', array_map(function ($field) use ($term_key) {
return "{$field} LIKE {$term_key}";
}, $fields));
}
$where_clauses_sql = implode(' AND ', array_map(function ($term_clause) {
// quote each term clause with bracket for proper OR logic to work
return '(' . $term_clause . ')';
}, $or_where_clauses);
The resulting where clauses sql would be:
(title like :term0 OR url like :term0 OR keywords like :term0 OR description like :term0)
AND
(title like :term1 OR url like :term1 OR keywords like :term1 OR description like :term1)
AND
...
...
(title like :termN OR url like :termN OR keywords like :termN OR description like :termN)
Putting Everything Together
I can then build the SQL string and bind the terms to the query accordingly:
<?php
// prepare the select statement
$query = $this->con->prepare("SELECT *
FROM sites WHERE {$where_clauses_sql}
ORDER BY clicks DESC
LIMIT :fromLimit,:pageSize");
// bind the terms
foreach ($term_params as $key => $value) {
$query->bindParam($key, $value);
}
$query->bindParam(":fromLimit", $fromLimit, PDO::PARAM_INT);
$query->bindParam(":pageSize", $pageSize, PDO::PARAM_INT);
$query->execute();
// ...
Shortcomings and Potential Solution
Please note that this approach does not understand the number of occurrence of terms in the field. And the way to separate words is far from perfect. For example:
It didn't handle punctuation marks.
It couldn't search for plural terms with the singular, or visa verse.
It could be matching part of a word by mistake (e.g. search "sing" and match "dressing").
It couldn't deal with CJK or languages without space).
You can use software like Elastic Search for better feature. With plugin and proper config, you can give even sort results with relevance, or give different field a different importance in the search process etc.
But that is an entirely different software than SQL server to use. You'd need to learn and plan how to index your contents there as well as just saving your data to SQL.
we dont know how u get search words from user, but as i guess u get them as an array. so you can try below code:
<?php
...
$textSearch = "";
for($i = 0 ; $i< count($userInputs);$i++){
if($i !== count($userInputs) -1){
$userInputData = $userInputs[$i];
$textSearch .= "'%$userInputData%' OR";
}else{
$textSearch .= "'%$userInputData%'";
}
}
and put $textSearch into your query.
remember , $userInputs is an array that u had before.
UPDATE
as your images shown, you can add $userInput = explode(" ",$textFromUser) in very begin of given code.
Related
I have in mysql database table column keywords there are csv keywords like "hotel, new hotel, good hotel".
Now when user enter hotel it works(select data) but not for hotels(it shouldn't). Now I want user enter hotels then it should also match hotel keyword.
In-short with suffix search should work. currently i implemented following.
$queried = trim(mysqli_real_escape_string($con,$_POST['query']));
$keys = explode(" ",$queried);
$sql = 'SELECT name FROM image WHERE keyword LIKE "%$queried%"';
foreach($keys as $k){
$k= trim(mysqli_real_escape_string($con,$k));
if(count($keys) > 1)
{
$sql .= ' OR keyword LIKE "%$k%" ';
}
}
you'd have to (additionally) ask whether the search term contains any of the words in the rows. Currently you're doing the opposite (which is fine for the opposite situation, so don't get rid of it)
Something like:
$sql = 'SELECT name FROM image WHERE $queried LIKE "%" + keyword + "%"';
(Apologies if MySQL syntax isn't quite right, not used it for a while).
It might occasionally throw up unwanted things though, e.g. if the user wrote "aparthotel" it'd still return "hotel", you may or may not want that. Or it could even something entirely irrelevant depending on the words involved.
Once you get onto anything more complex than that though, you're probably into the realms of search engines and natural language processing.
i did this way it's not what i want but it works for my criteria.
$suffix = array('','s','es','ing','ment'); // suffix you want to ad
$sql = 'SELECT name FROM image WHERE keyword LIKE "%$queried%"';
foreach($keys as $k)
{
$k= trim(mysqli_real_escape_string($con,$k));
for ($i=1; $i < sizeof($suf) ; $i++)
{
if(substr($k, (-1 * strlen($suf[$i])))==$suf[$i])
{
$wp=substr( $k, 0, (-1 * $i));
}
}
if($wp!="")
{
$sql .= " OR keyword LIKE '%$k%' OR keyword LIKE '%$wp%' ";
}
}
My code let me perform search, as long as the order of the words is correct.
Let's say I'm searching for big dog, but I also want to search for dog big. It get more complicated with 3 or more words.
Is there a way to create a SQL query which would let me search through values with any order?
Only way I can think of this is by having multiple queries, where I change order of PHP variables manually...
<?php
if(isset($_GET['query']) && !empty($_GET['query'])) {
$query = $_GET['query'];
$query_array = explode(' ', $query);
$query_string = '';
$query_counter = 1;
foreach($query_array as $word) {
$query_string .= '%' . $word . (count($query_string) == $query_counter++ ? '%' : '');
}
$query = "SELECT * FROM pages WHERE Name LIKE '$query_string'";
$result = sqlsrv_query($cms->conn, $query);
while($row = sqlsrv_fetch_array($result)) {
extract($row);
echo ''.$Name.'<br>';
}
sqlsrv_free_stmt($stmt);
}
else {
//echo 'NO GET';
}
?>
You could assemble your conditions and check for each word on it's own:
$query_array = explode(' ', $query);
$queryParts = array();
foreach ($query_arra AS $value){
$queryParts[]="Name like '%".mysql_real_escape_string($value)."%'";
}
$searchString = implode(" AND ", $queryParts);
The Search string would now be Name like '%big%' AND Name like '%dog%' ... depending on how much search-keywords have been there.
I use the same approach very often, also when it is required that ALL keywords appear in at least ONE of the columns. Then you need one more loop to create the required AND conditions:
$search = "Big Dog";
$keywords = explode (" ", $search);
$columns = array("Name", "description");
$andParts = array();
foreach ($keywords AS $keyword){
$orParts = array();
foreach($columns AS $column){
$orParts[] = $column . " LIKE '%" . mysql_real_escape_string($keyword) . "%'";
}
$andParts[]= "(" . implode($orParts, " OR ") . ")";
}
$and = implode ($andParts, " AND ");
echo $and;
this would produce the query part (Name like '%Big%' OR description like '%Big%') AND (Name like '%Dog%' or description like '%Dog%')
So, it will find any row, where dog and big are appearing in at least one of the columns name or description (could also be both in one column)
Since your original querystring is something like %big%dog%, so I assume you are okay with matching big wild dog. In this case, you can just use the AND operator.
(Name LIKE '%big%" and Name LIKE '%dog%")
myisam supports full text search:
http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html
One thing you could look into is Full Text Search for ms sql server.
https://msdn.microsoft.com/en-us/library/ms142571.aspx
it's similar to a "search engine" in that it works off of an algorithm to rank results and even similar words (think thesaurus type lookups)
It's not exactly trivial to set up, but it's easy enough to find a tutorial on the subject and how to query from FTS (as the syntax is different than say LIKE '%big%dog%')
Here's a sample query from the page linked above:
SELECT product_id
FROM products
WHERE CONTAINS(product_description, ”Snap Happy 100EZ” OR FORMSOF(THESAURUS,’Snap Happy’) OR ‘100EZ’)
AND product_cost < 200 ;
I have a problem in creating advanced search with custom query and using $wpdb->get_results($query , OBJECT);
In Normal search in wordpress when we search xxx yyyy or search yyyy xxx we have same results and it's good.
But when I am forced to use query to create an advanced search then sequence of words in search fields are important and further xxx yyyy or search yyyy xxx aren't same result.
I want to say with an example:
I create two input field one for Title and another for Author of my posts(Author is an example only and in this place is a custom fields )
I try to read these fields and search them in wordpress
<?php
$t = $_REQUEST['title'];
$a = $_REQUEST['author'];
global $wpdb;
$query = "SELECT DISTINCT wp_posts.* FROM wp_posts, wp_postmeta WHERE wp_posts.ID = wp_postmeta.post_id";
if ($t != '') {
$t_sql = " AND wp_posts.post_title like '%$t%' ";
}
if ($a != '') {
$a_sql = " AND wp_postmeta.meta_key = 'Author' AND wp_postmeta.meta_value like '%$a%' ";
}
$query .= $t_sql;
$query .= $a_sql;
$pageposts = $wpdb->get_results($query , OBJECT);
global $post;
if ($pageposts):
foreach ($pageposts as $post):
setup_postdata($post);
//...
endforeach;
endif;
?>
In Your idea, What do I must to do?
You can split up your search terms by a Space character and then construct your query to look up every possible order of the words. Here is an example for your Title field:
// Assuming the title is "One Two Three"
$t = $_REQUEST['title'];
// Split the TITLE by space character
$terms = explode(' ', $t); // $terms = ["One", "Two", "Three"]
// Concats each search term with a LIKE operator
$temp = array();
foreach ($terms as $term) {
$temp[] = "title LIKE '%".$term."%'";
// $temp = ["title LIKE %One%", "title LIKE %Two%", ...
}
// Adds an AND operator for each $temp to the query statement
$query = "SELECT * FROM titleTable WHERE (".implode(' AND ', $temp).")";
// $query = SELECT * FROM titleTable WHERE
// (title LIKE '%One%' AND title LIKE '%Two%' AND title LIKE '%Three%')
Making custom query which search in the WP DB is not a good way. Use the WP_Query to do this.
Here is a link where someone was experiencing the same problem:
https://wordpress.stackexchange.com/questions/18703/wp-query-with-post-title-like-something
Actually, I've review your code on web developer portals, specially the SQL queries. And I also didn't knew why by searching xxx yyyy and yyyy xxx aren't same result with your PHP script. But the only tips that I can give to you:
$query .= $t_sql;
$query .= $a_sql;
// is to add an sql order by keyword like this
$query .= " ORDER BY wp_posts . post_title ";
Give it a try! And don't forget to addslashes() when you used $_GET, $_POST or $_COOKIE variable if the PHP of your server doesn't runs addslashes() on those variables. You can check this by using the function get_magic_quotes_gpc().
i have a variable and an user_name i want to search on a string(function_description) of the user_name for it
whats wrong with this :
$function_keywords = mysql_real_escape_string($_POST['function_keywords']);
if($function_keywords=="" || empty($function_keywords)){
redirect("show.php?functions=PHP");
}
//trim whitespace from the stored variable
$trimmed = trim($function_keywords);
//separate key-phrases into keywords
$trimmed_keywords = explode(" ",$trimmed);
// Build SQL Query for each keyword entered
foreach ($trimmed_keywords as $trimm){
// MySQL "MATCH" is used for full-text searching.
//this code is ebv weird , should check out soon!
$query = "SELECT *
FROM functions
WHERE isEnabled=1 AND isPrivate=0
AND function_description LIKE '{$trimm}'
AND user_name='{$user_name}'
";
// Execute the query to get number of rows that contain search kewords
$results=mysql_query ($query,$connection);
as far as "like" syntax goes you have to use the '%' symbol. if you query for
select * from table where column like '%yourkeyword%'
then it returns any rows with 'yourkeyword' inside the table column.
your statement will be true only if the column = 'yourkeyword'
That's highly inefficient. If someone puts in 5 keywords, you'd be running the search 5 times and getting 5 sets of results. Try something more along these lines:
$words = $_POST['function_keywords'];
if ($words == '') {
... abort ...
}
$parts = trim(explode(' ', $words));
$clauses = array();
foreach($parts as $part) {
$clauses[] = "function_description LIKE '%" . mysql_real_escape_string($part) . "%'";
}
$clause = implode(' OR ' , $clauses);
$sql = "SELECT .... WHERE (isEnabled=1) AND (isPrivate=1) AND (user_name='$user_name') AND ($clause)";
$result = mysql_query($sql) or die(mysql_error());
This'll build up a long series of or statements for each keyword specified, and run the whole thing as a single query.
To see if the function_description contains the keyword you need to use '%' which stands for anything much the way '*' does in unix. Try function_description LIKE '%{$trimm}%'
Quick MYSQL/PHP question. I'm using a "not-so-strict" search query as a fallback if no results are found with a normal search query, to the tune of:
foreach($find_array as $word) {
clauses[] = "(firstname SOUNDS LIKE '$word%' OR lastname SOUNDS LIKE '$word%')";
}
if (!empty($clauses)) $filter='('.implode(' AND ', $clauses).')';
$query = "SELECT * FROM table WHERE $filter";
Now, I'm using PHP to highlight the results, like:
foreach ($find_array as $term_to_highlight){
foreach ($result as $key => $result_string){
$result[$key]=highlight_stuff($result_string, $term_to_highlight);
}
}
But this method falls on its ass when I don't know what to highlight. Is there any way to find out what the "sound-alike" match is when running that mysql query?
That is to say, if someone searches for "Joan" I want it to highlight "John" instead.
Note that SOUNDS LIKE does not work as you think it does. It is not equivalent to LIKE in MySQL, as it does not support the % wildcard.
This means your query will not find "John David" when searching for "John". This might be acceptable if this is just your fallback, but it is not ideal.
So here is a different suggestion (that might need improvement); first use PHPs soundex() function to find the soundex of the keyword you are looking for.
$soundex = soundex($word);
$soundexPrefix = substr($soundex, 0, 2); // first two characters of soundex
$sql = "SELECT lastname, firstname ".
"FROM table WHERE SOUNDEX(lastname) LIKE '$soundexPrefix%' ".
"OR SOUNDEX(firstname) LIKE '$soundexPrefix%'";
Now you'll have a list of firstnames and lastnames that has a vague similarity in sounding (this might be a lot entries, and you might want to increase the length of the soundex prefix you use for your search). You can then calculate the Levenshtein distance between the soundex of each word and your search term, and sort by that.
Second, you should look at parameterized queries in MySQL, to avoid SQL injection bugs.
The SOUND LIKE condition just compares the SOUNDEX key of both words, and you can use the PHP soundex() function to generate the same key.
So, if you found a matching row and needed to find out which word to highlight, you can fetch both the firstname and lastname, and then use PHP to find which one matches and highlight just that word.
I made this code just to try this out. (Had to test my theory xD)
<?php
// A space seperated string of keywords, presumably from a search box somewhere.
$search_string = 'John Doe';
// Create a data array to contain the keywords and their matches.
// Keywords are grouped by their soundex keys.
$data = array();
foreach(explode(' ', $search_string) as $_word) {
$data[soundex($_word)]['keywords'][] = $_word;
}
// Execute a query to find all rows matching the soundex keys for the words.
$soundex_list = "'". implode("','", array_keys($data)) ."'";
$sql = "SELECT id, firstname, lastname
FROM sounds_like
WHERE SOUNDEX(firstname) IN({$soundex_list})
OR SOUNDEX(lastname) IN({$soundex_list})";
$sql_result = $dbLink->query($sql);
// Add the matches to their respective soundex key in the data array.
// This checks which word matched, the first or last name, and tags
// that word as the match so it can be highlighted later.
if($sql_result) {
while($_row = $sql_result->fetch_assoc()) {
foreach($data as $_soundex => &$_elem) {
if(soundex($_row['firstname']) == $_soundex) {
$_row['matches'] = 'firstname';
$_elem['matches'][] = $_row;
}
else if(soundex($_row['lastname']) == $_soundex) {
$_row['matches'] = 'lastname';
$_elem['matches'][] = $_row;
}
}
}
}
// Print the results as a simple text list.
header('content-type: text/plain');
echo "-- Possible results --\n";
foreach($data as $_group) {
// Print the keywords for this group's soundex key.
$keyword_list = "'". implode("', '", $_group['keywords']) ."'";
echo "For keywords: {$keyword_list}\n";
// Print all the matches for this group, if any.
if(isset($_group['matches']) && count($_group['matches']) > 0) {
foreach($_group['matches'] as $_match) {
// Highlight the matching word by encapsulatin it in dashes.
if($_match['matches'] == 'firstname') {
$_match['firstname'] = "-{$_match['firstname']}-";
}
else {
$_match['lastname'] = "-{$_match['lastname']}-";
}
echo " #{$_match['id']}: {$_match['firstname']} {$_match['lastname']}\n";
}
}
else {
echo " No matches.\n";
}
}
?>
A more generalized function, to pull out the matching soundex word from a strings could look like:
<?php
/**
* Attempts to find the first word in the $heystack that is a soundex
* match for the $needle.
*/
function find_soundex_match($heystack, $needle) {
$words = explode(' ', $heystack);
$needle_soundex = soundex($needle);
foreach($words as $_word) {
if(soundex($_word) == $needle_soundex) {
return $_word;
}
}
return false;
}
?>
Which, if I am understanding it correctly, could be used in your previously posted code as:
foreach ($find_array as $term_to_highlight){
foreach ($result as $key => $result_string){
$match_to_highlight = find_soundex_match($result_string, $term_to_highlight);
$result[$key]=highlight_stuff($result_string, $match_to_highlight);
}
}
This wouldn't be as efficient tho, as the more targeted code in the first snippet.