Dynamic OR/AND in query - php

Based on user selections, I need to filter results in my query, but I'm stuck on how to correctly implement dynamic OR statements after my AND. I'm currently using Active Record in CodeIgniter, but this may have to change.
I essentially need to create the following snippet: "WHERE city.id = 9 AND (eventType = 8 or eventType = 9)"
if there are no OR statements, then I don't need the AND
there could be 1-n OR statements
My code currently is as follows:
$this->db->where('city.id =', $cityID);
if ($eventTypes != NULL){
foreach ($eventTypes as $item){
$eventTypeID = intval($item);
$this->db->or_where('eventtype.id =', $eventTypeID);
}
}
This produces: WHERE city.id = 13 OR eventtype.id = 6 OR eventtype.id = 8 ... so I need the AND (

Codeigniter's "ActiveRecord" (airquotes...) Class is fairly limited and doesn't do well with more advanced AND/OR scoping, and points you back to raw sql for "more advanced" queries.
I would formulate your own WHERE string to build the specific query you are wanting and then just use
$this->db->where($sql)
example...
$where = "city.id = $cityID ";
if ($eventTypes != NULL)
{
$where .= "AND ( ";
foreach ($eventTypes as $i => $type)
{
$eventTypeID = intval($type);
// if it's not the first element
// (assumes $eventTypes is non-associative)
if ($i !== 0)
{
$where .= "OR ";
}
$where .= "eventtype.id = $eventTypeID ";
}
$where .= " ) ";
}
$this->db->where($where);

Related

Writing a PDO search query from a PHP array

I'm building an application using PHP 7 and a PDO connection to a MySQL database.
One part of the application contains a search form which allows a user to search for a training course by 3 different fields: the course category, the course name, and a date.
The types of elements on the form are:
Course category - dropdown, with numerical (int) ID's.
Course name - text input
Date - date picker (using HTML 5 type="date" parameter to get a calendar in the browser).
These fields can be used in conjunction, or on their own. This means a user could search, for example, just by (1), or (2 & 3), or all (1 & 2 & 3).
I've written the PHP to get the POST data and it's now in an array - for example:
$search_data = [
'category' => 3,
'name' => 'Hazard training',
'date' => ''
]
I want to use this within a PDO query but I don't know what the best way to write it is because (1) and (3) would be an = query condition, whereas (2) is a LIKE. My solution was going to be looping through the search terms and then trying to construct a query, e.g.
$sql = ' WHERE ';
foreach ($search_data as $key => $value) {
if ($key == 'category') {
$sql .= ' category = ' . $value;
}
if ($key == 'course_name') {
$sql .= ' course_name LIKE % ' . $value ' % ';
}
if ($key == 'date') {
$sql .= ' date = ' . $value;
}
}
The trouble with this is it doesn't work because of having to bind the parameters in PDO. It also doesn't work because I can't find a way to get the AND between each query (if there is a preceding statement).
I'm lost with this now and unsure what the best way to write this is.
Any help would be appreciated.
Edit: I realise that hardcoding the names, e.g. ($key == 'course_name') isn't ideal, but this is only being done because of the different query conditions (LIKE vs =). I assume that one could make $search_data multi-dimensional to say which type of query it was, but this is beyond my initial problem and probably another post.
Here`s a simple solution to your problem:
$sql = 'SELECT ..... FROM ... WHERE 1 ';
$where = '';
$pdoData = [];
foreach ($search_data as $key => $value) {
if(!$value) continue; // skip empty values
if ($key === 'category') {
$pdoData[':category'] = $value;
$where .= ' AND category = :category ';
}
if ($key === 'course_name') {
$pdoData[':course_name'] = '%'.$value.'%';
$where .= ' AND course_name LIKE (:course_name) ';
}
if ($key === 'date') {
$pdoData[':date'] = $value;
$where .= ' AND date = :date ';
}
}
$sql = $sql.$where;
$stmt = $this->ci->db->prepare($sql);
$stmt->execute($pdoData);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
And you have $pdoDate array holding the binded data.

MySql Search Query for Two Combined Fields

I am working on a site that allows users to list boats and yachts for sale. There is a mysql database that has a table "yachts" and among other fields ther are "make" and "model".
When people come to the site to look for boats for sale there is a search form, one of the options is to enter the make and/or model into a text field. The relevant where clause on the results page is the following
WHERE ( make LIKE '%$yacht_make%' OR model LIKE '%$yacht_make%')
This is working if someone enters either the make or model but not if they enter both.
For example, if someone enters "Jeanneau", the make, it finds the boat with that make, or if they enter "Sun Odyssey", the model, it finds the boat of that model, but if they enter "Jeanneau Sun Odyssey" it comes up empty.
Is there is a way to write a query where all three ways of entering the above search criteria would find the boat?
Here is the site http://yachtsoffered.com/
Thanks,
Rob Fenwick
Edit:
The query is built with a php script here is the script
if(!empty($yacht_id)) {
$where = " WHERE yacht_id = $yacht_id ";
} else {
$where = " WHERE ( make LIKE '%$yacht_make%' OR model LIKE '%$yacht_make%') ";
if(!empty($year_from) && !empty($year_to)){
$where .= "AND ( year BETWEEN $year_from AND $year_to ) ";
}
if(!empty($length_from) && !empty($length_to)){
$where .= "AND ( length_ft BETWEEN $length_from AND $length_to ) ";
}
if(!empty($price_from) && !empty($price_to)){
$where .= "AND ( price BETWEEN $price_from AND $price_to ) ";
}
if ($sail_power != 2){
$where .= "AND ( sail_power = $sail_power ) ";
}
if (count($material_arr) > 0){
$material = 'AND (';
foreach ($material_arr as $value) {
$material .= ' material LIKE \'%' . $value . '%\' OR';
}
$material = substr_replace ( $material , ') ' , -2 );
$where .= $material;
}
if (count($engine_arr) > 0){
$engine = 'AND (';
foreach ($engine_arr as $value) {
$engine .= ' engine LIKE \'%' . $value . '%\' OR';
}
$engine = substr_replace ( $engine , ') ' , -2 );
$where .= $engine;
}
if (count($type_arr) > 0){
$type = 'AND (';
foreach ($type_arr as $value) {
$type .= ' type LIKE \'' . $value . '\' OR';
}
$type = substr_replace ( $type , ') ' , -2 );
$where .= $type;
}
if (count($region_arr) > 0){
$region = 'AND (';
foreach ($region_arr as $value) {
$region .= ' region LIKE \'' . $value . '\' OR';
}
$region = substr_replace ( $region , ') ' , -2 );
$where .= $region;
}
$where .= 'AND ( active = 1 ) ORDER BY yacht_id DESC';
}
$sql = "SELECT * FROM $tbl_name $where LIMIT $start, $limit";
$result = mysql_query($sql);
There are many ways to do it, the easiest one in my opinion is:
$search = preg_replace('/\s+/','|', $yacht_make);
$sql = "select * from yacht where concat_ws(' ',make,model) rlike '$search'";
This replaces all whitespace with |, that is used as OR in regexp-powered-like query on concatenation of all searchable fields. The speed of it may be questionable in heavy trafic sites but is quite compact and easy to add more fields.
Your problem is that in your query you are searching for the exact substring "Jeanneau Sun Odyssey", which is neither a make nor a model.
The easiest solution is to use to separate input boxes for make and model. But if you really need to use a single input box, your best bet would be to split on spaces and add clauses for each separate word, so your query will end up looking something like
WHERE make like '%sun%' OR model like '%sun%'
OR make like '%odyssey%' OR model like '%odyssey%'
OR make like '%Jeanneau%' OR model like '%Jeanneau%'
Thanks everyone I ended up using dev-null-dweller's solution above
I revised my code as following,
to get the value and escape here is the code,
if (isset($_GET['yacht_make'])) {
$yacht_make = cleanString($_GET['yacht_make']);
$yacht_make = preg_replace('/\s+/','|', $yacht_make);
} else {
$yacht_make = NULL;
}
And I revised the first few lines of the php query building code I posted above to read,
if(!empty($yacht_id)) {
$where = " WHERE yacht_id = $yacht_id ";
} else {
$where = " WHERE ( yacht_id LIKE '%' )";
if(!empty($yacht_make)){
$where .= "AND ( CONCAT_WS(' ',make,model) RLIKE '$yacht_make') ";
}
It is working nicely, although it brings up more results than I would like for "Jeanneau Sun Odyssey" as it brings up "Bayliner Sunbridge", I assume because it is matching "Sun".
But it is a big improvement from what I had.
Thanks All
Rob Fenwick
You could also use:
WHERE make LIKE '%$yacht_make%'
OR model LIKE '%$yacht_make%'
OR '$yacht_make' LIKE CONCAT('%', make, '%', model, '%')
OR '$yacht_make' LIKE CONCAT('%', model, '%', make, '%')
It will not be very efficient and still not catch all possibilities, e.g. if the user provide the make and a part of the model name, like 'Jeanneau Odyssey' or in wrong order: Sun Jeanneau Odyssey.
After checking out your site it appears you are only taking input from one text field and searching for that string in each field.
So, you can either use full text searches (linkie) or you could split your string by spaces and generate a WHERE cause on the fly. Here is a rough example:
$searchterms = explode (' ', $input);
$sql .= "... WHERE"; /* obviously need the rest of your query */
foreach ($searchterms as $term) {
if ((substr ($string, -5) != 'WHERE') &&
(substr ($string, -3) == ' ||') { $sql .= " ||"; }
$sql .= " make LIKE '%$term%' || model LIKE '%$term%'";
}

Search by criteria not returning any records

I am trying to check if the POST or the GET has my search variables and then add the variables to my query. I then want to pass the array name of those variables into the URL for paginating my search results. With someone's help, this is how far I have gone.
$criteria = array('ctitle', 'csubject', 'creference', 'cat_id', 'cmaterial', 'ctechnic', 'cartist', 'csource', 'stolen');
$likes = "";
$url_criteria = '';
foreach ( $criteria AS $criterion ) {
if ( ! empty($_POST[$criterion]) ) {
$value = ($_POST[$criterion]);
$likes .= " AND `$criterion` = '%$value%'";
$url_criteria .= '&'.$criterion.'='.htmlentities($_POST[$criterion]);
} elseif ( ! empty($_GET[$criterion]) ) {
$value = mysql_real_escape_string($_GET[$criterion]);
$likes .= " AND `$criterion` = '%$value%'";
$url_criteria .= '&'.$criterion.'='.htmlentities($_GET[$criterion]);
}
}
$sql = "SELECT * FROM collections WHERE c_id>0" . $likes . " ORDER BY c_id ASC";
echo $sql;
The problem I have here is that after modifying the query I had before, any criteria I use to search does not return any records even when those records exist. I also echoed thequery and it printed the following line:
SELECT * FROM collections WHERE c_id>0 AND `cmaterial` = '%wood%' ORDER BY c_id ASC
Please, what am I missing here?
You should use LIKE keyword instead of = when concatenating parts of criteria. Your condition means searching for exact match including % symbols, while LIKE means searching by pattern.
$likes .= " AND `$criterion` LIKE '%$value%'";

Running PHP search script with empty parameters returns entire MySQL table

When I run the following MySQL query via PHP and all of the elements of $_GET() are empty strings, all the records in the volunteers table are returned (for obvious reasons).
$first = $_GET['FirstName'];
$last = $_GET['LastName'];
$middle = $_GET['MI'];
$query = "SELECT * FROM volunteers WHERE 0=0";
if ($first){
$query .= " AND first like '$first%'";
}
if ($middle){
$query .= " AND mi like '$middle%'";
}
if ($last){
$query .= " AND last like '$last%'";
}
$result = mysql_query($query);
What is the most elegant way of allowing empty parameters to be sent to this script with the result being that an empty $result is returned?
my solution:
$input = Array(
'FirstName' => 'first',
'LastName' => 'last',
'MI' => 'mi'
);
$where = Array();
foreach($input as $key => $column) {
$value = trim(mysql_escape_string($_GET[$key]));
if($value) $where[] = "`$column` like '$value%'";
}
if(count($where)) {
$query = "SELECT * FROM volunteers WHERE ".join(" AND ", $where);
$result = mysql_query($query);
}
There's no point in running a (potentially) expensive query if there's nothing for that query to do. So instead of trying to come up with an alternate query to prevent no-terms being searched, just don't run the search at all if there's no terms:
$where = '';
... add clauses ...
if ($where !== '') {
$sql = "SELECT ... WHERE $where";
... do query ...
} else {
die("You didn't enter any search terms");
}
With your current code, if everything is empty, you will get the WHERE 0=0 SQL which is TRUE for all rows in the table.
All you have to do is remove the if statements...

Building MySQL query based on posted variables

This seems like such a simple task, but I'm having a hard time finding a solution that I like for this. I can't find anything I would consider anything other than clunky. Here's what I'm working with:
There is a search form that posts variables to the processing script. These variables are the filters for the data being queried. Depending on the rights of the user, there may be more or less variables coming in, depending on the filters they have access to. Each filter refers to a field in the table the results are coming from, basically. One option for each filter is "ANY" as well, so no WHERE clause is needed.
What's a good way to build the query string. Let's say there's four variables coming back: $firstname, $lastname, $age, $dob. But only some users have access to filter by $age and $dob.
$query = "SELECT * FROM people";
if(($firstname != 'ANY' && !empty($firstname)) ||
($lastname != 'ANY' && !empty($lastname)) ||
($age != 'ANY' && !empty($age)) ||
($dob != 'ANY' && !empty($dob))) {
$query .= " WHERE";
}
if($firstname != 'ANY' && !empty($firstname)) {
$query .= " firstname='$firstname'";
}
if($lastname != 'ANY' && !empty($lastname)) {
if($firstname != 'ANY' || !empty($firstname)) {
$query .= " AND";
}
$query .= " lastname='$lastname'";
}
...
And so on. But that just looks dumb, horrible, and ridiculously inefficient to me. I'm using a slightly modified MVC pattern, so would it make sense to build out methods in the search model for each possible filter?
I'd go for this:
$query = "SELECT * FROM people";
$whereClause = " WHERE 1 = 1 ";
if($firstname != 'ANY' && !empty($firstname)) {
$whereClause .= " AND firstname='$firstname' ";
}
if($lastname != 'ANY' && !empty($lastname)) {
$whereClause .= " AND lastname='$lastname' ";
}
$query .= $whereClause;
You could alternatively collect all statements into an array and just go:
if (count($arr)>0) {
$query = "$query
WHERE ". implode(" AND ",$arr);
}
You can extend this:
http://code.google.com/p/mysql-query-builder/
here's some code that will pull all posted variables and string them together.
foreach($_POST as $name=>$value){
$arrFields[] = $name." = '".$value."'";
}
$sSql = "SELECT * FROM people WHERE 1 AND ".implode(" AND ",$arrFields);
OR if your field names are not the same as your table names, or if you want to treat the fields differently in your SQL, you can use a switch.
foreach($_POST as $name=>$value){
switch($name){
case "firstname":
$arrFields[] = "fName = '".$value."'";
break;
case "lastname":
$arrFields[] = "lName = '".$value."'";
break;
case "age":
$arrFields[] = "bioAge >= ".$value;
break;
}
}
$sSql = "SELECT * FROM people WHERE 1 AND ".implode(" AND ",$arrFields);

Categories