I have four possible variables, only one of them is required. My question is, how do I prepare a statement / construct the sql query if there parts of the query that may or not show up.
Something like this, I guess:
sql = "SELECT * FROM dogs WHERE name = ?"
if(isset($dogid)) {
sql .= "AND WHERE id = ?";
}
}
if(isset($dogcolour)) {
sql .= "AND WHERE colour = ?";
}
My brain is totally broken, and I figure there has to be a better way to prepare it than using something like $dogID = "true"; in each if, and then binding it at the end with a bunch of if statements.
Well I would define some empty value, and then check everything in database.
$sql = "SELECT * FROM dogs WHERE (IF(? = 0, 1 = 1, name = ?)) AND (IF(? = 0, 1 = 1, colour = ?))"
this solution has one drawback, you need to pass all parameters twice:
$statement->bind_param('ssss', $name, $name, $colour, $colour);
if name or $colour variable is 0 then 1 = 1 is executed in where statement (witch is always true). There should be posibility to pass NULL value to mysqli, however for me it doesn't work, if it work then could be possible to simplify query.
$sql = "SELECT * FROM dogs WHERE name = (IFNULL(?, name)) AND colour = (IFNULL(?, colour))"
Related
I have a prepared mysqli query like this:
$query = $database->prepare("SELECT * FROM items WHERE inStock > ? AND size < ? AND name LIKE ?");
$query->bind_param('iis', $inStock, $size, $name);
$query->execute();
There are many various conditions in the WHERE clause which filter out the results.
The problem is, that those parameters are supplied in a search form and they aren't mandatory. For example, someone can search by using only the name, or only by using the size and name, or by using the size, name and inStock, all at the same time.
I need some way to adjust the query so I can supply only the parameters I want. The only solution I can think of, is to make a huge if..else structure where prepared queries with all combinations of the search options exist, but that is out of the question as there are thousands of combinations.
The only actual realistic solution I can think of, would be to use a not prepared query, where I glue the conditions together with from pieces like $query .= "AND name LIKE '%".escapestuff($_POST['name'])."%'"
But that is very ugly and I would very much like to stay with the prepared query system.
You can build up a list of the criteria and add into a list the bind values and types, here is a quick mock up which uses two of the fields you refer to...
$data = [];
$params = "";
$where = [];
if ( !empty($name)) {
$data[] = $name;
$params.="s";
$where[] = "name like ?";
}
if ( !empty($size)) {
$data[] = $size;
$params.="i";
$where[] = "size < ?";
}
$sql = "SELECT * FROM items";
if ( count($where) > 0 ){
$sql .= " where ". implode ( " and ", $where);
}
$query = $database->prepare($sql);
$query->bind_param($params, ...$data);
$query->execute();
Notice that the bind_param() uses the ... to allow you to pass an array instead of the individual fields.
I have a prepared mysqli query like this:
$query = $database->prepare("SELECT * FROM items WHERE inStock > ? AND size < ? AND name LIKE ?");
$query->bind_param('iis', $inStock, $size, $name);
$query->execute();
There are many various conditions in the WHERE clause which filter out the results.
The problem is, that those parameters are supplied in a search form and they aren't mandatory. For example, someone can search by using only the name, or only by using the size and name, or by using the size, name and inStock, all at the same time.
I need some way to adjust the query so I can supply only the parameters I want. The only solution I can think of, is to make a huge if..else structure where prepared queries with all combinations of the search options exist, but that is out of the question as there are thousands of combinations.
The only actual realistic solution I can think of, would be to use a not prepared query, where I glue the conditions together with from pieces like $query .= "AND name LIKE '%".escapestuff($_POST['name'])."%'"
But that is very ugly and I would very much like to stay with the prepared query system.
You can build up a list of the criteria and add into a list the bind values and types, here is a quick mock up which uses two of the fields you refer to...
$data = [];
$params = "";
$where = [];
if ( !empty($name)) {
$data[] = $name;
$params.="s";
$where[] = "name like ?";
}
if ( !empty($size)) {
$data[] = $size;
$params.="i";
$where[] = "size < ?";
}
$sql = "SELECT * FROM items";
if ( count($where) > 0 ){
$sql .= " where ". implode ( " and ", $where);
}
$query = $database->prepare($sql);
$query->bind_param($params, ...$data);
$query->execute();
Notice that the bind_param() uses the ... to allow you to pass an array instead of the individual fields.
I'm having some trouble using a variable declared in PHP with an SQL query. I have used the resources at How to include a PHP variable inside a MySQL insert statement but have had no luck with them. I realize this is prone to SQL injection and if someone wants to show me how to protect against that, I will gladly implement that. (I think by using mysql_real_escape_string but that may be deprecated?)
<?php
$q = 'Hospital_Name';
$query = "SELECT * FROM database.table WHERE field_name = 'hospital_name' AND value = '$q'";
$query_result = mysqli_query($conn, $query);
while ($row = mysqli_fetch_assoc($query_result)) {
echo $row['value'];
}
?>
I have tried switching '$q' with $q and that doesn't work. If I substitute the hospital name directly into the query, the SQL query and PHP output code works so I know that's not the problem unless for some reason it uses different logic with a variable when connecting to the database and executing the query.
Thank you in advance.
Edit: I'll go ahead and post more of my actual code instead of just the problem areas since unfortunately none of the answers provided have worked. I am trying to print out a "Case ID" that is the primary key tied to a patient. I am using a REDCap clinical database and their table structure is a little different than normal relational databases. My code is as follows:
<?php
$q = 'Hospital_Name';
$query = "SELECT * FROM database.table WHERE field_name = 'case_id' AND record in (SELECT distinct record FROM database.table WHERE field_name = 'hospital_name' AND value = '$q')";
$query_result = mysqli_query($conn, $query);
while ($row = mysqli_fetch_assoc($query_result)) {
echo $row['value'];
}
?>
I have tried substituting $q with '$q' and '".$q."' and none of those print out the case_id that I need. I also tried using the mysqli_stmt_* functions but they printed nothing but blank as well. Our server uses PHP version 5.3.3 if that is helpful.
Thanks again.
Do it like so
<?php
$q = 'mercy_west';
$query = "SELECT col1,col2,col3,col4 FROM database.table WHERE field_name = 'hospital_name' AND value = ?";
if($stmt = $db->query($query)){
$stmt->bind_param("s",$q); // s is for string, i for integer, number of these must match your ? marks in query. Then variable you're binding is the $q, Must match number of ? as well
$stmt->execute();
$stmt->bind_result($col1,$col2,$col3,$col4); // Can initialize these above with $col1 = "", but these bind what you're selecting. If you select 5 times, must have 5 variables, and they go in in order. select id,name, bind_result($id,name)
$stmt->store_result();
while($stmt->fetch()){ // fetch the results
echo $col1;
}
$stmt->close();
}
?>
Yes mysql_real_escape_string() is deprecated.
One solution, as hinted by answers like this one in that post you included a link to, is to use prepared statements. MySQLi and PDO both support binding parameters with prepared statements.
To continue using the mysqli_* functions, use:
mysqli_prepare() to get a prepared statement
mysqli_stmt_bind_param() to bind the parameter (e.g. for the WHERE condition value='$q')
mysqli_stmt_execute() to execute the statement
mysqli_stmt_bind_result() to send the output to a variable.
<?php
$q = 'Hospital_Name';
$query = "SELECT value FROM database.table WHERE field_name = 'hospital_name' AND value = ?";
$statement = mysqli_prepare($conn, $query);
//Bind parameter for $q; substituted for first ? in $query
//first parameter: 's' -> string
mysqli_stmt_bind_param($statement, 's', $q);
//execute the statement
mysqli_stmt_execute($statement);
//bind an output variable
mysqli_stmt_bind_result($stmt, $value);
while ( mysqli_stmt_fetch($stmt)) {
echo $value; //print the value from each returned row
}
If you consider using PDO, look at bindparam(). You will need to determine the parameters for the PDO constructor but then can use it to get prepared statements with the prepare() method.
I want to know, can I just use prepared statements one time?
Here is my script:
$stm = $db->prepare("UPDATE
qanda AS ans1
JOIN qanda AS ans2 ON ans2.related = ans1.related
JOIN qanda AS ques ON ans2.related = ques.id
SET ans1.acceptedanswer = 1,
ans1.aadate = IF( ans1.id <> ?, ans1.aadate, ?)
WHERE ques.author_id = ? AND ans2.author_id = ?
");
$stm->execute(array($answer_id, time(), $_SESSION["Id"], $author_id));
$done = $stm->rowCount();
if ($done){
/* I don't use prepared statement here */
$stm1 = $db->prepare("UPDATE user SET rep = rep + 15 WHERE id = $author_id");
$stm1->execute();
}
As you see I didn't use prepared statement for second query. Because I did it for first query and if first query is working then I'm sure arguments are valid and don't need to bind them by prepared statement.
Please don't ask me why you don't want to use prepared statemen't for second query, because the reason is too long.
So what I'm doing is correct? Isn't there any security problem?
The straight answer is: yes, you can.
The reason why is actually up to you, since it's anyway good practice to use prepared statement whenever you pass values.
Also consider that, if you are not binding any parameter, it makes more sense to use the query() method, just to be explicit on the fact that you are not going to bind anything. So your second query would be
$stm1 = $db->query("UPDATE user SET rep = rep + 15 WHERE id = $author_id");
(see http://php.net/manual/en/pdo.query.php)
instead of
$stm1 = $db->prepare("UPDATE user SET rep = rep + 15 WHERE id = $author_id");
$stm1->execute();
Moreover you mentioned a dynamic query, but this is not the case of your sample code. Anyway I will give you an example of how two use prepared statement also on queries dynamically generated.
It's a silly example, but should be enough to give you an idea.
Assume we have some values to update 'email', 'date_of_birth' and 'website'. Let's say we want to do some check on this data before inserting them. I'll pretend we have a valid() function already in place.
$dynamic_sql = array();
$parameters[':date_of_bird'] = $date_of_birth;
if(valid($email)) {
$dynamic_sql['email_sql'] = "email = :email";
$parameters[':email'] = $email;
}
if(valid($website)) {
$dynamic_sql['website_sql'] = "website = :website";
$parameters[':website'] = $website;
}
if(count($dynamic_sql)>0) {
$dynamic_sql = ','.implode($dynamic_sql);
}
$query = "UPDATE user
SET date_of_birth = :date_of_birth $dynamic_sql
WHERE
user_id = :user_id";
$stm = $db->prepare($query);
$stm->execute($parameters);
This kind of approach will allow you to keep using prepared statement also with dynamically generated SQL.
Not sure how I can do this. Basically I have variables that are populated with a combobox and then passed on to form the filters for a MQSQL query via the where clause. What I need to do is allow the combo box to be left empty by the user and then have that variable ignored in the where clause. Is this possible?
i.e., from this code. Assume that the combobox that populates $value1 is left empty, is there any way to have this ignored and only the 2nd filter applied.
$query = "SELECT * FROM moth_sightings WHERE user_id = '$username' AND location = '$value1' AND english_name = $value2 ";
$result = mysql_query($query) or die(mysql_error());
$r = mysql_numrows($result);
Thanks for any help.
C
Use
$where = "WHERE user_id = '$username'";
if(!empty($value1)){
$where .= "and location = '$value1'";
}
if(!empty($value2 )){
$where .= "and english_name= '$value2 '";
}
$query = "SELECT * FROM moth_sightings $where";
$result = mysql_query($query) or die(mysql_error());
$r = mysql_numrows($result);
Several other answers mention the risk of SQL injection, and a couple explicitly mention using prepared statements, but none of them explicitly show how you might do that, which might be a big ask for a beginner.
My current preferred method of solving this problem uses a MySQL "IF" statement to check whether the parameter in question is null/empty/0 (depending on type). If it is empty, then it compares the field value against itself ( WHERE field1=field1 always returns true). If the parameter is not empty/null/zero, the field value is compared against the parameter.
So here's an example using MySQLi prepared statements (assuming $mysqli is an already-instantiated mysqli object):
$sql = "SELECT *
FROM moth_sightings
WHERE user_id = ?
AND location = IF(? = '', location, ?)
AND english_name = ?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param('ssss', $username, $value1, $value1, $value2);
$stmt->execute();
(I'm assuming that $value2 is a string based on the field name, despite the lack of quotes in OP's example SQL.)
There is no way in MySQLi to bind the same parameter to multiple placeholders within the statement, so we have to explicitly bind $value1 twice. The advantage that MySQLi has in this case is the explicit typing of the parameter - if we pass in $value1 as a string, we know that we need to compare it against the empty string ''. If $value1 were an integer value, we could explicitly declare that like so:
$stmt->bind_param('siis', $username, $value1, $value1, $value2);
and compare it against 0 instead.
Here is a PDO example using named parameters, because I think they result in much more readable code with less counting:
$sql = "SELECT *
FROM moth_sightings
WHERE user_id = :user_id
AND location = IF(:location_id = '', location, :location_id)
AND english_name = :name";
$stmt = $pdo->prepare($sql);
$params = [
':user_id' => $username,
':location_id' => $value1,
':name' => $value2
];
$stmt->execute($params);
Note that with PDO named parameters, we can refer to :location_id multiple times in the query while only having to bind it once.
if ( isset($value1) )
$query = "SELECT * FROM moth_sightings WHERE user_id = '$username' AND location = '$value1' AND english_name = $value2 ";
else
$query = "SELECT * FROM moth_sightings WHERE user_id = '$username' AND english_name = $value2 ";
But, you can also make a function to return the query based on the inputs you have.
And also don't forget to escape your $values before generating the query.
1.) don't use the simply mysql php extension, use either the advanced mysqli extension or the much safer PDO / MDB2 wrappers.
2.) don't specify the full statement like that (apart from that you dont even encode and escape the values given...). Instead use something like this:
sprintf("SELECT * FROM moth_sightings WHERE 1=1 AND %s", ...);
Then fill that raw query using an array holding all values you actually get from your form:
$clause=array(
'user_id="'.$username.'"',
'location="'.$value1.'"',
'english_name="'.$value2.'"'
);
You can manipulate this array in any way, for example testing for empty values or whatever. Now just implode the array to complete the raw question from above:
sprintf("SELECT * FROM moth_sightings WHERE 1=1 AND %s",
implode(' AND ', $clause) );
Big advantage: even if the clause array is completely empty the query syntax is valid.
First, please read about SQL Injections.
Second, $r = mysql_numrows($result) should be $r = mysql_num_rows($result);
You can use IF in MySQL, something like this:
SELECT * FROM moth_sightings WHERE user_id = '$username' AND IF('$value1'!='',location = '$value1',1) AND IF('$value2'!='',english_name = '$value2',1); -- BUT PLEASE READ ABOUT SQL Injections. Your code is not safe.
Sure,
$sql = "";
if(!empty($value1))
$sql = "AND location = '{$value1}' ";
if(!empty($value2))
$sql .= "AND english_name = '{$value2}'";
$query = "SELECT * FROM moth_sightings WHERE user_id = '$username' {$sql} ";
$result = mysql_query($query) or die(mysql_error());
$r = mysql_numrows($result);
Be aware of sql injection and deprecation of mysql_*, use mysqli or PDO instead
I thought of two other ways to solve this:
SELECT * FROM moth_sightings
WHERE
user_id = '$username'
AND location = '%$value1%'
AND english_name = $value2 ";
This will return results only for this user_id, where the location field contains $value1. If $value1 is empty, this will still return all rows for this user_id, blank or not.
OR
SELECT * FROM moth_sightings
WHERE
user_id = '$username'
AND (location = '$value1' OR location IS NULL OR location = '')
AND english_name = $value2 ";
This will give you all rows for this user_id that have $value1 for location or have blank values.