I have a list of 300 RSS feeds of news articles stored in a database and every few minutes I grab the contents of every single feed. Each feed contains around 10 articles and I want to store each article in a DB.
The Problem: My DB table is over 50,000 rows and rapidly growing; each time I run my script to get new feeds, it's adding at least 100 more rows. It's to the point where my DB is hitting 100% CPU Utilzation.
The Question: How do I optimize my code / DB?
Note: I do not care about my server's CPU (which is <15% when running this). I greatly care about my DB's CPU.
Possible solutions I'm seeing:
Currently, every time the script runs, it goes to $this->set_content_source_cache where it returns an array of array('link', 'link', 'link', etc.) from all the rows in the table. This is used to later cross-reference to make sure there are no duplicating links. Would not doing this and simply changing the DB so the link column is unique speed things up? Possibly throw this array in memcached instead so it has to only create this array once an hour / day?
break statement if the link is set so that it moves on to the next source?
only checking links that are less than a week old?
Here's what I'm doing:
//$this->set_content_source_cache goes through all 50,000 rows and adds each link to an array so that it's array('link', 'link', 'link', etc.)
$cache_source_array = $this->set_content_source_cache();
$qry = "select source, source_id, source_name, geography_id, industry_id from content_source";
foreach($this->sql->result($qry) as $row_source) {
$feed = simplexml_load_file($row_source['source']);
if(!empty($feed)) {
for ($i=0; $i < 10 ; $i++) {
// most often there are only 10 feeds per rss. Since we check every 2 minutes, if there are
// a few more, then meh, we probably got it last time around
if(!empty($feed->channel->item[$i])) {
// make sure that the item is not blank
$title = $feed->channel->item[$i]->title;
$content = $feed->channel->item[$i]->description;
$link = $feed->channel->item[$i]->link;
$pubdate = $feed->channel->item[$i]->pubdate;
$source_id = $row_source['source_id'];
$source_name = $row_source['source_name'];
$geography_id = $row_source['geography_id'];
$industry_id = $row_source['industry_id'];
// random stuff in here to each link / article to make it data-worthy
if(!isset($cache_source_array[$link])) {
// start the transaction
$this->db->trans_start();
$qry = "insert into content (headline, content, link, article_date, status, source_id, source_name, ".
"industry_id, geography_id) VALUES ".
"(?, ?, ?, ?, 2, ?, ?, ?, ?)";
$this->db->query($qry, array($title, $content, $link, $pubdate, $source_id, $source_name, $industry_id, $geography_id));
// this is my framework's version of mysqli_insert_id()
$content_id = $this->db->insert_id();
$qry = "insert into content_ratings (content_id, comment_count, company_count, contact_count, report_count, read_count) VALUES ".
"($content_id, '0', '0', 0, '0', '0')";
$result2 = $this->db->query($qry);
$this->db->trans_complete();
if($this->db->trans_status() == TRUE) {
$cache_source_array[$link] = $content_id;
echo "Good!<br />";
} else {
echo "Bad!<br />";
}
} else {
// link alread exists
echo "link exists!";
}
}
}
} else {
// feed is empty
}
}
}
I think you answered your own question:
Currently, every time the script runs, it goes to
$this->set_content_source_cache where it returns an array of
array('link', 'link', 'link', etc.) from all the rows in the table.
This is used to later cross-reference to make sure there are no
duplicating links. Would not doing this and simply changing the DB so
the link column is unique speed things up?
Yes, creating a primary key or unique index and allowing the DB to throw an error if there is a duplicate is a much better practice and should be much more efficient.
REFERENCE EDIT:
mysql 5.0 indexes - Unique vs Non Unique
http://dev.mysql.com/doc/refman/5.0/en/create-index.html
Related
My question is pretty straight-forward, and I assume that people need to do this pretty often; yet, after hours of searching online, nothing especially enlightening has come up. I am aware of a thread on here about doing the same thing using PDO, but I want to stick to mysqli to be consistent across my whole site.
So, I need to move one or several rows from one database to another (and NOT from one table to another within a single database).
The code I have so far is sure to get some laughs, as this task proved to be considerably beyond my ability. Anyway, here goes:
To begin with, I have two mysqli_connect... not sure if that is encouraged, although it has not caused me problems so far.
$connect_MAIN = mysqli_connect($servername_MAIN, $username_MAIN, $password_MAIN, $dbname_MAIN);
$connect_TEMP = mysqli_connect($servername_TEMP, $username_TEMP, $password_TEMP, $dbname_TEMP);
Here is the meat in the sandwich in terms of code:
///Prepare this in advance because the 'IN' values are taken from a $_GET
$sql_select = "SELECT * FROM Concert WHERE id IN (1, 2, 3, 4)";
///Connect to source DB
$result = $connect_TEMP->query($sql_select);
if ($result->num_rows != 0) {
///Connect to destination DB
$stmt = $connect_MAIN->prepare('INSERT INTO Concert (venue_id, date, ensemble_id, info, title, repertoire, time) VALUES (?, ?, ?, ?, ?, ?, ?)');
$venue_id = $date = $ensemble_id = $info = $title = $repertoire = $time = null;
$stmt->bind_param("isissss", $venue_id, $date, $ensemble_id, $info, $title, $repertoire, $time);
while($row = $result->fetch_assoc()) {
$venue_id = $row["venue_id"];
$date = $row["date"];
$ensemble_id = $row["ensemble_id"];
$info = $row["info"];
$title = $row["title"];
$repertoire = $row["repertoire"];
$time = $row["time"];
if ($stmt->execute() === TRUE) {
if ($show_once == 1) {
echo "Info successfully submitted.";
$show_once = 0;
}
} else {
echo "Hmm, something went wrong..." . $connect_MAIN->error;
}
}
}
$stmt->close();
mysqli_close($connect_TEMP);
}
So, I know that this is a bit of a jumble... as I said, I am not sure if it is possible to connect to DB2 in the middle of a while loop returning values from DB1. Feel free to mock, but only if you have something useful to suggest!
Use a parametrized query instead of concatenating strings.
///Prepare this in advance because the 'IN' values are taken from a $_GET
$sql_select = "SELECT * FROM Concert WHERE id IN (1, 2, 3, 4)";
///Connect to source DB
$result = $connect_TEMP->query($sql_select);
if ($result->num_rows != 0) {
///Connect to destination DB
$stmt = $connect_MAIN->prepare('INSERT INTO Concert (venue, date, info, time) VALUES (?, ?, ?, ?)');
$venue = $date = $info = $time = null;
$stmt->bind_param("ssss", $venue, $date, $info, $time);
while($row = $result->fetch_assoc()) {
$venue = $row["venue"];
$date = $row["date"];
$info = $row["info"];
$time = $row["time"];
if ($stmt->execute()) {
///This is just to stop message from appearing multiple times.
if ($show_once == 1) {
echo "Info successfully submitted.";
$show_once = 0;
}
} else {
echo "Hmm, something went wrong..." . $stmt->error;
}
}
$stmt->close();
mysqli_close($connect_TEMP);
}
Yes, you may establish a second DB connection at any point in your script. There's nothing preventing it nor there's a reason to discourage it. It's far more common than you'd think (for different reasons). However, considering the overhead that actually connecting to the database carries, I would advice you to not establish a connection on each iteration (this is the best practice, performance-wise)
What I would do:
First, connect to the source database, get the data you need
Not much to say here, you already did and it looks OK. Check, as you did, that there's at least one result before moving on. If the resultset is empty, don't go any further (exit)
If there was at least one row in the resultset, establish a connection to the destination database
Do this before starting to loop to avoid the performance penalty of establishing multiple connections. If you're looking at hundreds/thousands or more rows, you'll really notice the difference. Once the connection is established, move on
Loop through the source data and prepare the insert statements
You have a choice here. Either a) loop through the whole resultset and prepare one single insert with multiple rows which you'll insert all at once at the end of the loop or b) create a single insert (single row) on each iteration, run the insert and then move on to the next iteration.
There's reasons for and against both strategies. Choose the one that suits you better.
In algorythmical terms, your code is pretty much ready (just connect to the destination DB once before you start looping). You may benefit from using a parameterized query with bindings instead of writing the full insert string, but other than that you're pretty much there.
If you are running your own MySql server, you may do this in a simpler way using the Federated engine.
The federated table behaves like a normal table, but it stores the data in a remote database. With this engine you can do someting like this:
INSERT INTO remoteTable (values) SELECT * FROM localTable
In a nutshell, my code seems to be looping twice as much as it should (writing four rows when it should be writing two rows). This should be an easy solution but I'm not having any luck.
Here is my php loop . . . must be one very simple yet invisible thing that no one has yet been able to locate why this baby is not working:
//query statement before the for loop
$stmt="INSERT INTO o70vm_invoices_invoices
(`id`, `created_by`, `user_id`, `added`, `to_name`, `to_address`, `invoice_num`, `real_invoice_num`, `from_name`, `from_address`, `from_num`, `invoice_date`, `publish`, `notes`, `template_id`, `taxes`, `start_publish`, `end_publish`, `currency_before`, `currency_after`, `status`, `to_email`, `to_company`, `from_phone`, `from_url`, `from_email`, `discount`, `invoice_duedate`, `admin_notes`, `to_city`, `to_state`, `to_country`, `to_vatid`, `to_zipcode`, `rec_year`, `rec_month`, `rec_day`, `rec_nextdate`, `is_recurrent`) VALUES ";
// loop through number of invoices user selected to create
for($x = 0; $x < $invoiceCount; $x++)
{
// add the user identified days to each invoice
$date->modify("+7 days");
$invoiceDateNew = $date->format ('Y-m-d 00:00:00');
$invoiceDueDateNew = $date->format ('Y-m-d H:m:s');
$startPubNew = $date->format ('Y-m-d 00:00:00');
// getting the values per row
$ValuesAddToQuery[] ="(NULL, '792', '$userID', '$todayDate', '$parentName', 'unknown address', '0000', '0000', '', '', '', '".$invoiceDateNew."', '1', '', '2', '', '".$startPubNew."', '0000-00-00 00:00:00', '$', '', '', '$email', '$childName', '', '', '', '0.00', '".$invoiceDueDateNew."', '', '', '', '', '', '', '0', '0', '0', '0000-00-00', '0')";
}
$stmt .= implode(',',$ValuesAddToQuery);
mysql_query($stmt) or exit(mysql_error());
I store the number of invoices as:
$invoiceCount
I have echoed out the value of $invoiceCount and the value is always the same value as the user inputs. IE, user selects 2 invoices to create, displays 2 invoices in the variable, yet creates 4 invoices in the MySQL table.
Stranger more: When I check for the rows affected with:
mysql_affected_rows()
It returns the user-selected number of invoices / rows (not the actual rows I can see are added in the MySQL Table). For example, it will say "2" rows have been affected when four rows have been added.
Even more wild . . . when I echo out the MySQL query:
echo $stmt;
my query also shows just two rows have been added when the user selected two rows to add but the code wrote 4 actual rows.
Adventurous, I even tried to slice the array to see if I could alter the code that is sent:
//implode the values into the statement
$stmt .= implode(',',$ValuesAddToQuery);
//limit the length of the array
array_slice($ValuesAddToQuery,0,2);
mysql_query($stmt) or exit(mysql_error());
And, you guessed it, it changes absolutely nothing. I put the array_slice on top of the implode statement. Again, no change in the 4 rows inputted when I only want 2 rows.
The more I look at this, I can't tell in this code why it is doubling.
Any help, much appreciated.
For detailed explanation of some of my input fields and what I'm doing, follow below:
To start, I am letting the user select how many rows to copy and update the invoice date as required. I am getting the values of FREQUENCY (7 days, 14 days, or 30 days) of recurring invoices and the DURATION (number of invoices to create/copy) using these input fields:
<select name="freqOfInvoices">
<option value="7">Weekly</option>
<option value="14">Bi-Weekly</option>
<option value="30">Monthly</option>
</select>
<input type="number" title="numberOfInvoices" name="numberOfInvoices" size="2" id="numberOfInvoices" value="numberOfInvoices" />
I have similar input fields for the three dates I'm looking to ADD x number of days to:
// assigning variables
$freqOfInvoices = htmlentities($_POST['freqOfInvoices'], ENT_QUOTES);
$numberOfInvoices = htmlentities($_POST['numberOfInvoices'], ENT_QUOTES);
$invoiceDate = htmlentities($_POST['invoice_date'], ENT_QUOTES);
$invoiceDueDate = htmlentities($_POST['invoice_duedate'], ENT_QUOTES);
$startPub = htmlentities($_POST['start_publish'], ENT_QUOTES);
//assigning number of invoices
$countInvoices=$numberOfInvoices;
It seems you may only need 1 loop to construct the values.
//query statement before the foreach loop
$stmt="INSERT INTO o70vm_invoices_invoices (`id`, `.....`, etc) VALUES ";
$ValuesAddToQuery = [];
for($x = 0; $x < $arrayLength; $x++) {
// add the user identified days to the date
$date->modify("+7 days");
$invoiceDateNew = $date->format ('Y-m-d 00:00:00');
$ValuesAddToQuery[]="(NULL, '....', ".$invoiceDateNew.")";
}
$stmt .= implode(',',$ValuesAddToQuery);
mysql_query($stmt) or exit(mysql_error());
If you echo $stmt does the query string look correct or are your values getting doubled?
I'm answering my own question as I figured out a workaround that "actually" works. I'm keeping this question online for others as if I can save them four days of work, it is my pleasure.
Because I could not figure out why my code was creating twice as many invoices as the user requested, I simply added this code:
//////////////////////////
// Work around CODE as it keeps on doubling the records in DB
// Deleting the same amount of last entries as there are valid entries
//////////////////////////
$RecordsToDelete=$invoiceCount;
$DeleteQuery="DELETE FROM o70vm_invoices_invoices ORDER BY id DESC limit $RecordsToDelete";
mysql_query($DeleteQuery) or exit(mysql_error());
immediately after my original implode / execute query code:
$stmt .= implode(',',$ValuesAddToQuery);
mysql_query($stmt) or exit(mysql_error());
The code works because my "infected" code was writing the full series of invoices (with the user selected dates) once, and then doing the same series again. So, that translated into the first set of invoices (2) to be correct and the last invoices (2) to be duplicates. So, presto, just delete from the last entries the count of your invoices.
I admit, it is a workaround. Hopefully someday I will figure out why my original code produced duplicates. But, happy day regardless. :)
I have a bunch of photos on a page and using jQuery UI's Sortable plugin, to allow for them to be reordered.
When my sortable function fires, it writes a new order sequence:
1030:0,1031:1,1032:2,1040:3,1033:4
Each item of the comma delimited string, consists of the photo ID and the order position, separated by a colon. When the user has completely finished their reordering, I'm posting this order sequence to a PHP page via AJAX, to store the changes in the database. Here's where I get into trouble.
I have no problem getting my script to work, but I'm pretty sure it's the incorrect way to achieve what I want, and will suffer hugely in performance and resources - I'm hoping somebody could advise me as to what would be the best approach.
This is my PHP script that deals with the sequence:
if ($sorted_order) {
$exploded_order = explode(',',$sorted_order);
foreach ($exploded_order as $order_part) {
$exploded_part = explode(':',$order_part);
$part_count = 0;
foreach ($exploded_part as $part) {
$part_count++;
if ($part_count == 1) {
$photo_id = $part;
} elseif ($part_count == 2) {
$order = $part;
}
$SQL = "UPDATE article_photos ";
$SQL .= "SET order_pos = :order_pos ";
$SQL .= "WHERE photo_id = :photo_id;";
... rest of PDO stuff ...
}
}
}
My concerns arise from the nested foreach functions and also running so many database updates. If a given sequence contained 150 items, would this script cry for help? If it will, how could I improve it?
** This is for an admin page, so it won't be heavily abused **
you can use one update, with some cleaver code like so:
create the array $data['order'] in the loop then:
$q = "UPDATE article_photos SET order_pos = (CASE photo_id ";
foreach($data['order'] as $sort => $id){
$q .= " WHEN {$id} THEN {$sort}";
}
$q .= " END ) WHERE photo_id IN (".implode(",",$data['order']).")";
a little clearer perhaps
UPDATE article_photos SET order_pos = (CASE photo_id
WHEN id = 1 THEN 999
WHEN id = 2 THEN 1000
WHEN id = 3 THEN 1001
END)
WHERE photo_id IN (1,2,3)
i use this approach for exactly what your doing, updating sort orders
No need for the second foreach: you know it's going to be two parts if your data passes validation (I'm assuming you validated this. If not: you should =) so just do:
if (count($exploded_part) == 2) {
$id = $exploded_part[0];
$seq = $exploded_part[1];
/* rest of code */
} else {
/* error - data does not conform despite validation */
}
As for update hammering: do your DB updates in a transaction. Your db will queue the ops, but not commit them to the main DB until you commit the transaction, at which point it'll happily do the update "for real" at lightning speed.
I suggest making your script even simplier and changing names of the variables, so the code would be way more readable.
$parts = explode(',',$sorted_order);
foreach ($parts as $part) {
list($id, $position) = explode(':',$order_part);
//Now you can work with $id and $position ;
}
More info about list: http://php.net/manual/en/function.list.php
Also, about performance and your data structure:
The way you store your data is not perfect. But that way you will not suffer any performance issues, that way you need to send less data, less overhead overall.
However the drawback of your data structure is that most probably you will be unable to establish relationships between tables and make joins or alter table structure in a correct way.
I have spent many hours debugging, and scouring the internet for a solution to this unusual problem. Heres the deal:
I am working on a Work Order Submission and Tracking system. There are two databases involved:
The database where the submissions data gets posted, which is located on the same physical machine, but on a separate virtual machine as the webserver serving the php. They are on the same class C subnet.
The database of our tracking system. Located on a different physical server on a different IP altogether, also a virtual machine.
Our work order system allows for multiple 'services requested', stored in an array. In our sumbissions database, this is stored as a comma separated string, i.e. "40,60,70" but in our tracking system database, each 'service requested' needs a separate entry, as to allow the different aspects of the project to be tracked and completed at different times, by different staff.
THE PROBLEM IS: When I place my second insert statement, the one destined for the tracking database, in a for loop, it completely hangs, and takes maybe 5 to 15 minutes, before it passes that point in the code, and sends the confirmation email. The data does not get inserted either.
When I take it out of the for loop and simply do one insert in the submissions database and one insert into the tracking system, it works fine.
First, Ill post the code that works, but only posts one 'service' to the tracking system:
public function insertOrder()
{
$services = implode( ',', $this->model->chk );
$curdate = $this->model->getMySQLDate( $this->model->curdate );
$dueDate = $this->model->getMySQLDate( $this->model->dueDate );
$sql = "INSERT INTO orders VALUES(DEFAULT,
{$this->sanitize($services)},
{$this->sanitize($curdate)},
{$this->sanitize($this->model->submittedBy)},
{$this->sanitize($this->model->shortDesc)},
{$this->sanitize($this->model->projDetails)},
{$this->sanitize($dueDate)},
{$this->sanitize($this->model->dueDateNotes)},
{$this->sanitize( $this->model->approveBy)},
{$this->sanitize( $this->model->cost )} )";
$this->execute( $sql );
$this->convertServicesToTracks();
$notes = $this->model->getTracksNotes();
$dueDate = $dueDate.' 12:00:00';
$shortDescNoQuotes = str_replace("\"","'",$this->model->shortDesc);
$sqlTracks = "INSERT INTO todos VALUES(DEFAULT,
{$this->sanitizeTracks($this->model->chk[0])},
NULL,
{$this->sanitizeTracks($shortDescNoQuotes)},
{$this->sanitizeTracks($notes)},
now(),
{$this->sanitizeTracks($dueDate)},
NULL,
12,
NULL,
'active',
NULL,
now() );";
//echo $sqlTracks;
$this->executeTracks( $sqlTacks );
} private function executeTracks( $sql )
{
$db = $this->getTracksDB( );
$this->check4Error( $db, $sql );
return $result;
}
private function getTracksDB()
{
if (!$this->tracksdb) $this->tracksdb = new mysqli(AbstractSQL::TRACKS_HOST, AbstractSQL::USER, AbstractSQL::PASS, AbstractSQL::TRACKS_SCHEMA);
return $this->tracksdb;
}
private function convertServicesToTracks()
{
//converts submission data to tracking system data
}
private function sanitizeTracks($arg)
{
if (!isset($arg)) return "NULL";
if (is_numeric($arg) && !is_double( $arg) ) return $arg;
return "'{$this->getTracksDB()->escape_string($arg)}'";
}
When I add this simple for loop around the second INSERT statement, it hangs, even if the array only has one item!
for($i = 0; $i < count($this->model->chk); ++$i)
{
$sqlTracks = "INSERT INTO todos VALUES(DEFAULT,
{$this->sanitizeTracks($this->model->chk[$i])},
NULL,
{$this->sanitizeTracks($shortDescNoQuotes)},
{$this->sanitizeTracks($notes)},
now(),
{$this->sanitizeTracks($dueDate)},
NULL,
12,
NULL,
'active',
NULL,
now() );";
//echo $sqlTracks;
$this->executeTracks( $sqlTacks );
}
Any help would be greatly appreciated. And I apologize for the long code snippets!!
Is it iterating through the for loop? I see you have an echo, did that write anything out? How many items does it have to iterate through? 5 min seems like a long time but if there are a lot of items that could be why it's taking so long. Are you seeing any errors in your logs?
Something you might try is hold the count in a variable so it doesn't have to calculate that each time. It might speed up your for loop but I'm not sure it will insert the data.
for($i = 0, $count = count($this->model->chk); $i < $count; ++$i)
{
$sqlTracks = "INSERT INTO todos VALUES(DEFAULT,
{$this->sanitizeTracks($this->model->chk[$i])},
NULL,
{$this->sanitizeTracks($shortDescNoQuotes)},
{$this->sanitizeTracks($notes)},
now(),
{$this->sanitizeTracks($dueDate)},
NULL,
12,
NULL,
'active',
NULL,
now() );";
//echo $sqlTracks;
$this->executeTracks( $sqlTacks );
}
I found this in the PHP for loop reference: http://php.net/manual/en/control-structures.for.php
Well, this may not be the problem, but shouldn't you generally use a foreach loop to avoid hitting parts of the array that may not exist? There is more about this here. If you loop through an empty index, it would break your SQL statement. Like this:
foreach($this->model->chk as $val)
I have a table in MySQL with "text", "date_posted", and "user". I currently query all text from user=Andy, and call those questions. All of the other text fields from other users are answers to the most recent question.
What I want is to associate those answers with the most recent question, with a loop similar to "for each text where user=Andy, find the text where user!=Andy until date>the next user=Andy (question)"
This seems awfully contrived, and I'm wondering if it can be done roughly as I've outlined, or if I can save myself some trouble in how I'm storing the data or something.
Thanks for any advice.
EDIT: I've added in the insert queries I've been using.
$url = "http://search.twitter.com/search.json?q=&ands=&phrase=&ors=¬s=RT%2C+%40&tag=andyasks&lang=all&from=amcafee&to=&ref=&near=&within=1000&units=mi&since=&until=&tude%5B%5D=%3F&rpp=50)";
$contents = file_get_contents($url);
$decode = json_decode($contents, true);
foreach($decode['results'] as $current) {
$query = "INSERT IGNORE INTO andyasks (questions, date, user) VALUES ('$current[text]','$current[created_at]','Andy')";
mysql_query($query);
}
$url2 = "http://search.twitter.com/search.json?q=&ands=&phrase=&ors=¬s=RT&tag=andyasks&lang=all&from=&to=amcafee&ref=&near=&within=15&units=mi&since=&until=&rpp=50";
$contents2 = file_get_contents($url2);
$decode2 = json_decode($contents2, true);
foreach($decode2['results'] as $current2) {
$query2 = "INSERT IGNORE INTO andyasks (questions, date, user) VALUES ('$current2[text]','$current2[created_at]','$current2[from_user]')";
mysql_query($query2);
}
And then on the SELECT side, this is where I am currently:
$results = mysql_query("SELECT * FROM andyasks");
$answers = mysql_query("SELECT * FROM andyasks WHERE 'user' != 'Andy'");
while($row = mysql_fetch_array($results))
{
if ($row['user'] == 'Andy') {
print(preg_replace($pattern, $replace, "<p>".$row["questions"]."</p>"));
}
}
while($row = mysql_fetch_array($answers))
{
print(preg_replace('/#amcafee/', '', "<p>".$row["questions"]."</p>"));
}
What you have in mind could, I believe, be done with subtle use of JOIN or nested SELECT, ORDER BY, LIMIT, etc, but, as you surmise, it would be "awfully contrived" and likely pretty slow.
As you suspect, you would save yourself a lot of trouble at SELECT time if you added a column to the table, which, for answers, has the primary key of the question they're answering (that could be easily obtained at INSERT time, since it's the latest entry with user equal Alex). Then the retrieval would be easier!
If you can alter your schema this way, but need help with the SQL, pls comment or edit your answer to indicate that and I'll be happy to follow up (similarly, I'd be happy to follow up if you're stuck with this schema and need the "awfully contrived" SQL -- I just don't know which of the two possibilities applies!-).
Edit: since the schema's changed, the INSERT could be (using form :name to indicate parameters you should bind):
INSERT IGNORE INTO andyasks
(questions, date, user, answering)
SELECT :text, :created_at, :from_user,
IF(:from_user='Andy', NULL, aa.id)
FROM andyasks AS aa
WHERE user='Andy'
ORDER BY date DESC
LIMIT 1
i.e.: use INSERT INTO ... SELECT' to do a query-within-insertion, which picks the latest post by Andy. I'm assuming you do also have a primary keyid` that's auto-increment, which is the normal arrangement of things.
Later to get all answers to a given question, you only need to select rows whose answering attribute equals that question's id.
If I understand you correctly you want something like:
$myArr = array("bob","joe","jennifer","mary");
while ($something = next($myArr)) {
if ($nextone = next($myArr)) {
//do Something
prev($myArr)
}
}
see http://jp2.php.net/next as well as the sections on prev, reset and current