Search only reads one keyword: SQLSTATE[HY093]: Invalid parameter number - php

I'm having an issue with my search feature of my website. It's not handling search terms with more than one word. try a live example of the search bar problem over at http://mobile.mixtapemonkey.com/
search "It's Better This Way"
as soon as you type in B I get an error. other than that it works, what do you think the issue is?
if (empty($errors)) {
$name_explode = explode(' ', $name); // explode keywords to get each individual keyword, and put into array
$name_count = count($name_explode); // count keywords from array
foreach($name_explode as $name_single) {
$x++; // increment x each loop
$keyword = "%".$name_single."%";
$where .= '`keywords` LIKE :keyword'; // append to where clause
if ($name_count!=$x) {
$where .= ' AND '; // as long as keyword isn't the last, append AND.
}
}
$sql = "SELECT `name`, `thumb`, `id`, `title` FROM `mixtapes` WHERE ".$where." ORDER BY `id` DESC LIMIT 40";
$search = $db->prepare($sql); // perform query
$search->bindParam(':keyword', $keyword, PDO::PARAM_STR);
$search->execute();
$search_num_rows = $search->rowCount(); // get number of rows (results) returned

Please check the code I have added.the reason you got that error is due to your foreach loop. In that loop you add :keyword everytime. But that count dont match with the bindParam value count. So I changed you code and posting below
if (empty($errors)) {
$name_explode = explode(' ', $name); // explode keywords to get each individual keyword, and put into array
$name_count = count($name_explode); // count keywords from array
$x = 0;
foreach($name_explode as $name_single) {
$x++; // increment x each loop
$keyword[':keyword'.$x] = "%".$name_single."%";
$where .= '`keywords` LIKE :keyword'.$x; // append to where clause
if ($name_count!=$x) {
$where .= ' OR '; // as long as keyword isn't the last, append AND.
}
}
$sql = "SELECT `name`, `thumb`, `id`, `title` FROM `mixtapes` WHERE ".$where." ORDER BY `id` DESC LIMIT 40";
$search = $db->prepare($sql); // perform query
$search->execute($keyword);
$search_num_rows = $search->rowCount(); //

Related

Add additional filter in PHP SQL search funtion

The following is the PHP code I am using for a simple search feature in my website.
The search simply shows refults if it matches the SQL column "tags".
I would like to add one more filter in the SQL query.
I want to filter the search results based on city.
The city data is already in the SQL, but I dont know how to add it here without breaking the properly working search funtion.
I tried $data_sql .= " AND city='newyork' "; after the 8th line, but it didnt work.
$name=str_replace(' ', '%', $_POST['query']);
$newsearch = "%$name%";
$base_sql = "SELECT %s FROM posts WHERE tags LIKE ?";
$count_sql = sprintf($base_sql, "count(*)");
$stmt = $connect->prepare($count_sql);
$stmt->execute([$newsearch]);
$total_data = $stmt->fetchColumn();
$data_sql = $count_sql = sprintf($base_sql, "*")." LIMIT ?,?";
$stmt = $connect->prepare($data_sql);
$stmt->execute([$newsearch, $start, $limit]);
$result = $stmt->fetchAll();
So your additional filter must be before LIMIT ?, ?
if you try adding it after the 8th line the query will look like this:
SELECT * FROM posts WHERE tags LIKE 'search' LIMIT 0, 100 AND city='newyork'
so what can you do:
$data_sql = sprintf($base_sql, "*");//we will add the limit before preparation
//don't know why do you need that $count_sql here
$data_sql .= " AND city='newyork' ";
//IF you need some GROUP BY do it here
//If you need some ORDER BY do it here
$data_sql .= " LIMIT ?, ?";
$stmt = $connect->prepare($data_sql);
$stmt->execute([$newsearch, $start, $limit]);
$result = $stmt->fetchAll();
The line $data_sql .= " AND city='newyork' "; won't work as it will add the string after the LIMIT which is not a valid sql query.
You should instead edit the line with the base_sql like this:
$base_sql = "SELECT %s FROM posts WHERE tags LIKE ? AND city='newyork'";
And of course if 'newyork' needs to be a variable you can do thr same thing like you did for the tags
First, let's add the new criteria:
$base_sql = "SELECT %s FROM posts WHERE tags LIKE ? and city = ?";
Then make sure that you pass the city as a parameter
$stmt->execute([$newsearch, 'newyork', $start, $limit]);

Bind parameters with PDO in PHP

I am using PDO in php. But when my query have any keyword like " ' " means hyphen it breaks and through an error.
I ready through on internet and find solution to bind parameters with query and it works fine.
But the issue is i am building the query in loop and i am not able to bind parameters within loop.
Here is code in which i am splitting the array with space and run query on every keyword.
The first 3 words will have only like query and more then 3 words i am using loop to concatenate the all array elements and same with more then 6 words i am using MATCH query.
Is there any way to escape that hyphen or how can we bind parameters using loop in my case?
$keyword = ($_POST['keyword']);
$keyword_array = split(' ',$keyword);
/* Query For first Three Words */
if(count($keyword_array)<=3){
$sql = "SELECT * FROM faq WHERE question LIKE '%$keyword%' limit 14";
}
/* Query through all array when words are greater then 3 */
if(count($keyword_array)< 6){
$sql = "SELECT * FROM faq WHERE question ";
for($i = 0 ; $i<count($keyword_array); $i++){
if($i==0){
$sql.=" LIKE '%$keyword_array[$i]%'";
}else{
$sql.=" or question LIKE '%$keyword_array[$i]%' ";
}
}
$sql .= " ORDER BY question ASC LIMIT 0, 8";
}
/* Appl FULL TEXT in natual language mode once we have enough phrase */
else if(count($keyword_array)>=6){
$sql = "SELECT * FROM faq WHERE ";
for($i = 0 ; $i<count($keyword_array); $i++){
if($i==0){
$sql.=" MATCH (answer) AGAINST ('$keyword_array[$i]' in natural language mode) ";
}else{
$sql.=" or MATCH(answer) AGAINST('$keyword_array[$i]' in natural language mode) ";
}
}
$sql .= " limit 0,5";
}
$execute_faq_query = $conn->query($sql);
$execute_faq_query->setFetchMode(PDO::FETCH_ASSOC);
while ($list = $execute_faq_query->fetch()){
}
When building a dynamic query you need to separate those parts of the query that are static from those that are dynamic.
You can see that the following code is static.
"SELECT * FROM faq ";
The rest of the code is dynamic. When filtering records the WHERE clause is used and the AND & OR operators are used to filter records based on more than one condition. The AND operator displays a record if both the first condition AND the second condition are true. The OR operator displays a record if either the first condition OR the second condition is true. so for the first condition WHERE is used but after that AND or OR must be used(using OR in your example)
// Static code
sql = "SELECT * FROM `faq`"
// Set initial condition to WHERE
clause = "WHERE";
if( !empty( filter )){
Add clause to sql
Add condition to sql
change clause to OR or AND as required
}
Repeat for each filter
Note the filter is not changed until a filter is applied and remains changed once changed.
The remaining static code, if any,is added after all the filters have been handled.
I have used Switch Case to apply filters and unnamed parameters ?.
Use "lazy" binding when possible - passing data into execute will dramatically shorten your code. See PDO info.
//Test $POST[] remove after testing
$_POST['keyword'] ="one two three four five six";
$keyword = ($_POST['keyword']);
$keyword_array = split(' ',$keyword);
$words = count($keyword_array);
echo $words;
//You need an array to store parameters
$paramArray =array();
//Initial clause
$clause = "WHERE";
//Start with a basic stub
$sql = "SELECT * FROM faq ";
switch (true) {
case $words <= 3:
$sql .= " $clause question LIKE ?";
$keyword = "%$keyword%";
array_push($paramArray,$keyword);
$limit = " LIMIT 14";
break;
case $words < 6:
for($i = 0 ; $i<count($keyword_array); $i++){
$sql .= " $clause question LIKE ?";
$keyword = "%$keyword_array[$i]%";
array_push($paramArray,$keyword);
$clause = "OR";
$limit = " ORDER BY question ASC LIMIT 0, 8";
}
break;
case $words >=6:
$clause = "";
for($i = 0 ; $i<count($keyword_array); $i++){
$sql.=" $clause MATCH (answer) AGAINST (? in natural language mode) ";
array_push($paramArray,$keyword_array[$i]);
$clause = "OR";
$limit = " limit 0,5";
}
break;
}
//echo query and parameter array remove after testing
echo $sql;
echo "<br>";
print_r($paramArray);
//Prepare and execute query
$execute_faq_query = $conn->prepare($sql);
$execute_faq_query->execute($paramArray);
$execute_faq_query->setFetchMode(PDO::FETCH_ASSOC);
while ($list = $execute_faq_query->fetch()){
}

Multiple keywords search

I'm getting trouble to add the search in my site. I cannot figure out the way to make it done.
I 've a table as follow:
TABLE A
id text
1 Hello there whats up. I'm trying to code.
2 there need to be this code
Now I want search using the
Keywords = hello code
And the results should provide me both the rows because both the rows contains some portion of the keyword as follow:
id text
1 **Hello** there whats up. I'm trying to **code**
2 there need to be this **code**
Also the result should provide the row with max number of keywords matched first.
I tried doing this but it only provide me some of my desire results.
<?php
$keyword = 'hello code';
$exloded = explode(' ', $keyword);
foreach($exploded as value):
$sth = $db->query("SELECT * FROM A WHERE `text` LIKE :value");
$sth->execute(array(':value' => '%'.$value.'%'));
$rows = $sth->fetchAll();
endforeach;
echo $rows;
?>
Updated
I simply Did this and it worked fine for me. But I want to know whether this is the correct way to get the work done.
$keyword = hello code;
$query ="SELECT *, MATCH(`page_content`) AGAINST('$keyword' IN BOOLEAN MODE) AS score FROM super_pages WHERE MATCH(`page_content`) AGAINST('$keyword' IN BOOLEAN MODE) ORDER BY score DESC";
$sth = $this->db->query($query);
$result = $sth->fetchAll();
$rows will have the data where your keyword code matches in your table you can rewrite your code to match for both keywords as
$keyword = 'hello code';
$exloded = explode(' ', $keyword);
$query = 'SELECT * FROM A ';
$i = 0;
$params = array();
foreach ($exploded as $value):
if ($i == 0) {
$query .= ' WHERE `text` LIKE :value_'.$i;
} else {
$query .= ' OR `text` LIKE :value_'.$i;
}
$params[':value_'.$i] = '%'.$value .'%';
$i++;
endforeach;
$sth = $db->query($query);
$sth->execute($params);
$rows = $sth->fetchAll();
echo '<pre>';print_r($rows);echo '</pre>';
Build your query in loop(over your provided keywords) and assign unique placeholders in query to match for all values
Edit for full text search
Using full text search you can match exact same phrase with provided keyword,In order to work with full text search you need an index of type FULLTEXT.
ALTER TABLE `A` ADD FULLTEXT INDEX `fulltextindex` (`text`);
And query will be like
$keyword = 'hello code';
$exloded = explode(' ', $keyword);
$where = '';
$i = 0;
$select = array();
$params = array();
foreach ($exploded as $value):
$select[]= ' MATCH(`text`) AGAINST(:value_'.$i.' IN BOOLEAN MODE) ';
if ($i == 0) {
$where .= ' WHERE MATCH(`text`) AGAINST(:value_'.$i.' IN BOOLEAN MODE)';
} else {
$where .= ' OR MATCH(`text`) AGAINST(:value_'.$i.' IN BOOLEAN MODE)';
}
$params[':value_'.$i] = $value ;
$i++;
endforeach;
$query ='SELECT *,'. implode( ' + ',$select).' AS score FROM A '.$where.' ORDER BY score DESC';
$sth = $db->query($query);
$sth->execute($params);
$rows = $sth->fetchAll();
echo '<pre>';print_r($rows);echo '</pre>';
Above code will produce a query like
SELECT *,
MATCH(`text`) AGAINST('hello' IN BOOLEAN MODE)
+
MATCH(`text`) AGAINST('code' IN BOOLEAN MODE) AS score
FROM A
WHERE MATCH(`text`) AGAINST('hello' IN BOOLEAN MODE)
OR MATCH(`text`) AGAINST('code' IN BOOLEAN MODE)
ORDER BY score DESC
Alias score in above query will have value for each row and its matched score thus you can order your result in descending manner to show the records first which has a highest score.
Note: You can use Full text search in Myisam but for innodb you have
to upgrade your Mysql to 5.6 which supports full text searching in
innodb too
you can use the following code:
<?php
$keyword = 'hello code';
$exloded = explode(' ', $keyword);
$sql = "SELECT * FROM A WHERE ";
foreach($exploded as $value):
$sql .= "text LIKE '" . $value "%' OR ";
endforeach;
// remove last 'OR '
$sql = substr($sql, -3);
$result = mysqli_query($con, $sql);
//................
?>

Variable sql query depending on number of search parameter

I need to do a sql query in php for search some entries (so using WHERE). But the field used to search could be of variable number.
I have a page with a search form, with 4 Field. It sends via POST the fields to a search.php that make a query:
$gomme_sql = $data->query("SELECT * FROM table WHERE parameter1 = '$_POST['name1']' AND parameter2 = '$_POST['name2']' ORDER BY id ASC");
But I don't know which field are filled. So, if I don't enter anything in field1 from the search form, I shouldn't have parameter1 = '$_POST['name1']' in the WHERE query.
Have you any idea how to obtain this?
Thank you
You can check the post data before appending that clause to the query in a way like this:
edit: adding additional check:
$sql="select something from someTable ";
if(!empty($_POST['name1']) || !empty($_POST['name2'])) // add as many as you like
{
$sql.=" where ";
if(!empty($_POST['name1']))
{
$sql.="parameter1= $_POST['name1']";
}
// etc etc...
}
$sql.=" ORDER BY id ASC";
and so on.
Having said that, please, please use prepared statements with this sort of input from the user. This is SUPER open to sql injection. Please do read this: How can I prevent SQL injection in PHP?
You can write generic sql select function like this , if you need more complex SQL just modify it.
<?php
function sqlSelect($table, $sel, $wh = '', $groupby = '', $order = '', $add = '') {
$tb = $table;
if (is_array($table)) {
$tb = implode(',', $table);
}
if ($wh) {
if (is_array($wh)) {
$w = array();
foreach ($wh as $k => $v) {
$v = mysqli_real_escape_string($v);
if (is_null($v))
$w [] = "$k=null ";
else
$w [] = "$k ='$v'";
}
$wh = 'where ' . implode(' and ', $w);
}else {
$wh = "where $wh";
}
}
if ($groupby)
$groupby = "group by $groupby";
if ($order)
$order = "order by $order";
$sql = "select $sel from $tb $wh $groupby $order $add ";
return $sql;
}
//set _GET as this is console test
$_GET['name1']='Bob';
$where = array(
'name1'=>$_GET['name1']
);
echo sqlSelect('sometable' , '*' , $where) ."\n";
// select * from sometable where name1 ='Bob'
//or some complex stuff
echo sqlSelect('persons', "age,status" , array('name'=>'Maria' , 'likes'=>'PHP') , null, 'age' , 'limit 20');
//select age,status from persons where name ='Maria' and likes ='PHP' order by age limit 20

How to eliminate duplicate search results in MySQL?

I am having some trouble displaying text from database using PHP and SQL. Below is a script similar to what I have.
$search_split = explode(" ", $search); //$search is what user entered
foreach ($search_split as $searcharray) {
$searched = mysqli_query($connect, "SELECT * FROM people WHERE `description` LIKE '%$searcharray%'");
while($info = mysqli_fetch_array($searched)) {
echo $info['description'];
}
}
So, for example the user enter 'He is male'. I split the word into three part 'He', 'is' and 'male' using 'explode' function. After that, I search the database for words that is similar to those three word. However, if a row have all the three words, it would display the row three times. How can I make it to display only once?
You could do something like this:
$search = 'test search me';
$search_split = array_map(function($piece) use ($mysqli_connection){
return "'%" . $mysqli_connection->real_escape_string($piece) . "%'";
}, explode(' ', $search)); //$search is what user entered
$search_split = implode(' OR `description` LIKE ', $search_split);
$sql = "SELECT * FROM people WHERE `description` LIKE $search_split";
echo $sql; // SELECT * FROM people WHERE `description` LIKE '%test%' OR `description` LIKE '%search%' OR `description` LIKE '%me%'
$searched = mysqli_query($connect, $sql);
Can you use full text search?
Add a full text index to the table
ALTER TABLE people ADD FULLTEXT(description);
Then you can use a query like this
SELECT *
FROM people
WHERE
MATCH ( description )
AGAINST ('+He +is +male' IN BOOLEAN MODE)
First store your results into one array then display it. Refer below code.
$search_split = explode(" ", $search); //$search is what user entered
foreach ($search_split as $searcharray) {
$searched = mysqli_query($connect, "SELECT * FROM people WHERE `description` LIKE '%$searcharray%'");
while($info = mysqli_fetch_array($searched)) {
$results[$info['YOUR_PRIMARY_KEY']] = $info['description']; // this will over write your previous record
}
}
foreach($results as $result){
echo $result;
}
Now every records display only once.
You have put your db query in a foreach loop, which loops 3 times (with the current data: he, is and male). What you want to do is put all the search variables in one query, something like:
$search_split = explode(" ", $search); //$search is what user entered
$querypart = "'%" . implode("%' AND '%", $search_split) . "%'"; // use OR or AND, to your liking
$searched = mysqli_query($connect, "SELECT * FROM people WHERE `description` LIKE " . $querypart);
while($info = mysqli_fetch_array($searched)) {
echo $info['description'];
}
This does not take any escaping/sanitizing of the query input, be aware...
$result = array();
$search_split = explode(" ", $search); //$search is what user entered
foreach ($search_split as $searcharray) {
$searched = mysqli_query($connect, "SELECT * FROM people WHERE `description` LIKE '%$searcharray%'");
while($info = mysqli_fetch_array($searched)) {
$result[] = $info['description'];
}
}
$finalres = array_unique($result);
so, finalres contains unique results
for($i = 0; $i < count($finalres); $i++)
echo $finalres[$i];

Categories