PHP, MySQL: Making a better search? - php

I've made a search box feature that allows me to type in a word and then it finds any matches of the word in my database. However, it only finds EXACT matches. I'm looking for suggestions on how to make the search better.
The code below is what i currently use for searching the databases for users that might be matches for the user searching.
$search_keys = array('fname', 'lname', 'email' );
foreach ( $search_keys as $key )
{
$result = mysql_query( "SELECT id FROM users WHERE " . $key . " LIKE " . "\"{$str}\"" ) or die(mysql_error());
while ( $row = mysql_fetch_array( $result ) )
{
// Get the User
$tmp_user = new User();
$tmp_user->getUserById( $row['id'] );
// Add User to list of potential candidates
array_push($users, $tmp_user);
}
}

I see two points where you can improve your code fragment. But first of all take care that $str is properly formatted to be safely used in your SQL query. Otherwise you will run into a problem called SQL Injection. I assume that now for your code.
Use wildcards with the LIKE SQL function.
Search across the three fields with one SQL query.
Please see the example code which contains both suggestions. First the SQL query is build. There is only need to run one query for all (three) fields instead of one query per field. That's useful if you extend your search later.
/* build SQL query */
$conditions = array();
$search_keys = array('fname', 'lname', 'email' );
foreach ( $search_keys as $key )
{
$conditions[] = "{$key} LIKE \"%{$str}%\""; # Wildcard (%); [] works like array_push()
}
$query = sprintf('SELECT id FROM users WHERE (%s)', implode(' OR ', $conditions));
/* run SQL query */
$result = mysql_query($query) or die(mysql_error());
while ( $row = mysql_fetch_array( $result ) )
{
// Get the User
$tmp_user = new User();
$tmp_user->getUserById( $row['id'] );
// Add User to list of potential candidates
array_push($users, $tmp_user);
}

Well there is always the simplest method of adding the wildcard operator, so if they enter 'Jam' it would be
SELECT id FROM users WHERE fname LIKE '%Jam%'
etc...
EDIT: The point being it would return a match on JAMIE or JAMES or LogJam or (you get the idea), which means they don't need to remember the whole name, just some part of it.

I would suggest to use a fulltext database as solr or sphinx for this kind of behaviour.
If you can't or don't want to install it, you should add % to your code.
$result = mysql_query( "SELECT id FROM users WHERE " . $key . " LIKE " . "\"%{$str}%\"" ) or die(mysql_error());

you should try
$result = mysql_query( "SELECT id FROM users WHERE " . $key . " LIKE " . "%" . $str . "%" ) or die(mysql_error());

Related

SQL query not working but works in PHPMyAdmin

I have a web application and I'm trying to modify one of the queries. The query fetches information (from a table named voyage_list) and returns various fields.
I want to modify the query so that it is based on certain filters the user applies (which will be placed in the URL).
I can't get the query to work in the web application, but if I copy the query and execute it directly within PHPMyAdmin, it works fine.
$vesselFilter = $_GET['vesselFilter'];
$vesselArray = explode(',', $vesselFilter);
$arrayCount = count($vesselArray);
$sqlExtend = ' status = 1 AND';
foreach ($vesselArray as $value) {
$i = $i + 1;
$sqlExtend .= " vesselID = '$value'";
if ($i < $arrayCount){
$sqlExtend .= " OR";
}
}
$newQuery = "SELECT * FROM voyage_list WHERE" . $sqlExtend;
echo $newQuery;
$query = $db->query($newQuery)->fetchAll();
I appreciate the above is pretty messy, but it's just so I can try and figure out how to get the query to work.
Any help would be greatly appreciated!
Thanks
That query probably doesn't return what you think it does. AND takes precedence over OR, so it will return the first vessel in the list if the status is 1, and also any other vessel in the list, regardless of status.
You'd do better to create a query with an IN clause like this:
SELECT * FROM voyage_list WHERE status = 1 AND vesselID IN(8,9,10)
Here's some code to do just that:
$vesselFilter = $_GET['vesselFilter'];
// Validate data. Since we're expecting a string containing only integers and commas, reject anything else
// This throws out bad data and also protects against SQL injection.
if (preg_match('/[^0-9,]/', $vesselFilter)) {
echo "Bad data in input";
exit;
}
// filter out any empty entries.
$vesselArray = array_filter(explode(',', $vesselFilter));
// Now create the WHERE clause using IN
$sqlExtend = 'status = 1 AND vesselID IN ('.join(',', $vesselArray).')';
$newQuery = "SELECT * FROM voyage_list WHERE " . $sqlExtend;
echo $newQuery;
$query = $db->query($newQuery)->fetchAll();
var_dump($query);

Is there a way to use SQL query that would return results if values can be in any order?

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 ;

Display data from several fields in MySQL-table with specific id

I have the following code (SQL-query tested in phpMyAdmin and seems to work) that fetch data from selected columns:
$ids = isset($_REQUEST['id']) ? $_REQUEST['id'] : array();
if (is_array($ids)) $ids = implode(',', $ids);
if (!empty($ids)) {
$sql = "SELECT upload, upload2, upload3, upload4, upload5 FROM wp_site_table WHERE cid IN($ids) ORDER BY FIELD(cid, $ids)";
$results = $wpdb->get_results($sql) or die(mysql_error());
foreach( $results as $result ) { ... } }
The problem I have is that I want to output all fields from selected cid.
This code will only display the first cid result.
echo $result->upload;
Let's say $ids contain cid 1, 4 and 7 I'd like to output upload, upload2, upload4, upload5 from all those specific rows. How? Use an array in some way?
kind regards
Johan
If you want to echo all the fields, just do it:
echo $result->upload . '<br>' . $result->upload2 . '<br>' . $result->upload3 .
'<br>' . $result->upload4 . '<br>' . $result->upload5;
You should perhaps consider redesigning your database so you don't have repeated fields in each row. It would be better to have another table where each upload is in a row of its own, so there's no hard-coded limit on the number of these values. Then you can use a JOIN query to get all the uploads, and they'll be in an array.
With '$wpdb->get_results' output defaults to OBJECT type. Try using pre defined constant : ARRAY_A , it will result the output as an associative array.
$sql = "SELECT upload, upload2, upload3, upload4, upload5 FROM wp_site_table WHERE cid IN($ids) ORDER BY FIELD(cid, $ids)";
$results = $wpdb->get_results($sql, ARRAY_A) or die(mysql_error());
To access, simply use :
foreach( $results as $result ){
echo $result['upload'];
}

error on query , trying to make a search by keywords

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}%'

Sql query only printing first row

I am coding in php and the code takes data from an array to fetch additional data from a mysql db. Because I need data from two different tables, I use nested while loops. But the code below always only prints out (echo "a: " . $data3[2]; or echo "b: " . $data3[2];) one time:
foreach($stuff as $key)
{
$query3 = "SELECT * FROM foobar WHERE id='$key'";
$result3 = MySQL_query($query3, $link_id);
while ($data3 = mysql_fetch_array($result3))
{
$query4 = "SELECT * FROM foobar_img WHERE id='$data3[0]'";
$result4 = MySQL_query($query4, $link_id);
while ($data4 = mysql_fetch_array($result4))
{
$x += 1;
if ($x % 3 == 0)
{
echo "a: " . $data3[2];
}
else
{
echo "b: " . $data3[2];
}
}
}
}
First and foremost, improve your SQL:
SELECT
img.*
FROM
foobar foo
INNER JOIN foobar_img img ON
foo.id = img.id
WHERE
foo.id = $key
You will only have to iterate through one array.
Also, it appears that you're actually only selecting one row, so spitting out one row is expected behavior.
Additionally, please prevent yourself from SQL injection by using mysql_real_escape_string():
$query3 = "SELECT * FROM foobar WHERE id='" .
mysql_real_escape_string($key) . "'";
Update: As Dan as intimated, please run this query in your MySQL console to get the result set back, so you know what you're playing with. When you limit the query to one ID, you're probably only pulling back one row. That being said, I have no idea how many $keys are in $stuff, but if it spins over once, then it will be one.
You may be better off iterating through $stuff and building out an IN clause for your SQL:
$key_array = "";
foreach($stuff as $key)
{
$key_array .= ",'" . mysql_real_escape_string($key) . "'";
}
$key_array = substr($key_array, 1);
...
WHERE foo.id IN ($key_array)
This will give you a result set with your complete list back, instead of sending a bunch of SELECT queries to the DB. Be kind to your DB and please use set-based operations when possible. MySQL will appreciate it.
I will also point out that it appears as if you're using text primary keys. Integer, incremental keys work best as PK's, and I highly suggest you use them!
You should use a JOIN between these two tables. It the correct way to use SQL, and it will work much faster. Doing an extra query inside the loop is bad practice, like putting loop-invariant code inside a loop.

Categories