Trying to create a dynamically generated mySQL select statement using PHP - php

I have 5 variables which can either be true or false and I have to generate different sql SELECT statements for each different possible outcome. Right now I have a ton of if else statements but I'm wondering if theres a smarter way to do this?
so for example, i have
if (x=true AND y=false AND z=false AND a=false AND b =false) {
$sql= "SELECT...."
} else if(x=true AND y=true AND z=false AND a=false AND b=false) {
$sql= "SELECT...."
}
The first select statement is if the user entered text and didnt select anything else:
$sql="SELECT CompanyName, Keywords, Product, Industry, Link, region, hot FROM searchtest_tbl WHERE Keywords LIKE '%$formSearch%' OR CompanyName LIKE '%$formSearch%' OR Product LIKE '%$formSearch%' OR Industry LIKE '%$formSearch%' ORDER BY hot DESC, CompanyName";
this statement is if they only selected from the Industry dropdown:
$sql="SELECT CompanyName, Product, Industry, Link, hot, region FROM searchtest_tbl WHERE Industry='$formIndustry' ORDER BY hot DESC, CompanyName";
and here is one if they entered text, selected an industry, but didnt select anythign else:
$sql="SELECT CompanyName, Product, Industry, Link, hot, region FROM searchtest_tbl WHERE Industry='$formIndustry' AND (Keywords LIKE '%$formSearch%' OR CompanyName LIKE '%$formSearch%' OR Product LIKE '%$formSearch%') ORDER BY hot DESC, CompanyName";
The rest are basically like this, but if the other fields are selected it will say WHERE Product='$formProduct' AND.... etc

Perhaps you can break down your logic into smaller components. For example, if x=true corresponds to a specific part of your select statement, and y=false corresponds to a different part, build the select statement piece by piece:
$sql_statement = "SELECT ";
$sql_statement .= ($x) ? "`column_a` " : "`column_a`, `column_b` ";
$sql_statement .= "FROM ";
$sql_statement .= ($y) ? "`table_a` " : "`table_b` ";
If this approach is not suitable for you, can you post additional code so we can make a better judgement?

I'm going to go right out and assume that your true and false statements don't relate to your SQL, because if they did you could simply replace them with variables that could be placed directly into your SQL. For Example
Instead of having a true $x variable mean ASC order and a false variable mean DESC order. You could create a new variable (we'll call it a) and put the value ASC or DESC in it. That way instead of having
if($x){
//sql
}
else{
//sql
}
You could have someting to the effect of
SELECT * FROM search $a;
Now if for some reason these variables are completely random and unrelated.
And you have to use ifs and else ifs, I would suggest changing your code for comprehensibility and logic. But I'll be able to help you mor if you post your original code, or at least the variables.

You might want to consider using binary numbers and bitwise operators.
FLAG_1 = bindec('0001') = 1
FLAG_2 = bindec('0010') = 2
FLAG_3 = bindec('0100') = 4
FLAG_4 = bindec('1000') = 8
then you can combine all of the flags into one int using the bitwise operator "|"
so if flag 2 and 4 are on
$combined_flags = (FLAG_2 | FLAG_4) = bindec('1010') = 10;
you could then use the combined flag int to determine which SQL statement to generate
you can even test for individual flags using the bitwise & operator
if ($combined_flags & FLAG_2) // true
if ($combined_flags & FLAG_1) // false

Related

PHP search many inputs [duplicate]

This question already has answers here:
Search Form with One or More (Multiple) Parameters
(2 answers)
Closed 4 years ago.
I have search field in which user can specify a lot of values to search like price, surface, year, garden, balcony etc.
In my search there is not even one field required every one is optional so user can provide 0 inputs filled or all.
Basically all this info's are saved in my database but I don't really know how to structure my code.
At the moment I have PHP file which I call from front and in this file I'm checking which field was filled and I'm executing method from class which do select to db and return data. This is working fine for every input separately but when I combain for example 2 different fields like price and surface then none of methods will be executed.
Im basically asking about an idea for architecture of search where user can fullfill many different fields. Im not using any PHP framework.
I could do something like:
if(a & b & c & d & e & f) then execute method a
if(a & b & c & d & e) then execute method b
if(a & b & c & d) then execute method c
and so on.. where this letters(a, b, c etc...) are $_POST['something'] but I would have a lots of if's to check which POST (which inputs) user fullfill and sent. Later on I would need to create a lot of methods in class with different SELECTs to db basing on which POST we have... I don't think that's best solution because I would basically repeat my code.
Something like this
$sql = 'SELECT * FROM sometable';
$where = [];
$params = [];
if($a){
$where[] = 'a = :a';
$params[':a'] = $a;
}
if($b){
$where[] = 'b = :b';
$params[':b'] = $b;
}
if(!empty($where)){
$sql .= ' WHERE '.implode(' AND ', $where);
}
$stmt = $PDO->prepare($sql);
$res = $stmt->execute($params);
And so On.
It almost always preferable to use and array and implode for things like this instead of concatenation. Often concatenation will leave you with a hanging "separator" in this case " AND ". For example if we tried this with concatenation:
//if we put WHERE here and then nothing passes our conditions we wind up with:
//"SELECT * FROM sometable WHERE" which wont work
$sql = 'SELECT * FROM sometable ';
//we still need something like an array if we want to prepare our query.
//which is something we should always do
$params = [];
if($a){
//if we put WHERE here, then what if this condition doesn't pass
//do we put it in the next condition? How do we tell. .
$sql .= 'WHERE a = :a AND ';
$params[':a'] = $a;
}
if($b){
//again if the first condition didn't pass how do we know to put "WHERE" here.
//"SELECT * FROM sometable b = :b AND" which wont work
$sql .= 'b = :b AND ';
$params[':b'] = $b;
}
if($c){
//lets say the first 2 conditions passes but this last one failed
//"SELECT * FROM sometable WHERE a = :a AND b = :b AND" which wont work
$sql .= 'c = :c';
$params[':c'] = $c;
}
//we would need to do something like this to trim the last "AND" off
$sql = preg_replace('/\sAND\s$/', '', $sql);
//--------------------
//now if we were prepending "AND" instead of appending it, we're no better off.
//--------------------
//we can fix the where issue by using a string variable (and testing it latter)
$where = '';
if($a){
$where .= 'a = :a';
$params[':a'] = $a;
}
if($b){
//However lets say the first condition failed, we get this:
//"SELECT * FROM sometable WHERE AND b = :b" which wont work
$where .= ' AND b = :b';
$params[':b'] = $b;
//--------------------------
//so in every condition following we would have to test $where
//and if its not empty then we can prepend "AND"
if(!empty($where)) $where .= ' AND ';
$where .= 'b = :b';
$params[':b'] = $b;
}
if($c){
if(!empty($where)) $where .= ' AND ';
$where .= 'c = :c';
$params[':c'] = $c;
}
//finally to fix the "WHERE" issue we need to do something like this:
if(empty($where)) $sql .= ' WHERE '.$where;
//we could also try something like this in every condition:
if($d){
if(empty($where)) $where .= ' WHERE ';
//However, this breaks our fix for prepending "AND", because
//$where will never be empty when we test it.
//if(!empty($where)) $where .= ' AND ';
$where .= 'd = :d';
$params[':d'] = $d;
}
Hopefully that all makes sense. It's just so much easier to use an array and implode it later.
I just wanted to show that to help visualize the issues with concatenation. We wind writing more code, using the same number of variables and double the conditional logic. Or we can get into complicated things like Regex to trim the hanging AND off etc.
Hope that helps!
BECAUSE I mentioned it in the comments.
If you are using "OR" you can of course do the same thing with that, but typically "OR" will cause a full scan of the DB. It's just the way OR works. When we use "AND" the DB (basically) takes the return set and applies the next condition to that, because both have to pass. However, with "OR" rows that failed the first condition could still pass if the second condition passes. So the DB must scan the full record set for each or, as well as keep track of all the rows that passed in the previous conditions. It's just the way the logic works for "OR".
Now for improved "OR" performance we can use a sub-query that is a union. Like this:
$sql = 'SELECT * FROM sometable AS t';
$union = [];
$params = [];
if($a){
$union[] = 'SELECT id FROM sometable WHERE a = a:';
$params[':a'] = $a;
}
if($b){
$union[] = 'SELECT id FROM sometable WHERE b = b:';
$params[':b'] = $b;
}
if(!empty($union)){
$sql .= '
JOIN( '.
implode(' UNION ', $union).
' ) AS u ON t.id = u.id
}
What we wind up with is something like this query:
SELECT
*
FROM
sometable AS t
JOIN (
SELECT id FROM sometable WHERE a = a:
UNION
SELECT id FROM sometable WHERE b = b:
) AS u ON t.id = u.id
When we use "OR" as our dataset grows the DB must store these results in temp table as well as search the entire dataset. Because we are pulling all the columns in the table, this dataset will quickly grow. Once it hits a certian size it will get swapped to Disc and our performance will take a big hit for that.
With the Union query, we also create a temp table. But because we are only concerned with pulling out the ids this temp table will be very small. Union unlike Union ALL will also automatically remove duplicate records further reducing our dataset. So we want to use Union and not Union ALL.
Then we join this back on the table in the outer query and use that to pull the all the columns from just the rows that we need.
Basically we are accepting the fact that we need a temp table and minimizing the impact of that.
This might not seem like it would be much faster, and in some cases it might not be (when no swapping happens). But for me, using a query like you describe where users can search on multiple fields, I was able to reduce the time it took from about 15 seconds to under 1 second. My query had several joins in it such as if a user put in a state, I had to join on participant then participants_addresses (junction table) and then addresses and then finally on states. But if they put in a phone I had to join participant > participants_phones > phone etc.
I can't guarantee this will work in every case and you should use Explain and SQL_NO_CACHE when benchmarking your queries. For example EXPLAIN SELECT SQL_NO_CACHE * FROM .... Explain will tell you how the indexes are working and No Cache prevents the DB from caching the query if you run it multiple times. Caching will make it look like it is fast when it's really not.
You can do something similar when sorting, which also kills performance.
SELECT
*
FROM
sometable AS t
JOIN (
SELECT id FROM sometable WHERE a = a: ORDER BY date DESC
) AS u ON t.id = u.id
This has a similar effect of only sorting the id's in the temp table (instead of the whole dataset), and then when we join it, it actually keeps the order the ids are in. I forget if the order of the subquery vs the outer query matter.
For fun you can even combine the two with 2 nested sub-queries, with the Union as the deepest query (it's something like this).
SELECT
*
FROM
sometable AS t
JOIN (
SELECT id FROM sometable AS t0 JOIN (
SELECT id FROM sometable WHERE a = a:
UNION
SELECT id FROM sometable WHERE b = b:
) AS u ON t0.id = u.id
ORDER BY t0.date DESC
) AS t1 ON t.id = t1.id
It can get pretty complicated though ... lol.
Anyway, I was bored and maybe, just maybe, it will work for someone like it did for me. (this is what happens when I don't get sleep) :)
UPDATE
IF you have problems with the parameters you can output the SQL with the values filled in by doing this:
echo str_replace(array_keys($params), $params, $sql)."\n";
But use this only for Debugging, not for putting the data into the query because that would defeat the purpose of using prepared statements and open you up to SQLInjection attacks. That said, it can make it easier to see if you are missing anything or have any spelling errors. I also use this when I just want to test the query in PHPMyAdmin, but am to lazy to cut an paste the data into it. Then I just copy the output put it in PHPMyAdmin and then I can rule out any issues with PHP or tweak the query if need be.
You can also have issues if you have to many elements in the array, AKA extra placeholders that are not in the query.
For that you can do
//count the number of : in the query
$num_placeholders = substr_count(':', $sql);
//count the elements in the array
$num_params = count($params);
if($num_placeholders > $num_params ) echo "to many placeholders\n";
else if($num_placeholders < $num_params ) echo "to many params\n";
One last thing to be mindful of when mixing "AND" and "OR" is stuff like this
SELECT * FROM foo WHERE arg1 = :arg1 OR arg2 = :arg2 AND arg3 = :arg3
The way it executes this is like this
SELECT * FROM foo WHERE arg1 = :arg1 OR (arg2 = :arg2 AND arg3 = :arg3)
This will return all rows that match arg1 regardless of the rest of the query.
Most of the time this would not be what you want. You would actually want it to do it this way:
SELECT * FROM foo WHERE (arg1 = :arg1 OR arg2 = :arg2) AND arg3 = :arg3
Which is called an "Exclusive OR". This will return all rows that match arg1 OR arg2 AND arg3
Hope that helps.
You could also create an wanted list of nesseccary items and Check If each Item is Set by the PHP function isset().

Filter using PHP and MYSQL

I am an programming amateur, working on a small project of mine but i got stucked when I wanted make filters for my mysql output.
All works when I fill all search input fields and submit, correct filtered result appears. But when I leave one field out nothing shows up (only else command) as using AND condition. If I use OR and leave it empty it shows all result without caring what has been filled in the required fields.
Would there be any way how to show result even if one of the field stays empty? I tried to play with a code from different post here in stackoverflow but no luck yet as i am not really much experienced and cannot figure out how to use this inside of my code.
if(isset($_POST["profilename"]) && $_POST["profilename"] != "")
$sql .= " AND profilename = '". $_POST["profilename"] ."'";
Below here is my code, if you could have a look and suggest what i could edit so the filtering would be working.
<select class="form-control" id="Select1" name="departure">
<select class="form-control" id="Select2" name="destination">
<select class="form-control" id="Select3" name="layover">
...
<?php
include_once("connect.php");
if (isset($_GET['submit'])) {
$departure = mysqli_real_escape_string($connection, $_GET['departure']);
$destination = mysqli_real_escape_string($connection, $_GET['destination']);
$layover = mysqli_real_escape_string($connection, $_GET['layover']);
$result = mysqli_query($connection, "SELECT * FROM crud
WHERE departure LIKE '$departure%'
AND kam LIKE '$kam'
AND layover LIKE '$layover'
ORDER BY id DESC");
if($make = mysqli_num_rows($result) > 0){
while($r = mysqli_fetch_assoc($result)){
echo '<div style="display:block;"'.$r['departure'].'</div>';
echo '<div style="display:block;"'.$r['kam'].'</div>';
echo '<div style="display:block;"'.$r['layover'].'</div>';
//following code..
}
}else{
echo'<h4>No match found!</h4>';
print ($make);
}
mysqli_free_result($result);
mysqli_close($connection);
}
?>
For example: Filter Departure with Destination and leave Layover empty -> should return a result of desired Departure and Destination with ANY Layover.
or 2nd example: Fill Destination with Layover but leave Departure empty would result in desired search of Destination and Layover with ANY Departure.
Please let me know if its possible with my code and possibly how.
Thank you so much guys!!
You can check each input against an empty string in an OR condition within each of the different AND conditions.
"SELECT * FROM crud
WHERE ('$departure' = '' OR departure LIKE '$departure%')
AND ('$kam' = '' OR kam LIKE '$kam')
AND ('$layover' = '' OR layover LIKE '$layover')
ORDER BY id DESC"
This way, if one of the input parameters isn't given, the corresponding part of the condition will be interpreted as, for example, ('' = '' OR departure LIKE '$departure%') which will evaluate as true for every row since '' always equals '' regardless of the LIKE comparison.
A couple of notes - first, without any wildcard characters, your LIKE comparisons basically work like =, (e.g. kam LIKE '$kam' will match the same rows as kam = '$kam'). So either just use = or add some wildcards in order for those to be useful.
Second, consider using prepared statements instead of concatenating values into your SQL like this. The concatenation approach makes your code vulnerable to SQL injection and various annoying errors (even if you escape the strings).
something like this:
$result = mysqli_query($connection, "SELECT * FROM crud
WHERE (departure LIKE '$departure%' OR departure is null)
AND kam LIKE '$kam'
AND (layover LIKE '$layover' OR departure is null)
ORDER BY id DESC");
Let's put the "dont build your queries using user input" aside.
If you want to go that path, you could use conditionals to build the query step by step.
$query = "SELECT * FROM crud WHERE ";
if (!empty($departure)) {
$query .= "departure LIKE '$departure%' AND ";
}
if (!empty($destination)) {
$query .= "destination LIKE '$destination%' AND ";
}
if (!empty($layover)) {
$query .= "layover LIKE '$layover' AND ";
}
$query .= "1 ORDER BY id DESC";
You will end up with some alternatives in the $query string:
NOTHING SELECTED:
SELECT * FROM crud WHERE 1 ORDER BY id DESC
SOMETHING SELECTED:
SELECT * FROM crud WHERE departure LIKE '$departure%' AND layover LIKE '$layover' AND 1 ORDER BY id DESC
ALL SELECTED:
SELECT * FROM crud WHERE departure LIKE '$departure%' AND destination LIKE '$destination%' AND layover LIKE '$layover' AND 1 ORDER BY id DESC
This should cover it.
Have a good day

Arrays display order

So this script works like this. It takes the tags you have chosen as your interests and compares it with the other users tags. This script below gives me the output of how many tags me and the other person have common.
$get_userinfo = mysql_query("SELECT * FROM users") or die(mysql_error());
while($userinfo = mysql_fetch_array($get_userinfo)) {
$usertags = $userinfo['tags'];
$tagsdata = explode(" ", $usertags);
$interestsdata = explode(" ", $interests);
$result = array_intersect($interestsdata, $tagsdata);
echo "Count under this belongs to ".$userinfo['name']."";
echo count($result);
echo "<br />";
}
Now, I want first, this script also displays me in the list so I see how many tags I have common with myself and I want to remove myself. And second, how do I list it so the person with highest number (most tags common) get displayed at the top and descending.
Add a condition to exclude yourself from your own query:
SELECT * FROM users WHERE user_id != [your user id]
To order by the number of tags, you need to issue an aggregate function in your SQL statement to group results by the tag and user. Without seeing your table structure I can only offer a very generalized statement
SELECT username, tag, count(tag) FROM users JOIN tags ON x = y WHERE user_id != [your user id] GROUP BY user_id, tag_id ORDER BY count(tag) DESC
Another option is to collect your data, and sort through it with PHP after you already know how many tags each user has in common. See http://php.net/manual/en/function.sort.php for information on sorting arrays.
You should also start using PDO or mysqli as the mysql functions have been officially deprecated.
I also strongly advise you to not put multiple tags in one database field.

How can I include (user selected - dynamic) columns in select statement and where clause?

I would like the users to choose which fields they want to see and which they do not want to see.
Table: Companies(cid, cname, state, project_manager, site_supervisor, elec_engg, mech_engg, hydraulics, .....)
Note: All the columns from project_manager to the last column have the value 'Yes/No'
Lets say the user wants to find the companies that have Project managers and electrical engineers in NSW.
The Query will be:
Select cid, cname, project_manager, elec_engg
from companies
where state='NSW'
AND project_manager='Yes'
AND elec_engg ='Yes';
I was wondering how can I make this search dynamic. Displaying all job titles in a HTML form and having check boxes next to each job title and with search button. Something like below.
Query:
select cid, cname, (dynamic user input of columns)
from companies
where state="NSW"
AND Dynamic input column1 ='Yes'
and Dynamic input column2 ='Yes'
AND Dynamic input column3 ='Yes'.....
AND Dynamic input columnn ='Yes';
I'm assuming you're just ANDing all the parameters together. If you can set the name for each HTML the elements in the form exactly what you expect for the column name this should work. When you get the post back (assuming search submits the form as a post), you can loop through the post values. I'd create an array of valid columns to check against as a whitelist to avoid ever having a broken query.
Here's an example using oldschool mysql escaping or you to get the idea, but I'd really use PDO with prepared statements to populate the values of the query. The key is to protect both your where parameters and values when dynamically creating SQL. Shoot me a message if you're using PDO and would like to see that, but this will give you a place to start:
$sql = 'SELECT cid, cname, project_manager, elec_engg FROM companies ';
$whereClause = null;
//whitelist of valid where clause parameters
$validParams = array('site_supervisor', 'elec_engg', 'mech_engg', 'hydraulics');
foreach($_POST as $key=>$value){
if(array_search($key, $validParams)!==false){ //make sure you use !== and not !=
if(empty($whereClause)){
$whereClause = " WHERE ";
}else{
$whereClause .= " AND ";
}
//IMPORTANT: use whatever you're db needs to escape things or use prepare
//statement replacement
$whereClause .= "$key='".mysql_escape_string($value)."' ";
}
}
$sql = $sql.$whereClause;
echo $sql;
You can also add a loop check for just values of 'Yes' and exclude 'No' values...
As noted below in a comment mysql_escape_string($value) is bad... no doubt, to do this correctly and safely with prepared statements and pdo you would change the code to:
$sql = 'SELECT cid, cname, project_manager, elec_engg FROM companies ';
$whereClause = null;
//whitelist of valid where clause parameters
$validParams = array('site_supervisor', 'elec_engg', 'mech_engg', 'hydraulics');
foreach($_POST as $key=>$value){
if(array_search($key, $validParams)!==false){ //make sure you use !== and not !=
if(empty($whereClause)){
$whereClause = " WHERE ";
}else{
$whereClause .= " AND ";
}
//IMPORTANT: use whatever you're db needs to escape things or use prepare
//statement replacement
$whereClause .= " $key=:$key ";
}
}
$sql = $sql.$whereClause;
$db = new PDO($someDsnString);
$statement = $db->prepare($sql);
foreach($_POST as $key=>$value){
if(array_search($key, $validParams)!==false){ //make sure you use !== and not !=
$statement->bindValue(":$key", $_POST[$key]);
}
}
$statement->execute();
$result = $statement->fetchAll();
Both the prepared statements on the values and the whitelist on the parameter values will make this query safe.
just put the result of each field into a string, so something like
$builderworks = $POST_['builderworks'];
$hydrolics = $POST_['hydrolics'];
then just select from the database using all the string values, just make sure you sanitise the POST data above first using mysql_real_escape_string.
so something like
SELECT jobname FROM jobtable WHERE hydrolics='$hydrolics' AND $builderworks='$builderworks'
ect ect
so basically $builderworks etc is whatever has been POSTED in that field, it is stored inside everytime someone submits, so if fo example I went to the form, selected the field builderworks to yes, $builderworks would then equal yes when I hit submit, whatever is stored in each string is then compared using the database query then so if $builderworks has yes inside it, selecting all fields that are equal to $builderworks would select all fields equal to yes, or equal to no if I had selected no in the form.
Don't do this on your database layer. It's a really, really bad idea for security, stability and maintainability all at once. Separate your concerns. Your data access layer should not be dependent on your user's permissions and should definitely not be dependent on your view.
Extract your data from your database as you normally would (into a "model" or some other structure) and then selectively show the data of that structure in your rendering layer as needed (the "view").

Search mySQL with PHP, using a WHERE wildcard or an IF statement?

I'm letting users search my database for data by city.
My query looks like:
$results = mysql_query("SELECT * FROM mydb WHERE City='".$city."' LIMIT 10");
I want a user to be able to search 'all cities', so I'd like to either remove the WHERE statement if $city=='all cities'; or use a wildcard for the WHERE statement that matches all cities in the db.
I used to have an IF statement that switched between two queries, but I want to add more filters like country/all countries, zipcode/all zipcodes, etc, So I'd rather keep one dynamic SQL query.
Well, you could still have just one query and build the where clause dynamically, as such:
$where = '';
// conditional statements (if/else, switch) for populating the where clause
$where .= " WHERE City = '{$city}'";
$where .= " AND Country = '{$country}'";
$results = mysql_query("SELECT * FROM mydb{$where} LIMIT 10");
One way would be a case statement:
WHERE City = case when '$city' = 'All cities' then City else '$city' end
If the user is searching for 'All cities', this turns the WHERE statement into:
WHERE City = City
Which is always true (at least for non-null cities ;))
P.S. Make sure you're running these queries using a read-only MySQL account. The user could enter funny stuff into the $city parameter!
You could try
WHERE City like '$city'
and permit the users to enter wildcards, if you think they'd be up to it.
although not PHP programmer, this pseudocode might offer an option... conditionally build out your where clause. Additionally, I would do it with parameterized queries instead of direct string building to prevent sql-injection attacks.
cYourSQL = "select * from YourTable where "
cAndRequired = ""
if city is NOT "all cities"
cYourSQL = cYourSQL + cAndRequired + " city = 'YourParameterValueProvided' "
cAndRequired = " AND "
endif
Now, always add your country selection
cYourSQL = cYourSQL + cAndRequired + " country = 'YourCountryValue' LIMIT 10 "
Run the query

Categories