After looking over the other answers, it helped me realize an underlying problem.
The articles table and article_images table both need to have an additional index.
This is what you have now:
CREATE TABLE `articles` (
`id` int(10) unsigned not null auto_increment,
`author_id` int(10) unsigned not null,
`date_created` datetime not null,
PRIMARY KEY(id)
) ENGINE=InnoDB;
CREATE TABLE `article_images` (
`article_id` int(10) unsigned not null,
`filename` varchar(100) not null,
`date_added` datetime not null,
UNIQUE INDEX(article_id, filename)
) ENGINE=InnoDB;
This is what you need:
CREATE TABLE `articles` (
`id` int(10) unsigned not null auto_increment,
`author_id` int(10) unsigned not null,
`date_created` datetime not null,
PRIMARY KEY(id),
INDEX (date_created DESC)
) ENGINE=InnoDB;
CREATE TABLE `article_images` (
`article_id` int(10) unsigned not null,
`filename` varchar(100) not null,
`date_added` datetime not null,
UNIQUE INDEX (article_id, filename),
INDEX (article_id, date_added),
) ENGINE=InnoDB;
articles
The new index for ordering the articles by insertion date in descending order
article_images
The first index is still needed since it will guard against attaching the another image with the same filename to an article.
The second index will make finding the most recent image simple,
With those new indexes in place, here is the query that will do your bidding:
SELECT
AAA.author_id,
AAA.date_created,
IFNULL(BBB.title,'<NO_TITLE>') title,
IFNULL(CCC.filename,'<NO-IMAGE>') filename
FROM
(
SELECT
AA.id,
AA.date_added,
BB.author_id,
BB.date_created
FROM
(
SELECT
A.id,IFNULL(MAX(B.date_added),'1900-01-01 00:00:00') date_added
FROM (SELECT id FROM articles ORDER BY date_created DESC LIMIT 40) A
LEFT JOIN article_images B ON A.id = B.article_id
GROUP BY A.id
) AA
INNER JOIN articles BB USING (id)
) AAA
LEFT JOIN article_contents BBB ON AAA.id=BBB.article_id
LEFT JOIN article_images CCC
ON (AAA.id=CCC.article_id AND AAA.date_added=CCC.date_added)
ORDER BY AAA.date_created DESC;
The goal is first to create an internal subquery that only has 40 rows. That is what AA should bring back. Subquery AAA should have the article info plus the date the last image was inserted. The last set of joins should connect the 40 ids with a title and an image. The final step is to present the result set in descensding order.
Since I do not have sample data, I wrote three stored procedures in a sample database called stuff
1 to make the tables
DELIMITER $$
DROP PROCEDURE IF EXISTS `stuff`.`MakeTables` $$
CREATE PROCEDURE `stuff`.`MakeTables` ()
BEGIN
DROP TABLE IF EXISTS articles;
DROP TABLE IF EXISTS article_contents;
DROP TABLE IF EXISTS article_images;
CREATE TABLE `articles` (
`id` int(10) unsigned not null auto_increment,
`author_id` int(10) unsigned not null,
`date_created` datetime not null,
PRIMARY KEY(id),
INDEX (date_created DESC)
) ENGINE=InnoDB;
CREATE TABLE `article_contents` (
`article_id` int(10) unsigned not null,
`title` varchar(100) not null,
`content` text not null,
PRIMARY KEY(article_id)) ENGINE=InnoDB;
CREATE TABLE `article_images` (
`article_id` int(10) unsigned not null,
`filename` varchar(100) not null,
`date_added` datetime not null,
UNIQUE INDEX (article_id, filename),
INDEX (article_id, date_added)
) ENGINE=InnoDB;
END $$
DELIMITER ;
1 to make the data
DELIMITER $$
DROP PROCEDURE IF EXISTS `stuff`.`LoadSampleData` $$
CREATE PROCEDURE `stuff`.`LoadSampleData` ()
BEGIN
DECLARE x,y,z INT;
SET x = 1;
WHILE x <= 100 DO
INSERT INTO articles (author_id,date_created) VALUES
(RAND() * POWER(2,31),
DATE('1970-01-01 00:00:00') + INTERVAL (RAND() * POWER(2,30)) SECOND);
SET x = x + 1;
END WHILE;
SET x = 1;
WHILE x <= 100 DO
SET y = FLOOR(RAND() * 100);
IF y >= 30 THEN
INSERT INTO article_contents
VALUES (x,
CONCAT('TITLE_',FLOOR(RAND() * POWER(2,31))),
CONCAT('CONTENT_',FLOOR(RAND() * POWER(2,31))));
END IF;
SET x = x + 1;
END WHILE;
SET x = 1;
WHILE x <= 100 DO
SELECT COUNT(1) INTO y FROM article_contents WHERE article_id = x;
IF y = 1 THEN
SET y = 0;
WHILE y < 20 DO
SET y = y + 1;
SET z = FLOOR(RAND() * 10);
IF z >= 5 THEN
INSERT INTO article_images
VALUES (x,
CONCAT('IMAGE_',FLOOR(RAND() * POWER(2,31))),
DATE('1970-01-01 00:00:00') + INTERVAL (RAND() * POWER(2,30)) SECOND);
END IF;
END WHILE;
END IF;
SET x = x + 1;
END WHILE;
SELECT COUNT(1) INTO x FROM articles;
SELECT COUNT(1) INTO y FROM article_contents;
SELECT COUNT(1) INTO z FROM article_images;
SELECT CONCAT('Articles:',x,' Titles:',y,' Images: ',z) Results;
END $$
DELIMITER ;
1 to run the query
DELIMITER $$
DROP PROCEDURE IF EXISTS `stuff`.`ShowLast40` $$
CREATE PROCEDURE `stuff`.`ShowLast40` ()
BEGIN
SELECT
AAA.author_id,
AAA.date_created,
IFNULL(BBB.title,'<NO_TITLE>') title,
IFNULL(CCC.filename,'<NO-IMAGE>') filename
FROM
(
SELECT
AA.id,
AA.date_added,
BB.author_id,
BB.date_created
FROM
(
SELECT
A.id,IFNULL(MAX(B.date_added),'1900-01-01 00:00:00') date_added
FROM (SELECT id FROM articles ORDER BY date_created DESC LIMIT 40) A
LEFT JOIN article_images B ON A.id = B.article_id
GROUP BY A.id
) AA
INNER JOIN articles BB USING (id)
) AAA
LEFT JOIN article_contents BBB ON AAA.id=BBB.article_id
LEFT JOIN article_images CCC
ON (AAA.id=CCC.article_id AND AAA.date_added=CCC.date_added)
ORDER BY AAA.date_created DESC;
END $$
DELIMITER ;
Here is an example output when I ran it on my Windows Desktop (MySQL 5.5.12):
mysql> call maketables; call loadsampledata;
Query OK, 0 rows affected (0.31 sec)
+------------------------------------+
| Results |
+------------------------------------+
| Articles:100 Titles:67 Images: 666 |
+------------------------------------+
1 row in set (23.73 sec)
mysql> call showlast40;
+------------+---------------------+------------------+------------------+
| author_id | date_created | title | filename |
+------------+---------------------+------------------+------------------+
| 1576679519 | 2004-01-02 14:05:17 | TITLE_1757853515 | IMAGE_1601858163 |
| 992840519 | 2003-05-17 05:48:11 | TITLE_319026350 | IMAGE_1913708631 |
| 1366977197 | 2003-03-20 19:37:23 | <NO_TITLE> | <NO-IMAGE> |
| 1705517382 | 2003-02-07 16:48:56 | <NO_TITLE> | <NO-IMAGE> |
| 1529899841 | 2002-11-02 20:59:26 | <NO_TITLE> | <NO-IMAGE> |
| 1756824942 | 2002-08-28 16:01:59 | <NO_TITLE> | <NO-IMAGE> |
| 175825630 | 2002-05-08 13:48:56 | TITLE_240812804 | IMAGE_1214850809 |
| 757530551 | 2002-02-08 00:20:17 | TITLE_4447486 | IMAGE_1511850161 |
| 840251261 | 2002-01-25 20:06:56 | TITLE_1160842143 | IMAGE_206027488 |
| 964653347 | 2001-12-26 19:15:47 | TITLE_1552408257 | IMAGE_717719932 |
| 2106039126 | 2001-11-11 17:09:29 | <NO_TITLE> | <NO-IMAGE> |
| 2085991608 | 2001-08-01 12:48:20 | <NO_TITLE> | <NO-IMAGE> |
| 1895462094 | 2000-12-02 05:31:41 | <NO_TITLE> | <NO-IMAGE> |
| 1589384729 | 2000-04-28 23:55:50 | TITLE_1040850308 | IMAGE_1200414639 |
| 514341550 | 2000-04-20 07:25:05 | TITLE_188288840 | IMAGE_164856430 |
| 887359583 | 2000-02-13 03:30:47 | <NO_TITLE> | <NO-IMAGE> |
| 1156687499 | 1999-06-16 00:29:17 | TITLE_686398996 | IMAGE_670200418 |
| 1561242593 | 1998-12-08 05:50:17 | <NO_TITLE> | <NO-IMAGE> |
| 1117889993 | 1998-10-23 17:02:44 | TITLE_1491217221 | IMAGE_649630126 |
| 740063135 | 1998-09-16 23:52:41 | TITLE_579374776 | IMAGE_757313192 |
| 429699232 | 1998-04-19 01:41:17 | TITLE_73748980 | IMAGE_1881818111 |
| 1827051060 | 1998-02-27 01:01:50 | TITLE_1588619991 | IMAGE_1657322715 |
| 1442984429 | 1997-11-19 21:23:35 | TITLE_184173382 | IMAGE_597809368 |
| 152267158 | 1997-05-02 20:25:50 | <NO_TITLE> | <NO-IMAGE> |
| 1323598169 | 1997-03-14 16:30:38 | TITLE_1355869397 | IMAGE_1058313818 |
| 66122740 | 1997-01-05 15:12:20 | TITLE_1259073183 | IMAGE_198280936 |
| 5161474 | 1996-06-28 10:47:26 | TITLE_1876022823 | IMAGE_1138098675 |
| 1865082792 | 1996-03-01 19:09:11 | TITLE_1288151615 | IMAGE_245974646 |
| 1923481146 | 1995-08-07 00:36:11 | TITLE_922744000 | IMAGE_2067090321 |
| 1725218958 | 1995-03-18 05:15:29 | TITLE_583120586 | IMAGE_592773824 |
| 117806248 | 1995-01-05 02:34:32 | <NO_TITLE> | <NO-IMAGE> |
| 1428777335 | 1993-06-06 01:52:32 | TITLE_661148588 | IMAGE_633345518 |
| 1091245943 | 1993-06-05 05:51:47 | TITLE_1407444563 | IMAGE_538936256 |
| 2088382260 | 1993-03-25 06:03:29 | TITLE_1144364681 | IMAGE_1790013089 |
| 625878569 | 1992-12-21 07:41:26 | TITLE_1319355723 | IMAGE_921580624 |
| 110555110 | 1992-01-01 20:49:59 | <NO_TITLE> | <NO-IMAGE> |
| 1110532475 | 1991-11-20 07:19:32 | <NO_TITLE> | <NO-IMAGE> |
| 1726795146 | 1990-10-09 00:23:44 | TITLE_782624350 | IMAGE_1760322575 |
| 370183888 | 1990-03-30 15:59:17 | <NO_TITLE> | <NO-IMAGE> |
| 1497483317 | 1990-02-19 01:25:41 | TITLE_776483948 | IMAGE_665824222 |
+------------+---------------------+------------------+------------------+
40 rows in set (0.00 sec)
Give it a Try !!!
UPDATE
I made absolutely sure that the 40 datetimes you are reading are in fact the top 40. They are. I ran the query : select * from articles ORDER BY date_created DESC; to make sure.
UPDATE 2011-05-17 14:06
mysql> call maketables; call loadsampledata;
Query OK, 0 rows affected (0.45 sec)
+-------------------------------------+
| Results |
+-------------------------------------+
| Articles:100 Titles:67 Images: 6739 |
+-------------------------------------+
1 row in set (3 min 45.45 sec)
Query OK, 0 rows affected (3 min 45.45 sec)
mysql> call showlast40;
+------------+---------------------+------------------+------------------+
| author_id | date_created | title | filename |
+------------+---------------------+------------------+------------------+
| 196582776 | 2004-01-05 14:09:04 | <NO_TITLE> | <NO-IMAGE> |
| 1880371016 | 2003-07-31 05:50:37 | TITLE_1191518827 | IMAGE_1562208019 |
| 22204986 | 2003-02-16 14:09:22 | <NO_TITLE> | <NO-IMAGE> |
| 355490160 | 2002-11-21 02:35:19 | <NO_TITLE> | <NO-IMAGE> |
| 869510149 | 2001-12-27 22:07:52 | TITLE_1528616779 | IMAGE_223327284 |
| 2063556512 | 2001-04-16 18:47:46 | TITLE_1839975091 | IMAGE_1282187005 |
| 529754190 | 2000-07-14 19:44:01 | TITL