【问题标题】:MySQL - Getting results on a range by column valueMySQL - 按列值获取范围内的结果
【发布时间】:2026-02-14 21:45:02
【问题描述】:

解决方案 B

因为我接受了@Strawberry 的回答,并且这个解决方案不属于帖子本身的问题,所以我不会把它写成答案,而是把它留给任何可能有用的人。

主要问题是查询性能和结构问题。事实上,我正在搜索所有 slides 的所有属性,然后限制结果使它变得如此缓慢。解决方案是首先获取我想要获取的幻灯片,然后从较短的结果集中搜索所有额外信息。

因此,最终查询(运行时间不到 0.2 秒并获得我希望的所有结果)如下:

SELECT 
    sl.slide_id AS slide_id,        
    sl.time_in AS time_in,
    sl.time_out AS time_out, 
    sl.duration AS duration, 
    sl.slide_order AS slide_order,
    sl.title AS title,
    sl.slide_type AS slide_type, 
    sl.report_id AS report_id, 
    sltg.tag_category_id AS tag_category_id,
    sltg.tag_value_id AS tag_value_id,
    sltgc.txt AS tag_category_text,
    sltgv.txt AS tag_value_text,
FROM (SELECT 
        sl.time_in AS time_in,
        sl.time_out AS time_out, 
        sl.duration AS duration, 
        sl.slide_order AS slide_order,
        sl.media_id AS media_id,
        sl.title AS title,
        sl.slide_type AS slide_type, 
        slrep.report_id AS report_id, 
        slrep.slide_id AS slide_id
    FROM er_slides sl 
    INNER JOIN er_slides_in_report slrep 
        ON slrep.slide_id = sl.unique_id 
            AND slrep.report_id IN (1461317308472,1461597566425,1461598458236)  
            AND slrep.deleted_date IS NULL 
    LIMIT 0, 5
) sl  
INNER JOIN er_slides_tags sltg 
    ON sltg.deleted_date IS NULL 
        AND sltg.slide_id = sl.slide_id 
INNER JOIN er_slide_tags_categories sltgc
    ON sltgc.id = sltg.tag_category_id 
INNER JOIN er_slide_tags_values sltgv
    ON sltgv.id = sltg.tag_value_id 
ORDER BY slide_id, tag_value_id;

如您所见,我在搜索其他内容之前限制了集合。之后,查询运行得非常快。我希望这种方法可以帮助人们改进他们自己的查询,以我原来的查询为例,说明你不能做的事情。

原来的问题

我有一个表格,其中包含一些元素(幻灯片),这些元素(幻灯片)被分组在一些容器(报告)上,每个元素都有一些属性(标签值)和分配给它们的类别(标签类别)。这就像一个标记系统,其中元素 X 可以具有来自类别 Z 的标记 Y。

数据库结构

CREATE TABLE IF NOT EXISTS `er_reports` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `unique_id` varchar(64) COLLATE utf8_unicode_ci NOT NULL DEFAULT '0',
  `name` varchar(256) COLLATE utf8_unicode_ci NOT NULL,
  `user_id` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
  `author` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
  `report_status` varchar(32) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'incomplete',
  `num_slides` int(4) NOT NULL DEFAULT '0',
  `report_type` varchar(25) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'report',
  `target_id` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `created_date` datetime DEFAULT NULL,
  `uploaded_date` datetime DEFAULT NULL,
  `deleted_date` datetime DEFAULT NULL,
  `modified_date` datetime DEFAULT NULL,
  `item_reference` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `is_favourite` tinyint(4) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_id` (`unique_id`),
  KEY `unique_id_2` (`unique_id`),
  KEY `unique_id_3` (`unique_id`),
  KEY `user_id` (`user_id`),
  KEY `deleted_date` (`deleted_date`),
  KEY `is_favourite` (`is_favourite`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=714 ;


CREATE TABLE IF NOT EXISTS `er_slides` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `unique_id` varchar(64) COLLATE utf8_unicode_ci NOT NULL DEFAULT '0',
  `report_id` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `action_id` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `media_id` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `thumbnail_id` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `media_offset` int(6) NOT NULL DEFAULT '0',
  `time_in` decimal(9,3) DEFAULT NULL,
  `time_out` decimal(9,3) DEFAULT NULL,
  `duration` decimal(9,3) NOT NULL DEFAULT '10.000',
  `title` text COLLATE utf8_unicode_ci,
  `slide_comment` text COLLATE utf8_unicode_ci,
  `note_id` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `content` text COLLATE utf8_unicode_ci,
  `media_object` text COLLATE utf8_unicode_ci,
  `slide_type` varchar(32) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'action',
  `user_id` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
  `slide_order` int(4) NOT NULL DEFAULT '0',
  `count_slide` tinyint(1) NOT NULL DEFAULT '1',
  `visible` tinyint(1) NOT NULL DEFAULT '1',
  `deleted_date` datetime DEFAULT NULL,
  `created_date` datetime DEFAULT NULL,
  `uploaded_date` datetime DEFAULT NULL,
  `modified_date` datetime DEFAULT NULL,
  `item_reference` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `unique_id` (`unique_id`),
  KEY `report_id` (`report_id`),
  KEY `deleted_date` (`deleted_date`),
  KEY `action_id` (`action_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=31899 ;


CREATE TABLE IF NOT EXISTS `er_slides_in_report` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `report_id` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
  `slide_id` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
  `slide_order` int(3) NOT NULL DEFAULT '1',
  `added_date` datetime DEFAULT NULL,
  `modified_date` datetime DEFAULT NULL,
  `deleted_date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `report_id` (`report_id`,`slide_id`,`deleted_date`),
  KEY `slide_order` (`slide_order`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=16843 ;


CREATE TABLE IF NOT EXISTS `er_slides_tags` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `slide_id` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
  `tag_category_id` bigint(20) NOT NULL,
  `tag_value_id` bigint(20) NOT NULL,
  `created_date` datetime NOT NULL,
  `deleted_date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `slide_id` (`slide_id`,`tag_category_id`,`tag_value_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=220623 ;    

CREATE TABLE IF NOT EXISTS `er_slide_tags_categories` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `txt` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  KEY `txt` (`txt`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=17 ;


CREATE TABLE IF NOT EXISTS `er_slide_tags_values` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `txt` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  KEY `txt` (`txt`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=439 ;

我尝试过的

SELECT @slide_num := if(@sl_id = slrep.slide_id collate utf8_unicode_ci, @slide_num, @slide_num := @slide_num+1) as slide_num,
    @sl_id := slrep.slide_id collate utf8_unicode_ci as sl_id,
    slrep.report_id AS report_id, 
    slrep.slide_id AS slide_id,
    sltg.tag_category_id AS tag_category_id,
    sltg.tag_value_id AS tag_value_id,
    sltgc.txt AS tag_category_text,
    sltgv.txt AS tag_value_text,
    sl.time_in AS time_in,
    sl.time_out AS time_out, 
    sl.duration AS duration, 
    sl.slide_order AS slide_order,
    sl.title AS title,
    sl.slide_type AS slide_type 
    FROM (SELECT @sl_id:=1, @slide_num := 0) v, er_slides sl
    INNER JOIN er_slides_in_report slrep 
        ON slrep.slide_id = sl.unique_id 
            AND slrep.report_id IN (1461317308472,1461597566425,1461598458236) 
            AND slrep.deleted_date IS NULL 
    INNER JOIN er_slides_tags sltg 
        ON sltg.deleted_date IS NULL 
            AND sltg.slide_id = sl.unique_id 
    INNER JOIN er_slide_tags_categories sltgc
        ON sltgc.id = sltg.tag_category_id 
    INNER JOIN er_slide_tags_values sltgv
        ON sltgv.id = sltg.tag_value_id 
    WHERE @slide_num >= 0 AND @slide_num <= 5
    ORDER BY slide_id, tag_value_id;

我已经放了一些虚假的报告 ID 供您查看查询是如何构建的。

问题

问题在于这还不够快 - 获取 5 张幻灯片、200 行的信息大约需要 3 秒 - 而且我无法修改 from 限制。如果我写:

WHERE @slide_num &gt;= 10 AND @slide_num &lt;= 15

我得到一个空结果(当然,我已经检查过有足够的幻灯片)。

我也不明白为什么需要 3 秒才能获得 200 行。

我需要什么

我需要能够以最快的方式仅查询所选范围之间的幻灯片,这是动态的。

如果您发现缺少什么,请评论是什么,以便我发布。

谢谢。

编辑:解释查询(草莓方法)

正如@strawberry 建议的那样,我尝试应用他的方法。但是,查询的响应时间与写入 BETWEEN 0 AND 5 的范围与写入 BETWEEN 0 AND 200 的响应时间相同(两者都大约 17 秒)。

这可能是因为索引错误,我决定在这里写EXPLAIN,因为我看不到任何错误索引(WHERE 子句中的每个条件都有其索引)。

【问题讨论】:

  • 完成了——至少,我试过了。谢谢!
  • 是的,那是因为我删除了一些问题不需要的查询额外信息。现在已经解决了。谢谢!
  • 事实上,我需要获取标签而不仅仅是幻灯片,一张幻灯片可以有多个标签,导致每张幻灯片多行。所以,LIMIT 0, 5 会限制行数,这意味着可能只有 1 张带有 5 个标签的幻灯片,而我想要的是获得前 5 张幻灯片的所有标签。

标签: mysql query-optimization limit query-variables


【解决方案1】:

考虑这个简化的例子...

DROP TABLE IF EXISTS slides;

CREATE TABLE slides 
(slide_id INT NOT NULL);

INSERT INTO slides VALUES
(1),
(2),
(4),
(5),
(6),
(7);

DROP TABLE IF EXISTS slides_tags;

CREATE TABLE slides_tags
(slide_id INT NOT NULL
,tag_id INT NOT NULL
,PRIMARY KEY(slide_id,tag_id)
);

INSERT INTO slides_tags VALUES
(1,101),
(1,103),
(1,105),
(1,107),
(2,102),
(2,104),
(2,106),
(2,108),
(4,105),
(4,110),
(5,101);

SELECT slide_id
     , tag_id 
     , i
  FROM 
     ( SELECT s.*
            , st.tag_id
            , CASE WHEN @prev = s.slide_id THEN @i:=@i ELSE @i:=@i+1 END i
            , @prev:=s.slide_id 
         FROM slides s 
         LEFT 
         JOIN slides_tags st 
           ON st.slide_id = s.slide_id 
         JOIN 
            ( SELECT @prev:=null,@i:=0) vars 
        ORDER 
           BY s.slide_id
     ) x 
 WHERE i BETWEEN 3 AND 5;
+----------+--------+------+
| slide_id | tag_id | i    |
+----------+--------+------+
|        4 |    105 |    3 |
|        4 |    110 |    3 |
|        5 |    101 |    4 |
|        6 |   NULL |    5 |
+----------+--------+------+

为了清楚起见,我在结果中包含了i 列。当然,如果不需要,请从超级查询中省略。

编辑:

看来您可以按如下方式重写此查询,但老实说,我不明白为什么会这样:

SELECT s.slide_id 
     , st.tag_id
     , CASE WHEN @prev = s.slide_id THEN @i:=@i ELSE @i:=@i+1 END i
     , @prev:=s.slide_id 
  FROM (SELECT @i:=0, @prev := 0) vars
  JOIN slides s
  LEFT
  JOIN slides_tags st
    ON st.slide_id = s.slide_id
 HAVING i BETWEEN 3 AND 5
 ORDER 
    BY slide_id
     , tag_id;

+----------+--------+------+-------------------+
| slide_id | tag_id | i    | @prev:=s.slide_id |
+----------+--------+------+-------------------+
|        4 |    105 |    3 |                 4 |
|        4 |    110 |    3 |                 4 |
|        5 |    101 |    4 |                 5 |
|        6 |   NULL |    5 |                 6 |
+----------+--------+------+-------------------+

【讨论】:

  • 感谢您的回答!这就是我以前的样子。问题——或者至少,我认为是这样——是作为一个子查询,它获取整个数据库,因为x 表上没有WHERE 子句。因此,它给出了正确的结果,但它需要在没有WHERE 的情况下查询整个数据库(在我的测试中,这样做会持续 70 秒或更长时间,只返回一小部分幻灯片)。
  • 这听起来不对。在一个适当索引的表上,这个查询应该非常快。
  • 那么,有一点我无法理解。当我在问题上发布查询时,我在 3 秒内得到了正确的结果。如果我按照您的建议进行相同的查询(使用相同的BETWEEN),那么我会在 17 秒内得到相同的结果。如果我将BETWEEN 值更改为更短或更大的值,我在响应时间上没有任何差异。我认为它的索引很好,但无论如何,无论我写什么范围,我都会得到相同的时间,这表明它每次都在做同样的工作:获取整个数据库。不是吗?我怎样才能看到它?
  • 是的,我得到了正确的结果,只是我无法编辑 BETWEEN 范围。我得到一个空的结果。而且,虽然 3 秒似乎是个好时机,但我发现 5 张幻灯片的响应太高了,所以我也问了 the fastest way possible
  • 对不起,也许我解释得不好。我将您制作子查询SELECT * FROM (SELECT ....) x WHERE i BETWEEN 3 AND 5; 的方法应用于我的查询。正如我之前评论的那样,我得到了正确的结果,但是我使用BETWEEN 3 AND 5BETWEEN 0 AND 200 得到了相同的 17 秒时间,所以它没有过滤子查询上的结果,这与查询整个数据库相同每一次。感谢您的回答!
最近更新 更多