MySQL教程之:常见查询示例

下面举例说明如何用MySQL解决一些常见问题。

一些示例使用表shop来保存某些交易者(经销商)的每件商品的价格(商品编号)。假设每个交易者每件商品都有一个固定的价格,那么(articledealer)是记录的主键。

启动命令行工具mysql并选择一个数据库:

sql 复制代码
$> mysql your-database-name

要创建和填充示例表,请使用以下语句:

sql 复制代码
CREATE TABLE shop (
    article INT UNSIGNED  DEFAULT '0000' NOT NULL,
    dealer  CHAR(20)      DEFAULT ''     NOT NULL,
    price   DECIMAL(16,2) DEFAULT '0.00' NOT NULL,
    PRIMARY KEY(article, dealer));
INSERT INTO shop VALUES
    (1,'A',3.45),(1,'B',3.99),(2,'A',10.99),(3,'B',1.45),
    (3,'C',1.69),(3,'D',1.25),(4,'D',19.95);

发出SQL后,查询结果应具有以下内容:

sql 复制代码
SELECT * FROM shop ORDER BY article;

+---------+--------+-------+
| article | dealer | price |
+---------+--------+-------+
|       1 | A      |  3.45 |
|       1 | B      |  3.99 |
|       2 | A      | 10.99 |
|       3 | B      |  1.45 |
|       3 | C      |  1.69 |
|       3 | D      |  1.25 |
|       4 | D      | 19.95 |
+---------+--------+-------+

列的最大值

"最高的货号是多少?"

sql 复制代码
SELECT MAX(article) AS article FROM shop;

+---------+
| article |
+---------+
|       4 |
+---------+

保持某列最大值的行

任务:找出最贵商品的数量、经销商和价格。

这可以通过子查询轻松完成:

sql 复制代码
SELECT article, dealer, price
FROM   shop
WHERE  price=(SELECT MAX(price) FROM shop);

+---------+--------+-------+
| article | dealer | price |
+---------+--------+-------+
|    0004 | D      | 19.95 |
+---------+--------+-------+

另一种解决方案是使用LEFT JOIN,如下所示:

sql 复制代码
SELECT s1.article, s1.dealer, s1.price
FROM shop s1
LEFT JOIN shop s2 ON s1.price < s2.price
WHERE s2.article IS NULL;

您还可以通过按价格降序对所有行进行排序,并使用MySQL特定的LIMIT子句仅获取第一行,如下所示:

sql 复制代码
SELECT article, dealer, price
FROM shop
ORDER BY price DESC
LIMIT 1;

如果有几个最贵的物品,每个价格为19.95英镑,LIMIT解决方案将只显示其中一个。

每个组的最大列数

任务:找到每件物品的最高价格。

sql 复制代码
SELECT article, MAX(price) AS price
FROM   shop
GROUP BY article
ORDER BY article;

+---------+-------+
| article | price |
+---------+-------+
|    0001 |  3.99 |
|    0002 | 10.99 |
|    0003 |  1.69 |
|    0004 | 19.95 |
+---------+-------+

保持某个列的组最大值的行

任务:对于每件物品,找到价格最贵的一个或多个经销商。

这个问题可以通过像这样的子查询来解决:

sql 复制代码
SELECT article, dealer, price
FROM   shop s1
WHERE  price=(SELECT MAX(s2.price)
              FROM shop s2
              WHERE s1.article = s2.article)
ORDER BY article;

+---------+--------+-------+
| article | dealer | price |
+---------+--------+-------+
|    0001 | B      |  3.99 |
|    0002 | A      | 10.99 |
|    0003 | C      |  1.69 |
|    0004 | D      | 19.95 |
+---------+--------+-------+

前面的示例使用了相关子查询,这可能效率很低(参见"相关子查询")。解决这个问题的其他可能性是在FROM子句中使用不相关的子查询、LEFT JOIN或带有窗口函数的公共表表达式。

不相关子查询:

sql 复制代码
SELECT s1.article, dealer, s1.price
FROM shop s1
JOIN (
  SELECT article, MAX(price) AS price
  FROM shop
  GROUP BY article) AS s2
  ON s1.article = s2.article AND s1.price = s2.price
ORDER BY article;

LEFT JOIN

sql 复制代码
SELECT s1.article, s1.dealer, s1.price
FROM shop s1
LEFT JOIN shop s2 ON s1.article = s2.article AND s1.price < s2.price
WHERE s2.article IS NULL
ORDER BY s1.article;

LEFT JOIN的基础上,当s1.price处于最大值时,没有更大的s2.price值,因此相应的s2.articleNULL

带有窗口函数的公用表表达式:

sql 复制代码
WITH s1 AS (
   SELECT article, dealer, price,
          RANK() OVER (PARTITION BY article
                           ORDER BY price DESC
                      ) AS `Rank`
     FROM shop
)
SELECT article, dealer, price
  FROM s1
  WHERE `Rank` = 1
ORDER BY article;

使用用户定义变量

您可以使用MySQL用户变量来记住结果,而不必将它们存储在客户端的临时变量中。

例如,要找到价格最高和最低的物品,您可以这样做:

sql 复制代码
mysql> SELECT @min_price:=MIN(price),@max_price:=MAX(price) FROM shop;
mysql> SELECT * FROM shop WHERE price=@min_price OR price=@max_price;
+---------+--------+-------+
| article | dealer | price |
+---------+--------+-------+
|    0003 | D      |  1.25 |
|    0004 | D      | 19.95 |
+---------+--------+-------+

也可以将数据库对象(如表或列)的名称存储在用户变量中,然后在SQL语句中使用该变量;但是,这需要使用准备好的语句。有关详细信息,请参阅,"准备好的语句"。

使用外键

MySQL支持允许跨表交叉引用相关数据的外键和有助于保持相关数据一致的外键约束。

外键关系包括保存初始列值的父表和引用父列值的列值的子表。在子表上定义了外键约束。

下面的示例通过单列外键关联parent表和child,并显示外键约束如何强制引用完整性。

使用以下SQL语句创建父表和子表:

sql 复制代码
CREATE TABLE parent (
    id INT NOT NULL,
    PRIMARY KEY (id)
) ENGINE=INNODB;


CREATE TABLE child (
    id INT,
    parent_id INT,
    INDEX par_ind (parent_id),
    FOREIGN KEY (parent_id)
        REFERENCES parent(id)
) ENGINE=INNODB;

在父表中插入一行,如下所示:

sql 复制代码
mysql> INSERT INTO parent (id) VALUES ROW(1);

验证数据是否已插入。您只需选择parent中的所有行即可执行此操作,如下所示:

sql 复制代码
mysql> TABLE parent;
+----+
| id |
+----+
|  1 |
+----+

使用以下SQL语句在子表中插入一行:

sql 复制代码
mysql> INSERT INTO child (id,parent_id) VALUES ROW(1,1);

插入操作成功,因为父表中存在parent_id1。

向子表中插入父表中不存在的具有parent_id值的行会被拒绝并出现错误,如下所示:

sql 复制代码
mysql> INSERT INTO child (id,parent_id) VALUES ROW(2,2);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails 
(`test`.`child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) 
REFERENCES `parent` (`id`))

操作失败,因为指定的parent_id值在父表中不存在。

尝试从父表中删除先前插入的行也会失败,如下所示:

sql 复制代码
mysql> DELETE FROM parent WHERE id VALUES = 1;
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails 
(`test`.`child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) 
REFERENCES `parent` (`id`))

此操作失败,因为子表中的记录包含引用的id(parent_id)值。

当操作影响父表中与子表中的行匹配的键值时,结果取决于FOREIGN KEY子句的ON UPDATEON DELETE子句指定的引用操作。省略ON DELETEON UPDATE子句(如当前子表定义)与指定RESTRICT选项相同,该选项拒绝影响父表中与父表中的行匹配的键值的操作。

要演示ON DELETEON UPDATE引用操作,请删除子表并重新创建子表,使其包含带有CASCADE选项的ON UPDATEON DELETE子句。CASCADE选项在删除或更新父表中的行时自动删除或更新子表中匹配的行。

sql 复制代码
DROP TABLE child;

CREATE TABLE child (
    id INT,
    parent_id INT,
    INDEX par_ind (parent_id),
    FOREIGN KEY (parent_id)
        REFERENCES parent(id)
        ON UPDATE CASCADE
        ON DELETE CASCADE
) ENGINE=INNODB;

使用此处显示的语句将一些行插入子表:

sql 复制代码
mysql> INSERT INTO child (id,parent_id) VALUES ROW(1,1), ROW(2,1), ROW(3,1);

验证数据是否已插入,如下所示:

sql 复制代码
mysql> TABLE child;
+------+-----------+
| id   | parent_id |
+------+-----------+
|    1 |         1 |
|    2 |         1 |
|    3 |         1 |
+------+-----------+

使用此处显示的SQL语句更新父表中的ID,将其从1更改为2:

sql 复制代码
mysql> UPDATE parent SET id = 2 WHERE id = 1;

通过从父表中选择所有行来验证更新是否成功,如下所示:

sql 复制代码
mysql> TABLE parent;
+----+
| id |
+----+
|  2 |
+----+

验证ON UPDATE CASCADE引用操作是否更新了子表,如下所示:

sql 复制代码
mysql> TABLE child;
+------+-----------+
| id   | parent_id |
+------+-----------+
|    1 |         2 |
|    2 |         2 |
|    3 |         2 |
+------+-----------+

要演示ON DELETE CASCADE引用操作,请从parent_id = 2的父表中删除记录;这将删除父表中的所有记录。

sql 复制代码
mysql> DELETE FROM parent WHERE id = 2;

因为子表中的所有记录都与parent_id = 2相关联,所以ON DELETE CASCADE引用操作从子表中删除所有记录,如下所示:

sql 复制代码
mysql> TABLE child;
Empty set (0.00 sec)

双键搜索

使用单个键的OR得到了很好的优化,AND的处理也是如此。

一个棘手的情况是在两个不同的键上结合OR进行搜索:

sql 复制代码
SELECT field1_index, field2_index FROM test_table
WHERE field1_index = '1' OR  field2_index = '1'

此案例已优化。参见,"索引合并最优化"。

您还可以通过使用组合两个单独SELECT输出的UNION有效地解决问题。参见,"UNION子句"。

每个SELECT只搜索一个键,可以优化:

sql 复制代码
SELECT field1_index, field2_index
    FROM test_table WHERE field1_index = '1'
UNION
SELECT field1_index, field2_index
    FROM test_table WHERE field2_index = '1';

计算每天的访问量

以下示例显示了如何使用位图函数来计算用户每月访问网页的天数。

sql 复制代码
CREATE TABLE t1 (year YEAR, month INT UNSIGNED,
             day INT UNSIGNED);
INSERT INTO t1 VALUES(2000,1,1),(2000,1,20),(2000,1,30),(2000,2,2),
            (2000,2,23),(2000,2,23);

示例表包含表示用户对页面的访问的年-月-日值。要确定这些访问每个月发生多少不同的日子,请使用以下查询:

sql 复制代码
SELECT year,month,BIT_COUNT(BIT_OR(1<<day)) AS days FROM t1
       GROUP BY year,month;

返回:

sql 复制代码
+------+-------+------+
| year | month | days |
+------+-------+------+
| 2000 |     1 |    3 |
| 2000 |     2 |    2 |
+------+-------+------+

该查询计算每个年/月组合在表中出现的不同天数,并自动删除重复条目。

使用AUTO_INCREMENT

可以使用AUTO_INCREMENT属性为新行生成唯一标识:

sql 复制代码
CREATE TABLE animals (
     id MEDIUMINT NOT NULL AUTO_INCREMENT,
     name CHAR(30) NOT NULL,
     PRIMARY KEY (id)
);

INSERT INTO animals (name) VALUES
    ('dog'),('cat'),('penguin'),
    ('lax'),('whale'),('ostrich');

SELECT * FROM animals;
sql 复制代码
+----+---------+
| id | name    |
+----+---------+
|  1 | dog     |
|  2 | cat     |
|  3 | penguin |
|  4 | lax     |
|  5 | whale   |
|  6 | ostrich |
+----+---------+

没有为AUTO_INCREMENT列指定值,MySQL自动分配序列号。您也可以显式地为列分配0以生成序列号,除非启用了NO_AUTO_VALUE_ON_ZEROSQL模式。例如:

sql 复制代码
INSERT INTO animals (id,name) VALUES(0,'groundhog');

如果列声明为NOT NULL,也可以为列分配NULL以生成序列号。例如:

sql 复制代码
INSERT INTO animals (id,name) VALUES(NULL,'squirrel');

当您将任何其他值插入AUTO_INCREMENT列时,该列将设置为该值并重置序列,以便下一个自动生成的值从最大的列值开始依次跟随。例如:

sql 复制代码
INSERT INTO animals (id,name) VALUES(100,'rabbit');
INSERT INTO animals (id,name) VALUES(NULL,'mouse');
SELECT * FROM animals;
+-----+-----------+
| id  | name      |
+-----+-----------+
|   1 | dog       |
|   2 | cat       |
|   3 | penguin   |
|   4 | lax       |
|   5 | whale     |
|   6 | ostrich   |
|   7 | groundhog |
|   8 | squirrel  |
| 100 | rabbit    |
| 101 | mouse     |
+-----+-----------+

更新现有AUTO_INCREMENT列值也会重置AUTO_INCREMENT序列。

您可以使用LAST_INSERT_ID()SQL函数或mysql_insert_id()C API函数检索最近自动生成的AUTO_INCREMENT值。这些函数是特定于连接的,因此它们的返回值不受另一个也在执行插入的连接的影响。

为AUTO_INCREMENT列使用最小整数数据类型,该列大到足以容纳所需的最大序列值。当列达到数据类型的上限时,下一次生成序列号的尝试失败。如果可能,请使用UNSIGNED属性以允许更大的范围。例如,如果您使用TINYINT,最大允许序列号为127。对于TINYINT UNSIGNED,最大值为255。请参阅,"整数类型(精确值)-INTEGER、INT、SMALLINT、TINYINT、MEDIUMINT、BIGINT"用于所有整数类型的范围。

注意:对于多行插入,LAST_INSERT_ID()和mysql_insert_id()实际上返回第一个插入行的AUTO_INCREMENT键。这使得多行插入能够在复制设置中的其他服务器上正确再现。

要从1以外的AUTO_INCREMENT值开始,请使用CREATE TABLE或ALTER TABLE设置该值,如下所示:

sql 复制代码
mysql> ALTER TABLE tbl AUTO_INCREMENT = 100;
InnoDB说明

有关特定于InnoDB的AUTO_INCREMENT用法的信息,请参见"InnoDB中的AUTO_INCREMENT处理"。

MyISAM说明

对于MyISAM表,您可以在多列索引中的辅助列上指定AUTO_INCREMENT。在这种情况下,AUTO_INCREMENT列的生成值计算为MAX(auto_increment_column) + 1 WHERE prefix=given-prefix。当您想将数据放入有序组时,这很有用。

sql 复制代码
CREATE TABLE animals (
    grp ENUM('fish','mammal','bird') NOT NULL,
    id MEDIUMINT NOT NULL AUTO_INCREMENT,
    name CHAR(30) NOT NULL,
    PRIMARY KEY (grp,id)
) ENGINE=MyISAM;

INSERT INTO animals (grp,name) VALUES
    ('mammal','dog'),('mammal','cat'),
    ('bird','penguin'),('fish','lax'),('mammal','whale'),
    ('bird','ostrich');

SELECT * FROM animals ORDER BY grp,id;
sql 复制代码
+--------+----+---------+
| grp    | id | name    |
+--------+----+---------+
| fish   |  1 | lax     |
| mammal |  1 | dog     |
| mammal |  2 | cat     |
| mammal |  3 | whale   |
| bird   |  1 | penguin |
| bird   |  2 | ostrich |
+--------+----+---------+
  • 在这种情况下(当AUTO_INCREMENT列是多列索引的一部分时),如果您删除任何组中AUTO_INCREMENT值最大的行,AUTO_INCREMENT值将被重用。即使对于MyISAM表也是如此,因为AUTO_INCREMENT值通常不会被重用。

  • 如果AUTO_INCREMENT列是多个索引的一部分,MySQL使用以AUTO_INCREMENT列开头的索引生成序列值(如果有的话)。例如,如果animals表包含索引PRIMARY KEY (grp, id)INDEX (id),MySQL将忽略用于生成序列值的PRIMARY KEY。因此,该表将包含单个序列,而不是每个grp值的序列。

进一步阅读

有关AUTO_INCREMENT的更多信息,请参见:

  • 如何将AUTO_INCREMENT属性分配给列:参考"CREATE TABLE语句"和,"ALTER TABLE语句"。
  • 如何AUTO_INCREMENT行为取决于NO_AUTO_VALUE_ON_ZEROSQL模式:,"服务器SQL模式"。
  • 如何使用LAST_INSERT_ID()函数查找包含最新AUTO_INCREMENT值的行:参考,"信息函数"。
  • 设置要使用的AUTO_INCREMENT值:参考,"服务器系统变量"。
  • 参考"InnoDB中的AUTO_INCREMENT处理"
  • AUTO_INCREMENT和复制:参考,"复制和AUTO_INCREMENT"。
  • 与可用于复制的AUTO_INCREMENT(auto_increment_increment和auto_increment_offset)相关的服务器系统变量:参考,"服务器系统变量"。

在Apache中使用MySQL

有一些程序允许您从MySQL数据库验证您的用户,也允许您将日志文件写入MySQL表。

通过将以下内容放入Apache配置文件,您可以将Apache日志格式更改为易于MySQL读取:

sql 复制代码
LogFormat \
        "\"%h\",%{%Y%m%d%H%M%S}t,%>s,\"%b\",\"%{Content-Type}o\",  \
        \"%U\",\"%{Referer}i\",\"%{User-Agent}i\""

要将该格式的日志文件加载到MySQL中,可以使用如下语句:

sql 复制代码
LOAD DATA INFILE '/local/access_log' INTO TABLE tbl_name
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' ESCAPED BY '\\'

应创建命名表,使其具有与LogFormat行写入日志文件的列相对应的列。

相关推荐
NineData9 小时前
NineData 迁移评估功能正式上线
数据库·dba
NineData15 小时前
数据库迁移总踩坑?用 NineData 迁移评估,提前识别所有兼容性风险
数据库·程序员·云计算
赵渝强老师17 小时前
【赵渝强老师】PostgreSQL中表的碎片
数据库·postgresql
全栈老石21 小时前
拆解低代码引擎核心:元数据驱动的"万能表"架构
数据库·低代码
倔强的石头_2 天前
kingbase备份与恢复实战(二)—— sys_dump库级逻辑备份与恢复(Windows详细步骤)
数据库
jiayou643 天前
KingbaseES 实战:深度解析数据库对象访问权限管理
数据库
于眠牧北3 天前
MySQL的锁类型,表锁,行锁,MVCC中所使用的临键锁
mysql
李广坤4 天前
MySQL 大表字段变更实践(改名 + 改类型 + 改长度)
数据库
Turnip12025 天前
深度解析:为什么简单的数据库"写操作"会在 MySQL 中卡住?
后端·mysql
爱可生开源社区5 天前
2026 年,优秀的 DBA 需要具备哪些素质?
数据库·人工智能·dba