I want to build a query to insert photo details in database tables. Here's my tables structure:
photos(photo_id, photo_title, caption, ....)
tags(tag_id, tag_name)
tag_asc(tag_id, photo_id)
A photo has title, caption, and tags array. I want to:
Insert photo_title and caption in photos table.
Insert each of tag from tags array in tags table.
Insert tag_id and photo_id in tag_asc table (to link tags with photos).
I have come up with following three queries to do above task.
mysql_query("INSERT INTO photos (photo_title, caption)
VALUES ($title, $caption)");
$photo_id = mysql_insert_id(); //get photo_id
foreach ($tags as $tag){
mysql_query("INSERT INTO tags (tag_name)
VALUES ($tag)");
$tag_id = mysql_insert_id(); //get tag_id
mysql_query("INSERT INTO tag_asc (tag_id, photo_id)
VALUES ($tag_id, $photo_id)");
}
My question
If the above approach is good, or if there is more efficient way to do same thing?
Most of what you're doing looks fine. The main problem I see is the potential for duplicate tags.
It would be better to check if a tag exists and fetch the existing tag's ID if it does, rather than creating a new tag each time. This would prevent duplicate tag names from being inserted into the tags table. If you know each tag is unique, this could speed things up later — for example, you could search photos by their tag IDs rather than having to do some messy JOIN stuff.
Give tags.tag_name a unique index if it doesn't have one already.
Use the following function to fetch a tag ID. If the tag already exists, the existing row ID will be returned. If not, a new row will be created and its ID will be returned.
-
function select_or_create_tag($tag_name) {
$tag_name = addslashes($tag_name);
$result = mysql_query("SELECT id FROM tags WHERE tag_name='$tag_name'");
if ( mysql_num_rows($result) > 0 )
return mysql_result($result, 0, 0);
$insert = mysql_query("INSERT INTO tags (tag_name) VALUES ('$tag_name')");
return mysql_insert_id($insert);
}
Example usage, based on your code:
mysql_query("INSERT INTO photos (photo_title, caption)
VALUES ($title, $caption)");
$photo_id = mysql_insert_id();
foreach ( $tags as $tag ) {
$tag_id = select_or_create_tag($tag);
mysql_query("INSERT INTO tag_asc (tag_id, photo_id)
VALUES ($tag_id, $photo_id)");
}
Also:
Make sure you have non-unique indices set up on tag_asc.tag_id and tag_asc.photo_id for faster querying.
As OMG Ponies mentioned, it might be worth converting this code to a stored SQL procedure. That would get rid of a lot of the overhead involved with making all these SQL queries individually.
Related
I am trying to insert multiple or single tags to a book, I have 3 tables such as books(books record), tags(tags record), books_tag(relation for books and tags), from this Should I store tags in a text field or in a separate table? question I got this idea.
I am first inserting books along with tags, then I am getting the last added ID of the book and trying to insert into books_tag table along with tag ID. My code working perfectly for newly adding tags (which is not existed on tags table) for a book, but if I try to insert existed tag, it is always insert tag ID as 0 (should be existed tag ID from tags table) on books_tag table, Here is my code
public function addTag($tags, $book_id)
{
// Loop tags to insert
foreach ($tags as $tag) {
// insert tags if not existed
$this->db->query('INSERT INTO tags (tag) VALUES (:tag) ON DUPLICATE KEY UPDATE tag = :tag');
// Bind values
$this->db->bind(':tag', $tag);
// Execute query
$this->db->execute();
// Get lsat id
$last_tag_id = $this->db->lastId();
$this->db->query('INSERT INTO books_tag (book_id, tag_id) VALUES (:book_id, :tag_id)');
// Add tag to books
$this->db->bind(':book_id', $book_id);
$this->db->bind('tag_id', $last_tag_id);
// Execute query
$this->db->execute();
}
}
I am an amateur hobbying a video site, where users can submit video urls. Everything works, but now I want multiple categories to be able to be selected for one same video. Categories are stored in seperate tables. I am sure you will understand from the code:
mysql_query("INSERT INTO videos
(title, source, thumb_large, author, email_by) VALUES('$title', '$video_url', '$thumb_url', '$author', '$email') ") or die(mysql_error());
mysql_query("INSERT INTO videos_categories
(category_id) VALUES('$category_name_id') ") or die(mysql_error());
What I want to do is to add another category for the same video ID the tables are both on AUTO INCREMENT synced with each other at the moment:
mysql_query("INSERT INTO videos_categories (category_id) VALUES('$category_name_id_two') ") or die(mysql_error());
The variable is currently registered as:
$category_name_id = mysql_real_escape_string(htmlspecialchars($_POST['category_id']));
And is currently selected through dropdown (select with name: category_id) and it works fine. However, I want to change this ofcourse into select boxes, so mulitple, or upto 3 categories can be selected, instead of having 3 dropdown menus.
I hope you can help me out with this, I am stuck here.
Edit:
I wanted to add that there are three tables, one from which categories are retrieved, one that stores the video_id+category_id which should make at possible to have multiple categories per video, and one with the videos.
Thanks
So you need to change the select box to something like this:
<select name="category_id[]" multiple="multiple">
<!-- Options -->
</select>
...
In backend, After this query:
mysql_query("INSERT INTO videos
(title, source, thumb_large, author, email_by) VALUES('$title', '$video_url',
'$thumb_url', '$author', '$email') ") or die(mysql_error());
Do:
$video_id = mysql_insert_id();
$categories = $_POST['category_id'];
$values = '';
foreach($categories as $category_id) {
$values .= "('$video_id', '$category_id'),";
}
$values = rtrim($values, ',');
$query = "INSERT INTO videos_categories(video_id,category_id) VALUES $values";
mysql_query($query);
Few Suggestions:
Since mysql_* apis are deprecated, please don't use them. Switch to mysqli.
Do the validation and sanitization of the data before using in sql queires.
$insert = $dbh->prepare('INSERT INTO tags (tag_name) VALUES (:tag)');
$insert->bindParam(':tag', $tag, PDO::PARAM_STR);
foreach($tags as $tag) {
$insert->execute();
$tag_id = $dbh->lastInsertID();
echo $tag_id."+".$photo_id."<br />";
$sql = "INSERT INTO tagrefs (tag_id, photo_id) VALUES (:tag_id,:photo_id)";
$q = $dbh->prepare($sql);
$q->execute(array(':tag_id'=>$tag_id,
':photo_id'=>$photo_id));
}
This particular piece of code inserts tags related to uploaded photos into a table called 'tags'. It links the tag_id to the photo_id in a table called 'tagrefs'. This all works fine, until I use a tag twice. Which is logical, because nothing is inserted (tags are unique, I simply want the entry in 'tagrefs' to list the photo_id for my next photo with tag_id's that already exist)
How do I make it so that my code compares the tags the user put in and compares them, or that the values of existing tags are returned and put into 'tagrefs' properly? Thank you very much in advance for your time.
If you use INSERT ... ON DUPLICATE KEY UPDATE, then lastInsertID() will return the AUTO_INCREMENT field's value of a matched row even if an UPDATE is performed instead of an insertion.
To ensure that it also works in versions of MySQL prior to v5.1.12, one can explicitly set the insertion id with MySQL's LAST_INSERT_ID() function:
INSERT INTO tags
(tag_name)
VALUES
(:tag)
ON DUPLICATE KEY UPDATE
id = LAST_INSERT_ID(id)
I've created mini content management system. Now got afew questions
I'm filtering posts with following function
function filter($data, $db)
{
$data = trim(htmlentities(strip_tags($data)));
if (get_magic_quotes_gpc())
$data = stripslashes($data);
$data = $db->escape_string($data);
return $data;
}
And the PHP code looks like that
$name=filter($_POST['name'], $db);
$title=filter($_POST['title'], $db);
$parent=filter($_POST['parent'],$db);
$switch=filter($_POST['switch'], $db);
if($switch=''){
echo "Return back and select an option";
die();
}
$parentcheck=filter($_POST['parentcheck'],$db);
if($parentcheck=='0')
{
$parent=$parentcheck;
}
$purifier = new HTMLPurifier();
$content = $db->real_escape_string( $purifier->purify( $_POST['content']) );
if(isset($_POST['submit'])&&$_POST['submit']=='Ok'){
$result=$db->query("INSERT INTO menu (parent, name, showinmenu) VALUES ('$parent', '$name', '$switch'") or die($db->error);
$result2=$db->query("INSERT INTO pages (id, title, content) VALUES ('<what?>', '$title', '$content'") or die($db->error);
}
And that's how my tables look like
Table named "pages"
And "menu"
My questions are followings:
I'm trying to get autoincremented id value from menu table after
('$parent', '$name', '$switch'") insertion and set this id in pages table
while inserting ($title, $content). How to do it? Is it possible with single
query?
$content's value is the text with HTML tags. I'm using html purifier.
May I filter it's value too before inserting into db table? Any
suggestion/advice?
Should be
$result2=$db->query("INSERT INTO pages (id, title, content) VALUES (LAST_INSERT_ID(), '$title', '$content'") or die($db->error);
Filtering using real_escape_string( ) should be safe. Is there something else that you want to filter?
Looks like you're using mysqli as the DB library, so you can use $db->insert_id() to retrieve the LAST id created by an insert operation by that particular DB handle. So your queries would become:
$result=$db->query("INSERT INTO menu (parent, name, showinmenu) VALUES ('$parent', '$name', '$switch'") or die($db->error);
$new_id = $db->insert_id();
$result2=$db->query("INSERT INTO pages (id, title, content) VALUES ($new_id, '$title', '$content'") or die($db->error);
^^^^^^^
You can't really do it in a single query, as mysql does not make the ID value available for the insert_id function until AFTER the query completes. So you do have to do this in a 3 step process: insert, get id, insert again.
The rule for DB filtering (better known as escaping) is to escape ANYTHING that's user-provided. This even includes data you've retrieve in other db queries and are re-inserting. Escaping isn't really there as a security measure - it's there to make sure that whatever you're putting into the query string doesn't BREAK the query. Preventing SQL injection attacks is just a side effect of this.
I'm new to php. So, please forgive me if this seems like a dumb question.
Say i have a MySQL insert statement insert into table (a,b) values (1,2),(3,4),(5,6). table 'table' has a auto increment field called 'id'.
how can I retrieve all the ids created by the insert statement above?
It will be great if i get an example that uses mysqli.
You can't. I would suggest that you maintain your own ids (using guid or your own auto-increment table) and use it when you insert into the table.
But it's possible to get the auto-increment value for the last inserted using LAST_INSERT_ID():
http://dev.mysql.com/doc/refman/5.0/en/getting-unique-id.html
AngeDeLaMort's answer is almost right. Certainly, the most appropriate way to deal with the problem is to insert one row at a time and poll the insert_id or generate the sequence elsewhere (which has additional benefits in terms of scalability).
I'd advise strongly against trying to determine the last insert_id and comparing this the most recent insert_id after the insert - there's just too may ways this will fail.
But...an alternative approach would be:
....
"INSERT INTO destn (id, data, other, trans_ref)
SELECT id, data, other, connection_id() FROM source";
....
"SELECT id FROM destn WHERE trans_ref=connection_id()";
....
"UPDATE destn SET trans_ref=NULL where trans_ref=connection_id()";
The second query will return the ids generated (note that this assumes that you use the same connection for all 3 queries). The third query is necessary because connection ids to go back into the pool when you disconnect (i.e. are reused).
C.
In some cases, if you have another identifier of sort such as a UserID, you could filter your query by UniqueID's greater than or equal to mysql_insert_id(), limit by the number of affected rows and only display those by the user. This would really only work inside of a transaction.
$SQL = "INSERT INTO Table
(UserID, Data)
VALUES
(1,'Foo'),
(1,'Bar'),
(1,'FooBar')";
$Result = mysql_query($SQL);
$LastID = mysql_insert_id();
$RowsAffected = mysql_affected_rows();
$IDSQL = "SELECT RecordID
FROM Table
WHERE UserID = 1
AND RecordID >= '$LastID'
LIMIT '$RowsAffected'";
$IDResult = mysql_query($IDSQL);
as a follow up to AngeDeLaMort:
You could seperate your inserts and do it something like this:
$data = array (
array(1,2),
array(3,4),
array(5,6)
);
$ids = array();
foreach ($data as $item) {
$sql = 'insert into table (a,b) values ('.$item[0].','.$item[1].')';
mysql_query ($sql);
$id[] = mysql_insert_id();
}
Now all your new id's are in the $id array.
Maybe I can do this
$insert = "insert into table (a,b) values (1,2),(3,4),(5,6)";
$mysqli->query($insert);
$rows_to_be_inserted=3;
$inserted_id = $mysqli->insert_id // gives me the id of the first row in my list
$last_row_id = ($inserted_id+$rows_to_be_inserted)-1;
$mysql->query("select * from table where id between $inserted_id and $last_row_id");
what to you guys say?