I have the following code:
$sql = "SELECT name, address, city FROM tableA, tableB WHERE tableA.id = tableB.id";
if (isset($price) ) {
$sql = $sql . ' AND price = :price ';
}
if (isset($sqft) ) {
$sql = $sql . ' AND sqft >= :sqft ';
}
if (isset($bedrooms) ) {
$sql = $sql . ' AND bedrooms >= :bedrooms ';
}
$stmt = $dbh->prepare($sql);
if (isset($price) ) {
$stmt->bindParam(':price', $price);
}
if (isset($sqft) ) {
$stmt->bindParam(':price', $price);
}
if (isset($bedrooms) ) {
$stmt->bindParam(':bedrooms', $bedrooms);
}
$stmt->execute();
$result_set = $stmt->fetchAll(PDO::FETCH_ASSOC);
What I notice is the redundant multiple IF statements I have.
Question: is there any way to clean up my code so that I don't have these multiple IF statements for prepared statements?
This is very similar to a question a user asked me recently the forum for my book SQL Antipatterns. I gave him an answer similar to this:
$sql = "SELECT name, address, city FROM tableA JOIN tableB ON tableA.id = tableB.id";
$params = array();
$where = array();
if (isset($price) ) {
$where[] = '(price = :price)';
$params[':price'] = $price;
}
if (isset($sqft) ) {
$where[] = '(sqft >= :sqft)';
$params[':sqft'] = $sqft;
}
if (isset($bedrooms) ) {
$where[] = '(bedrooms >= :bedrooms)';
$params[':bedrooms'] = $bedrooms;
}
if ($where) {
$sql .= ' WHERE ' . implode(' AND ', $where);
}
$stmt = $dbh->prepare($sql);
$stmt->execute($params);
$result_set = $stmt->fetchAll(PDO::FETCH_ASSOC);
Instead of if else just use PHP ternary operator
if (isset($_POST['statusID']))
{
$statusID = $_POST['statusID'];
}
else
{
$statusID = 1;
}
instead of that you can do:
$statusID = (isset($_POST['statusID'])) ? $_POST['statusID'] : 1;
The format of the ternary operator is: $variable = condition ? if true : if false
The beauty of it is that you will shorten your if/else statements down to one line and if compiler ever gives you errors, you can always go back to that line instead of 3 lines.
Related
The Aim
Hi, I'm trying to shorten my code by building the query dynamically based on the $_GET. Current I have every possible If statement with the relevant SELECT query. However I would like to create a dynamic system for feature updates.
Current Progress
//Set Filter based on url
if ($_GET[GAME] != "") { $gameFilter = $_GET[GAME]; } else { $gameFilter = ''; }
if ($_GET[Region] != "") { $regionFilter = $_GET[Region]; } else { $regionFilter = ''; }
if ($_GET[Console] != "") { $consoleFilter = $_GET[Console]; } else { $consoleFilter = ''; }
$result = get_matchfinder($gameFilter, $regionFilter, $consoleFilter);
//The Function
function get_matchfinder($gameFilter, $regionFilter, $consoleFilter) {
//Set Varibles
$database = 'matchFinder';
$order = 'DESC';
$limit = '20';
//Query Function
$connection = connection();
$sql = 'SELECT * FROM '. $database .' WHERE game = "'.$gameFilter.'" AND region = "'.$regionFilter.'" AND console = "'.$consoleFilter.'" ORDER BY ID '. $order .' LIMIT '. $limit .'';
$response = mysqli_query($connection, $sql);
//Return
return $response;
}
Problem
Currenly it works when all of the filters are active but if one of them isn't the whole query fails, I know thats because it is try to SELECT something matching ''.
So my questions is how do I make it search for all when that filters is not set?
You should build the query parts depending on the length of the filter:
$sql = '
SELECT * FROM '.$database.'
';
$filters = array();
if (strlen($gameFilter) > 0) {
$filters[] = 'game = "'.mysqli_escape_string($connection, $gameFilter).'"';
}
if (strlen($regionFilter) > 0) {
$filters[] = 'region = "'.mysqli_escape_string($connection, $regionFilter).'"';
}
if (strlen($consoleFilter ) > 0) {
$filters[] = 'console= "'.mysqli_escape_string($connection, $consoleFilter).'"';
}
if (count($filters) > 0) {
$sql .= ' WHERE '.implode(' AND ', $filters);
}
if (strlen($oder) > 0) {
$sql .= ' ORDER BY ID '.$order;
}
if ($limit > 0) {
$sql .= ' LIMIT '.$limit;
}
$response = mysqli_query($connection, $sql);
What you're doing there is building up an array of conditions, based on the length of the condition. If the condition's input is an empty string, it isn't added to the array. At the end, if there are any filters, use implode to bind the conditions into a string. The way implode works, if there's only one condition, the glue string isn't used.
It also bears mentioning that you are exposing yourself to SQL injection. The above code shows the use of mysqli_escape_string to escape the input, but you should look in to parameterized queries to take full precaution: http://php.net/manual/en/mysqli.quickstart.prepared-statements.php -- the above sample would only be slightly different if you used paraterized queries, but significantly more safe.
Documentation
strlen - http://php.net/manual/en/function.strlen.php
implode - http://php.net/manual/en/function.implode.php
Mysql parameterized queries - http://php.net/manual/en/mysqli.quickstart.prepared-statements.php
You would have to build your search dynamically
You could start your base query with
$sql='SELECT * FROM '. $database .' WHERE 1=1'
Then, if $gameFilter!="", append to your existing $sql string with "and game=$gameFilter"
The syntax for appending would be like this
if ($gameFilter!="")
{
$sql.=' and game=$gameFitler'
}
and so on checking for all of your search conditions.
Make you string like this
$sql = 'SELECT * FROM '. $database .' WHERE 1=1 {0} {1} {2}'
if ( $gameFilter <> '')
$sql = str_replace("{0}", "AND game = '".$gameFilter."'" , $sql);
else
$sql = str_replace("{0}", "" , $sql);
if ( $regionFilter <> '')
$sql = str_replace("{1}", "AND region = '".$regionFilter."'" , $sql);
else
$sql = str_replace("{1}", "" , $sql);
if ( $consoleFilter <> '')
$sql = str_replace("{2}", "AND console = '".$consoleFilter."'" , $sql);
else
$sql = str_replace("{2}", "" , $sql);
The prepared statement that gets generated dynamically from my PHP (as an example) looks like this:
SELECT COUNT(exuid) as result_count FROM full_db3 WHERE `Age Range` = :Age Range
Age Range is one of my column names.
The problem here is that having the space in between "Age" and "Range" in my parameter, but I'm not sure how to handle this. The query is generated dynamically like so (only relevant code shown):
$all_attributes = $_POST['attris'];
$sql = "SELECT COUNT(exuid) as result_count FROM {$table}";
$any_condition = false;
foreach($all_attributes as $key=>$val) {
if (!empty($val) && in_array($key,$validKeys)) {
if ($any_condition) {
$sql .= ' AND `'.$key.'` = :'.$key;
} else {
$sql .= ' WHERE `'.$key.'` = :'.$key;
$any_condition = true;
}
}
}
$stmt = $dbh->prepare($sql);
foreach($all_attributes as $key=>$val) {
if (!empty($val) && in_array($key,$validKeys)) {
$stmt ->bindValue(':'.$key, $val, PDO::PARAM_STR);
}
}
$stmt->execute();
If I change my column name in the DB to Age_Range everything works perfectly fine. For a number of reasons, I'd like to be able to exclude that underscore, as I display my column names in a select and all the underscores look terrible.
Using my idea, and copying syntax from u_mulder's comment, this should work?
$all_attributes = $_POST['attris'];
$sql = "SELECT COUNT(exuid) as result_count FROM {$table}";
$any_condition = false;
foreach($all_attributes as $key=>$val) {
if (!empty($val) && in_array($key,$validKeys)) {
if ($any_condition) {
$sql .= ' AND `'.$key.'` = :'.str_replace(' ', '_', $key);
} else {
$sql .= ' WHERE `'.$key.'` = :'.str_replace(' ', '_', $key);
$any_condition = true;
}
}
}
It leaves the field names as is, and only changes the parameter names.
But this
$stmt ->bindValue(':'.$key, $val, PDO::PARAM_STR)
will probably need changed to this as well
$stmt ->bindValue(':'.str_replace(' ', '_', $key), $val, PDO::PARAM_STR)
I'm new to PDO and am currently trying to rewrite all of my queries. One query that I'm having trouble rewriting is this one because it's written inside of a loop:
$search = $_GET['search'];
$code = explode(" ", $search);
$code_count = count($code);
$query = "SELECT * FROM table";
if($search != "")
{
if($code_count == 1)
{
$query .= " WHERE team LIKE '%".mysql_real_escape_string($search)."%'";
} elseif($code_count > 1)
{
for($j=0;$j<$code_count;$j++)
{
if($j != 0)
{
$query .= " OR ";
} else
{
$query .= " WHERE team LIKE '%".mysql_real_escape_string($code[$j])."%' OR ";
}
$query .= " team LIKE '%".mysql_real_escape_string($code[$j])."%'";
}
$query .= "ORDER BY team ASC";
}
} else
{
$query = "SELECT * FROM table ORDER BY team ASC";
}
$result = mysql_query($query)or die(mysql_error());
With PDO, I have tried the following.
$query = "SELECT * FROM table";
if($search != "")
{
if($code_count == 1)
{
$query .= " WHERE team LIKE ?";
$stmt = $db->prepare($query);
$stmt->bindValue(1, "%$search%", PDO::PARAM_STR);
$stmt->execute();
} elseif($code_count > 1)
{
for($j=0;$j<$code_count;$j++)
{
if($j != 0)
{
$query .= " OR ";
} else
{
$query .= " WHERE team LIKE ? OR ";
}
$query .= " team LIKE ?";
$stmt = $db->prepare($query);
$stmt->bindValue(1, "%$code[$j]%", PDO::PARAM_STR);
$stmt->execute();
}
$query .= "ORDER BY team ASC";
}
} else
{
$query = "SELECT * FROM table ORDER BY team ASC";
}
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
Not much luck with this method. I keep getting an error message reading: "nvalid parameter number: number of bound variables does not match number of tokens"
Any ideas?
Thanks,
Lance
The bind parameters are named 1 to n when you don't assign a name yourself. You need to change this line:
$stmt->bindValue(1, "%$code[$j]%", PDO::PARAM_STR);
To this:
$stmt->bindValue($j + 1, "%" . $code[$j] . "%", PDO::PARAM_STR);
Since one can pass an array of parameters to PDOStatement::execute() instead of binding each manually, one can hugely simplify the whole thing:
$code = explode(' ', $_GET['search']);
$stmt = $db->prepare('
SELECT *
FROM table
WHERE FALSE ' . str_repeat(' OR team LIKE ?', count($code)) . '
ORDER BY team ASC
');
$stmt->execute($code);
You have several errors in your rewrite:
you are calling prepare / bind / execute several times during the construction of the query. You should call prepare just once, after your query string is fully constructed, and bind+execute after the query construction.
in every iteration of your loop, you add one or two ( if j == 0) parameter to your query, but you try to bind just one per loop - so the numbers won't add up.
Generally, to use parametric queries, you need to follow the following structure:
build your query string
prepare your query string
every time you want to run your query:
bind parameters
execute
fetch
So your code should be like:
// building query
if($search != "")
{
$query = 'SELECT * FROM table ';
if($code_count == 1)
{
// note: this if is unneccessary, the loop below would generate a good SQL even for code_count 0 or 1
$query .= "WHERE team LIKE ?";
} elseif($code_count > 1)
{
for($j=0;$j<$code_count;$j++)
{
if($j != 0)
{
$query .= " OR ";
} else
{
$query .= " WHERE ";
}
$query .= " team LIKE ? ";
}
$query .= "ORDER BY team ASC";
}
} else
{
$query = "SELECT * FROM table ORDER BY team ASC";
}
// preparing
$stmt = $db->prepare($query);
// binding parameters
if($search != '' && $code_count >= 1) {
for($j=0;$j<$code_count;$j++){
$stmt->bindValue($j+1, "%".$code[$j]."%", PDO::PARAM_STR);
}
}
// execute
$stmt->execute();
// fetch
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
I am passing a number of values to a function and then want to create a SQL query to search for these values in a database.
The input for this is drop down boxes which means that the input could be ALL or * which I want to create as a wildcard.
The problem is that you cannot do:
$result = mysql_query("SELECT * FROM table WHERE something1='$something1' AND something2='*'") or die(mysql_error());
I have made a start but cannot figure out the logic loop to make it work. This is what I have so far:
public function search($something1, $something2, $something3, $something4, $something5) {
//create query
$query = "SELECT * FROM users";
if ($something1== null and $something2== null and $something3== null and $something4== null and $something5== null) {
//search all users
break
} else {
//append where
$query = $query . " WHERE ";
if ($something1!= null) {
$query = $query . "something1='$something1'"
}
if ($something2!= null) {
$query = $query . "something2='$something2'"
}
if ($something3!= null) {
$query = $query . "something3='$something3'"
}
if ($something4!= null) {
$query = $query . "something4='$something4'"
}
if ($something5!= null) {
$query = $query . "something5='$something5'"
}
$uuid = uniqid('', true);
$result = mysql_query($query) or die(mysql_error());
}
The problem with this is that it only works in sequence. If someone enters for example something3 first then it wont add the AND in the correct place.
Any help greatly appreciated.
I would do something like this
criteria = null
if ($something1!= null) {
if($criteria != null)
{
$criteria = $criteria . " AND something1='$something1'"
}
else
{
$criteria = $criteria . " something1='$something1'"
}
}
... other criteria
$query = $query . $criteria
try with array.
function search($somethings){
$query = "SELECT * FROM users";
$filters = '';
if(is_array($somethings)){
$i = 0;
foreach($somethings as $key => $value){
$filters .= ($i > 0) ? " AND $key = '$value' " : " $key = '$value'";
$i++;
}
}
$uuid = uniqid('', true);
$query .= $filters;
$result = mysql_query($query) or die(mysql_error());
}
// demo
$som = array(
"something1" => "value1",
"something2" => "value2"
);
search( $som );
Here's an example of dynamically building a WHERE clause. I'm also showing using PDO and query parameters. You should stop using the deprecated mysql API and start using PDO.
public function search($something1, $something2, $something3, $something4, $something5)
{
$terms = array();
$values = array();
if (isset($something1)) {
$terms[] = "something1 = ?";
$values[] = $something1;
}
if (isset($something2)) {
$terms[] = "something2 = ?";
$values[] = $something2;
}
if (isset($something3)) {
$terms[] = "something3 = ?";
$values[] = $something3;
}
if (isset($something4)) {
$terms[] = "something4 = ?";
$values[] = $something4;
}
if (isset($something5)) {
$terms[] = "something5 = ?";
$values[] = $something5;
}
$query = "SELECT * FROM users ";
if ($terms) {
$query .= " WHERE " . join(" AND ", $terms);
}
if (defined('DEBUG') && DEBUG==1) {
print $query . "\n";
print_r($values);
exit();
}
$stmt = $pdo->prepare($query);
if ($stmt === false) { die(print_r($pdo->errorInfo(), true)); }
$status = $stmt->execute($values);
if ($status === false) { die(print_r($stmt->errorInfo(), true)); }
}
I've tested the above and it works. If I pass any non-null value for any of the five function arguments, it creates a WHERE clause for only the terms that are non-null.
Test with:
define('DEBUG', 1);
search('one', 'two', null, null, 'five');
Output of this test is:
SELECT * FROM users WHERE something1 = ? AND something2 = ? AND something5 = ?
Array
(
[0] => one
[1] => two
[2] => five
)
If you need this to be more dynamic, pass an array to the function instead of individual arguments.
The following code needs to make use of 3 variables given by the user. By default all of these variables equal to 0.
time (textbox)
city (drop down list)
type (drop down list)
If for example time and city is given by the user, but lets the type zero, it will not return any results.
My question is what is an effective and efficient way to modify my existing code so that if the user chooses not to select time, city or type or any combination of these, there will be results returned?
For example if time 21:00 is added with city number 3, it will show all the types that meet the 2 criteria should be listed.
$question= 'SELECT * FROM events WHERE ABS(TIMESTAMPDIFF( HOUR , `time`, :time )) < 2 AND city=:city AND type=:type';
$query = $db->prepare($question);
$query->bindValue(":time", $time, PDO::PARAM_INT);
$query->bindValue(":city", $city, PDO::PARAM_INT);
$query->bindValue(":type", $type, PDO::PARAM_INT);
$query->execute();
<?php
$question= 'SELECT * FROM events WHERE ';
$hasTime = false;
if(!empty($time)) { // #note better validation here
$hasTime = true;
$question .= 'ABS(TIMESTAMPDIFF( HOUR , `time`, :time )) < 2 ';
}
$hasCity = false;
if(!empty($city)) { // #note better validation here
$hasCity = true;
$question .= 'AND city=:city ';
}
$hasType = false;
if(!empty($type)) { // #note better validation here
$hasType = true;
$question .= 'AND type=:type';
}
$query = $db->prepare($question);
if($hasTime)
$query->bindValue(":time", $time, PDO::PARAM_INT);
if($hasCity)
$query->bindValue(":city", $city, PDO::PARAM_INT);
if($hasType)
$query->bindValue(":type", $type, PDO::PARAM_INT);
$query->execute();
$results = $query->fetchAll();
if(empty($results))
echo 'no results';
else
// $results is an array of arrays
I prefer using an array of conditions, and checking through to see if the conditions exist, to built the individual parts of the SQL query:
$conditions = array(); // Creating an array of conditions.
if ($time) // Checks to see if value exists.
{
$timeCondition = "ABS(TIMESTAMPDIFF( HOUR , `time`, :time )) < 2";
$conditions[] = $timeCondition; // Adds this condition string to the array.
}
if ($city)
{
$cityCondition = "city=:city";
$conditions[] = $cityCondition;
}
if ($type)
{
$typeCondition = "type=:type";
$conditions[] = $typeCondition;
}
$conditionString = implode(" AND ", $conditions); // Gluing the values of the array with " AND " in between the string conditions.
if (count($conditions) > 0) // If conditions exist, add "WHERE " to the condition string.
{
$conditionString = "WHERE ".$conditionString;
}
else // Otherwise, the condition string is blank by default.
{
$conditionString = '';
}
$question= 'SELECT * FROM events '.$conditionString; // If no conditions, will return all from events. Otherwise, conditions will be slotted in through $conditionString.
$query = $db->prepare($question);
if($time)
$query->bindValue(":time", $time, PDO::PARAM_INT);
if($city)
$query->bindValue(":city", $city, PDO::PARAM_INT);
if($type)
$query->bindValue(":type", $type, PDO::PARAM_INT);
$query->execute();
You can use a series of IF() statements in your SQL statement, and return true if the value isn't set. So something like this:
...WHERE IF(:time, ABS(TIMESTAMPDIFF(HOUR, `time`, :time)) < 2, 1)
AND IF(:city, city=:city, 1) AND IF(:type, type=:type, 1)
Build the query dynamically so that if the field is at its default, do not include it in the where clause.
$conditions = array();
if ($_POST['time']) {
$conditions[] = "ABS(TIMESTAMPDIFF( HOUR , `time`, :time )) < 2";
}
if ($_POST['city']) {
$conditions[] = "city=:city";
}
if ($_POST['type']) {
$conditions[] = "type=:type";
}
$conditionString = implode(" AND ", $conditions);
if (count($conditions) > 0) {
$conditionString = "WHERE " . $conditionString;
}
else {
$conditionString = '';
}
$question = 'SELECT * FROM events ' . $conditionString;