php foreach loop search mysql table with several words - php

I have a mysql table called foods with columns "id, name, addinfo, picname, mg1cup, mg100g". I have a form where the user can submit between 1-20 food names. The php file takes the submitted food names in an array called $terms[]. I need to search the sql table for all terms and return results for all columns for each of the terms.
However, the results are only showing the first term submitted, repeated as many times as there are inputs (for example, if two words were inputted, the first term gets outputted in the results twice - instead of first word results, then second word results).
I don't know what I'm doing wrong. Here's my code (I haven't added the function to sanitize the strings yet):
<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');
//connect to the wordpress (bluehost)DB
require_once '../../../wp-config.php';
$link = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD) or die('Could not connect to mysql');
$Db = mysql_select_db(DB_NAME, $link) or die('Could not select database.');
//check to see if the search term has a value, matches "name" column in DB, and if so, put it into an array called terms
if (isset($_POST['terms'])) {
$terms = ($_POST['terms']);
if (count($terms) > 0 ) {
$results=array();
foreach($terms as $term) {
$sql = 'SELECT * from `wpqr_foods` WHERE `name` like "%'. $term .'%" ORDER BY name ASC';
$Q1 = mysql_query($sql);
if(mysql_num_rows($Q1)) {
while ($Res = mysql_fetch_assoc($Q1)) {
$results[] = $Res;
}
}
//$results = mysql_query($sql);
$sql = 'SELECT * from wpqr_foods WHERE name LIKE "%'. $term .'%" ORDER BY name ASC';
$Q2 = mysql_query($sql);
//get results
if(mysql_num_rows($Q2)) {
while ($Res = mysql_fetch_assoc($Q2)) {
$results[] = $Res;
}
}
}
if (count($results) > 0 ) {
foreach ($results as $CurRes ) {
echo $CurRes['name']. "<br/>";
echo $CurRes['addinfo']. "<hr/>";
/*
[id] => 5
[name] => Apples
[value] => Yes
[addinfo] => They can eat apples. They can also eat the skin of the apple and the leaves of the apple tree, but the tree leaves are high in calcium, so limit their intake. They can also chew on the apple tree branches.
[picname] => apple
[mgc1cup] => 5.8
[mgc100g] => 4.6
*/
}
}
else {
echo "(nothing entered)";//MEANS $TERM IS EMPTY
}
}
else {
echo "(nothing entered)";//means $_POST IS NOT SET (end of if(isset($_POST['term'])) { )
}
}
//function to sanitize array values from form
function sanitizeString($var) {
if (is_array($val))
{
foreach ($val as $k => $v)
{
$val[$k] = htmlentities(strip_tags($v),ENT_QUOTES);
}
}
else
{
$val = htmlentities(strip_tags($terms),ENT_QUOTES);
}
return $val;
}
?>
The HTML is
<div class="my-form">
<form role="form" id="step1" method="post" action="/wp-content/themes/mia/vitcdata.php">
<p class="text-box"><label for="box1">Food <span class="box-number">1 </span></label><input type="text" name="terms[]" value="" placeholder="apples" id="box1" /> <a class="add-box" href="#">Add More</a></p>
<p><input type="submit" value="Submit" /></p>
</form>
The form has dynamic form fields which users can add via javascript, and they can add up to 20 text inputs, in which all inputted terms get added to the $terms[] array.
So, if I test it, use two fields, and input "apples" and "bananas", the results show all the data for "apples" repeated twice.

Check this, you can check a related question here:
Using OR in LIKE Query in MySQL to compare multiple fields
You might have to do something like this in your query.
$sql = 'SELECT * from wpqr_foods WHERE name LIKE "%'. $term .'%" OR addinfo LIKE "%'. $term .'%" ORDER BY name ASC';
OR,
$sql = 'SELECT * FROM `wpqr_foods` WHERE CONCAT(name, addinfo ) LIKE "%'. $term .'%" ORDER BY name ASC';

I would recommend the following two ways forward:
It looks like the code block that queries the database and pushes results into $results is duplicated. You probably want to get rid of the duped block as that will create 2x results for each distinct term.
I suspect what's actually getting passed to the server through the form is a string containing whitespace-separated (or commas, or whichever separator the user chose to enter) terms. Even though you've chosen to pass it (and receive it server-side) as an array, it is actually a string and you will have to explode it (or some other method) yourself into an array. To check if that's the case (and if you haven't do so already), somewhere inside the foreach ($terms as $term) loop you should inspect $term to see if it is what you expect it to be.

Related

How to search multiple words?

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.

Improving Ajax auto-complete to return better result

I am using ajax & php to fetch elements that matches keys(letters) entered by user with elements resembling the database.
for e.g database contain manufacturer names as:
Hydfloo, Rexflex, Easton, Vickters, EVER GUSH, Thomas Hydraulics, AVT Pumps
and say for example user has entered the letter "H" into the input box. Then I only receive Hydfloo as return and not Thomas Hydraulics along with it. Also if "p" is typed I wd expect to see "AVT Pumps". What changes do I need to make to my php in order to be able to have all the values returned that matches either the first or even the second word of the manufacturer name.
PHP code
<?php
require('../config/connection.php');
if(!$dbc) {
echo 'Could not connect to the database.';
} else {
if(isset($_POST['queryString'])) {
$queryString = mysqli_real_escape_string($dbc, $_POST['queryString']);
if(strlen($queryString) >0) {
$query = mysqli_query($dbc, "SELECT distinct name FROM prdct_subcat WHERE name LIKE '$queryString%' LIMIT 10");
echo '<ul>';
while ($result = mysqli_fetch_assoc($query)) {
echo '<li onClick="fill(\''.addslashes($result['name']).'\');">'.$result['name'].'</li>';
}
echo '</ul>';
}
}
}
?>
Use a regular expression instead of LIKE, so you can match a word boundary anywhere in the value.
Change
WHERE name LIKE '$queryString%'
to:
WHERE name REGEXP '[[:<:]]$queryString'
in the SQL.

Displaying whole table with optionally empty fields

Here is how the database looks like
So I would like to display it like
Champion name
name of the column e.g. Q name of the spell - Surging Tides
the rest of spells for that champion
Next Champion etc.,
This is the way I display Champion names right now
$champions = $conn->prepare("SELECT *
FROM champions
Where Patch_No = ?");
$champions->bind_param('s', $Patch_No);
$champions->execute();
$champions_result = $champions->get_result();
while($row = $champions_result->fetch_assoc()){
echo $row['Champion'].' '.$row['NumNotNull'].'<br>';
}
I can't really think of an easy way to do this with the least amount of queries possible.
Here is another example how it should look like
$row is an associative array, so you can loop through it with foreach and test whether the column is empty.
while($row = $champions_result->fetch_assoc()){
echo $row['Champion'].' '.$row['NumNotNull'].'<br>';
foreach ($row as $column_name => $column) {
if ($column_name == 'Champion' || $column_name == 'NumNotNull') {
continue; // These fields were already displayed above
}
if (!empty($column)) {
echo "$column_name $column<br>";
}
}
}

Php mysql match (multiple) words in multiple columns with spaces

I am trying to figure out how and what function i need for my query, still not sure on using Like, concat or what part etc.
My situation is as such
1.) I have multiple columns(Country, City, State, Location)
2.) Only 1 search input
3.) Search input can be 1 word, or multiple words also ignore spacing(e.g. "Center City or CenterCity or Center City Philadelphia) etc
And it will return the rows that matches the words from the different columns.
Below is my attempt, but it is not returning anything at the moment. Thanks for your time
Php:
<?php
ini_set('display_errors', 1); error_reporting(E_ALL);
include 'connect.php';
if($_POST)
{
$searchaddress = ($_POST['searchaddress']);
$result=mysqli_query($con,"SELECT *
FROM Listing WHERE CONCAT(country,state,city,Location) LIKE '%$searchaddress%' AND Status='Open'") or die( mysqli_error($con));
$output = array();
// fetch your results
while( $row = mysqli_fetch_assoc($result) )
{
// add result row to your output's next index
$output[] = $row;
}
// echo the json encoded object
echo json_encode( $output );
}
?>
Without knowing your exact data and what $searchaddress is like, it is hard to tell why it fails.
You are talking about ingoring whitespaces, but just pass in a single searchtag - and the expression LIKE '%something something else%' will not ignore whitespaces.
If you want to have the least amount of results with all given words matching, you should put in more effort and use a or/and combination of searchtags / columns. You can do this programmatically.
Assuming, you have 2 keywords entered: Center Detroid, you basically want to generate the searchquery:
FROM Listing WHERE
(
country LIKE '%Center%' OR
state LIKE '%Center%' OR
city LIKE '%Center%' OR
Location LIKE '%Center%'
)
AND
(
country LIKE '%Detroid%' OR
state LIKE '%Detroid%' OR
city LIKE '%Detroid%' OR
Location LIKE '%Detroid%'
)
To achieve that, you need to know two things:
The fieldnames you want to search in.
The keywords.
Then, the following snippet will generate the where part as required:
$search = "Detroid City Center";
$keywords = explode (" ", $search);
$columns = array("country", "state", "city", "location");
$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;
The example given in the array would produce
(
country LIKE '%Center%' OR
state LIKE '%Center%' OR
city LIKE '%Center%' OR
location LIKE '%Center%'
)
AND
(
country LIKE '%City%' OR
state LIKE '%City%' OR
city LIKE '%City%' OR
location LIKE '%City%'
)
AND
(
country LIKE '%Detroid%' OR
state LIKE '%Detroid%' OR
city LIKE '%Detroid%' OR
location LIKE '%Detroid%'
)
This will Match ANY row, where Center, City or Detroid is appearing AT LEAST ONCE in ONE of all (search-)fields per row.
Updated answer for searching each word in the address fields:
$searchaddress = "some address to find";
$address_parts = explode(" ", trim($searchaddress));
$sql_parts = array();
foreach($address_parts as $part) {
$sql_parts[] = 'full_address LIKE "%'.$part.'%"';
}
$query = 'SELECT *, CONCAT(country,state,city,Location) AS full_address FROM Listing WHERE `Status` = "Open" HAVING '.implode(' OR ', $sql_parts);

Live search engine logic behind query

I have problems creating a multiple field live search engine using AJAX and PHP.
Up to now there was no need to search for multiple fields, so I had a simple query that worked fine for only one field.
Because of a live search using onkeyup function I recognized some problems. The first problem i need to explain:
The structure of the table is quite simple: zipcode | city
For example, someone enters 12345; of course this would be the zipcode. But what in case if someone enters 12345 hometown so the first keyword would be the zipcode, second the city?
The keywords will be split by using preg_split('/[\s]+/', $search_term) so as a result I receive an array with the single keywords that will be searched for. in the case above it would be: key[0] => 12345 and key[1] => hometown.
The query I use is like:
$where = "";
$search_term = preg_split('/[\s]+/', $search_term); //splits the whole string into single keywords
$total_search_terms = count($search_term); //counts the array-keys from $search_term
foreach ($search_term as $key=>$single_term) {
$where .= "`zipcode` LIKE '$single_term%' OR `city` LIKE '$single_term%' ";
if ($key != ($total_search_terms - 1)){ //adds AND to the query in case in case of a non empty array-key
$where .= " AND ";
}
}
$query = $db->query("SELECT COUNT(*) FROM table WHERE $where");
...
Okay, so far so good. Now the problem is that the keywords can match each field again and again.
To give further example:
In case from above entering 12345 hometown it means that key[0] => 12345 can match field zipcode OR city. This condition is egual to key[1] => hometown, even this can match field zipcode OR city. So ,even when entering other way round: hometown 12345 means the same.
And this is the first problem I have.
I'm looking for a logic to structure the query. So in case of entering 12345 hometown I would like to have something like that:
When key[0] => 12345 matches field zipcode dont check key[1] => hometown if that matches in zipcode OR city because key[0] matches already in zipcode so it´s quite logical that key[1] needs to be city.
UPDATE
Okay, to tell my second problem I would like you to have a look at the answer from david strachan
He mentioned that a problem will be caused when the city contains more than one string. Let's say the search string would be something like:
12345 New York
The keys would be:
key[0] => 12345, key[1] => New, key[2] => York
Okay, now the problem is that I could check if one of the keys contains integers or not and in case of a string-length of exactly 5 I know that it will be the zipcode.
key[0] => 12345 //if ( stringlen(key[0|) === 5) === true && is_int(key[0]) === true) {zipcode}
So far so good but the real problem is the logic behind the strings of the cities. My first thoughts were that I could say that all keys that do not contain integers must be the city so I could convert them into one key.
key[1] => New, key[2] => York //if ( !is_int(key[1|) === true && !is_int(key[2|) === true) {$create_one_key = array_fill_keys(key[1], key[1]+key[2]);}
Alright, but what in case I would like to add street names in the future? I do not know how to separate and test for street name and even for the city name.
Another approach is to use JQuery Autocomplete to search MySQL database. The following code uses PDO.
<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css" />
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.2/jquery-ui.js"></script>
<link rel="stylesheet" href="/resources/demos/style.css" />
<script type="text/javascript">
jQuery(document).ready(function(){
$('#zipsearch').autocomplete({source:'suggest.php', minLength:2});
});
</script>
<style type="text/css">
li.ui-menu-item { font-size:10px}
</style>
</head>
<body>
<h2>jQuery UI Autocomplete - With MySQL </h2>
<form onsubmit="return false;">
Search:
<input id="zipsearch" type="text" size ="60"/>
</form>
suggest.php
require("dbinfo.php");//db connection strings
// if the 'term' variable is not sent with the request, exit
if ( !isset($_REQUEST['term']) )
exit;
$term = $_REQUEST['term'];
if (is_numeric($term)){
$query = "SELECT * from ziptest WHERE zip LIKE ? OR address LIKE ? LIMIT 0,10";
}else{
$query = "SELECT * from ziptest WHERE city LIKE ? OR state LIKE ? LIMIT 0,10";
}
$term.="%";
// connect to the database
try {
$dbh = new PDO("mysql:host=$host;dbname=$database", $username, $password);
$dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
// Prepare statement
$stmt = $dbh->prepare($query);
// Assign parameters
$stmt->bindParam(1,$term);
$stmt->bindParam(2,$term);
// setting the fetch mode
$stmt->setFetchMode(PDO::FETCH_ASSOC);
$stmt->execute();
$data = array();
while($row = $stmt->fetch()) {
$data[] = array(
'label' => $row['address'] .', '. $row['city'] .', '. $row['state'] .', '.$row['zip'] ,
'value' => "ID ".$row['id'] .': '.$row['address'] .', '. $row['city'] .', '. $row['state'] .', '.$row['zip']
);
}
echo json_encode($data);
}
catch(PDOException $e) {
echo "I'm sorry I'm afraid you can't do that.". $e->getMessage() ;// Remove or modify after testing
file_put_contents('PDOErrors.txt',date('[Y-m-d H:i:s]'). $e->getMessage()."\r\n", FILE_APPEND);
}
// close the connection
$dbh = null;
DEMO
Check to see if $search_term[0] is string(city) or int(zipcode) by addition of 0 Then format query accordingly.
$search_term = '1234 paisley';
$search_term = preg_split('/[\s]+/', $search_term); //splits the whole string into single keywords
$total_search_terms = count($search_term); //counts the array-keys from $search_term
$test = $search_term[0]+0;//If string returns 0
if($total_search_terms == 1){
if ($test == 0 ){
$where = "WHERE `city` LIKE `%$search_term[0]%`";
}else{
$where = "WHERE `zipcode` LIKE `%$search_term[0]%` ";
}
}else{
if ($test == 0 ){
$where = "WHERE `zipcode` LIKE `%$search_term[1]%` AND `city` LIKE `%$search_term[0]%`";
}else{
$where = "WHERE `zipcode` LIKE `%$search_term[0]%` AND `city` LIKE `%$search_term[1]%`";
}
}
$query = "SELECT COUNT(*) FROM table $where";
echo $query;
One problem will be how you treat New York. I will leave you how to work this out.
EDIT
New York $total_search_terms > 2

Categories