我有一个我正在处理的媒体中心网站,其中有一个显示媒体类别的页面。每个类别可以分配多个媒体项目,每个媒体项目可以分配到多个类别。
该页面包含一个文本输入,必须能够过滤显示的类别。
我需要做的是获取与当前用户($user_id
)相关联的每个类别以及属于该用户的媒体项目(未显示未使用的类别)。通常情况下这很简单,但我还必须能够根据与媒体相关的其他表中的字段过滤类别。
我需要能够应用文本过滤器的字段如下:
message_number
在media
表中
{li} keywords
在media
表中
{li} speaker_name
在media_speakers
表中
{li} series_name
在media_series
表中
{li} book_name
在media_books
表中
{li} category_name
在media_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;
答案 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;