Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
478 views
in Technique[技术] by (71.8m points)

mysql - Fetching a Single Row from Join Table

Here are my tables:

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_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)
) ENGINE=InnoDB;

Every article can have one or more images associated with it. I'd like to display the last 40 written articles on a page, along with the most recent image associated with the article. What I can't figure out is how to join with the article_images table, and only retrieve a single row.

Edit: It's important that the solution performs well. The solutions I've seen so far -- which use derived tables -- take a minute or more to complete.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...