I am currently trying to write complex MySQL WHERE clauses that are generated from $_GET variables (which themselves come from select dropdowns). First, a bit of code so you know what I am talking about:
if(!isset($_GET['order'])){
$order= 'start asc';
} elseif ($_GET['order'] == "dateasc") {
$order= 'start asc';
} elseif ($_GET['order'] == "titleasc") {
$order= 'title asc';
} elseif ($_GET['order'] == "titledesc") {
$order= 'title desc';
};
if(!isset($_GET['cat'])){
$cat= '0';
} else {
$cat = $_GET['cat'];
};
if(!isset($_GET['loc'])){
$loc= '0';
} else {
$loc = $_GET['loc'];
};
if (isset($_GET['sd']) || isset($_GET['ed']) || isset($_GET['cat']) || isset($_GET['loc']) || isset($_GET['order']) ) {
$where = 'WHERE ';
if (isset($_GET['sd'])) {
$where .= "start = " . $_GET['sd'];
};
if (isset($_GET['ed'])) {
$where .= "AND end = " . $_GET['ed'];
};
if (isset($_GET['cat'])) {
$where .= "AND category = " . $_GET['cat'];
};
if (isset($_GET['loc'])) {
$where .= "AND location = " . $_GET['loc'];
};
};
$result = mysql_query("SELECT * FROM " . TABLE . $where . " ORDER BY " . $order);
Obviously this isn't working, otherwise I wouldn't be here. :) Basically, I have 4 variables that I want to conditionally use for sorting in my query: start date, and end date, a category, and a location. My problem is that all 4 of these may not always be used.. so given the above example, there might be a case where someone selects a category ($cat) but NOT a start date ($sd)... which means my WHERE clause would start off with 'AND', which is obviously invalid. So how do I build a query based off variables that may or may not be used?
I really feel like I am overthinking this, and I am afraid of writing 9000 lines of isset tests to account for every combination of $_GET variable usage. Surely there a simple way to build a WHERE clause from multiple $_GETs that may or may not be used every time..? I've tried Googling but can only find solutions that suggest using a framework for building complex queries and that just seems overly... clunky... for such a simple problem.
If you're just worried about having a where clause that starts with AND you can add 1=1 to account for no filters.
WHERE 1=1
Then, if you have any filters, it will look like this:
WHERE 1=1 AND col1=? AND col2=?
This may not be the cleanest solution, but it should be fairly simple to understand and implement.
if (isset($_GET['sd']) || isset($_GET['ed']) || isset($_GET['cat']) || isset($_GET['loc']) || isset($_GET['order']) ) {
$where = 'WHERE ';
if (isset($_GET['sd'])) {
if(strlen($where) > 6) {
$where .= " AND ";
}
$where .= "start = " . $_GET['sd'];
}
if (isset($_GET['ed'])) {
if(strlen($where) > 6) {
$where .= " AND ";
}
$where .= "end = " . $_GET['ed'];
}
if (isset($_GET['cat'])) {
if(strlen($where) > 6) {
$where .= " AND ";
}
$where .= "category = " . $_GET['cat'];
}
if (isset($_GET['loc'])) {
if(strlen($where) > 6) {
$where .= " AND ";
}
$where .= "location = " . $_GET['loc'];
}
}
Related
I was tasked (stuck) with trying to update an old mysql_query code to be PDO compliant.
This was (is) a messy search form, that was dynamically creating the query string based on field values if (or not) there were any key words submitted along with the form. (ie: any key word is parsed by spaces, and used for BOTH column searches)
So if a search term of 'dog' was entered.. it would search name & title for the key word of 'dog'..
I think I made my way through it.. keeping the main 'function' in-tact for the most part.. and updating when I needed to.
My approach was to take the function that is dynamically adding more criteria to the query string.... and also add this value field name & value to an array, so I can loop through it later on and dynamically bindValues with it..
I am now stick with the ever so popular Invalid Parameters error!!
However its not saying the counts dont match.. its saying it was defined at all.
I'm not clear where my error is stemming from.. (or how to easily see the computed/parsed query string.. or the actual bound parameters) I can just output the sql statement (before it parses any data).. or echo out my values in the array I loop through to (potentially) bind the data to the PDO call..
WHen I echo out the query (string).. and even the values I am attempting to dynamically bind... they all look legit to me:
Query Check: SELECT * FROM pid_information WHERE 1=1 AND (((title LIKE :title0) OR (name LIKE :name0)) AND ((title LIKE :title1) OR (name LIKE :name1))) ORDER BY title, name, link
PARAM CHECK: ':title0' -> %cat%
PARAM CHECK: ':name0' -> %cat%
PARAM CHECK: ':title1' -> %dog%
PARAM CHECK: ':name1' -> %dog%
To re-cap:
addCriteria() function is used to dynamically (concat) add to the query 'string'
I also populate an array to be used later to loop through and bindValues with.
Yes I know it is long.. yes I know ugly.. (please, just bear with me!) LOL
//dynamically add criteria to query
$boundSearchValues = array();
function addCriteria($targetFields, $criteriaString, $targetOperator='LIKE'){
global $boundSearchValues;
$fieldCount = 0;
$tempString = "";
if($criteriaString != ""){
$criteriaArray = explode(" ", $criteriaString);
$tempString .= " AND (";
foreach($criteriaArray as $criteriaIndex => $criteriaValue){
//is array of fields
if(is_array($targetFields)){
$tempString .= "(";
foreach ($targetFields as $targetField => $fieldName){
if($targetOperator != 'LIKE') {
$tempString .= "($fieldName ".$targetOperator." :". $fieldName.$fieldCount .")";
$boundSearchValues[] = [$fieldName.$fieldCount, $criteriaValue];
}else{
$tempString .= "($fieldName LIKE :". $fieldName.$fieldCount .")";
$boundSearchValues[] = [$fieldName.$fieldCount, '%'.$criteriaValue.'%'];
}
if($targetField+1 < count($targetFields)){
$tempString .= " OR ";
}
}
$tempString .= ")";
if($criteriaIndex+1 < count($criteriaArray)){
$tempString .= " AND ";
}
//not an array of fields
}else{
if($targetOperator != 'LIKE') {
$tempString .= "(".$targetFields . $targetOperator . " :" . $fieldName.$fieldCount . ")";
$boundSearchValues[] = [$fieldName.$fieldCount, $criteriaValue];
} else {
$tempString .= "(". $targetFields . " LIKE " . $fieldName . $fieldCount . ")";
$boundSearchValues[] = [$fieldName.$fieldCount, '%'.$criteriaValue.'%'];
}
}
$fieldCount++; //increment counter
}
$tempString .= ")";
}
return $tempString;
}
//start serach query
$searchDetails_sql = "SELECT * FROM $tablename ";
//dynamically update query string
if($clean_keywords != "") {
$whereClause = addCriteria(array('title', 'name'), $clean_keywords);
}else{
if($title != "" && $title != "all"){
$whereClause .= " AND title = :" . $title;
}
if($name != "" && $name != "all"){
$whereClause .= " AND name = :" . $name;
}
if($link != "" && $link != "all"){
$whereClause .= " AND link = :" . $link ;
}
}
$searchDetails_sql .= "WHERE 1=1 ". $whereClause;
$searchDetails_sql .= " ORDER BY title, name, link";
$searchDetails_stmt = $conn->prepare($searchDetails_sql);
//dynamically bind values
for($i=0; $i<count($boundSearchValues); $i++){
$searchDetails_stmt->bindValue("':".$boundSearchValues[$i][0] ."'", $boundSearchValues[$i][1]);
//$searchDetails_stmt->bindParam("':".$boundSearchValues[$i][0] ."'", $boundSearchValues[$i][1]);
echo '<br>PARAM CHECK: ' . $boundSearchValues[$i][0] . " / " . $boundSearchValues[$i][1];
}
$searchDetails_stmt->execute();
$searchDetails_stmt->setFetchMode(PDO::FETCH_ASSOC);
$searchDetails = $searchDetails_stmt->fetchAll(); //returns multi-dimensional array (and correct count)
I think you just messed up the string concatenation in this line
$searchDetails_stmt
->bindValue("':".$boundSearchValues[$i][0] ."'", $boundSearchValues[$i][1]);
You dont actually need the : so you could do this
$searchDetails_stmt
->bindValue($boundSearchValues[$i][0], $boundSearchValues[$i][1]);
Or fix the concatenation and keep the :
$searchDetails_stmt
->bindValue(":".$boundSearchValues[$i][0], $boundSearchValues[$i][1]);
Im creating detailed product search.I verified that ,my variables are posted correctly, but the query don't finding anything. My question is:
What could be wrong in this query and what could be the best solution for detailed search in SQL?
<?php
if (
isset($_POST["productName"]) || isset($_POST["searchCategory"]) ||
isset($_POST["searchManufacturer"]) || isset($_POST["costFrom"]) ||
isset($_POST["costTo"])
){
$stmt=$user_home->runQuery("SELECT* FROM Products
WHERE (productTitle='$_POST[productName]' OR '$_POST[productName]' IS NULL)
AND (category='$_POST[searchCategory]' OR '$_POST[searchCategory]' IS NULL)
AND (manufacturer='$_POST[searchManufacturer]' OR '$_POST[searchManufacturer]' IS NULL)
");
echo $stmt->rowCount();
}
Try to proper forming WHERE statement. You should add conditions for productTitle, category, manufacturer fields only then isset proper POST fields.
Try this code:
<?php
if (
isset($_POST["productName"]) || isset($_POST["searchCategory"]) ||
isset($_POST["searchManufacturer"]) || isset($_POST["costFrom"]) ||
isset($_POST["costTo"])
){
$conditions = array();
if (isset($_POST['productName'])) {
$conditions[] = "(productTitle='".$_POST['productName']."')";
}
if (isset($_POST['category'])) {
$conditions[] = "(category='".$_POST['searchCategory']."')";
}
if (isset($_POST['searchManufacturer'])) {
$conditions[] = "(manufacturer='".$_POST['searchManufacturer']."')";
}
$where = implode(' AND ', $conditions);
if ($where) {
$where = 'WHERE '.$where;
} else {
$where = "";
}
$stmt=$user_home->runQuery("SELECT * FROM Products ". $where);
echo $stmt->rowCount();
}
i have got 4 checkboxes for filterung mysql result. Checkboxes can be activated all or single, too. I don't know how to make sql statement. DO i really have to use all combined possibilities manually or is there a simplier solution? Perhaps with "switch"?
No as first statement i have:
if ($vart1 == "1" AND !isset($vart2) AND !isset($vart2) AND !isset($vart2) AND !isset($vart4)) {
$tname_sql .= " a.tdesc = 'option1' AND";
};
How many variations are there?
Thank you for help.
Regards,
Olaf.
You can make a simple PHP function which returns WHERE or AND depending how many times has been called:
$wa = 0;
function whereAnd() {
global $wa;
if ($wa == 0) {
$wa = 1;
return ' WHERE ';
} else {
return ' AND ';
}
}
$query = "SELECT * FROM table1 t ";
if (isset($var1)) {
$query .= whereAnd() . "t.field1 = " . $var1;
}
if (isset($var2)) {
$query .= whereAnd() . "t.field2 = " . $var2;
}
if (isset($var3)) {
$query .= whereAnd() . "t.field3 = " . $var3;
}
if (isset($var4)) {
$query .= whereAnd() . "t.field4 = " . $var4;
}
First call will returns WHERE, all other calls will return AND no matter what kind of combinations you may have. You don't need to care about how many checkboxes are passed.
I have a form that requires the user to only fill out at least 1 (out of four) fields. They can then submit and get a search result based off of their input.
The problem is, I can't get a character to set my variables to that will match any database value. Here is my code for some context;
if (isset($_POST['buildname']) ||
isset($_POST['weapon']) ||
isset($_POST['category']) ||
isset($_POST['id']))
{
if ($_POST['buildname'] == "")
{
$buildname = ".*";
}
if ($_POST['weapon'] == "")
{
$weapon = ".*";
}
if ($_POST['category'] == "")
{
$category = ".*";
}
if ($_POST['id'] == "")
{
$id = ".*";
}
$buildname = sanitizeString($_POST['buildname']);
$weapon = ($_POST['weapon']);
$category = ($_POST['category']);
$id = ($_POST['id']);
$searchstring = "SELECT buildname,weapon,category,id,author FROM weapons " .
"WHERE buildname='$buildname' AND weapon='$weapon' AND category='$category' AND id='$id'";
As you can see, the code looks at if one of the variables is set, then submits a form. If a variable isn't set, it assigns a character of ".*" (which I thought would match anything). It then queries the database to match any rows. I get no results unless I enter EVERY field with a correct entry.
Any ideas?
Thanks!
I would not use %, instead do something like this
if (isset($_POST['buildname']) || isset($_POST['weapon']) || isset($_POST['category']) || isset($_POST['id'])){
$sqlArray = array();
if(isset($_POST['buildname'])){
$sqlArray[] = "buildname='" . mysqli_real_escape_string($connection,$_POST['buildname']) . "'";
}
if(isset($_POST['weapon'])){
$sqlArray[] = "weapon='" . mysqli_real_escape_string($connection,$_POST['weapon']) . "'";
}
if(isset($_POST['category'])){
$sqlArray[] = "category='" . mysqli_real_escape_string($connection,$_POST['category']) . "'";
}
if(isset($_POST['id'])){
$sqlArray[] = "id='" . mysqli_real_escape_string($connection,$_POST['id']) . "'";
}
$searchstring = "SELECT buildname,weapon,category,id,author FROM weapons " .
"WHERE " . implode(' AND ', $sqlArray);
}
The wildcard character for MySQL is: %
The query you are executing, you "thought would match anything" wont. The statement uses no regular expressions.
WHERE buildname='$buildname' AND weapon='$weapon'
Which is essentially saying you need to have the following fields equal their string value of:
WHERE buildname='.*' AND weapon='.*'
I doubt you have any building with a name of .*.
It would be better to not filter on that field. basically remove the WHERE cause criteria if the variable is not defined.
You can do this dynamically, buliding the SQL statement only when you need to filter by that field.
if (isset($_POST['somevalue']) && ! empty($_POST['somevalue'])) {
$where .= 'column_name = ?';
$values[] = sanitizeString($_POST['somevalue]);
}
I've also used positional parameters which assumes you will be using the PDO or MySQLi libraries for querying.
No, you are using = operator, that only compares 2 values. In your case it will search for '.*' - and fail. If you want to ignore the fields, that were not filled, just don't put them into the query: no need for regexps. So, if the weapon and category are missing, your query should be like this
$searchstring = 'SELECT buildname,weapon,category,id,author FROM weapons WHERE ';
$fields = array('buildname', 'weapon', 'category', 'id');
$data = array();
foreach($fields as $value)
{
if (isset($_POST[$value]) && ($_POST[$value] != "") )
{
$data[] = sanitizeString($_POST[$value]);
}
}
$n = count($data);
if($n > 0)
{
$searchstring .= implode(' AND ', $data);
//do MySQL request and output result
}
Don't overcomplicate simple things. Also your code is vulnerable to SQL injection as some fields are not escaped.
You can do it like this:
$fields = array('buildname', 'weapon', 'category', 'id');
$sql = 'SELECT buildname, weapon, category, id, author FROM weapons';
$prefix = ' WHERE ';
foreach ($fields as $field) {
if (isset($_POST[$field]) && strlen($_POST[$field])>1) {
$sql .= $prefix . $field . '=\''
. sanitizeString($_POST[$field]) . '\'';
$prefix = ' AND ';
}
}
if ($prefix == ' AND ') {
// send the query
}
Notice: if you want to perform search with incomplete values, you could use LIKE instead of =, example:
$sql .= $prefix . $field . ' LIKE \'%' . sanitizeString($_POST[$field]) . '%\'';
But keep in mind that LIKE is slower than =
I want to make our search module be able to search keywords like *lex. Using * to substitute the letters before that. I found the % I add into my query doesn't work and returns error. Please help me to have a look at my code. thanks
if(substr($searchfirst_name, 0, 1) == '*'){
$subquery .= " AND first_name LIKE %".Formatter::sql($searchfirst_name);
}elseif(substr($searchfirst_name, -1, 1) == '*'){
$subquery .= " AND first_name LIKE ".Formatter::sql($searchfirst_name)."%";
}else{
$subquery .= " AND first_name LIKE ".Formatter::sql($searchfirst_name);
}
I think the problem is because of the function Formatter::sql(). It filter the variable and add slash to it. How do I modify or create a new function to do this?
public static function sql($value, $fieldName = false) {
if($fieldName) {
return '`' . $value . '`';
}
else if( is_string($value) ) {
return '\'' . addslashes($value) . '\'';
}
else if($value === null) {
return 'NULL';
}
else if($value === true) {
return 1;
}
else if($value === false) {
return 0;
}
else if( is_numeric($value) ) {
return $value;
}
else {
return '\'' . addslashes($value) . '\'';
}
}
You have to use quotes I think:
LIKE '%...' or LIKE '...%'
EDITED:
Well, the error is clear now:
query run using formatter is like .... LIKE %'....' which is completely wrong.
When you use LIKE clause don't use formatter, but use $searchfirst_name directly!!
Try this
if(substr($searchfirst_name, 0, 1) == '*'){
$subquery .= " AND first_name LIKE '%".Formatter::sql($searchfirst_name)."'";
}elseif(substr($searchfirst_name, -1, 1) == '*'){
$subquery .= " AND first_name LIKE '".Formatter::sql($searchfirst_name)."%'";
}else{
$subquery .= " AND first_name LIKE '".Formatter::sql($searchfirst_name)."'";
}
The first two look ok, but your third else cannot be LIKE. See below.
if(substr($searchfirst_name, 0, 1) == '*'){
$subquery .= " AND first_name LIKE %".Formatter::sql($searchfirst_name);
}elseif(substr($searchfirst_name, -1, 1) == '*'){
$subquery .= " AND first_name LIKE ".Formatter::sql($searchfirst_name)."%";
}else{
$subquery .= " AND first_name = ".Formatter::sql($searchfirst_name);
}
You had an % outside of string quotes and you probably need to get rid of * characters too
So instead of whole if contruct:
$subquery .= " AND first_name LIKE ".str_replace('*','%',Formatter::sql($searchfirst_name));
but beware this gives the functionality to substitute * everywhere in the string
Just do:
if(substr($searchfirst_name, 0, 1) == '*'){
$subquery .= " AND first_name LIKE ".Formatter::sql('%'.$searchfirst_name);
}elseif(substr($searchfirst_name, -1, 1) == '*'){
$subquery .= " AND first_name LIKE ".Formatter::sql($searchfirst_name.'%');
}else{
$subquery .= " AND first_name LIKE ".Formatter::sql($searchfirst_name);
}
This will work because the escape functionality in Formatter::sql won't affect teh '%'
In your sql function, replace addslashes with the database provided escape function, e.g., mysql_real_escape_string. Always use the database mechanism for escaping strings (or better yet, use parameterized queries with PDO).