One of the column in table has been set as NULL, will contain or won't contain value.
Now, if user input is NULL then select all row that contains null. If in case of non-empty input, then select accordingly.
WHERE student.section = NULL // tried like '', 'NULL'
But I found this is not a valid way
So I do following logic, but I thought it is not well-structured, I thought it can be shorten, to prevent repetitive code.
if section is null
if(empty($_POST['section'])){
$result=$con->prepare(
"SELECT
...
FROM student
WHERE student.class=:cid AND student.section IS NULL"
)
$result->bindParam(':cid',$_POST['class']);
$result->execute();
} else {
$result=$con->prepare(
"SELECT
...
FROM student
WHERE student.class=:cid AND student.section=:sid"
)
$result->bindParam(':cid',$_POST['class']);
$result->bindParam(':sid',$_POST['section']);
$result->execute();
}
Remove the superficial table usage. If there are no joins you don't need to put it in again, this will make it "shorter" but keep the same logic.
SELECT
...
FROM student
WHERE class=:cid AND section=:sid
There I saved you about 14 keystrokes and0<14 so it's "reduced".
I know I said IS NOT NULL in the comments but logically there is a difference between
AND student.section=:sid
AND
AND student.section IS NOT NULL
IF student.section = 10 and :sid is 20, you won't get the record, but if you change it to IS NOT NULL then you will because 10 certainly is not null, but it's also not 20, and thus the logic has been changed. In once case you don't get a record in the other you do.
UPDATE
This is the only other thing I can think of
$sql = 'SELECT
...
FROM student
WHERE class=:cid AND section';
if(empty($_POST['section']))
$sql .= ' IS NULL';
else
$sql .= ' = :sid';
$result=$con->prepare($sql);
$result->bindParam(':cid',$_POST['class']); //this is wrong
$result->execute();
UPDATE1
But, you really shouldn't do this either.
$result=$con->prepare($sql); //this is PDOStatment not a result
$result->bindParam(':cid',$_POST['class']); //this is wrong
$result->execute(); //your not setting a result variable
Also it will be hard to conditionally bind to stmt (PDOStatment object) because you don't yet have it when the query is being constructed. You could do prepare 2x and bind to cid 2x. You could do the condition 2x, and then bind in the second condition. But, we are shorting it and fortunately we can pass the arguments in as an array for execute.
So I would change that to use an array of parameters
$sql = 'SELECT
...
FROM student
WHERE class=:cid AND section';
$param = [':cid' => $_POST['class']];
if(empty($_POST['section'])){
$sql .= ' IS NULL';
}else{
$sql .= ' = :sid';
$param[':sid'] = $_POST['section'];
}
$stmt=$con->prepare($sql);
$result = $stmt->execute($param);
$data = $result->fetchAll(PDO::FETCH_ALL);
The way you worded the question made it difficult to understand. This is just basically DRY (Don't Repeat Yourself) principles which is good. Unlike WET (Write Everything Twice) ... lol. So when someone says your coding is all WET you know what they mean now. :)
In fact I was so confused I almost missed the $result issue.
Cheers!
When selecting NULL values you shouldn't use equal statements. You should use IS statements, like so:
For null values
SELECT foo
FROM bar
WHERE status IS NULL;
or for not null values
SELECT foo
FROM bar
WHERE status IS NOT NULL;
Related
I am trying to copy a column of data (which is numeric) to a different column (VARCHAR) in the same table. Seems straight forward to just run an update query and set the varchar column to whatever the $_POST column will be. The problem is numeric columns will contain 5 trailing zeros. I need to reformat it to be an INT type in the copy as well. Here is a copy of the original query in PHP:
try{
$q = "UPDATE table1 SET text1 = " . $_POST["htmlinputfield"] .
" WHERE account_id = :aid AND fieldapplication_id = :fid";
$stmt = $this->app->db->prepare($q);
$stmt->execute(array(":fid"=>$this->app->fid,":aid"=>$this->app->aid));
} catch(exception $e){
$this->app->logErroredQuery($e,$q);
return $e->getMessage();
}
I tried changing the datatype using php in an if statement then sticking into the query. Couldn't get that to work either. Here is what that looked like:
$htmlfield = $_POST["htmlinputfield"];
if(is_numeric($htmlfield)){
(int)$htmlfield;
}else{
(string)$htmlfield;
}
try{
$q = "UPDATE table1 SET text1 = :htmlfield
WHERE account_id = :aid AND fieldapplication_id = :fid";
$stmt = $this->app->db->prepare($q);
$stmt->execute(array(":fid"=>$this->app->fid,
":aid"=>$this->app->aid,
":htmlfield"=>$htmlfield));
} catch(exception $e){
$this->app->logErroredQuery($e,$q);
return $e->getMessage();
}
This resulted in changing all of the results to be the name of the column instead of a copy of the data. The original query works fir everything but numeric situations.
I imagine that this might be a CAST situation but my SQL abilities are not very good. The end result would be that whatever the database column that is being selected in the $_POST will be formatted to an int or a varchar. Any help would be much appreciated.
The first query with $q = "UPDATE table1 SET text1 = " . $_POST["htmlinputfield"] is the general way this needs to be done. You will need to insert the column name directly into the SQL string, but it's not safe to blindly use a value from $_POST like that. You should verify that $_POST["htmlinputfield"] is a valid column name before you use it in the query. You can use a whitelist method and check it against an array of acceptable column names in your PHP code.
The second way with $q = "UPDATE table1 SET text1 = :htmlfield can't work, because the column name is an identifier, and you can't bind identifiers, only values.
This part:
$htmlfield = $_POST["htmlinputfield"];
if(is_numeric($htmlfield)){
(int)$htmlfield;
}else{
(string)$htmlfield;
}
does not really make sense, because $_POST["htmlinputfield"] is the column name if I understood you correctly, and using is_numeric() in PHP isn't going to tell you what the data type of that column is, it's just going to check if the column name is numeric.
I think one way to solve this is to check if the value is numeric in your update query and remove the decimal places if so, otherwise take it as is. So first validate the posted column name as I mentioned above. After you've done that, you can safely use it in the query like this:
"UPDATE table1 SET text1 = CASE
WHEN `$column_name` REGEXP '^[0-9]+(\.[0-9]+)?$'
THEN FLOOR(`$column_name`)
ELSE `$column_name`
END
WHERE account_id = :aid AND fieldapplication_id = :fid"
There are different ways to check if a value is numeric, and different ways to convert the value to int, but I think the general idea will still be something like this.
Try the following query in your last example:
UPDATE table1 SET text1 = CAST(:htmlfield AS UNSIGNED) WHERE account_id = :aid AND fieldapplication_id = :fid
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().
I have a user form that sets a single record "current". No more than one record can be set to current at a time. So, I present the user a single drop down list, they choose the item they want to set current and hit "UPDATE" at the bottom of the form.
The PHP/Mysqli needs to go in and set all records column "current" to a value of 0 then update the one from the form to a value of "1".
Initially, I just did a simple count the number of rows, and run a bunch of queries to update the column to 0 or 1 if the loop counter = the id of the row. Well... that broke quick as I started doing testing on other portions and the index numbers got higher than the total number of rows. Yes, dumb way to do it initially!
Here's what I tried to do with the PHP / MySQL code:
// $link is the database link defined elsewhere. This does work as I use it all over the place
$setCurrent = X; // This is the number passed from my form
$init_query = "SELECT id, current FROM myTable";
if ($stmt = $link->$prepare($init_query) {
$stmt->execute() or die ($stmt->error);
$stmt ->bind_result($id, $current)
while ($stmt->fetch()){
if ($id == $setCurrent){
$update_sql = "UPDATE myTable SET current ='1' WHERE id='".$setCurrent."'";
$stmt2 = $link->prepare($update_sql);
$stmt2->execute;
}
else {
$update_sql = "UPDATE myTable SET current ='0' WHERE id='".$id."'";
$stmt2 = $link->prepare($update_sql);
$stmt2->execute;
}
$stmt->close();
This fails and gives me a Fatal error: Uncaught Error: Call to a member function execute on boolean in .....
I am racking my brain over this and can't figure out what the heck is going on. Its been a few years since I have worked in PHP/MySql and this is my first forray into OO Mysqli. Please be gentle :)
You're missing two closing curly braces. One for the first if() and the other for while()
why do them one at a time? You can do it in one query
$setCurrent = X;
$query = 'UPDATE myTable
SET `current` = (id = :current)';
$stmt = $link->prepare($query);
$stmt->bindValue(':current', $setCurrent);
$stmt->execute();
(and misusing the fact that if id equals $setCurrent, the part between ( ) resolves to true, which is 1.)
some explaining:
SELECT 10=10; would give a kind of "TRUE". But as Mysql does not give true, it give 1.
the same goes for:
SELECT 10=20; This is FALSE, so gives you 0.
Now back to your query: you want to get a value 0 for all record for which id not equal to some-number. And you want 1 when equal:
So you have to compare the column id's value to $setCurrent. When they match you get 1 and you put that 1 into the column "current"
And when they don't match, all other cases, then you get a 0 and that 0 goes into the column Current.
And yes, this could also be done as:
UPDATE mytable
SET `current` = CASE id
WHEN $setCurrent THEN 1
ELSE 0
END CASE
or using IF,
But they other syntax is way shorter
edit
backtics are needed around column name, as current is a reserved word
I've looked all over the interwebs, and cannot find that simple answer I'm looking for - possibly because it doesn't exist, but.. possibly because I don't know the correct terms to search for.
ok, so, i've got a variable - it's actaully a key value pair in an array that i'm passing into my function. the key is args[comments_mentioned] and the value is dynamically generated for me - it's ALWAYS going to be number, separated by commas (i.e. 1,2,3,4,5)
so, just to be super clear:
$args[comments_mentioned] == "1,2,3,4"; //could be any amount of number, not just 1,2,3,4,5
i'd like to pass this into a sql statement as a variable to use in an "IN" clause, like so:
$sr_sql = <<<SQL
SELECT *
FROM $wpdb->commentmeta
WHERE meta_value = %s
AND comment_ID in ($args[comments_mentioned])
ORDER BY meta_id DESC
SQL;
Then, Prepare it using the wordpress prepare and get results
$sr_query = $wpdb->prepare( $sr_sql, $args[user_id]) );
//receive the correct sql statement, and plug 'er in.
$sr_comment_rows = $wpdb->get_results($sr_query);
and run my foreach loop:
foreach ($sr_comment_rows as $sr_comment) {
$sResults .= 'do something with $sr_comment';
}
now, i realize the code above won't work - i can't just pass the variable in there like that. BUT, i can't pass it as a string (%s), because it wraps it in '1,2,3,45', and so it looks for the entire string, and not each number. I can't pass it as an int (%d), because of the commas...
In other posts, they mentioned create a temp table or variable, but, i'm not sure if that's the correct way to do it in mysql, or how to reference it once I do.
so, how do I do this? preference for actual code that works ;)
Thank you for reading and helping out!
One option, if you cannot normalize your data, is to enclose your string in commas such that it be ",1,2,3,4," and then you could do:
AND LOCATE( CONCAT(',',comment_ID,',') , ($args[comments_mentioned]) )
which will match if it finds a eg. ',3,' in ',1,2,3,4,' (Using 3 as an example)
I believe this should be enough:
$params = $args[comments_mentioned];
$table = $wpdb->commentmeta;
$sr_sql = "
SELECT *
FROM $table
WHERE meta_value = %s
AND comment_ID in ($params)
ORDER BY meta_id DESC
";
It will be result something like:
SELECT *
FROM table_on_variable
WHERE meta_value = %s
AND comment_ID in (1,2,3,4)
ORDER BY meta_id DESC
If your mainly issue is regarding the in clause, so you will not have problems if you use double quotes and single variable as illustrated above.
look at this table please
table
|id| |name| |order|
i must get the rows, where name = something and order = somevalue
so i write
select `id` from `table` where `name` = 'something' and `order` = 'somevalue'
but depend on php logic, sometimes i need to get all rows, where name = something, independently of order value. i don't want to change the query structure, because in practise there are many number of fields, and possible count of queries will become very big. so i want to save the structure of query, and when i need to select just by name, i want to write something like this:
select `id` from `table` where `name` = 'something' and `order` = any value
is it possible?
thanks
Well, it's kind of a hack, but if you really need to do this, it'll work like this:
select `id` from `table` where `name` = 'something' and `order` = `order`
Then you're just saying "wherever order is the same as itself", so it's always true.
No, this is not possible. You need to change the structure (optionally to a LIKE so you can use '%', but that's very ugly).
However, you don't need to write a different query to handle every possible combination. You can simply create the query dynamically:
//create base query
$query = "select `id` from `table` where `name` = 'something' ";
//add order if we need it
if ($use_order)
$query .= "and `order` = 'somevalue' ";
//repeat for any other optional part
Note that you should of course still take proper measures to avoid SQL injection and other security issues - I have not included this here in order to keep things simple.
If you are using bound parameters, it would be impossible.
If you just substitute the values, you can do the following:
select `id` from `table` where `name` = 'something' and `order` = `order`
This is a common theme with database queries - you need a variable query depending on how much filtering you wish to apply to the data it queries. You could go the route of having your query repeated as a string throughout your code, but that is bad practice as it increases the complexity of the code needlessly. Chances for errors occur if you need to change the query for some reason, and have to change it in multiple places as a result.
The better solution is to create a function which builds the query for you execute:
function buildMyQuery($name, $order = null) {
$sql = "SELECT `id` FROM `table` WHERE `name`='$name'";
if ($order != null) {
$sql .= " AND `order`='$order'";
}
return $sql;
}
You could then run this for just using the 'name' field:
$query = buildMyQuery("somename");
Or this for using both fields:
$query = buildMyQuery("somename", "someorder");
As someone mentioned above, this code is deliberately simplified and contains no contingency for possibly dangerous data passed in via $name or $order. You would need to use mysql_real_escape_string or something similar to clean the data first, at the beginning of the function before either piece of data is used.
Dynamic query generation is a fact of life as Byron says, so I would become accustomed to it now rather than using hack-ish workarounds.
I don't think you have any choice... Once you do a selection you can't "unfilter" and get more rows.
You should just use two queries-- either two independent queries, or one that selects on the name into a temp table, and then (optionally) one that further selects on the order attribute.
Like Chad said above, just set the column equal to itself. But be careful, on some platforms / install configurations, NULL != NULL:
select `id` from `table` where `name` = 'something' and coalesce(`order`,'') = coalesce(`order`,'')
On reflection, I have a better answer. My colleague showed me a way this can be done.
My example...
Select rentals.* From rentals Where ((? = '') OR (user_id = ?))
The variables must be the same.
If they are both 5 for example, the first boolean will be false, but the second will be true, for the rows where the users id is 5.
If you require "all", setting as an empty string will result in all rows being seen to meet the where clause condition.
Can't you just use a not null query here?
select `id` from `table` where `name` = 'something' and `order` is not null;
You should be able to do it like this:
select `id` from `table` where `name` <>'' and `order` <>''
That will select anywhere that the value is not equal to blank.
$sql = "SELECT * FROM auctions WHERE id = id ";
if ($category !== "ANY") {
$sql .= "AND category = $category "; }
if ($subcategory !== "ANY") {
$sql .= "AND subcategory = $subcategory "; }
if ($country !== "ANY") {
$sql .= "AND country = $country "; }
$sql .= "ORDER BY $order $sort LIMIT $limit OFFSET $offset";