SQL: Query to Search All possible words - php

I have a form with an input box where the user can specify their names, the names may be two or more words, eg John Smith or John Michael Smith.
I have to write a query that returns records containing all words in the name presented. So query will return records that has the name of all those words, but may have different order.
For example, if the search string is John Michael Smith, the query should be able to return records with names like Michael John Smith, Michael Smith John, John Smith Michael or some combination of all these words there. As can be seen only return records that still has all the words in the name field, but may have different order. However, it should return results that do not contain part of the name, for example, John Michael should not be returned because it does not have Smith in there.
I tried query like this:
SELECT id, name FROM users WHERE
name LIKE '%Michael%' &&
name LIKE '%Smith%' &&
name LIKE '%John%'
The same problem happens with:
SELECT * FROM users WHERE MATCH (name)
AGAINST ('+Michael +Smith +John' IN BOOLEAN MODE);
Both queries also returns John Michael instead of records that contain all three words :(
I can not figure out how to write a query to the requirement so that I have. Please help.

Use a fulltext index. That's the easiest/quickest method. Otherwise you're stuck parseing your search string, converting
John Michael Smith
into
SELECT ... WHERE (name LIKE '%John%') OR (name LIKE '%Michael%') OR (name LIKE '%Smith%')
which very quickly gets painful to do, and VERY slow to perform, as LIKE %..% searches cannot use indexes.
Note that Fulltext indexes are currently restricted to MyISAM tables, so if you're on InnoDB, you'll have to use workarounds (parallel tables in MyISAM format to hold the searchable data with triggers to keep the two copies synchronized). Apparently Fulltext for InnoDB is FINALLY in the development pipeline, but it's not here yet.

If you save the name in the field 'name' your sql query should be like this
first explode the string by space
$params = explode(' ', $input_name);
then append this on your query:
$i = 0;
$q= '';
foreach($params as $p){
$p = strtolower($p);
if($i > 0) $q.= " and ";
$q.= "LOWER(name) like '%$p%'";
$i++;
}
$query="select .... where ".$q;
the lower things are needed to make it case insensitive.

Whatever variable you are setting up as the value of this input, you want to use explode on:
http://php.net/manual/en/function.explode.php
$names = explode(' ', $userinput);
with a space as your delimiter. This will give you an array with the 3 different elements (or however many there are).
$names[0] = "John";
$names[1] = "Michael"
$names[2] = "Smith";
Now you need to run them through strtolower so that case is not an issue.
http://us3.php.net/manual/en/function.strtolower.php
foreach ($names as $name)
{
$name = strtolower($name);
}
Now we have
$names[0] = "john";
$names[1] = "michael"
$names[2] = "smith";
Next you need to build a function that reassembles these variables in all possible orders. But because you might have 1,2,3, or 4 elements in your array, you don't know how many queries you will need. So you check:
$numberofnames = count($names);
Then create a function to build your different name combinations:
$namecombo = "";
function createNames($numberofnames)
{
for($i=0; $i <= $Numberofnames; $i++)
{
$namecombos[i] .= $names[i];
}
So. This is an incomplete answer because here we run into a problem. You need a for loop to generate the name combos, but you also need a for loop generating how many for loops you need. Not something I can think of a way to code. But... Hope this gets you closer to an answer.
for($i=0; $i <= $Numberofnames; $i++)
{
$secondnamecombo[i] = $names[$i - 1]
etc...
}
}

Related

PHP SQL Match like value from array

So I am using PHP and SQL (PDO) to write a query that would take values from an array of check box's (names[]) so for instance someone could submit the values "Bob", "John" and "Bill" and I want to match those with a column in the database.
Problem: Database is an import from Excel spreadsheet used for years and there is no set format for the column I match with, some examples of values stored the database could look like any of the following:
"Bill & Bob"
Bill
John and Bill
"Bill"
John, Bill, and Bob
John will perform the task
As you can see I need to use Like %value% to match values because The post request will send just Bill, John and Bob. My problem is how do I do it with the array passed because it could be just John or it could be 2 or all 3, but you can't use something like WHERE column LIKE in(array) correct? Would my best bet be to run the query once for each name and append the results to a single array? I know I could make some sort of dynamic statement that is something like:
WHERE column LIKE '%Bob%' OR
column LIKE '%John%' OR
column LIKE '%Bill%';
My problem doing it that way is that the above method is susceptible to SQL injection if the input isn't satisfied correct? Even if I use a bindValue() and make each one a bindable variable then I have to figure out how many binds there are and I feel like if I have to add names in the future it would be more difficult than it should be. Is the best way to do it what I said above about one query executed multiple times for, once for each name and then append the results together?
Thanks in advance.
EDIT:
Driver is PDO, Engine is MSSQL
You can do it like this.
//Prepare an array with names
$names = [
':name_1' => '%Bob%',
':name_2' => '%Bill%',
];
//build the where
$whereQuery = '';
foreach($names as $bind => $value) {
if(empty($whereQuery)) $whereQuery .= ' OR ';
$whereQuery .= ' column LIKE '.$bind;
}
//here is missing the code with SQL and pdo preparing query
//after preparing query just execute with $names
$sth->execute($names);
Be careful this example provide only code helping you to figure out the solution.
Hope this helps
You should definitely normalize your database.
But just quick approach and since php was mentioned you can generate query like
$names = ['John','Bob','Bill'];
$query = 'SELECT * FROM my_weird_table WHERE ';
$first = true;
foreach ($names as $name) {
if ($first) {
$query .= '`column` LIKE "%'.$name.'%" ';
$first = false;
} else {
$query .= 'OR `column` LIKE "%'.$name'.'%" ';
}
}

Function that Checks if a string exists until it finds the one that doesn't exist

I'm creating a simple lottery script.
The idea is that in one lottery there could be a few winners and I'm having troubles with checking if a new winner is a person who already won in this lottery.
I store this kind of data in DB.
list [longtext] - column with a list of contestants (separated with spaces or comas)
winner [longtext] - column with a list of winners in this lottery (separated with spaces)
My loop:
//$won_this is person who won in this round
$old_winners = $draw[winner];
$czy = strpos($old_winners, "$won_this");
while($czy == FALSE)
{
$add_winner = $won_this;
}
$sql = "update `draws` set `winner`= concat(winner, ' $add_winner') where code='$draw['number']'";
mysql_query($sql) or die(mysql_error());
My loop doesn't work. It will loop forever or not at all. I have no idea how to write this.
How can I create a loop that runs when a winner is duplicated and works until the new winner is found?
The first thing I would do is convert the old winners into an array:
$winners = explode(' ', $draw['winner']);
Then I would add the new winner to the array:
$winners[] = $won_this;
And finally I would call array_unique on the array to ensure uniqueness and then convert the array back into a string to be inserted into the database:
$winners_string = implode(' ', array_unique($winners));
$stmt = $connection->prepare("update `draws` set `winner`= ? where code = ?");
// Use bing_param('si'...) if $draw['number'] is an integer, not a string
$stmt->bind_param('ss', $winners_string, $draw['number']);
$stmt->execute();
Although ideally, and as mentioned in the comments to your question, there are better ways to store the data, e.g. have a new table with a draw_number column and a winner column and simply add a new row for each winner.
$czy is always false so nothing will happen in this script. It is always false because you are using the wrong syntax to search the array. Change your solution for checking your array Michael example is correct. Try it

MySQL query that can handle missing characters

I'm trying to improve my MySQL query.
SELECT gamename
FROM giveaway
WHERE gamename LIKE '$query'
I got an input that consists of URL's that are formed like:
http://www.steamgifts.com/giveaway/l7Jlj/plain-sight
http://www.steamgifts.com/giveaway/okjzc/tex-murphy-martian-memorandum
http://www.steamgifts.com/giveaway/RqIqD/flyn
http://www.steamgifts.com/giveaway/FzJBC/penguins-arena-sednas-world
I take the game name from the URL and use this as input for a SQL query.
$query = "plain sight"
$query = "tex murphy martian memorandum"
$query = "flyn"
$query = "penguins arena sednas world"
Now in the database the matching name sometimes has more characters like : ' !, etc.
Example:
"Plain Sight"
"Tex Murphy: Martian Memorandum"
"Fly'N"
"Penguins Arena: Sedna's World!"
So when putting in the acquired name from the URL this doesn't produce results for the 2nd, 3rd and 4th example.
So what I did was use a % character.
$query = "plain%sight"
$query = "tex%murphy%martian%memorandum"
$query = "flyn"
$query = "penguins%arena%sednas%world"
This now gives result on the 1st and 2nd example.
.
On to my question:
My question is, how to better improve this so that also the 3rd and 4th ones work?
I'm thinking about adding extra % before and after each character:
$query = "%f%l%y%n%"
$query = "%p%e%n%g%u%i%n%s%a%r%e%n%a%s%e%d%n%a%s%w%o%r%l%d%"
But I'm not sure how that would go performance wise and if this is the best solution for it.
Is adding % a good solution?
Any other tips on how to make a good working query?
Progress:
After a bit of testing I found that adding lots of wildcards (%) is not a good idea. You will get returned unexpected results from the database, simply because you just added a lot of ways things could match.
Using the slug method seems to be the only option.
If i get your question well, you are creating a way of searching through those informations. And if that is the case then try
$query = addslashes($query);
SELECT name
FROM giveaway
WHERE gamename LIKE '%$query%'
Now if you want to enlarge your search and search for every single word that looks like the words in your string, then you can explode the text and search for each word by doing
<?php
$query = addslashes($query);
//We explode the query into a table
$tableau=explode(' ',$query);
$compter_tableau=count($tableau);
//We prepare the query
$req_search = "SELECT name FROM giveaway WHERE ";
//we add the percentage sign and the combine each query
for ($i = 0; $i < $compter_tableau; $i++)
{
$notremotchercher=$tableau["$i"];
if($i==$compter_tableau) { $liaison="AND"; } else { $liaison=""; }
if($i!=0) { $debutliaison="AND"; } else { $debutliaison=""; }
$req_search .= "$debutliaison gamename LIKE '%$notremotchercher%' $liaison ";
}
//Now you lauch your query here
$selection=mysqli_query($link, "$req_search") or die(mysqli_error($link));
?>
By so doing you would have added the % to every word in your query which will give you more result that you can choose from.

PHP Search Using Multiple Words

I am trying to create a search function where a user can input two words into a text field and it will split the words and construct a MySQL query.
This is what I have so far.
$search = mysql_real_escape_string( $_POST['text_field']);
$search = explode(" ", $search);
foreach($search as $word)
{
$where = "";
$where .= "product_code LIKE '%". $word ."%'";
$where .= "OR description LIKE '%". $word ."%'";
$query = "SELECT * FROM customers WHERE $where";
$result = mysql_query($query) or die();
if(mysql_num_rows($result))
{
while($row = mysql_fetch_assoc($result))
{
$customer['value'] = $row['id'];
$customer['label'] = "{$row['id']}, {$row['name']} {$row['age']}";
$matches[] = $customer;
}
}
else
{
$customer['value'] = "";
$customer['label'] = "No matches found.";
$matches[] = $customer;
}
}
$matches = array_slice($matches, 0, 5); //return only 5 results
It constructs and runs the query, but returns funny results.
Any help would be appreciated.
MySQL has something called LIMIT, so you last row would be needless.
Use Full-Text-Search for this: http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html - It's faster and more elegant
If your database is on MyISAM table format you could do a Fulltext search on the columns you are interested as Sn0opy mentioned already
Personally I believe that when it comes to mySQL if you actually want to create a great search engine use Sphinx (http://sphinxsearch.com/) or Solr (http://lucene.apache.org/solr/)
There may be a learning curve on both of them, but the results are professional.
Any chance of anything more specific than "funny results"? Off the cuff there are several possibilities but it really depends upon the results that are being returned. My PHP is a bit rusty so I will apologize up front if my brain throws in some java rules instead, but at first blush...
Name the array something other than $search. It probably isn't the problem, but it looks odd to have the array created by explode() carry the name of the string being exploded. Try something like $searched = explode(" ", $search); and then use $searched in the subsequent foreach() loop.
What if the user only puts in one search term? If there is no space in $text_field then explode will return an empty array, which should thoroughly jack up your query. You should at least verify that there is a space in $text_field before exploding $search. Likewise, what if the user enters two search terms, but one of the terms is two words separated by a space? Again you are going to get "funny results" because you will get results that you don't want along with duplicated results as the query extends itself to both of the words in a term individually.
Without knowing more of what you mean by "funny results" it is really difficult to trouble shoot this one.

Specialized Search Query Refinement for Auto-Complete function

I am doing a query for an autocomplete function on a mysql table that has many instances of similar titles, generally things like different years, such as '2010 Chevrolet Lumina' or 'Chevrolet Lumina 2009', etc.
The query I am currently using is:
$result = mysql_query("SELECT * FROM products WHERE MATCH (name) AGAINST ('$mystring') LIMIT 10", $db);
The $mystring variable gets built as folows:
$queryString = addslashes($_REQUEST['queryString']);
if(strlen($queryString) > 0) {
$array = explode(' ', $queryString);
foreach($array as $var){
$ctr++;
if($ctr == '1'){
$mystring = '"' . $var . '"';
}
else {
$mystring .= ' "' . $var . '"';
}
}
}
What I need to be able to do is somehow group things so only one version of a very similar actually shows in the autosuggest dropdown, leaving room for other products with chevrolet in them as well. Currently it is showing all 10 spots filled with the same product with different years, options, etc.
This one should give some of you brainiacs a good workout :)
I think the best way to do this would be to create a new field on the products table, something like classification. All the models would be entered with the same classification (e.g. "Chevrolet"). You could then still MATCH AGAINST name, but GROUP BY classification. Assuming you are using MySQL you can cheat a little and get away with selecting values and matching against values that you are not grouping by. Technically in SQL this gives undefined results and many SQL engines will not even let you try to do this, but MySQL lets you do it -- and it returns a more-or-less random sample that matches. So, for example, if you did the above query, grouped by classification, only one model (picked pretty much at random) will show up in the auto-completer.

Categories