0

假设您在周日下午坐在家里,并且想从您的数据库中了解班级排名最低的学生。假设您的数据库表如下所示:

+----------------+-----------+------------+------------+----------------------------+------+-------+
| uid            | last_name | first_name | dob        | email                      | rank | grade |
+----------------+-----------+------------+------------+----------------------------+------+-------+
| 13428700000001 | Smith     | John       | 1990-12-03 | ...@gmail.com              |   99 |     4 |
| 13428721960000 | Li        | Kai Li     | 1979-02-15 | ...@miryclay.com           |   12 |     2 |
| 13428722180001 | Zhang     | Xi Xiong   | 1993-11-09 | ...@163.com                |    5 |     5 |
| 13428739950000 | Zhou      | Ji Hai     | 1991-06-06 | ...@msn.com                |  234 |     1 |
| 13428739950001 | Pan       | Yao        | 1992-05-12 | ...@email.com              |   43 |     2 |
| 13428740010001 | Jin       | Denny      | 1994-06-02 | ...@yahoo.com              |  198 |     3 |
| 13428740010002 | Li        | Fonzie     | 1991-02-02 | ...@hotmail.com            |   75 |     3 |
| 13428743370000 | Ma        | Haggar     | 1991-08-16 | ...@haggars.com            |   47 |     4 |
| 13428743590001 | Ren       | Jenny      | 1990-03-29 | ...@email.com              |    5 |     2 |
| 13428774040000 | Chen      | Dragon     | 1999-04-12 | ...@aol.com                |   23 |     5 |
| 13428774260001 | Wang      | Doctor     | 1996-09-30 | ...@yahoo.com              |    1 |     5 |
| 13430100000000 | Chanz     | Heyvery    | 1994-04-04 | ...@gmail.com              |  107 |     2 |
+----------------+-----------+------------+------------+----------------------------+------+-------+

我可以通过 a 来做到这一点,SELECT * FROM students WHERE rank = (SELECT MAX(rank) FROM students);但这是最简单、最常见的方法吗?

4

1 回答 1

4

这种方式也不少见:

SELECT s1.*
FROM students s1
LEFT JOIN students s2 ON s1.rank < s2.rank
WHERE s2.uid IS NULL;

LEFT JOIN 的工作原理是,当 s1.rank 处于最大值时,没有 s2.rank 具有更大的值,并且 s2 行的值将为 NULL。

但我想说的是,你的做法是最常见、最容易理解的做法,是的。

编辑:关于为什么有时速度较慢的问题:

此查询的性能取决于“编写的仔细程度”。以您的数据为例:

drop table if exists students;
CREATE TABLE students
    (`uid` bigint, `last_name` varchar(5), `first_name` varchar(8), `dob` varchar(10), `email` varchar(16), `rank` int, `grade` int)
;

INSERT INTO students
    (`uid`, `last_name`, `first_name`, `dob`, `email`, `rank`, `grade`)
VALUES
    (13428700000001, 'Smith', 'John', '1990-12-03', '...@gmail.com', 99, 4),
    (13428721960000, 'Li', 'Kai Li', '1979-02-15', '...@miryclay.com', 12, 2),
    (13428722180001, 'Zhang', 'Xi Xiong', '1993-11-09', '...@163.com', 5, 5),
    (13428739950000, 'Zhou', 'Ji Hai', '1991-06-06', '...@msn.com', 234, 1),
    (13428739950001, 'Pan', 'Yao', '1992-05-12', '...@email.com', 43, 2),
    (13428740010001, 'Jin', 'Denny', '1994-06-02', '...@yahoo.com', 198, 3),
    (13428740010002, 'Li', 'Fonzie', '1991-02-02', '...@hotmail.com', 75, 3),
    (13428743370000, 'Ma', 'Haggar', '1991-08-16', '...@haggars.com', 47, 4),
    (13428743590001, 'Ren', 'Jenny', '1990-03-29', '...@email.com', 5, 2),
    (13428774040000, 'Chen', 'Dragon', '1999-04-12', '...@aol.com', 23, 5),
    (13428774260001, 'Wang', 'Doctor', '1996-09-30', '...@yahoo.com', 1, 5),
    (13430100000000, 'Chanz', 'Heyvery', '1994-04-04', '...@gmail.com', 107, 2)
;

您的查询解释如下所示:

| ID | SELECT_TYPE |    TABLE | TYPE | POSSIBLE_KEYS |    KEY | KEY_LEN |    REF | ROWS |       EXTRA |
-------------------------------------------------------------------------------------------------------
|  1 |     PRIMARY | students |  ALL |        (null) | (null) |  (null) | (null) |   12 | Using where |
|  2 |    SUBQUERY | students |  ALL |        (null) | (null) |  (null) | (null) |   12 |             |

我的查询中的一个是这样的:

| ID | SELECT_TYPE | TABLE | TYPE | POSSIBLE_KEYS |    KEY | KEY_LEN |    REF | ROWS |       EXTRA |
----------------------------------------------------------------------------------------------------
|  1 |      SIMPLE |    s1 |  ALL |        (null) | (null) |  (null) | (null) |   12 |             |
|  1 |      SIMPLE |    s2 |  ALL |        (null) | (null) |  (null) | (null) |   12 | Using where |

差不多一样。两个查询都不使用索引,所有行都被扫描。现在我们在 column 上添加一个索引rank

drop table if exists students;
CREATE TABLE students
    (`uid` bigint, `last_name` varchar(5), `first_name` varchar(8), `dob` varchar(10), `email` varchar(16), `rank` int, `grade` int
    , key rankkey(rank)
    )
;

您查询的解释:

| ID | SELECT_TYPE |    TABLE |   TYPE | POSSIBLE_KEYS |     KEY | KEY_LEN |    REF |   ROWS |                        EXTRA |
-----------------------------------------------------------------------------------------------------------------------------
|  1 |     PRIMARY | students |    ref |       rankkey | rankkey |       5 |  const |      1 |                  Using where |
|  2 |    SUBQUERY |   (null) | (null) |        (null) |  (null) |  (null) | (null) | (null) | Select tables optimized away |

与我的相比:

| ID | SELECT_TYPE | TABLE | TYPE | POSSIBLE_KEYS |    KEY | KEY_LEN |    REF | ROWS |       EXTRA |
----------------------------------------------------------------------------------------------------
|  1 |      SIMPLE |    s1 |  ALL |        (null) | (null) |  (null) | (null) |   12 |             |
|  1 |      SIMPLE |    s2 |  ALL |       rankkey | (null) |  (null) | (null) |   12 | Using where |

您的查询使用索引,我的没有。

现在我们正在向表中添加一个主键。

drop table if exists students;
CREATE TABLE students
    (`uid` bigint, `last_name` varchar(5), `first_name` varchar(8), `dob` varchar(10), `email` varchar(16), `rank` int, `grade` int
    , key rankkey(rank)
    , primary key(uid)
    );

从您的查询中解释:

| ID | SELECT_TYPE |    TABLE |   TYPE | POSSIBLE_KEYS |     KEY | KEY_LEN |    REF |   ROWS |                        EXTRA |
-----------------------------------------------------------------------------------------------------------------------------
|  1 |     PRIMARY | students |    ref |       rankkey | rankkey |       5 |  const |      1 |                  Using where |
|  2 |    SUBQUERY |   (null) | (null) |        (null) |  (null) |  (null) | (null) | (null) | Select tables optimized away |

从我的:

| ID | SELECT_TYPE | TABLE |  TYPE | POSSIBLE_KEYS |     KEY | KEY_LEN |    REF | ROWS |                                EXTRA |
-------------------------------------------------------------------------------------------------------------------------------
|  1 |      SIMPLE |    s1 |   ALL |        (null) |  (null) |  (null) | (null) |   12 |                                      |
|  1 |      SIMPLE |    s2 | index |       rankkey | rankkey |       5 | (null) |   12 | Using where; Using index; Not exists |

这样,它们很可能同样快。这就是通常构建查询和表的方式。每个表都应该有一个主键,如果你经常在排名列上运行查询过滤,你当然应该有一个索引。所以几乎没有区别。现在这一切都取决于您的表中有多少行,如果它是唯一索引和/或聚集索引。但这会导致现在有点过分了。但请注意,在此示例中,检查的行数有所不同。小数据没有区别,大数据量肯定有。但是(!)这两个查询的行为可能会改变,具体取决于索引。

如果编写查询的人犯了错误怎么办?如果他这样写怎么办:

SELECT s1.*
FROM students s1
LEFT JOIN students s2 ON s1.rank < s2.rank
WHERE s2.last_name IS NULL;

查询仍然有效并且有效,但是

| ID | SELECT_TYPE | TABLE | TYPE | POSSIBLE_KEYS |    KEY | KEY_LEN |    REF | ROWS |       EXTRA |
----------------------------------------------------------------------------------------------------
|  1 |      SIMPLE |    s1 |  ALL |        (null) | (null) |  (null) | (null) |   12 |             |
|  1 |      SIMPLE |    s2 |  ALL |       rankkey | (null) |  (null) | (null) |   12 | Using where |

再次不使用索引。

如果我们再次删除主键并像这样编写查询会怎样:

SELECT s1.*
FROM students s1
LEFT JOIN students s2 ON s1.rank < s2.rank
WHERE s2.rank IS NULL;

| ID | SELECT_TYPE | TABLE |  TYPE | POSSIBLE_KEYS |     KEY | KEY_LEN |    REF | ROWS |                    EXTRA |
-------------------------------------------------------------------------------------------------------------------
|  1 |      SIMPLE |    s1 |   ALL |        (null) |  (null) |  (null) | (null) |   12 |                          |
|  1 |      SIMPLE |    s2 | index |       rankkey | rankkey |       5 | (null) |   12 | Using where; Using index |

再次使用索引。

结论:如果操作正确,两个查询的运行速度应该一样快。只要索引在排名列上,您的速度就会很快。如果在编写时考虑到索引,这同样适用于我的。

希望这可以帮助。

于 2013-06-28T08:59:56.937 回答