MATCH ... AGAINST doesn't come up with exact results - php

I'm working for the first time with MATCH...AGAINST in php sql but there is one bothering me and I can't figure out how to fix it. This is my code:
SELECT * FROM m_artist WHERE match(artist_name) against('". $_POST['article_content'] ."' IN BOOLEAN MODE)
And this is $_POST['article_content']:
Wildstylez Brothers Yeah Frontliner Waveliner
Now my output should be: Wildstylez, Frontliner and Waveliner cause that's in my database. And I do but besides that I also get the Vodka Brothers, 2 Brothers of Hardstyle and more cause of the word brothers. How do I fix that SQL only selects the literal match?

Full-text search actually is a quite misleading name: you can search the full text by your query (like google does) but it won't guarantee you, that the full text equals your query.
So, according to documentation on Boolean Full-Text Searches your input Wildstylez Brothers Yeah Frontliner Waveliner is interpreted as artist_name contains (at least) one of Wildstylez, Brothers, Yeah, Frontliner and Waveliner as word. This is why you get e.g. the Vodka Brothers, which contains Brothers. For google-like purposes this is just what you want, as you want to get details on something you only know part of as in show me articles on music.
You probably want to use
artist_name LIKE '%name_part1%' OR artist_name LIKE '%name_part2%' ...
or
artist_name IN ('exact_name1', 'exact_name2', ...)
simpliest case would be doing something like
$names = explode(' ', $_POST['article_content']);
$name_searches = array_map(function($a) {return 'artist_name = \''.mysql_real_escape_string($a).'\'';}, $names);
$sql = "SELECT * FROM m_artist WHERE ".implode(" OR ", $name_searches);
but you would loose the ability to find 2 Brothers of Hardstyle as the name itself contains a space.
Another approach can be to prefix all words by '+' and stick to MATCH() AGAINST() and you will find only artists which include every word given.
Please provide more context if this is not what you are looking for.

Related

Two partial MySQL matches in a concat_ws?

Here's my use case: I'm searching for a person by first and last name, but only type in a partial first and partial last name, how can I create a WHERE clause that catches all possible scenarios?
Example, I type "Joe Smith" and it has a result. I type "Joe" and it has Joe Smith and a few other Joe's. I type "Joe Sm" and it gives me Joe Smith.
I want to be able to type "J Smit" and get Joe Smith, is that possible? Do I need to break the search term on spaces in PHP before doing a LIKE?
Here's what I have so far that works with full matches:
WHERE CONCAT_WS(' ', owner.first_name, owner.last_name)
LIKE '%". $searchTerm ."%'
Any help would be greatly appreciated.
Why don't you do an explode(' ',$input) on your input in PHP and then compare all values of that array in your WHERE clause?
$inputArray = explode(' ',$input);
foreach ($inputArray as $part)
{
$whereArray = "CONCAT_WS(' ',owner.first_name,owner.last_name) LIKE '%$part%'";
}
$where = implode(' AND ',$whereArray);
And then use it like this:
$query = "SELECT * FROM owner WHERE $where";
Please pay attention to security, I didn't do that.
This still doesn't quite do what you want. Because when you want to search for "J Smit" you want the system to be intelligent enough, to search one part, say "J" in the first name column and the other part "Smit" in the last name column. Clearly that's more complex, and the complexity increases with the number of parts to match. There is a solution for that, but you won't like it, it's ugly.
Has anybody got a, not so ugly, solution to this?
It sounds like you do want split the search term into a first and last name component, and then run LIKE comparisons against owner.first_name and owner.last_name separately. Unfortunately, I don't know of native mySQL support for straightforward string splitting.
Splitting in PHP first is certainly an option (the answer from #KIKOSoftware seems to do a good job of that). If you want to try to do it all in mySQL as an alternative, this SO question offers some insight (you will have to modify for your use case, since you're delimiting on white space instead of commas):
How to split the name string in mysql?

How do I search Full text with partial matches?

I have a table, with not many rows, and neither many columns. I am doing a Full text search on 3 columns.
My code is
$search_input = trim($_GET['s']);
$search = mysql_real_escape_string($search_input);
$search = '+'.str_replace(' ', '* +', $search).'*';
$sql = "SELECT * FROM table WHERE
MATCH(def, pqr, xyz) AGAINST ('$search' IN BOOLEAN MODE)";
$result = mysql_query($sql);
I can correctly search for terms like abcdefgh, which are present as ... abcdefgh ....
But I am receiving empty set with search terms like abc, where in table entry is present something like abc-123, and also terms like abcdefghs. (notice this is plural of above)
Clearly I need to implement partial search, or something like that.
But how do I implement such a search? Any better way to do a entire table search on user input?
Do mention anything I am doing incorrectly.
EDIT : By adding * after each word, now I am able to also search for abcde, but above problems remains.
Do you mean you don't get results for 3 letter combinations? If so, you might be hitting the mysql index length (which is usually set to 3)
More info here - http://dev.mysql.com/doc/refman/5.1/en/fulltext-fine-tuning.html

how to implement the an effective search algorithm when using php and a mysql database?

I'm new to web design, especially backend design so I have a few questions about implementing a search function in PHP. I already set up a MySQL connection but I don't know how to access specific rows in the MySQL table. Also is the similar text function implemented correctly considering I want to return results that are nearly the same as the search term? Right now, I can only return results that are the exact same or it gives "no result." For example, if I search "tex" it would return results containing "text"? I realize that there are a lot of mistakes in my coding and logic, so please help if possible. Event is the name of the row I am trying to access.
$input = $_POST["searchevent"];
while ($events = mysql_fetch_row($Event)) {
$eventname = $events[1];
$eventid = $events[0];
$diff = similar_text($input, $event, $hold)
if ($hold == '100') {
echo $eventname;
break;
else
echo "no result";
}
Thank you.
I've noticed some of the comments mentioned more efficient ways of performing the search than with the "similar text" function, if I were to use the LIKE function, how would it be implemented?
A couple of different ways of doing this:
The faster one (performance wise) is:
select * FROM Table where keyword LIKE '%value%'
The trick in this one is the placement of the % which is a wildcard, saying either search everything that ends or begins with this value.
A more flexible but (slightly) slower one could be the REGEXP function:
Select * FROM Table WHERE keyword REGEXP 'value'
This is using the power of regular expressions, so you could get as elaborate as you wanted with it. However, leaving as above gives you a "poor man's Google" of sorts, allowing the search to be bits and pieces of overall fields.
The sticky part comes in if you're trying to search names. For example, either would find the name "smith" if you searched SMI. However, neither would find "Jon Smith" if there was a first and last name field separated. So, you'd have to do some concatenation for the search to find either Jon OR Smith OR Jon Smith OR Smith, Jon. It can really snowball from there.
Of course, if you're doing some sort of advanced search, you'll have to condition your query accordingly. So, for instance, if you wanted to search first, last, address, then your query would have to test for each:
SELECT * FROM table WHERE first LIKE '%value%' OR last LIKE '%value%' OR address LIKE '%value'
Look at below example :
$word2compare = "stupid";
$words = array(
'stupid',
'stu and pid',
'hello',
'foobar',
'stpid',
'upid',
'stuuupid',
'sstuuupiiid',
);
while(list($id, $str) = each($words)){
similar_text($str, $word2compare, $percent);
if($percent > 90) // Change percentage value to 80,70,60 and see changes
print "Comparing '$word2compare' with '$str': ";
}
You can check with $percent parameter for how strong match you want to apply.

MySQL LIKE question

I have a script:
$friendnotes = mysql_query("SELECT nid,user,subject,message FROM friendnote
WHERE tousers LIKE '%$userinfo[username]%' ");
And the content in the "tousers" table of the database:
Test
Example
User
That script appears to be working well
However, if there is a user called "Test2", it would also display content that has "Test2" in the database where $userinfo[username] is just "Test"
Is there any way to fix that problem? For example (this is just an example, I don't mind if you give another way) make it so that it searches whole lines?
EDIT: I don't think anyone understands, the "tousers" table contains multiple values (seperated by line) not just one, I want it to search each LINE (or anything that works similiar), not row
The condition
tousers LIKE '%Test%'
means that touser contains "Test" at some point, so it is true for "Test","MyTest","Test3","MyTest3", and so on.
If you want only to match the current user, try
... WHERE tousers = '$userinfo[username]'
EDIT If you really want to store multiple names in one column (separated by newlines), you could use a REGEXP pattern like
WHERE tousers REGEXP '(^|\\n)($userinfo[username])($|\\n)'
Be aware to make sure that $userinfo[username] does not contain any regular-expression-like characters ('$', '^', '|', '(', etc.). Also (as mentioned in the comments above) this solution is suboptimal in terms of security/performance/etc: It would be better to model an 1:n-Relationship between the friendnote table and some friendnotes_user table ...
Ok, so it sounds like the tousers field can contain values like 'stuff test option whatever' and 'foo test2 something blah blah', and you want to match the first but not the second. In that case, you need to include the delimiters around your search term. Assuming the search term will always have a space before and either a space or comma after it, you could do something like:
... WHERE tousers LIKE '%[ ]$userinfo[username][ ,]%'
This will encounter problems, however, if your search term can occur at the beginning of the field (no space character before it) or at the end of the field (no delimiter after it). In that case, you might need to have multiple LIKE clauses.
This will work if you remove the % signs, which are what allow for pattern matching.
$friendnotes = mysql_query("SELECT nid,user,subject,message FROM friendnote
WHERE tousers LIKE '$userinfo[username]' ");
But the consensus seems to be that using equals will be faster. See https://stackoverflow.com/questions/543580/equals-vs-like.
So in that case, change to
$friendnotes = mysql_query("SELECT nid,user,subject,message FROM friendnote
WHERE tousers = '$userinfo[username]' ");
Edit - regarding your edit, that is not a really good design. If a user can have multiple "tousers" (ie a one-to-many relationship), that should be represented as a separate table tousers, where each row represents one "touser" and has a foreign key on the user id to match it with the friendnote table. But if you absolutely can't change your design, you might want to match like this:
WHERE tousers LIKE '%$userinfo[username]\n%' ");
ensuring that there is a line break immediately following the username.
From what I understand, you should just use strict comparison:
where tousers = 'whatever'
That is because tousers like %whatever% matches any row, in which the tousers field has 'whatever' anywhere in its content, so it matches 'whatever', '123whatever', 'whatever321' and '123whatever321'. I hope you get the idea.
So you only want to search for exact name matches? If so, just use an = and remove the % wildcards:
$friendnotes = mysql_query("SELECT nid,user,subject,message FROM friendnote
WHERE tousers = '$userinfo[username]' ");
This is a perfect usage case for the MySQL REGEXP operator.

Php/ MySql 'Advanced Search' Page

I'm working on an 'advanced search' page on a site where you would enter a keyword such as 'I like apples' and it can search the database using the following options:
Find : With all the words, With the
exact phrase , With at least one of
the words, Without the words
I can take care of the 'Exact phrase' by:
SELECT * FROM myTable WHERE field='$keyword';
'At least one of the words' by:
SELECT * FROM myTable WHERE field LIKE '%$keyword%';//Let me know if this is the wrong approach
But its the 'With at least one of the words' and 'Without the words' that I'm stuck on.
Any suggestions on how to implement these two?
Edit: Regarding 'At least one word' it wouldn't be a good approach to use explode() to break the keywords into words, and run a loop to add
(field='$keywords') OR ($field='$keywords) (OR)....
Because there are some other AND/OR clauses in the query also and I'm not aware of the maximum number of clauses there can be.
I would suggest the use of MySQL FullText Search using this with the Boolean Full-Text Searches functionality you should be able to get your desired result.
Edit:
Requested example based on your requested conditions ("Its just one field and they can pick either of the 4 options (i.e 1 word, exact words, at least 1 word, without the term).")
I am assuming you are using php based on your initial post
<?php
$choice = $_POST['choice'];
$query = $_POST['query'];
if ($choice == "oneWord") {
//Not 100% sure what you mean by one word but this is the simplest form
//This assumes $query = a single word
$result = mysql_query("SELECT * FROM table WHERE MATCH (field) AGAINST ('{$query}' IN BOOLEAN MODE)");
} elseif ($choice == "exactWords") {
$result = mysql_query("SELECT * FROM table WHERE MATCH (field) AGAINST ('\"{$query}\"' IN BOOLEAN MODE)");
} elseif ($choice == "atLeastOneWord") {
//The default with no operators if given multiple words will return rows that contains at least one of the words
$result = mysql_query("SELECT * FROM table WHERE MATCH (field) AGAINST ('{$query}' IN BOOLEAN MODE)");
} elseif ($choice == "withoutTheTerm") {
$result = mysql_query("SELECT * FROM table WHERE MATCH (field) AGAINST ('-{$query}' IN BOOLEAN MODE)");
}
?>
hope this helps for full use of the operators in boolean matches see Boolean Full-Text Searches
You could use
With at least one of the words
SELECT * FROM myTable WHERE field LIKE '%$keyword%'
or field LIKE '%$keyword2%'
or field LIKE '%$keyword3%';
Without the word
SELECT * FROM myTable WHERE field NOT LIKE '%$keyword%';
I'm not sure you could easily do those search options in a naive manner as the other two.
It would be worth your while implementing a better search engine if you need to support those scenarios. A simple one that could probably get you by is something along these lines:
When an item is added to the database, it is split up into the individual words. At this point "common" words (the, a, etc...) are removed (probably based on a common_words table). The remaining words are added to a words table if they are not already present. There is then a link made between the word entry and the item entry.
When searching, it is then a case of getting the word ids from the word table and the appropriate lookup of item ids in the joining table.
Search is notoriously difficult to do well.
You should Consider using a third party search engine using something like Lucene or Sphider.
Giraffe and Re0sless pooseted 2 good answers.
notes:
"SELECT * " sucks... only select the columns that you need.
Re0sless puts a "OR" between keywords.
- you should eliminate common words (" ","i","am","and"..etc)
- mysql has a 8kb i belive limit on the size of the query, so for really long SELECTS you should slipt it into separate queries.
- try to eliminate duplicate keywords (if i search for "you know you like it" the SELECT should basically only search for "you" once and elimnate common words as "it")
Also try to use "LIKE" and "MATCH LIKE" (see mysql man page) it could do wonders for "fuzzy" searches

Categories