如何简化这个复杂的查询?

时间:2013-04-30 20:17:50

标签: php mysql mysqli subquery

我有一个我正在处理的媒体中心网站,其中有一个显示媒体类别的页面。每个类别可以分配多个媒体项目,每个媒体项目可以分配到多个类别。

该页面包含一个文本输入,必须能够过滤显示的类别。

我需要做的是获取与当前用户($user_id)相关联的每个类别以及属于该用户的媒体项目(未显示未使用的类别)。通常情况下这很简单,但我还必须能够根据与媒体相关的其他表中的字段过滤类别。

我需要能够应用文本过滤器的字段如下:

    {li} message_numbermedia表中 {li} keywordsmedia表中 {li} speaker_namemedia_speakers表中 {li} series_namemedia_series表中 {li} book_namemedia_books表中 {li} category_namemedia_categories表中

现在,查询需要几秒钟才能完成。我不是MySQL专家,所以我确信必须有更好的方法来做我需要做的事情。如果它有帮助,我通过PHP使用MySQLi。我的查询有几个子查询,我肯定是导致问题的原因,但我不知道其他任何方法可以做我想做的事。

以下是相关的表结构和当前查询。我已经包含尽可能多的信息,可以帮助我帮助我,但如果您需要更多信息,请告诉我。

media表(省略一些不相关的字段)(系列,扬声器和书籍字段包含相应表格中记录的ID):

  `id` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `date` date NOT NULL DEFAULT '0000-00-00',
  `message_number` varchar(32) DEFAULT NULL,
  `series` int(10) unsigned zerofill NOT NULL DEFAULT '0000000000',
  `speaker` int(10) unsigned zerofill NOT NULL DEFAULT '0000000000',
  `book` int(10) unsigned zerofill NOT NULL DEFAULT '0000000000',
  `keywords` text NOT NULL,
  PRIMARY KEY (`id`)

media_series表:

  `id` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `series_name` varchar(255) NOT NULL DEFAULT '',
  `cover` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)

media_speakers表:

  `id` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `speaker_name` varchar(255) NOT NULL DEFAULT '',
  `cover` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)

media_books表:

  `id` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,
  `book_name` varchar(64) NOT NULL DEFAULT '',
  `book_shortname` varchar(10) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)

media_categories表:

  `id` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `category_name` varchar(255) NOT NULL DEFAULT '',
  `cover` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `id` (`id`)

media_categories_assoc表:

  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(10) unsigned DEFAULT NULL,
  `media_id` int(10) unsigned zerofill DEFAULT NULL,
  `category_id` int(10) unsigned zerofill DEFAULT NULL,
  `marked_for_deletion` int(1) DEFAULT '0',
  PRIMARY KEY (`id`)

最后,过于复杂的查询:

 SELECT media_categories.id            `media_categories.id`,
       media_categories.user_id       `media_categories.user_id`,
       media_categories.category_name `media_categories.category_name`,
       media_categories.cover         `media_categories.cover`,
       (SELECT id
        FROM   media
        WHERE  user_id = '$user_id'
               AND media_categories.id IN (SELECT category_id
                                           FROM   media_category_assoc
                                           WHERE  user_id = '$user_id')
        ORDER  BY `date` DESC
        LIMIT  1)                     `media.id`,
       (SELECT `date`
        FROM   media
        WHERE  user_id = '$user_id'
               AND media_categories.id IN (SELECT category_id
                                           FROM   media_category_assoc
                                           WHERE  user_id = '$user_id')
        ORDER  BY `date` DESC
        LIMIT  1)                     `media.date`,
       (SELECT series
        FROM   media
        WHERE  user_id = '$user_id'
               AND media_categories.id IN (SELECT category_id
                                           FROM   media_category_assoc
                                           WHERE  user_id = '$user_id')
        ORDER  BY `date` DESC
        LIMIT  1)                     `media.series`,
       (SELECT speaker
        FROM   media
        WHERE  user_id = '$user_id'
               AND media_categories.id IN (SELECT category_id
                                           FROM   media_category_assoc
                                           WHERE  user_id = '$user_id')
        ORDER  BY `date` DESC
        LIMIT  1)                     `media.speaker`
FROM   media_categories
       LEFT JOIN media
              ON media.id IN (SELECT media_id
                              FROM   media_category_assoc
                              WHERE  media_id = media.id
                                     AND user_id = '$user_id')
       LEFT JOIN media_series
              ON media.series = media_series.id
       LEFT JOIN media_speakers
              ON media.speaker = media_speakers.id
       LEFT JOIN media_books
              ON media.book = media_books.id
WHERE  media_categories.user_id = '$user_id'
       AND media_categories.id IN (SELECT category_id
                                   FROM   media_category_assoc
                                   WHERE  user_id = '$user_id')
       AND ( media.title LIKE '%filter_text%'
              OR media.message_number LIKE '%filter_text%'
              OR media.keywords LIKE '%filter_text%'
              OR media_speakers.speaker_name LIKE '%filter_text%'
              OR media_categories.category_name LIKE '%filter_text%'
              OR media_series.series_name LIKE '%filter_text%'
              OR media_books.book_name LIKE '%filter_text%' )
GROUP  BY `media_categories.id`
ORDER  BY `media.date` DESC
LIMIT  0, 12;  

2 个答案:

答案 0 :(得分:1)

正如我在评论中提到的,子查询可能是查询中的瓶颈。首先,在查询上运行explain select...以检查执行计划。

参见参考手册:

现在,关于我使用临时表的建议,我将以你的第一个子查询为例。

你用这个:

SELECT 
...,
(SELECT id
    FROM   media
    WHERE  user_id = '$user_id'
           AND media_categories.id IN (SELECT category_id
                                       FROM   media_category_assoc
                                       WHERE  user_id = '$user_id')
    ORDER  BY `date` DESC
    LIMIT  1),
....

你可以这样做:

drop table if exists temp_step1;
create temporary table temp_step1
    select id
    from media
    where user_id = @user_id -- I'm assuming you are putting this in a stored procedure
        and media_categories.id in (SELECT category_id 
                                    FROM media_category_assoc
                                    WHERE  user_id = @user_id)
    order by `date` desc
    limit 1;

然后,您可以将此temp_step1表用作大查询的行源。

请注意,此示例仅返回一行,因此对此进行索引没有意义。对于那些包含多行并且在查询的FROM ... JOIN ...子句中使用的临时表,您需要在所有正在执行的字段上创建索引至少加入。为此,在创建临时表(例如temp_step_X)后,您应该这样做:

alter table temp_step_X
   add index idx_indexName(field1),
   ...;

希望这有助于你

答案 1 :(得分:1)

如果我能够正确理解查询,您会尝试从给定用户的每个类别的最新媒体中获取一些额外信息。据我所知,SELECT子句中的所有子查询都可以移动到FROM子句。

也许这样做会有所帮助?

SELECT media_categories.id,
       media_categories.user_id,
       media_categories.category_name,
       media_categories.cover,
       newest_media.id,
       newest_media.'date',
       newest_media.series,
       newest_media.speaker
FROM   media_categories
        LEFT JOIN media_category_assoc
              ON media_categories.id = media_category_assoc.category_id AND media_categories.user_id = media_category_assoc.user_id
        LEFT JOIN (
                    SELECT id, 'date', series, speaker
                    FROM   media
                    WHERE  media.id = media_category_assoc.media_id
                    ORDER  BY `date` DESC
                    LIMIT  1
                ) newest_media ON newest_media.user_id = '$user_id'
       LEFT JOIN media_series
              ON newest_media.series = media_series.id
       LEFT JOIN media_speakers
              ON newest_media.speaker = media_speakers.id
       LEFT JOIN media_books
              ON newest_media.book = media_books.id
       LEFT JOIN media
              ON media.id = media_category_assoc.media_id AND media.user_id = '$user_id'
WHERE  media_categories.user_id = '$user_id'
       AND ( media.title LIKE '%filter_text%'
              OR media.message_number LIKE '%filter_text%'
              OR media.keywords LIKE '%filter_text%'
              OR media_speakers.speaker_name LIKE '%filter_text%'
              OR media_categories.category_name LIKE '%filter_text%'
              OR media_series.series_name LIKE '%filter_text%'
              OR media_books.book_name LIKE '%filter_text%' )
GROUP  BY `media_categories.id`
ORDER  BY `media.date` DESC
LIMIT  0, 12;