INSERT INTO with joins? - mysql

I got trouble inserting data to a table that has joins to other tables, I've never done that before and the searchs I went through didn't help much.
I have an article page with a "comment" form that I want to send to my "comment" table. The "comment" table has joins with my "user" and "article" tables (to get which article the comment is part of, and which user sent it)
My comment table columns : comment_id, article_id, author_id, submitted_at, content
My user table columns :
user_id, username, password
My article table columns : article_id, preview_image, title, description, body, publishedAt, author_id
So this is what my query looks like :
public function setComment() {
try{
$stmt = $this->connection()->prepare("INSERT INTO comment (article_id, author_id, submitted_at, content) (SELECT comment.article_id, comment.author_id, submitted_at, content
FROM comment INNER JOIN user ON comment.author_id = user.user_id INNER JOIN article ON comment.article_id = article.article_id)");
$stmt->bindParam(':article_id', $_GET['id'], PDO::PARAM_INT);
$stmt->bindParam(':author_id', $_GET['id'], PDO::PARAM_INT);
$stmt->bindParam(':content', $_POST['comment'], PDO::PARAM_STR);
$stmt->execute();
} catch (PDOException $e){
echo $e->getMessage();
}
}
Well there is no error.... the only problem here is, when I submit my form all my rows in the comment table are duplicated by 2 lol... I just want to add a new row with the data in my bindParam()
So what's wrong?
BY THE WAY during my searchs I've seen people don't specify VALUE() when inserting to a table that has joins. So how do I insert the data that are in my bindParam()? That's weird
EDIT :: FOUND IT!! Actually I didn't need to use a SELECT at all
The correct query was :
INSERT INTO comment (article_id, author_id, submitted_at, content)
VALUES (:article_id, :author_id, NOW(), :content)
I had an error telling me "SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails"... So I thought I needed to join the tables or something but the actual problem was because in my query I bound the author_id to $_GET['id'] (that was 9)... and there is no user with the id of 9 in my user table. Shame on me
I used $_GET['id'] because I don't know how to properly get the id of the user sending the comment (and bindParam doesn't like it when I just use an integer)... with the session maybe idk, but that's a problem for another time

Related

Insert Select Query

I have 2 tables articles and users.
I want to insert title, content and name into the article table.
I have the the users id stored in a session, is there a way using an insert select query to add title, content and name to the article table whilst getting the name from the users table using the id.
Something like:
INSERT INTO article (title, content, name) VALUES ($post['title'], $post['content'], name) SELECT name FROM users WHERE id = mySessionId
use INSERT INTO..SELECT statement,
$title = $post['title'];
$content = $post['content'];
$insertStatement = "INSERT INTO article (title, content, name)
SELECT '$title', '$content', name
FROM users
WHERE id = mySessionId";
As a sidenote, the query is vulnerable with SQL Injection if the value(s) of the variables came from the outside. Please take a look at the article below to learn how to prevent from it. By using PreparedStatements you can get rid of using single quotes around values.
How to prevent SQL injection in PHP?

MySQL INSERT-SELECT a non-mandatory field with JOIN

I have a table (netStream), that has 2 foreign keys: (logSessions_logSessionID) and (accountSessions_accountSessionID).
The (logSessions_logSessionID) is mandatory the (accountSessions_accountSessionID) is NOT mandatory.
Here is the part of the block-scheme that shows the connections and the non-mandatory status:
(The background: logSessions are the sessions that every visitor have, accountSessions are the login sessions. Everybody has a logSession (since everybody is a visitor), but not everybody is logged in, so they do not have accountSession)
I want to insert a row into (netStream), in every case there is a (logSession), but it is not the same with (accountSession). So, when there is an (accountSession), I want to insert that ID too, if there is no (accountSession), then just leave that field in (netStream) NULL.
The hash values are stored in Binary(x), this is why I use UNHEX().
This is the MySQL I wrote, there is no error message, but it does not work. What is the problem?
INSERT INTO `test-db`.`netStream` (`netStreamHash`, `logSessions_logSessionID`, `accountSessions_accountSessionID`)
SELECT UNHEX("1faab"), `logSessions`.`logSessionID`, NULL FROM `logSessions` CROSS JOIN `accountSessions`
WHERE `logSessions`.`logSessionHash` = UNHEX("aac") AND
`accountSessions`.`accountSessionHash` = UNHEX("2fb");
If understand you correctly you are probably looking for something like this
INSERT INTO `test-db`.`netStream` (
`netStreamHash`,
`logSessions_logSessionID`,
`accountSessions_accountSessionID`)
SELECT UNHEX("1faab"),
(SELECT `logSessionID` FROM `logSessions`
WHERE `logSessionHash` = UNHEX("aac")),
(SELECT `accountSessionID` FROM `accountSessions`
WHERE `accountSessionHash` = UNHEX("2fb"));
If there is no matching row in accountSessions then you'll get NULL inserted in accountSessions_accountSessionID in netStream table

Is there a better way to run this mysql update?

I have the following code, which firstly retrieves each unique style from a database and then searches through the products table to find the terms associated with each style and updates the stylerefs table with the styleid and the productid:
$query = "Select id,style,terms from su_styles";
if ($results = $sudb->query($query)) {
while($result = $results->fetch_array(MYSQLI_ASSOC)) {
$id=$result['id'];
$style=$result['style'];
$terms=$result['terms'];
$query= "INSERT IGNORE INTO su_stylerefs (mykey,id)
SELECT mykey,$id FROM su_pref where (match(name) against ('$terms' in Boolean Mode))
ON DUPLICATE KEY UPDATE id=$id, mykey = su_pref.mykey";
$sudb->query($query);
Is there anyway I can rewrite this query into one, rather than looping through the results of the first query? I have 10 other similar queries that need to run every day and some of the tables in the first query could have 100s of records, which mean hundreds of connections to the database, which takes a while.
Thank you in advance
I would fetch all these rows first along with the match() case, and then do an update:
INSERT INTO su_stylerefs (mykey, id)
VALUES ($mykey[0], $id[0]), ($mykey[1] $id[1]), ...
ON DUPLICATE KEY UPDATE id=$id, mykey = su_pref.mykey
2nd solution:
INSERT IGNORE INTO su_stylerefs (mykey,id)
SELECT sp.mykey, ss.id
FROM su_styles AS ss
INNER JOIN su_pref AS sp
ON match(sp.name) against (ss.terms in Boolean Mode)
ON DUPLICATE KEY UPDATE id = VALUES(id), mykey = VALUES(mykey)
This may be a better solution.

MySql - Best way to do this kind of query

I need to return a single row with some datas taken from some tables not related each others.
So, for example, my actual queries are these (I done it trought a PHP script) :
$query=mysql_query("SELECT trackid FROM tracklist WHERE usersub='".$_SESSION['nickname']."'",$mydb);
echo mysql_num_rows($query);
$query=mysql_query("SELECT trackid FROM comments WHERE usercom='".$_SESSION['nickname']."'",$mydb);
echo mysql_num_rows($query);
$query=mysql_query("SELECT vote FROM vote WHERE uservote='".$_SESSION['nickname']."'",$mydb);
echo mysql_num_rows($query);
$query = mysql_query("SELECT datereg FROM users WHERE nickname='".$_SESSION['nickname']."'",$mydb);
echo mysql_result($query,0,'datereg');
But this will call the MySql server 4 times.
Whats your suggestion to better this situation?
If the tables are not related then you will have to make 4 seperate calls
If the tables COULD be related by foreign keys then you could join them in some way and possibly cut down your sql calls
Ultimately though if you need all of the data then you'll have to request it from the database
You could use a UNION. And, btw, mysql_result is poor. And FFS don't forget to sanitize your inputs!
<?php
$nickname = mysql_escape_string($_SESSION['nickname']);
$sql = "
SELECT COUNT(trackid) AS n FROM tracklist WHERE usersub='{$nickname}'
UNION
SELECT COUNT(trackid) FROM comments WHERE usercom='{$nickname}'
UNION
SELECT COUNT(vote) FROM vote WHERE uservote='{$nickname}'
UNION
SELECT datereg FROM users WHERE nickname='{$nickname}'
";
$result = mysql_query($sql, $db);
while ($row = mysql_fetch_assoc($result)) {
echo $row['n'];
}
?>
I wouldn't really recommend this as it's a bit of a mess combining "count" values with a date in the same column, but you can do it. It's the direct answer to your question.
Well, you could create a fifth table and use it as an index.
If all the values { trackid, vote, datareg } are integers, the index table could contain three columns - nickname, value, and table. When you add records to one of the other tables, add a corresponding record to the index table.
For example,
INSERT INTO vote (vote, uservote, ...) VALUES (123, 'abc', ...);
INSERT INTO myindex (nickname, nvalue, ntable) VALUES ('abc', 123, 'vote');
(I wouldn't actually store the table name as a string but as a numeric value, but you get the idea)
Then on a query, you just SELECT nvalue, ntable FROM myindex WHERE nickname = 'abc';
You will possibly get more than one row.
I think that this is a lot of work and you are better off sticking with the four original queries.
Have you tried combining the select statement together like
SELECT .. Actually.
Maybe you should normalise your database and set up links between your tables...
Edit :: And i'm not sure how you're preparing yourself against mysql injection, but be careful with where your $_SESSION[] comes from
If all the selects return a single row:
$query=mysql_query("
(SELECT trackid FROM tracklist WHERE usersub='".$_SESSION['nickname']."'") as tracklist,
(SELECT trackid FROM comments WHERE usercom='".$_SESSION['nickname']."'") as trackid,
(SELECT vote FROM vote WHERE uservote='".$_SESSION['nickname']."'") as vote,
(SELECT datereg FROM users WHERE nickname='".$_SESSION['nickname']."'") as datereg
"

MySQL Insert Select

Ran into a bit of trouble when trying to insert records into my DB from my forum
What it does when you create a thread is make an entry into 2 tables. First the forum_threads table with information on the thread title, description, poster, post time, etc. It will use thread_id with AUTO_INTEGER to generate the threads ID.
I then need to get that thread_id from the forum_threads and then put that as the thread_id in the forum_posts table.
I'm not sure if theres anyway I can select a row based on its ID after I just inserted it. Would I just have to select the most recent ID? Would that leave a margin of error? Other thought I had was to select based on user name and post time.
Thoughts?
<?php
if (isset($_POST['submit'])) {
$thread_sql = "
INSERT INTO forum_threads (
user_id,
forum_id,
thread_postdate,
thread_title,
thread_description,
thread_icon
) VALUES (
'$_SESSION[user_id]',
'$_GET[f]',
'$date',
'$_POST[topictitle]',
'$_POST[topicdescription]',
'$_POST[posticon]'
)
";
$thread_query = #mysqli_query ($db_connect, $thread_sql);
$post_sql = "
INSERT INTO forum_posts (
user_id,
thread_id,
post_message,
post_date
) VALUES (
'$_SESSION[user_id]',
'',
'$_POST[content]',
'$date'
)
";
$post_query = #mysqli_query ($db_connect, $post_sql);
}
?>
To first answer your question directly, the function mysql_insert_id() will return the ID that was assigned to the most recently inserted row. There's no guesswork involved; MySQL will happily tell you (through its own built-in LAST_INSERT_ID() function) what ID it assigned.
On a separate note, I see that you're directly inserting values from $_POST into your SQL statement. Never, ever, EVER do that. This exposes your application to SQL injection attacks.
Either use the mysql_real_escape_string() function to properly escape the values for use in a SQL statement, or, since you're already using mysqli_ functions, use placeholders (? values) to create a prepared statement.
MySqli_Insert_Id () will return the last auto generated ID. Call this function immediately after you insert your new thread.
INSERT
INTO forum_threads (…)
VALUES (…)
INSERT
INTO forum_posts (thread_id, …)
VALUES (LAST_INSERT_ID(), …)
You can just use the last_insert_id to get the ID of the row you just inserted; don't worry about anybody else inserting a row just after, the last_insert_id is per-connection, so unless they used your connection, you're safe.
There is an API-level call to retrieve this so you don't need to ask the server specifically.
Oh yes, also, do not use the # operator in PHP, ever, google for it if you want to know why it's bad.
Use MySQL's last_insert_id() function in sql code or the php wrapper for it mysql_insert_id() in a php script. These will give you the last auto_increment number generated in your connection. So it's thread safe.