I'm trying to copy a row from a structure I technically know nothing about.
This is what I have so far. This code does work but I'm pretty sure this isn't the most appropriate. Anyone have a better way or a right way of doing this? Any suggestions would be appreciated.
/*
$table is the table name
$id_field is the primary key
$id_value is the row I want to copy
*/
$selectEntity = $dbh->prepare("SELECT * FROM {$table} WHERE {$id_field} = :id_value");
$selectEntity->execute(array(':id_value' => $id_value));
$entity = $selectEntity->fetch(PDO::FETCH_ASSOC);
foreach ($entity as &$value){ if(is_null($value) == true) { $value = "NULL"; } else { $value = "'".htmlspecialchars($value, ENT_QUOTES)."'"; } }
//remove the primary key
$entity[$id_field] = "'"; // the ' was the only way I could get NULL to get in there
$insertCloned = $dbh->prepare("INSERT INTO {$table} (".implode(", ",array_keys($entity)).") VALUES ('".implode(", ",array_values($entity)).")");
$insertCloned->execute();
$lastInsertedID = $dbh->lastInsertId();
It's very messy.
Your quoting is not correct -- you have quotes around the entire VALUES list, they should be around each individual value. Also, you should use $dbh->escape($value) to escape the values; htmlspecialchars is for encoding HTML when you want to display it literally on a web page.
But it's better to use query parameters rather than substituting into the SQL. So try this:
$entity[$id_field] = null;
$params = implode(', ', array_fill(0, count($entity), '?'));
$insertCloned = $dbh->prepare("INSERT INTO {$table} VALUES ($params)");
$insertCloned->execute(array_values($entity));
You don't need to list the column names in the INSERT statement when the values are in the same order as the table schema. And since you used SELECT * to get the values in the first place, they will be.
Related
I have a problem with updating a table inside a MySQL database with about 388 columns. Inserting new rows works great, but updating existing ones doesn't (I do not get any error messages). I am also not very sure if the way I use to insert/udpate the
data is the very best as I do have a very long code, which is also very fragile for errors. Well, I am sure this could be solved a lot better!
Here is my code: https://codeshare.io/5DZODE
What if I use this code for every single of those 388 values?
insert into $table (field, value) values (:name, :value) on duplicate key update value=:value2
Help and ideas for improvement would be great! Thanks!
You could use the "REPLACE INTO" Statement instead of insert, that way you will only have to print the data once
eg
REPLACE INTO `values` (`id`,...) VALUES ($id,...)
Also to improve the php code(to make it more fail proof) I Would probably make an array containing all the variables and then print the SQL statement based on those values FX
$values = array('id'=> $id, ....);
$columns = array();
$replaceValues = array();
$insertValues = array();
foreach ($values as $key => $value)
{
$columns[] = "`$key`";
$replaceValues[] = ":$key";
$insertValues[":$key"] = $value;
}
$pdo = new PDO('mysql:host=xyz;dbname=xyz','xyz','xyz');
$statement = $pdo->prepare('REPLACE INTO `values` ('.implode(',',$columns).') VALUES ('.implode(',',$replaceValues).')');
$statement->execute($insertValues);
So this is more about getting your opinion on the best approach for this.
I have what I think is quite an elegant way of building a simple dynamic SQL statement with a straightforward WHERE clause. The WHERE clause can include multiple fields but it is limited as it does not allow for different operators (comparative or logical).
I can build the following with this:
SELECT * from table_name WHERE field_1 = "value_1" AND field_2 = "value_2";
//or I can do
SELECT * from table_name WHERE field_1 = "value_1" OR field_2 = "value_2";
//or I can do
SELECT * from table_name WHERE field_1 <> "value_1" AND field_2 <> "value_2";
I can not build the following:
SELECT * from table_name WHERE field_1 = "value_1" AND field_2 <> "value_2";
//nor can I do
SELECT * from table_name WHERE field_1 = "value_1" AND field_2="value_2" OR field_3 = "value_3
It becomes a real problem when working with numbers and dates when I want to look for records with values between meaning I need to pass the same filed in twice with two separate values.... doesnt it?
SELECT * from table_name WHERE price BETWEEN 10 AND 20;
SELECT * from table_name WHERE date BETWEEN "2016-08-01" AND "2016-08-15";
And not forgetting multiple criteria with "IN" or LIKE statements which this also does not build, i.e.:
SELECT * from table_name WHERE field_1 IN("value_1","value_2, "value_3");
SELECT * from table_name WHERE field_1 LIKE "val%";
Here is what my current code looks like:
// db contains my DB connection
$db = new DB();
$where = 'WHERE';
$criteria = array();
foreach ($_GET as $key => $value) {
$where = $where.' '.$key.'=? AND';
array_push($criteria,$value);
}
if(count($_GET) > 0){
// $sql will look like: SELECT * FROM table_name WHERE field_1 = ? AND field_2 = ?
// $criteria is an array of values to pair with the above prepared statement.
// Will look like: $criteria("value_1", "value_2")
$sql = 'SELECT * FROM mcl_data_gap '.$where;
$results = $db->query($sql,$criteria);
} else {
$sql = 'SELECT * FROM mcl_data_gap';
$results = $db->query($sql);
}
// .... continue on using above SQL statement
In the above code I have used get but my assumption is post would also work.
The only idea I have come up with is to insert more key value pairs that contain the operators required in a coded format that would allow me to then look for these operators and build the statement based on them but I just feel like there is a better way and that is what I am hoping you can help with.
Another option I have just thought of is building the SQL before passing it to the server and just executing that.
Or can I post objects that contain the whole segment of the WHERE statement?
You are using query parameters for the dynamic values (the right side of the equality comparison). This is good.
But you can't use a parameter for the dynamic column names (the left side of a comparison). This is how your code is vulnerable to SQL injection. Prepared statements don't help with that.
The solution is to make sure every column name that comes from your $_GET keys is actually one of the columns in your table. In other words, this is called whitelisting the input.
$mcl_data_gap_columns = ["field_1", "field_2", "field_3"];
You only want to process $_GET parameters that match columns in your list of columns that exist in your table. Anything that isn't in this list should be ignored.
As for predicates that have multiple values, you can access them in PHP by naming the GET parameter with "[]" at the end.
$terms = [];
$parameters = [];
// only look for $_GET keys that match one of the known columns.
// this automatically ignores all other $_GET keys.
foreach ($mc_data_gap_columns as $col) {
// get the single value, or the array of multiple values.
// convert to an array in either case.
if (isset($_GET[$col])) {
$values = (array) $_GET[$col];
$default_op = "=";
} elseif (isset($_GET[$col."[]"])) {
$values = $_GET[$col."[]"];
$default_op = "IN";
} else {
continue;
}
// if your comparison is anything other than equality,
// there should be another request parameter noting that.
if (isset($_GET[$col."_SQLOP"])) {
$op = $_GET[$col."_SQLOP"];
} else {
$op = $default_op;
}
Process only the known operations. If $op is not one of the specific supported operations, ignore it or else throw an error.
switch ($op) {
case "=":
case ">":
case "<":
case ">=":
case "<=":
case "<>":
// all these are simple comparisons of one column to one value
$terms[] = "$col $op ?";
$parameters[] = $values[0];
break;
case "BETWEEN":
// comparisons of one column between two values
if (count($values) != 2) {
error_log("$col BETWEEN: wrong number of arguments: " . count($values));
die("Sorry, there has been an error in your request.");
}
$terms[] = "$col BETWEEN ? AND ?";
$parameters[] = $values[0];
$parameters[] = $values[1];
break;
case "IN":
// comparisons of one column IN a list of any number of values
$placeholders = implode(",", array_fill(1, count($values), "?"));
$terms[] = "$col IN ($placeholders)";
$parameters = array_merge($parameters, $values);
break;
default:
error_log("Unknown operation for $col: $op");
die("Sorry, there has been an error in your request.");
}
}
Then finally after that's done, you'll know that $terms is either an empty array or else the array of search conditions.
if ($terms) {
$sql .= " WHERE " . join(" AND ", $terms);
}
$db->query($sql, $parameters);
I have not tested the above code, but it should illustrate the idea:
Never use $_GET input verbatim in your SQL queries
Always filter input against a fixed list of safe values
Or use switch to test against a fixed set of safe cases
Another option I have just thought of is building the SQL before passing it to the server and just executing that.
No, no, no! This would just be an invitation to get hacked. Never do this!
Your wrong if you think that your HTML page is the only way someone can submit a request to your server. Anyone can form any URL they want, and submit it to your site, even if it contains GET parameters and values you don't expect.
I'm hoping someone can give me a suggestion on a challenge I am facing. I am not sure that I'm able to do this the way I envision, so looking for advice from those more experienced.
I have a database table with around 20 columns. It's a lot of columns and unfortunately I cannot change that. The goal is to take a form submission and insert it into this table. So what I have is, the field names are identical to the column names in the database.
To try and keep the code cleaner, I would like to just pull the entire form (key and value) in, instead of doing the traditional $varWhatever = $_POST['whatever']; 20 times. Using something like this: foreach ($_POST as $key => $value)
Now my question is, if at all possible, how can I run that foreach loop in a way that will let me put the keys and values into a single SQL statement?
"INSERT INTO table_name (Loop all keys here) VALUES (Loop related values here)"
Is this even possible, or should I just go back to the more traditional way I mentioned above?
One way I am thinking is, before starting the loop, I could create the empty row and grab it's ID, then within the loop, I could run an update query on the row matching the ID. Sounds sloppy though.
Here is a solution I came up with. You first have to define an array of field names that acts as a whitelist of expected inputs. Then you just loop through that array to build a parameters array to bind the submitted values. And implode the array with a comma when building the query.
$fields = array('field1','field2','field3');
$binds = array();
foreach ($fields as $field) {
$binds[":$field"] = $_POST[$field];
}
$sql = "INSERT INTO table_name (" . implode(',',$fields) . ") VALUES (" . implode(',',array_keys($binds)) . ")";
$db->prepare($sql);
$db->execute($binds);
This assumes you are using PDO.
Yes, you can loop for all keys (eg. do an array_keys), but I don't recommend blindly taking any submission parameter and putting it into a SQL query.
Instead, I would keep a list of all valid columns of the form and work with that, remembering that each value needs sanitization, too.
For example:
<?php
$columns = array('column1', 'column2', 'column3', …);
foreach ($columns as $column) {
if (!isset($_POST[$column])) {
die("No data for column $column\n");
}
}
if (!check_csrf($_POST['csrt_token'])) { … }
# (setup database connection)
$SQL = "INSERT INTO table_name (" . implode(", ", $columns) . ") VALUES (";
foreach ($column as $column) {
$SQL .= "'" . $mysqli->real_escape_string($_POST[$column]) . "',";
}
$SQL[strlen($SQL)-1] = ')';
$mysqli->query($SQL);
I'm trying to add iterate through an object and add those object properties to mysql database. Using:
//This works
$sql = "CREATE TABLE $table ($ID int primary key auto_increment not null)";
mysql_query($sql);
//This works
function iterateObject($obj, $name='') {
foreach ($obj as $key=>$val) {
$myName = ($name !='') ? $name . "_" . $key : $key;
if ( is_object($val) || is_array($val) ) {
iterateObject($val, $myName);
} else {
//This works
$sql = ("ALTER TABLE home_timeline ADD COLUMN $myName VARCHAR(256);");
mysql_query($sql);
//This doesn't work
$sql2 = ("INSERT INTO home_timeline ($myName) VALUES ($val);");
mysql_query($sql2);
print "$myName - $val <br />";
}
}
}
The table is created and altered so that each iteration adds a new column to the table but when I try and add values to that column (second sql statement) everything is null and the script creates 20+ rows rather than having all the values appear on one row in the relevant column. Could someone help?
why not use functions like serialize() and unserialize() when converting objects to/from string?
second: if $val is string, then in the query put the string delimiters
"INSERT INTO home_timeline (`$myName`) VALUES ('$val');"
though inserting parameters via concatenation is a very bad practice prone to SQL injection.
If you have further problems, output the query before execution and put it here. You might be experiencing the case when you got a lot of columns which can't be nulls, and have no default values. Also output the table structure here.
I have this code (removed param escaping just to cut down some code):
private function _get_tag_id($value)
{
$sql = "INSERT INTO tags (tag, added) VALUES ('$value', ".time().") "
. "ON DUPLICATE KEY UPDATE tag_id = tag_id";
$id = execute($sql);
if (empty($id))
{
$sql = "SELECT tag_id FROM tags WHERE tag = '$value'";
$id = execute($sql);
}
return $id;
}
I'm really bad at organizing my code and I've been reading about the importance of keeping your code DRY. Does this include any queries you might have? For example, I need to perform these same queries for a few fields, and what I've done is change it to this:
private function _get_field_id($field, $value)
{
$sql = "INSERT INTO {$field}s ({$field}, added) VALUES ('$value', ".time().") "
. "ON DUPLICATE KEY UPDATE {$field}_id = {$field}_id";
$id = execute($sql);
if (empty($id))
{
$sql = "SELECT {$field}_id FROM {$field}s WHERE {$field} = '$value'";
$id = execute($sql);
}
return $id;
}
Although that reduces some similar functions, it also makes the query much harder to read at first. Another problem after doing this is what happens if sometimes the query may be slightly different for a field? Let's say if the field is tag, I don't need the added column any more and maybe the query would now change to:
$sql = "INSERT INTO {$field}s ({$field}".($field == 'tag' ? '' : ", added").") "
. "VALUES ('$value', ".($field == 'tag' ? '' : time()).") "
. "ON DUPLICATE KEY UPDATE {$field}_id = {$field}_id";
Now it's starting to get extra messy, but I have a feeling people don't actually do that.
Another thing I've read is that functions should only do one thing. So would I cut up this function like this?
private function _get_tag_id($value)
{
$id = $this->_add_tag_id($value);
if (empty($id))
{
$id = $this->_get_tag_id($value);
}
return $id;
}
Or would it be better to leave it the way it was before?
If you don't think either of the ways I've tried organizing the code is correct also feel free to suggest the way you'd do it, or in other words what would be the best way to organize these simple bits of code?
I would turn it upside down - select first, and insert if not found.
Two reasons:
1) You will select and find more often that select and miss, so select first is on average faster.
2) "on duplicate key" is a non-standard extension to INSERT that will cause problems in the future if you should ever move to a SQL database without it. (I think it is MySQL only).
As for which is better, I'd rather try to understand the first or third.