Ii am building a poor-man's search engine for in-house use. Back when I was doing this sort of thing using MySQLi or a pgsql db in code, I would write something along the lines of this:
$sql = "SELECT * FROM $table ";
$WC = array();
foreach($columns as $col )
{
$WC[] = $col ." like '%".$term."%'";
}
$sql .= ' WHERE ('. implode(') OR (', $WC). ')';
return $sql;
which would concatenate a number (unknown) of column names with the search term. The resulting SQL looking somewhat like this:
SELECT * FROM books WHERE (title like '%kids%') OR (publisher like '%kids%') OR (author like '%kids%');
Fairly crude but effective to a point. Having a small and well known data set allows this sort of thing to be quite useful.
However...
I cannot for the life of me figure out how to append an arbitrary number of OR clauses to a DB::table('books')-> ... construct. Documentation completely skirts this sort of construction. Can anyone point me in the right direction?
Just call orWhere repeatedly on the query object:
$query = DB::table('books');
foreach ($columns as $column)
{
$query->orWhere($column, 'like', "%{$term}%");
}
return $query;
You should better use MATCH AGAINST for that search.
Related
Is there any way to run mysqli query dynamically ? I am working on a small project who has dynamic form generation option. And then they want to filer those forms. Obviously we dont know how much will be form fields and how many filters they want. So is there any such way through which I can perform this action? Suppose if i can do something
SELECT * FROM table WHERE fld1 = 1 OR fld2 = 2 OR fld3 = 3....
Where those 1, 2, 3,... Can be something or maybe its empty depend on filters.
You can dynamically build your query in php by examining your $_POST values and then building out your where statement. Here's some pseudo code
foreach($_POST as $name=>$value)
{
$where[] = "`$key` = '$value'";
}
$sql = "SELECT * FROM table WHERE ".implode("OR", $where);
Of course you will need to either sanitize or use a prepared statement to make sure this is safe.
The best way to run it dynamically is by using PDO and classes, if youre confused about any of those two things check out the PHPGuru Jeffery Way found here: https://laracasts.com/series/php-for-beginners and check out his PHP tutorials, youll quickly learn what you need to do to be able to make a class that allows you to dynamically connect to your database!
Maybe do something like:
$Where = array();
foreach($_POST['form-field'] as $Field=>$Value){
if($Value){
$Where[] = $Field."=".$Value;
}
}
$Query = "SELECT * FROM table WHERE ".implode(" OR ",$Where);
You can use IN clause of in MySQL query.
Similar like this.
SELECT * FROM table
WHERE fld1 IN (1, 2, 3);
Hope this will help you.
$filter = '';
$filter .= 'fld1 = 1 OR ';
$filter .= 'fld2 = 2 OR ';
$filter .= 'fld3 = 3 OR ';
...
...
if(!empty($filter)) {
$filter = substr($filter,0,-2); // delete last OR
}
$query = "SELECT * FROM table WHERE ".$filter."";
...
Something like this would work, you have to modify the way you populate $filter
Hope this'll help.
This question already has answers here:
Google-like Search Engine in PHP/mySQL [closed]
(9 answers)
Closed 1 year ago.
I currently have a search option on my PHP+MYSQL website.
The MYSQL query is currently something like "SELECT pageurl WHERE name LIKE '%$query%'.
The reason I posted here is because I noticed that if the name of one of my products is "Blue Bike" and someone looks for "Bike Blue", no results are returned.
I am looking for a solution to this because I know that if I type on google same word, something appears.
I was thinking to create a PHP function to mix up all the words from the query if the query is having 4 or fewer words, generating around 24 queries.
Is there an easier solution to this?
Thanks for your time
As to not let this go without a working answer:
<?php
$search = 'this is my search';
$searchSplit = explode(' ', $search);
$searchQueryItems = array();
foreach ($searchSplit as $searchTerm) {
/*
* NOTE: Check out the DB connections escaping part
* below for the one you should use.
*/
$searchQueryItems[] = "name LIKE '%" . mysqli_real_escape_string($searchTerm) . "%'";
}
$query = 'SELECT pageurl FROM names' . (!empty($searchQueryItems) ? ' WHERE ' . implode(' AND ', $searchQueryItems) : '');
?>
DB connections escaping
mysqli_:
Keep using mysqli_real_escape_string or use $mysqli->real_escape_string($searchTerm).
mysql_:
if you use mysql_ you should use mysql_real_escape_string($searchTerm) (and think about changing as it's deprecated).
PDO:
If you use PDO, you should use trim($pdo->quote($searchTerm), "'").
use full text search instead of like
full text search based on indexed text and is very faster and beter than using like.
see this article for more information about full text search
What you are looking for is fulltext search.
Try Sphinx, it is very fast and integrates well with MySQL.
Sphinx website
I wrote a function that approaches Google's operation taking into account the double quotes for the elements to search as a whole block. It does NOT take into account the - or * instructions.
table: MySQL table to consider
cols: array of column to parse
searchParams: search to process. For example: red mustang "Florida 90210"
function naturalQueryConstructor($table, $cols, $searchParams) {
// Basic processing and controls
$searchParams = strip_tags($searchParams);
if( (!$table) or (!is_array($cols)) or (!$searchParams) ) {
return NULL;
}
// Start query
$query = "SELECT * FROM $table WHERE ";
// Explode search criteria taking into account the double quotes
$searchParams = str_getcsv($searchParams, ' ');
// Query writing
foreach($searchParams as $param) {
if(strpos($param, ' ') or (strlen($param)<4)) {
// Elements with space were between double quotes and must be processed with LIKE.
// Also for the elements with less than 4 characters. (red and "Florida 90210")
$query .= "(";
// Add each column
foreach($cols as $col) {
if($col) {
$query .= $col." LIKE '%".$param."%' OR ";
}
}
// Remove last ' OR ' sequence
$query = substr($query, 0, strlen($query)-4);
// Following criteria will added with an AND
$query .= ") AND ";
} else {
// Other criteria processed with MATCH AGAINST (mustang)
$query .= "(MATCH (";
foreach($cols as $col) {
if($col) {
$query .= $col.",";
}
}
// Remove the last ,
$query = substr($query, 0, strlen($query)-1);
// Following criteria will added with an AND
$query .= ") AGAINST ('".$param."' IN NATURAL LANGUAGE MODE)) AND ";
}
}
// Remove last ' AND ' sequence
$query = substr($query, 0, strlen($query)-5);
return $query;
}
Thanks to the stackoverflow community where I found parts of this function!
To have a google like search you'd need many database and index nodes, crazy algorithms.. now you come up with a SELECT LIKE ... lol :D
MySQL is slow in searching, you'd need fulltext and index set properly (MyISAM or Aria Engine). Combinations or different entities to search for are almost not implementable properly AND fast.
I'd suggest to setup an Elasticsearch server which is based on Apache's Lucene.
This searchs very fast and is easy to maintain. And you would not have to care about SQL injection and can still use the mysql server fast.
Elasticsearch (or other Lucene based search engines like SolR) can easily be installed on any server because they are written in Java.
Good documentation:
http://www.elasticsearch.org/guide/en/elasticsearch/client/php-api/current/
I would do an explode first:
$queryArray = explode(" ", $query);
and then generate the SQL query something like:
for ($i=0; $i< count($queryArray); $i++) {
$filter += " LIKE '%" + $queryArray[$i] + "%' AND" ;
}
$filter = rtrim ($filter, " AND");
$sql = "SELECT pageurl FROM ... WHERE name " + $filter
(note: haven't tested/run this code)
How can I pull multiple rows from a database using a function?
The function I have is:
function search($subject, $table) {
$query = "SELECT {$subject} ";
$query .= "FROM {$table} ";
$content = mysql_query($query);
return $content;
}
On the page which is calling the function I have:
if (isset($_POST['search'])){
$search = $_POST['search'];
}
$content = search($subjectName, $tableName);
while ($results = mysql_fetch_assoc($content)){
$phrase = $results[$subjectName];
//if phrase exists in database
if (strpos($search,$phrase) !== false) {
echo $phrase;
//if phrase does not exist in database
} else {
echo 'fail';
}
This setup does not work, however if I put everything into the function it works:
function search($subject, $table, $where = 0, $is = 0) {
global $search;
$query = "SELECT {$subject} ";
$query .= "FROM {$table} ";
if ($where > 0) {
$query .= "WHERE {$where} = '{$is}' ";
}
$content = mysql_query($query);
while ($results = mysql_fetch_assoc($content)){
$phrase = $results[$subject];
//if phrase exists in database
if (strpos($search,$phrase) !== false) {
echo $phrase;
//if phrase does not exist in database
} else {
echo 'fail';
}
}
return $content;
}
On Page:
search('main_subject', 'main_search');
The problem is that I would like to call that function again in the if statement to have it search for another phrase. Is there an easier way to do this?
EDIT: The current setup pulls the first item in an infinite loop.
There are a number of issues that should be addressed here:
First, if you are trying to search a field for a specific partial match, you would likely want to use SQL LIKE construct.
SELECT field FROM table WHERE field LIKE '%search phrase%'
Doing this would eliminate the need for you to iterate through each row trying to do a string comparison for your search phrase, as you would only be returned the rows that match the search phrase and nothing more.
Second, using global to make data available to your function is really poor practice. You really should be passing any data needed by the function to the function as a parameter. This would include your search string and probably your database connection/object.
function search($field, $table, $search, $db) {
...
}
Third, You are doing nothing at all to prevent against SQL injection right now. You need to escape the input data or, better yet use prepared statements.
Fourth, you really should not be using the mysql_* functions. They are deprecated. Try using mysqli or PDO (and I would highly recommend going ahead and learning how to use prepared statements with either of these.) You might start with mysqli ,at it provide procedural functions similar to mysql_* so the learning curve might be less steep (though really most experienced developers would prefer the object-oriented usage).
Fifth, to your original question. If you want search for multiple phrases, there are a couple approaches you can take. You can either pass all the phrases at once like this:
SELECT field FROM table WHERE field LIKE '%search phrase%' OR field LIKE '%another search phrase%'
Or, you could just make iterative function calls to get the results you want. This really depends on whether you only want to search for the second phrase if the first is not successful (use the iterative approach) or whether you just want all possible matches in one query (use the LIKE-OR-LIKE approach).
I have a sql query that is generated using php. It returns the surrogate key of any record that has fields matching the search term as well as any record that has related records in other tables matching the search term.
I join the tables into one then use a separate function to retrieve a list of the columns contained in the tables (I want to allow additions to tables without re-writing php code to lower ongoing maintenance).
Then use this code
foreach ($col_array as $cur_col) {
foreach ($search_terms_array as $term_searching) {
$qry_string.="UPPER(";
$qry_string.=$cur_col;
$qry_string.=") like '%";
$qry_string.=strtoupper($term_searching);
$qry_string.="%' or ";
}
}
To generate the rest of the query string
select tbl_sub_model.sub_model_sk from tbl_sub_model inner join [about 10 other tables]
where [much code removed] or UPPER(tbl_model.image_id) like '%HONDA%' or
UPPER(tbl_model.image_id) like '%ACCORD%' or UPPER(tbl_badge.sub_model_sk) like '%HONDA%'
or UPPER(tbl_badge.sub_model_sk) like '%ACCORD%' or UPPER(tbl_badge.badge) like '%HONDA%'
or UPPER(tbl_badge.badge) like '%ACCORD%' group by tbl_sub_model.sub_model_sk
It does what I want it to do however it is vulnerable to sql injection. I have been replacing my mysql_* code with pdo to prevent that but how I'm going to secure this one is beyond me.
So my question is, how do I search all these tables in a secure fashion?
Here is a solution that asks the database to uppercase the search terms and also to adorn them with '%' wildcards:
$parameters = array();
$conditions = array();
foreach ($col_array as $cur_col) {
foreach ($search_terms_array as $term_searching) {
$conditions[] = "UPPER( $cur_col ) LIKE CONCAT('%', UPPER(?), '%')";
$parameters[] = $term_searching;
}
}
$STH = $DBH->prepare('SELECT fields FROM tbl WHERE ' . implode(' OR ', $conditions));
$STH->execute($parameters);
Notes:
We let MySQL call UPPER() on the user's search term, rather than having PHP call strtoupper()
That should limit possible hilarious/confounding mismatched character set issues. All your normalization happens in one place, and as close as possible to the moment of use.
CONCAT() is MySQL-specific
However, as you tagged the question [mysql], that's probably not an issue.
This query, like your original query, will defy indexing.
Try something like this using an array to hold parameters. Notice % is added before and after term as LIKE %?% does not work in query string.PHP Manual
//Create array to hold $term_searching
$data = array();
foreach ($col_array as $cur_col) {
foreach ($search_terms_array as $term_searching) {
$item = "%".strtoupper($term_searching)."%";//LIKE %?% does not work
array_push($data,$item)
$qry_string.="UPPER(";
$qry_string.=$cur_col;
$qry_string.=") LIKE ? OR";
}
}
$qry_string = substr($qry_string, 0, -3);//Added to remove last OR
$STH = $DBH->prepare("SELECT fields FROM table WHERE ". $qry_string);//prepare added
$STH->execute($data);
EDIT
$qry_string = substr($qry_string, 0, -3) added to remove last occurrence of OR and prepare added to $STH = $DBH->prepare("SElECT fields FROM table WHERE". $qry_string)
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.