I have a feature in my web app where a table is "quick-editable", that is, its cells can be edited directly. When the user saves his changes, the client sends to the server the changed rows, with their changed columns (excluding non-changed columns, just to clarify), and their corresponding IDs.
In order to do UPDATE queries efficiently, I am using PDO's prepared statement feature. Here is an equivalent statement what I currently came up:
UPDATE table
SET
col1 = :arg_col1,
col2 = :arg_col2,
col3 = :arg_col3
WHERE
ID = :arg_ID
Then I came up with this problem in which I cannot set a column into its current value. Because only the edited column(s) in a row is/are submitted, I only need to bind the data to their respective column(s). For example, if only col1 and col2 are changed, the resulting statement should be
UPDATE table
SET
col1 = 'new data',
col2 = 'an edit',
col3 = col3 /* Use the current value of the column */
WHERE
ID = 153454
Modifying the statement directly would definitely nullify the performance improvement of using the same prepared statement for updating multiple rows. Sadly, PDO doesn't seem to have an option to bind a column to its current value.
How should I solve this problem?
ADDITIONAL: I do not wish to send all the columns, for performance reasons.
Unfortunately, an approach you are aiming to, won't actually work. You just can't prepare a statement in one call and then use it in all subsequent calls - you'll have to prepare it every time again.
So, there is no point in creating a generic query. Thus, you can create a custom query for the very data set. And this latter task can be automated: just create your UPDATE statement dynamically.
A solution, based on the tag wiki (scroll to the very bottom):
// first, have your update data in array (you can omit this line though)
$update = $_POST;
// next, list all fields a user allowed to
$allowed = array("col1","col2","col3");
// finally, create a SET statement query dynamically
$set = pdoSet($fields,$values, $update);
// voila - your query contains only fields were POSTed:
$sql = "UPDATE users SET $set WHERE id = :id";
$stm = $dbh->prepare($sql);
// just add an ID and execute
$values["id"] = $_POST['id'];
$stm->execute($values);
You actually don't want the col3 in the sql, what you need to do is to build the sql dynamically, only add the changed columns to the sql.
Related
Okay, basically I have a table that contains statements like:
incident.client_category = 1
incident.client_category = 8
incident.severity = 1
etc.
I would like to use the contents from this table to generate other tables that fulfill the conditions expressed in this one. So I would need to make it something like
SELECT * FROM incident WHERE incident.client_category = 1
But the last part of the where has to come from the first table. Right now what I'm trying to do is something like
SELECT * FROM incident WHERE (SELECT condition FROM condition WHERE id = 1)
id = 1 stands for the condition's id. Right now I only want to work with ONE condition for testing purposes. Is there a way to achieve this? Because if there isn't, I might have to just parse the first query's results through PHP into my incident query.
Table schemas:
Engineering Suggestion - Normalize the DB
Storing a WHERE clause, like id = 10, in a field in a MySQL table, is not a good idea. I recommend taking a look at MySQL Normalization. You shouldn't store id = 10 as a varchar, but rather, you should store something like OtherTableid. This allows you to use indices, to optimize your DB, and to get a ton of other features that you are deprived of by using fields as WHERE clauses.
But sometimes we need a solution asap, and we can't re-engineer everything! So let's take a look at making one...
Solution
Here is a solution that will work even on very old, v. 5.0 versions of MySQL. Set the variable using SET, prepare a statement using PREPARE, and execute it using EXECUTE. Let's set our query into a variable...
SET #query = CONCAT(
"SELECT * FROM incident WHERE ",
(SELECT condition FROM condition WHERE id = 1)
);
I know for a fact that this should work, because the following definitely works for me on my system (which doesn't require building any new tables or schema changes)...
SET #query = CONCAT("SELECT id FROM myTable WHERE id = ", (SELECT MAX(id) FROM myTable));
If I SELECT #query;, I get: SELECT id FROM myTable WHERE id = 1737901. Now, all we need to do is run this query!
PREPARE stmt1 FROM #query;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
Here we use a prepare to build the query, execute to execute it, and deallocate to be ready for the next prepared statement. On my own example above, which can be tested by anyone without DB schema changes, I got good, positive results: EXECUTE stmt1; gives me...
| id | 1737901 | .
here is one way to achieve your goal by using what is called dynamic sql, be ware that this works only select from condition table returns only one record.
declare #SQLSTRING varchar(4000)
, #condition VARCHAR(500) -- change the size to whatever condition column size is
SELECT #condition = condition
FROM
condition
WHERE
id = 1
SET #SQLSTRING= 'SELECT * FROM incident WHERE ' + #condition
exec sp_executesql(#SQLSTRING)
Since you have also tagged the question with PHP, I would suggest using that. Simply select the string from the condition table and use the result to build up a SQL query (as a string in PHP) including it. Then run the second query. Psudo-code (skipping over what library/framework you re using to call the db):
$query = "select condition from condition where id = :id";
$condition = callDbAndReturnString($query, $id);
$query = "select * from incident where " . $condition;
$result = callDb($query);
However, be very careful. Where and how are you populating the possible values in the condition table? Even how is your user choosing which one to use? You run the risk of opening yourself up to a secondary SQL injection attack if you allow the user to generate values and store them there. Since you are using the value from the condition table as a string, you cannot parametrise the query using it as you (hopefully!) normally would. Depending on the queries you run and the possible values there as conditions, there might also be risk even if you just let them pick from a pre-built list. I would seriously ask myself if this (saving parts of SQL queries as strings in another table) is the best approach. But, if you decide it is, this should work.
I am implementing a rating system that was similar to what you see in google play and many others. To do that, I created a table named tbl_ratings and columns PRIMARY, IID and sum_rating. Off course, the PRIMARY column is in auto-increment key, the IID is the item id and sum_rating is the cumulative sum of all user ratings.
So the tbl_ratings table can look like this:
PRIMARY IID sum_rating
21 2 100
Now this is what I intend to do, I want to avoid doing SELECT just to retrieve a value for use in PHP, since I can do a simple addition from within an update query, so each time there is a user that submits a rating, I will update the table following this:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
$stmt = $pdo->prepare("UPDATE tbl_ratings SET sum_rating=sum_rating+".$_POST['rating']." WHERE IID='2'")
$stmt->execute();
But of course as you know, this is a bad implementation cause this is open to SQL Injection. But hey it work! So now I want to do it much safer by doing
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
$stmt = $pdo->prepare("UPDATE tbl_ratings SET sum_rating=sum_rating+? WHERE IID='2'")
$stmt->execute(array($_POST['rating']));
If you notice its quite simple, I just replace the potential sql injection point by the ? placeholder as would a correct prepared statement should be constructed. The Bad Bad thing is, this does not work. I tried looking in the web but it seems to be not that fruitful.
What should be the proper way to achieve the calculation of a known column value plus a data in a 1 pdo prepared statement?
All values convert to string values in execute method. You must use bindParam method to set a value type, for example:
$sth->bindParam(1, $_POST['rating'], PDO::PARAM_INT);
I'm trying to update a mysql database with data I fetched. (btw I need to do this for specific individual items, but that's not the problem.) When it comes to creating separate statements for fetching or updating I can do that. Separately, I'm able to fetch data like this:
$query = "SELECT starting_amount FROM comp ORDER BY item DESC LIMIT 3, 1";
$result = $conn->query($query);
$row = mysqli_fetch_assoc($result);
and I'm able to update data like this:
$sql = "UPDATE comp SET final_amount=25 WHERE item='Y'";
but I can't put the two together (I tried several ways and failed). In other words, I am able to update a table record with data that I manually type, e.g. I type "25" manually in the update statement, which in this example is the data from 'staring_amount', but I don't know how to update with a statement that will automatically use data I fetch from the table. Again in other words, how do I write the update statement so that "SET final_amount=" is followed by fetched data? Thanks in advance for any help!
So, you just need to pass your fetched data into the query
$starting_amount = $row['starting_amount'];
$sql = "UPDATE comp SET final_amount=$starting_amount WHERE item='Y'";
Firstly, I highly recommend looking into prepared statements - using a prepared statement to insert data is an easy way to prevent SQL injection attacks and also will make what you want to do a little easier.
Here's an example of a prepared update statement using mysqli based on your example:
$statement = $conn->prepare("UPDATE comp SET final_amount=? WHERE item='Y'")
$statement->bind_param(25);
I'll assume for this answer that you want to use just the first row of the resultset.
Using your example above, you can replace the value in bind_param with a value from your row.
$statement->bind_param($row['starting_amount']);
There's no need to do them as separate statements, since you can join queries in an UPDATE.
UPDATE comp AS c1
JOIN (SELECT starting_amount
FROM comp
ORDER BY item DESC
LIMIT 3, 1) AS c2
SET c1.final_amount = c2.starting_amount
WHERE c1.item = 'Y'
I am trying to display the data from 'table' if a key inputted by the user is found in the database. Currently I have it set up so that the database checks if the key exists, like so:
//Select all from table if a key entry that matches the user specified key exists
$sql = 'SELECT * FROM `table` WHERE EXISTS(SELECT * FROM `keys` WHERE `key` = :key)';
//Prepare the SQL query
$query = $db->prepare($sql);
//Substitute the :key placeholder for the $key variable specified by the user
$query->execute(array(':key' => $key));
//While fetched data from the query exists. While $r is true
while($r = $query->fetch(PDO::FETCH_ASSOC)) {
//Debug: Display the data
echo $r['data'] . '<br>';
}
These aren't the only SQL statements in the program that are required. Later, an INSERT query along with possibly another SELECT query need to be made.
Now, to my understanding, using WHERE EXISTS isn't always efficient. However, would it be more efficient to split the query into two separate statements and just have PHP check if any rows are returned when looking for a matching key?
I took a look at a similar question, however it compares multiple statements on a much larger scale, as opposed to a single statement vs a single condition.
#MarkBaker Join doesn't have to be faster than exists statement. Query optymalizer is able to rewrite the query live if it sees better way to accomplish query. Exists statement is more readable than join.
Fetching all the data and making filtering directly in PHP is always bad idea. What if your table grow up to milions of records? MySQL is going to find the best execute plan for you. It will automaticaly cache the query if it is going to improve performance.
In other words, your made everything correctly as far as we can see your code now. For futher analyse show us all of your queries.
I'm trying to write a function that can alter the value of a column in a table, where the table, column, and values are not predetermined. Is it possible to do something like this:
UPDATE :tbl SET :column = :value;
to accomplish this, or can parameters only be bound for values?
EDIT:
Or is this the only way to accomplish this:
$query = "UPDATE".$tbl." SET ".$column." = ".$value.";";
It is not possible to do that. Prepared statements allow the database to optimize a query plan for the particular query. If it doesn't know what table or column, it cannot create the query plan.
Parameters can only be bound to values and not tables/columns.