当前位置: 首页 > article >正文

MySQL教程之:常见查询示例

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

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

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

$> mysql your-database-name

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

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后,查询结果应具有以下内容:

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 |
+---------+--------+-------+

列的最大值

“最高的货号是多少?”

SELECT MAX(article) AS article FROM shop;

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

保持某列最大值的行

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

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

SELECT article, dealer, price
FROM   shop
WHERE  price=(SELECT MAX(price) FROM shop);

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

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

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子句仅获取第一行,如下所示:

SELECT article, dealer, price
FROM shop
ORDER BY price DESC
LIMIT 1;

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

每个组的最大列数

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

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 |
+---------+-------+

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

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

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

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或带有窗口函数的公共表表达式。

不相关子查询:

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

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

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

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用户变量来记住结果,而不必将它们存储在客户端的临时变量中。

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

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语句创建父表和子表:

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;

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

mysql> INSERT INTO parent (id) VALUES ROW(1);

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

mysql> TABLE parent;
+----+
| id |
+----+
|  1 |
+----+

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

mysql> INSERT INTO child (id,parent_id) VALUES ROW(1,1);

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

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

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值在父表中不存在。

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

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选项在删除或更新父表中的行时自动删除或更新子表中匹配的行。

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;

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

mysql> INSERT INTO child (id,parent_id) VALUES ROW(1,1), ROW(2,1), ROW(3,1);

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

mysql> TABLE child;
+------+-----------+
| id   | parent_id |
+------+-----------+
|    1 |         1 |
|    2 |         1 |
|    3 |         1 |
+------+-----------+

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

mysql> UPDATE parent SET id = 2 WHERE id = 1;

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

mysql> TABLE parent;
+----+
| id |
+----+
|  2 |
+----+

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

mysql> TABLE child;
+------+-----------+
| id   | parent_id |
+------+-----------+
|    1 |         2 |
|    2 |         2 |
|    3 |         2 |
+------+-----------+

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

mysql> DELETE FROM parent WHERE id = 2;

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

mysql> TABLE child;
Empty set (0.00 sec)

双键搜索

使用单个键的OR得到了很好的优化,AND的处理也是如此。
一个棘手的情况是在两个不同的键上结合OR进行搜索:

SELECT field1_index, field2_index FROM test_table
WHERE field1_index = '1' OR  field2_index = '1'

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

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

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

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';

计算每天的访问量

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

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);

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

SELECT year,month,BIT_COUNT(BIT_OR(1<<day)) AS days FROM t1
       GROUP BY year,month;

返回:

+------+-------+------+
| year | month | days |
+------+-------+------+
| 2000 |     1 |    3 |
| 2000 |     2 |    2 |
+------+-------+------+

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

使用AUTO_INCREMENT

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

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;
+----+---------+
| id | name    |
+----+---------+
|  1 | dog     |
|  2 | cat     |
|  3 | penguin |
|  4 | lax     |
|  5 | whale   |
|  6 | ostrich |
+----+---------+

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

INSERT INTO animals (id,name) VALUES(0,'groundhog');

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

INSERT INTO animals (id,name) VALUES(NULL,'squirrel');

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

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设置该值,如下所示:

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。当您想将数据放入有序组时,这很有用。

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;
+--------+----+---------+
| 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读取:

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

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

LOAD DATA INFILE '/local/access_log' INTO TABLE tbl_name
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' ESCAPED BY '\\'

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


http://www.kler.cn/a/501157.html

相关文章:

  • 探索AGI:智能助手与自我赋能的新时代
  • 每日学习30分轻松掌握CursorAI:多文件编辑与Composer功能
  • uniapp中rpx和upx的区别
  • [人工智能自学] Python包学习-pandas
  • 后端服务集成ElasticSearch搜索功能技术方案
  • 嵌入式C语言:二维数组
  • TCP 套接字 方法
  • 【Linux】Linux命令
  • wow-agent 学习笔记
  • Bytebase 3.1.2 - 在 SQL 编辑器中为表、列和 PG 的视图注释显示鼠标悬浮提示
  • 了解Node.js
  • 【JavaSE】【网络协议】HTTPS
  • 《安富莱嵌入式周报》第348期:开源低功耗测试仪,开源创意万用表,续航100-300小时,开源PCB电机,自制shell和网络协议栈,开源水培自动化系统
  • MySQL核心揭秘:InnoDB存储引擎深度探索
  • 【RDMA学习笔记】1:RDMA(Remote Direct Memory Access)介绍
  • 【单片机学习第三课---单片机IO操作】
  • vue elemnt-ui自定义时间日期选择
  • Linux开发工具--vim编辑器-gcc/g++编译器-gdb调试器
  • 案例研究:UML用例图中的结账系统
  • 红队工具使用全解析:揭开网络安全神秘面纱一角
  • 微服务的CAP定理与数据一致性抉择
  • 算法练习7——拦截导弹的系统数量求解
  • GAMES101学习笔记(三):Rasterization 光栅化(三角形的离散化、抗锯齿、深度测试)
  • Dockerfile 构建java镜像并运行部署java项目
  • uniapp中rpx和upx的区别
  • Go语言中http.Transport的Keep-Alive配置与性能优化方法