I have a table called user_bio. I have manually entered one row for username conor:
id: 1
age: 30
studying: Business
language: English
relationship_status: Single
username: conor
about_me: This is conor's bio.
A bio is unique to a user, and obviously, a user cannot manually set their bio from inserting it in the database. Consider the following scenario's:
Logged in as Conor. Since Conor already has a row in the database, I simply want to run an UPDATE query to update the field where username is equal to conor.
Logged in as Alice. Since Alice has no row in the database corresponding to her username. Then I want to run an INSERT query. For all users, all users will have to have their details inputted, and then updated correspondingly.
At the moment, I am struggling with inserting data in the database when no rows exist in the database.
Here is my current approach:
$about_me = htmlentities(trim(strip_tags(#$_POST['biotextarea'])));
$new_studying = htmlentities(trim(strip_tags(#$_POST['studying'])));
$new_lang = htmlentities(trim(strip_tags(#$_POST['lang'])));
$new_rel = htmlentities(strip_tags(#$_POST['rel']));
if(isset($_POST['update_data'])){
// need to check if the username has data already in the db, if so, then we update the data, otherwise we insert data.
$get_bio = mysqli_query($connect, "SELECT * FROM user_bio WHERE username ='$username'");
$row_returned = mysqli_num_rows($get_bio);
$get_row = mysqli_fetch_assoc ($get_bio);
$u_name = $get_row['username'];
if ($u_name == $username){
$update_details_query = mysqli_query ($connect, "UPDATE user_bio SET studying ='$new_studying', language ='$new_lang',
relationship_status = '$new_rel', about_me = '$about_me' WHERE username ='$username'");
echo " <div class='details_updated'>
<p> Details updated successfully! </p>
</div>";
} else {
$insert_query = mysqli_query ($connect, "INSERT INTO user_bio
VALUES ('', '$age','$new_studying','$new_lang','$new_rel', '$username', '$about_me'");
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
echo " <div class='details_updated'>
<p> Details added successfully! $row_returned </p>
</div>";
}
}
The UPDATE query works fine, when logged in as Conor. But again, INSERT does not work when logged in as Alice.
MySQL support INSERT ... ON DUPLICATE KEY UPDATE type of queries. So you do not need to make few queries to check existance of row in your php code, just add corrct indexes and let your DB take care about this.
You can read about such type of queries here
Here are a few things you could do to make it work:
Prevent SQL injection
As this is an important issue, and the suggested corrections provided below depend on this point, I mention it as the first issue to fix:
You should use prepared statements instead of injecting user-provided data directly in SQL, as this makes your application vulnerable for SQL injection. Any dynamic arguments can be passed to the SQL engine aside from the SQL string, so that there is no injection happening.
Reduce the number of queries
You do not need to first query whether the user has already a bio record. You can perform the update immediately and then count the records that have been updated. If none, you can then issue the insert statement.
With the INSERT ... ON DUPLICATE KEY UPDATE Syntax, you could further reduce the remaining two queries to one. It would look like this (prepared):
INSERT INTO user_bio(age, studying, language,
relationship_status, username, about_me)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY
UPDATE studying = VALUES(studying),
language = VALUES(language),
relationship_status = VALUES(relationship_status),
about_me = VALUES(about_me);
This works only if you have a unique key constraint on username (which you should have).
With this statement you'll benefit from having the data modification executed in one transaction.
Also take note of some considerations listed in the above mentioned documentation.
NB: As in comments you indicated that you prefer not to go with the ON DUPLICATE KEY UPDATE syntax, I will not use it in the suggested code below, but use the 2-query option. Still, I would suggest you give the ON DUPLICATE KEY UPDATE construct a go. The benefits are non-negligible.
Specify the columns you insert
Your INSERT statement might have failed because of:
the (empty) string value you provided for what might be an AUTO_INCREMENT key, in which case you get an error like:
Incorrect integer value: '' for column 'id'
a missing column value, i.e. when there are more columns in the table than that you provided values for.
It is anyway better to specify explicitly the list of columns in an INSERT statement, and to not include the auto incremented column, like this:
INSERT INTO user_bio(age, studying, language,
relationship_status, username, about_me)
VALUES (?, ?, ?, ?, ?, ?)
Make sure you get notified about errors
You might also have missed the above (or other) error, as you set your error reporting options only after having executed your queries. So execute that line before doing any query:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
And also add there:
error_reporting(E_ALL);
ini_set('display_errors', 1);
In a production environment you should probably pay some more attention to solid error reporting, as you don't want to reveal technical information in the client in such an environment. But during development you should make sure that (unexpected) errors do not go unnoticed.
Do not store HTML entities in the database
It would be better not to store HTML entities in your database. They are specific to HTML, which your database should be independent of.
Instead, insert these entities (if needed) upon retrieval of the data.
In the below code, I removed the calls to htmlentities, but you should then add them in code where you SELECT and display these values.
Separate view from model
This is a topic on its own, but you should avoid echo statements that are inter-weaved with your database access code. Putting status in variables instead of displaying them on the spot might be a first step in the right direction.
Suggested code
Here is some (untested) code which implements most of the above mentioned issues.
// Calls to htmlentities removed:
$about_me = trim(strip_tags(#$_POST['biotextarea']));
$new_studying = trim(strip_tags(#$_POST['studying']));
$new_lang = trim(strip_tags(#$_POST['lang']));
$new_rel = trim(strip_tags(#$_POST['rel']));
// Set the error reporting options at the start
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
if (isset($_POST['update_data'])) {
// Do not query for existence, but make the update immediately
$update_stmt = mysqli_prepare ($connect,
"UPDATE user_bio
SET studying = ?,
language = ?,
relationship_status = ?,
about_me = ?
WHERE username = ?");
mysqli_stmt_bind_param($update_stmt, "sssss",
$new_studying, $new_lang, $new_rel, $about_me, $username);
mysqli_stmt_execute($update_stmt);
$num_updated_rows = mysqli_stmt_affected_rows($update_stmt);
mysqli_stmt_close($update_stmt);
if ($num_updated_rows === 0) {
$insert_stmt = mysqli_prepare ($connect,
"INSERT INTO user_bio(age, studying, language,
relationship_status, username, about_me)
VALUES (?, ?, ?, ?, ?, ?)");
mysqli_stmt_bind_param($insert_stmt, "isssss",
$age, $new_studying, $new_lang, $new_rel, $username, $about_me);
mysqli_stmt_execute($insert_stmt);
mysqli_stmt_close($insert_stmt);
}
// Separate section for output
$result = $num_updated_rows ? "Details updated successfully!"
: "Details added successfully!";
echo " <div class='details_updated'><p>$result</p></div>";
}
Aside from security issues and bad coding practices, here are a couple things you can do.
You don't need to compare the name to check if the bio already exists. You can just count the number of rows returned. If it is more than zero, then the user bio already exists.
When comparing strings, === is preferred over ==. You can read about it and find out why but here is an example (2nd answer)
You should really look into either REPLACE INTO or ON DUPLICATE KEY UPDATE. Just using either of there 2, depending on your use case pick one, you can pretty much eliminate more than half of your currently displayed code. Basically, both will insert and if the record already exists, they updates. Thus, you wouldn't even need to check if the record already exists.
Related
This question has kinda been asked already but I couldn't find my answer. I searched a while and found these related questions, but they didn't help me to understand or answer my problem.
SQL Insert Into with Inner Join
T-SQL INSERT INTO with LEFT JOIN
My question is how to insert data in 2 tables using joins. For example (with php) a user can enter his/her name and the foods he/she likes.
I store them in a variable and an array (the length of the array is not always 3 like below):
$name = "Niels"
$foodsHeLikes = array("apple", "pear", "banana");
This is how I want to store them:
USERS:
UserID name
1 Niels
FOODS:
FoodID userID name //userID is linked to UserID in users table
1 1 apple
2 1 pear
3 1 banana
The link to the first question I pasted above has an insert with a join but I don't see anywhere to put the values in like with a normal insert?
The query from that question:
INSERT INTO orders (userid, timestamp)
SELECT o.userid, o.timestamp FROM users u INNER JOIN orders o ON o.userid = u.id
Judging by what's been going on in the comment section, what you're asking is that you would like to have a more optimal query process. Right now you are using two different queries to populate your two tables, and you're wondering whether that could be done more optimally.
First things first, it's not possible to populate TWO different tables with ONE query.
However, what you could do, is use transactions.
The rest of this answer will follow the assumption that you are using PHP as your backend scripting language (as you tagged yourself).
Also, it is not inherently obvious whether you use prepared statements for your queries or not. In the case you don't, I would highly recommend using prepared statements. Otherwise, you're opening yourself up to SQL Injections (SQLI Attacks).
I will proceed by using mysqli prepared statements in this answer.
<?php
// Your input post variables
$name = $_POST['name'];
$foodArray = $_POST['foodArray'];
/*
I'm using a function to handle my queries,
simply because it makes large piles of code easier to read.
I now know that every time the function:
createUserAndFood($name, $foodArray);
is called, that it will populate my user and food table.
That way I don't have to worry about writing all the code multiple times.
*/
function createUserAndFood($name, $foodArray){
// food array values
$foodValues = array_values($foodArray);
// DB variables
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";
// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
// Check connection
if($conn->connect_error){
die("Connection failed: " . $conn->connect_error);
}
/*
Stops the query from auto commiting,
I'll explain later, you can "maybe" disregard this.
*/
$conn->autocommit(FALSE);
// Declare the query
$sql = "INSERT INTO userTable(name) VALUES(?)";
// Prepare and bind
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $name);
// Execute the query
$stmt->execute();
// Fetch last inserted id
$lastID = $conn->insert_id;
$sql = "INSERT INTO foodTable(userId, food) VALUES(?, ?)";
$stmt = $conn->prepare($sql);
for($i = 0; $length = count($foodValues) > $i; $i++){
$stmt->bind_param("is", $lastID, $food);
$food = $foodValues[$i];
$stmt->execute();
}
// Commits the query / queries
$conn->commit();
// Close connection
$stmt->close();
$conn->close();
}
?>
Since you wanted to optimize your queries, the general idea that we are using here, is that we are making use of the MySQL function LAST_INSERT_ID(); via PHP and store it into a variable.
Now, this is mainly relevant if you are using auto incremented id's. If you are not, you can disregard this specific logic and use something else. But if you are, then keep reading.
The reason why we are storing the last id into a variable is because we need to use it multiple times (the new user might have more than one favorite food afterall). If you were not to store the last id into a variable, it would instead take the auto incremented value of the second table after the initial insert, which means upon your third insert statement and forward, you would be working with the wrong id.
Now, as I promised to explain, the reason I'm using $conn->autocommit(FALSE); and $conn->commit(); is because you might not want incomplete data sets in your database. Imagine that a user input is happening, but your database crashes in the middle of it all. You'll have incomplete data sets. If this is not really a concern of yours, then you can disregard that.
To simplify what's going on at the MySQL side of things, think of it like this:
BEGIN;
INSERT userTable SET name = '$name';
SET #lastID = LAST_INSERT_ID();
INSERT foodTable SET id = #lastID, food = '$food';
COMMIT;
So I made a function to add photos to favorites, how do I make it so I don't keep adding the same images?
My code:
function addToFavorites($fileName)
{
global $conn;
$email = $_SESSION['email'];
$imageId = $_GET["id"];
$sql = "insert into favorites set UserEmail='".mysqli_real_escape_string($conn, $email)."', ImageID=".$imageId;
$result = mysqli_query($conn, $sql);
Any help would be great thanks!
You let the database do the work! Simply define a unique constraint or index on the table:
alter table favorites add constraint unq_favorites_useremail_imageid
unique (useremail, imageid);
With this constraint in place, the database will return an error if you attempt to insert a duplicate.
If you want to avoid the error, you can use on duplicate key update:
insert into favorites (UserEmail, ImageId)
values (?, ?)
on duplicate key update ImageId = values(ImageId);
Some notes about this:
The ? is a parameter placeholder. Learn to use parameters rather than munging values in query strings.
This does not use set. That is a MySQL extension. There is no advantage in this case; you might as well use the standard syntax.
The on duplicate key is a no-operation, but it prevents the code from returning an error when there is a duplicate.
If you want to avoid errors like Gordon Linof said you have execute query:
SELECT FROM favorites WHERE UserEmail='".mysqli_real_escape_string($conn, $email)."' AND ImageID=".$imageId
and if it returns 0 records to execute the insert one. This is the safest way.
I have some php variables that capture multiple data in the form of an array and i want to input these values in a mysql table.
$surname = unserialize(base64_decode($_POST['surname']));
$firstname = unserialize(base64_decode($_POST['firstname']));
$othername = unserialize(base64_decode($_POST['othername']));
$dob = unserialize(base64_decode($_POST['dob']));
$gender = unserialize(base64_decode($_POST['gender']));
$email = unserialize(base64_decode($_POST['email']));
$phone = unserialize(base64_decode($_POST['phone']));
$location = unserialize(base64_decode($_POST['location']));
$stmt = $connQlife->prepare("INSERT INTO cimbooking (surname, firstname, othername, dob, gender, email, phone, location) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
foreach($surname as $key=>$sname){
$stmt->bind_param('ssssssss', $sname, $firstname[$key], $othername[$key], $dob[$key], $gender[$key], $email[$key], $phone[$key], $location[$key]);
$stmt->execute();
}
$stmt->close();
What happens when this runs is what i can't seem to understand. Looking at the code, this should work just fine. Now in a scenario where the number of records is supposed to be 3, I should see 3 records in the table after submission, but only the first record is being entered into the mysql table. Furthermore, on troubleshooting, i find that when the code runs, the first record that enters the table occupies say id 1. If i refresh the page so the code runs again, the first record of that new command occupies id 4 and not id 2 like it should be. (The id field in the database is already set to auto increment).
Which seems to me like when one record enters the table, the other records enter and disappear or something. I don't seem to understand why.
I believe the problem is that once you've called $stmt->bind_param() then you can't do it again. So the $stmt->execute() is submitting the same data to the database each time. For some reason this is failing due to, I guess, some duplication rules.
Try using $stmt->bind_result() to see what happens when you bind and $stmt->get_result() to get the result of each execute.
I think you need to use $stmt->reset() after each execution so you can do the next one.
PHP.net manual for mysqli_stmt has links to all the above methods.
The code below does exactly what it's expected to do. It adds a client into the database successfully. But I never told the query to execute or add a new client all I did was store the query in a variable and checked to see if it was valid in the if statement. I need some help understanding how the query executed.
$query = "INSERT INTO clients(id, name, email, phone, address, company, notes, date_added) VALUES(NULL, '$clientName', '$clientEmail', '$clientPhone', '$clientAddress', '$clientCompany', '$clientNotes', CURRENT_TIMESTAMP)";
$result = mysqli_query($connection, $query);
// if query was successful
if( $result ){
header("LOCATION: clients.php?update=success");
} else{
// something went wrong
echo "Error: $query <br>" . mysqli_error($connection);
}
The way you should be doing this is a little more self-explanatory:
// Prepare this query with placeholders for where the user data will go.
// This creates a prepared statement handle ($stmt)
$stmt = $connection->prepare("INSERT INTO clients(name, email, phone, address, company, notes, date_added)
VALUES(?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)");
// Bind the user data to the statement so it will be escaped properly
// and inserted correctly.
$stmt->bind_param(
"ssssss",
$clientName,
$clientEmail,
$clientPhone,
$clientAddress,
$clientCompany,
$clientNotes
);
// Execute the statement now that everthing is in place. This actually
// sends the query to the MySQL server to be executed and waits
// for the result. The result of this function call indicates success
// or failure.
if ($stmt->execute()) {
// Query was successful then `execute()` returns a logically true value
// and this block of code will run.
header("Location: clients.php?update=success");
} else {
// If that previous condition didn't trigger, then we end up here and
// this code will run instead.
echo "Error: $query <br>" . $connection->error;
}
If you have an AUTO_INCREMENT column don't specify it in your list of VALUES, you can omit it and it will be populated automatically. Any column with a NULL default can also be omitted. There's no point in force-inserting NULL if that's how it will end up anyway.
You also need to pay careful attention to how you insert your data. You cannot use string interpolation to do this, it's extremely dangerous. The bind_param method takes care of adding the data in a safe manner if you've created a prepared statement that has placeholder values (?). This all but guarantees your code will be safe, secure and free from escaping errors that can take a lot of time to identify and repair.
I've also switched this to use the object-oriented style of mysqli. Not only is this significantly less verbose, it also becomes more clear as to what the operation is being performed on. $stmt->function() is obviously something making use of or manipulating the $stmt object. If it's just one argument of many that can be harder to identify.
Specifying arguments directly to functions instead of leaning on these intermediate variables is also a good habit to get into. Things like $sql tend to clutter up your code and confuse the intent of that string, plus if you have several of them you're juggling, like $sql3 and $sql8 there's an opportunity to make a tiny typo that causes real problems.
I'm trying to create a sign up page for a website using PHP and PHPMyAdmin.
I have set 2 fields in the form needed for registration: email and password, with password confirmation.
Both of them are treated as string in PHP and as varchar in database. I don't know why, but everytime the user insert email and password and confirm, the new user is being inserted in database, but with email and password as 0 insted their real value. I'm sure that email and password values inside the PHP variables are correct, because i printed to output their content immediatly before the mysqli query, so i'm assuming that is a database problem.
I'm currently using Wamp Server for Windows 8 and PHPMyAdmin 4.1.14. Database is of type InnoDB and with latin_swedish_ci characters.
The query used in PHP is:
"INSERT INTO users (email,password) VALUES (email = '$email', password = '$password')"
using mysqli_query for the execution.
Instead of doing column equals variable, do:
VALUES ('$email','$password')
if an INSERT is indeed what you wish to use.
that syntax VALUES (email = '$email', password = '$password') is for when you want to check if a column equals something, when using a WHERE clause for example.
WHERE email = '$email'
or
WHERE email = '$email' AND password = '$password'
when doing an UPDATE as another example:
UPDATE table_name SET email = '$email', password = '$password'
WHERE column_x='something'
or, when doing a SELECT as another example:
SELECT * FROM table_name
WHERE column_x = 'something' OR column_y = 'something else'
Sidenote: OR can be replaced by AND, depending on the desired query.
Yet, when you do fix it, that present method is open to SQL injection. Use prepared statements, or PDO with prepared statements, they're much safer.
Look into those, you will be glad you did.
A basic example to prevent SQL injection is:
$email = mysqli_real_escape_string($con, $_POST['email']);
$password = mysqli_real_escape_string($con, $_POST['password']);
Error checking sidenote:
Add error reporting to the top of your file(s) which will help during production testing.
error_reporting(E_ALL);
ini_set('display_errors', 1);
and or die(mysqli_error($con)) to mysqli_query() while replacing $con with your DB connection variable.
Matteo - also, you should consider parameterizing your php syntax to prevent SQL injections. Better to do it now than to have to come back to it later. You can find an explanation of the concepts here; https://stackoverflow.com/questions/25316766/understanding-parameterized-queries-and-their-usage-in-preventing-sql-injections
INSERT INTO users (email,password) VALUES ('$email', '$password')
you don't need column name after VALUES. You have already specified it after table name.
By the way, you may want to look at php prepared statement