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;
Related
I am quite new to PDO, and am trying to change my MySQLi procedurally structured php code to an Object Oriented PDO structure. I am just learning about preparing, executing, bindParam/bindValue and the like, to a degree of success.
My question is how do I prepare a query when the user submitted value is in a subquery of that query?
I have a variable used as a subquery in php (where $playerOne, $playerTwo are user submitted values).
$sqlPlayerOne = "(SELECT * FROM players WHERE Player_name = $playerOne)";
$sqlPlayerTwo = "(SELECT * FROM players WHERE Player_name = $playerTwo)";
This it to get all records for these players. I can then, as an example, compare what games they played against each other e.g.
$sqlWith = "SELECT * FROM $sqlPlayerOne s1
WHERE EXISTS (SELECT * FROM $sqlPlayerTwo s2 WHERE s1.Team_name = s2.Opposing_team)
Note: SELECT * is just used to make it more readable here.
Is it enough to do $pdoWith = $db->prepare($sqlWith) or should I be preparing the $sqlPlayerOne first, as this has the user submitted value?
I realise I could just copy/paste the subquery inside every single main query that needed it, but if I don't have to I'd rather not.
EDIT: Sorry for the lack of clarity. This was a section of my code before I changed it, as i wasn't sure how I would have to change it. It seems I will just have to do it similar to how #J-C FOREST pointed out:
$dsn = "mysql:host=localhost;dbname=database";
$username = "user";
$password = "pass";
$db = new PDO($dsn, $username, $password);
$stmt = $db->prepare("SELECT * FROM (SELECT * FROM players WHERE Player_name = :playerone)
s1 WHERE EXISTS (SELECT * FROM (SELECT * FROM players WHERE Player_name = :playertwo) s2
WHERE s1.Team_name = s2.Opposing_team)");
$stmt->bindValue(':playerone', $playerOne);
$stmt->bindValue(':playertwo, $playerTwo);
$stmt->execute();
You need to bind $playerOne, $playerTwo to your prepared statement as parameters. http://php.net/manual/en/mysqli.prepare.php
$mysqli = new mysqli('localhost', 'my_user', 'my_password', 'world');
/* check connection */
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
/* create a prepared statement */
$stmt = $mysqli->prepare("SELECT * FROM (SELECT * FROM players WHERE Player_name = ?) s1
WHERE EXISTS (SELECT * FROM (SELECT * FROM players WHERE Player_name = ?) s2 WHERE s1.Team_name = s2.Opposing_team)")
/* bind parameters for markers */
$stmt->bind_param("ss", $playerOne, $playerTwo);
/* execute query */
$stmt->execute();
The overall mechanism of prepared statements is the same in all database extensions that support it:
Replace number or string literals (and I mean that, literals, not random pieces of code) inside SQL with place-holders, either position-based ? or named :username (don't mix, pick one)
Prepare the query by calling the appropriate function that receives SQL as parameter
Execute the prepared query by calling the appropriate function(s) that receive values as paremeter
So if you're doing it right in mysqli, a switch to PDO will not require a change in your logic. Your code samples, though, suggest you are not using prepared statements at all: no place-holders, no data in a separate channel... I can see variable interpolation in double quoted strings, but that's a PHP feature, not a SQL feature. As such, it's totally useless to separate code and data and prevent SQL injection.
I suspect the root misunderstanding is not being fully sure of how PHP and SQL interact. The answer is that they don't: they are entirely different computer languages and they are executed by entirely different programs. The only relationship is that you use the former to generate the latter. No matter what you do, in the end you'll just submit a string (i.e. plain text) to the database server. How you generate that text is irrelevant because strings have no memory.
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.
If I have a loop which executes PDO statements, is there a way to collate those executions and run them after the loop in one go, or is it normal to execute each one individually?
For example, I am updating a field on every row in my database like this:
$pdo = new PDO(/* valid connnection arguments */);
$stmt = $pdo->prepare("SELECT id, username, password FROM users ORDER BY id ASC");
$stmt->execute();
foreach($stmt->fetchAll(PDO::FETCH_CLASS) as $user)
{
$token = sha1($salt . $user->username . $user->password);
$stmt = $pdo->prepare("UPDATE users SET token = :token WHERE id = :id LIMIT 1");
$stmt->execute(array(
":id" => $user->id,
":token" => $token
));
}
But I'm pretty confident this should be done differently, as one query to MySQL. I'm vaguely familiar with concatenating queries with ; in phpMyAdmin, so I'm guessing that's the key, but I'm not sure how to do that in conjunction with prepared statements.
You could let the database handle the update: http://dev.mysql.com/doc/refman/5.6/en/encryption-functions.html#function_sha1
UPDATE users SET token=sha1(token)
is it normal to execute each one individually?
Yes.
For such one-time tasks like one in question, it's all right with multiple queries. What you have to avoid is running large loops on a regular basis, as it doesn't really matter, if you can collide your statements or not - a database will have to run them all either way.
So, now you can run your massive update loop already and turn to more urgent matters.
The following topic is close to what I'm trying to ask, but it's outdated considering the mysql functions are deprecated in php now and there are prepared statements for preventing sql injection. insert all $_POST data into mysql using PHP?
Basically, I have a huge number of columns in my database that all need to get filled up when I submit this form. The form matches each column with an input field of the same name (the name attribute on the input field is the same as the column name it belongs in. So $_POST['firstName'] goes in the firstName column, and so on).
Is there a way using mysqli or PDO that I could easily just take all my POST data and automatically insert it into the MySQL table without going through each field by hand? I could code them all out using prepared statements, but there are a ton of columns and I'd like to get them done all at once if possible.
This is the beginning of the long version I don't really want to have to complete.
$stmt = $connection->prepare("INSERT INTO area_retreat
(user,firstName,lastName,...etc)
VALUES
(?,?,?,...etc)
ON DUPLICATE KEY UPDATE
user=VALUES(user),
firstName=VALUES(firstName),
lastName=VALUES(lastName),
...etc
");
$stmt->bind_param("sss",
$username,
$_POST['firstName'],
$_POST['lastName']
);
$stmt->execute();
INSERT INTO area_retreat VALUES (?, ?, ...) -- however, you have to match ALL columns as shown in the database.
If you have an auto increment ID, you will need to provide NULL for that column in the proper column order.
To avoid errors you definitely need to store the list of variables one way or another. It could be as simple as an array:
$fields = array('firstName', etc.);
Then you can loop through your array to generate your sql statement dynamically and using named placeholders instead of question marks, you only need to bind them once. You can also store the values in an array and send that array as a parameter to execute():
// start of query
$values = array();
$query = '...';
foreach ($fields as $field)
{
if (isset($_POST[$field]))
{
// add to query
$query .= "...";
// add value to array so that you can feed the array to `execute`
$values[':' . $field] = $_POST[$field];
}
}
// add end of query
$query .= '...';
$stmt->execute($values);
If you want to use the same variables in an ON DUPLICATE KEY UPDATE section, you can do another loop or build an insert section that you can use twice after looping once.
I'm trying to write a User Login System for my website. I'm running WAMP on my laptop and using Aptana for development. I've been stuck trying to get a User Creation function to work for about 3 days now. This function here:
function create_new_user($email, $pass, $level){
$db = new PDO('mysql:host=localhost;dbname=jadams', 'root', '');
$insertQuery = "INSERT INTO jadams.user VALUES ($email, $pass, $level);";
$db->query($insertQuery);
return TRUE;
}
I have rewritten this function several times, using prepared statements and various forms of conditional checks, this is just the simplest one in the hopes of figuring it out. No matter what I try I cannot get the insertion into the database to work. I have gotten this login function working by forcibly inserting users through phpMyAdmin:
function is_pass_correct($email, $pass){
$db = new PDO('mysql:host=localhost;dbname=jadams', 'root', '');
$email = $db->quote($email);
$selectQuery = "SELECT password FROM jadams.user WHERE email = $email;";
$rows = $db->query($selectQuery);
$db=NULL;
if($rows) {
foreach ($rows as $row) {
if($pass === $row["password"]) {return TRUE;} //MD5
}
}
return FALSE;
}
The structure of my Database is email varchar(256) not null primary, password varchar(256) not null, access int; I have also tried the query with and without a semicolon.
You're missing the column names in which to insert the values.
"INSERT INTO jadams.user (email, password, level) VALUES ($email, $pass, $level);"
Also, since you're using the PDO library consider using prepared statements to escape untrusted data.
$insertQuery = "INSERT INTO jadams.user (email, password, level)
VALUES (:email, :password, :level)";
$db = $conn->prepare($insertQuery);
$db->execute(array(':email'=>$email,
':password'=>$pass,
':level'=>$level));
Are you getting an error?
It's hard to diagnose without knowing the full DB structure, but at first blush it looks like maybe the columns in that table do not match up with the values you provide.
Technically, the column names are not required, but if you do not supply them then you must have appropriate values for each column in order. If there is a userID or other field that you are not setting, that could be the issue.
From the manual:
If you do not specify a list of column names for INSERT ... VALUES or INSERT ... SELECT, values for every column in the table must be provided by the VALUES list or the SELECT statement.
To be on the safe side, I would suggest explicitly setting the column names like so:
INSERT INTO
jadams.user (email, password, level)
VALUES ($email, $pass, $level)
Personally I prefer the INSERT INTO ... SET syntax. It feels more readable and less prone to mixing up columns.
INSERT INTO
jadams.user
SET
email = $email,
password = $password
level = $level
Of course, this doesn't get into parameter binding, password storage, and a whole host of other issues you'll also want to be thinking about.
It's also possible that the semicolon at the end of your query is causing an issue. I know for the old mysql_* functions the manual explicitly stated that you should not have a semicolon at the end of a query. However, there's nothing about that in PDO. I assume it's fine to have a semicolon at the end now but I would try removing it and see what happens. (Probably nothing).