1. 概述

今天我们主要学习如何使用 SQL 检索数据。

SELECT 可以说是 SQL 中最常用的语句了。你可以把 SQL 语句看作是英语语句,SELECT 就是 SQL 中的关键字之一,除了 SELECT 之外,还有 INSERTDELETEUPDATE 等关键字,这些关键字是 SQL 的保留字,这样可以很方便地帮助我们分析理解 SQL 语句。我们在定义数据库表名、字段名和变量名时,要尽量避免使用这些保留字。

SELECT 的作用是从一个表或多个表中检索出想要的数据行。今天我主要讲解 SELECT 的基础查询,后面我会讲解如何通过多个表的连接操作进行复杂的查询。

在这篇文章中,你需要重点掌握以下几方面的内容:

  1. SELECT 查询的基础语法;
  2. 如何排序检索数据;
  3. 什么情况下用SELECT*,如何提升 SELECT 查询效率?

2. 准备数据

在进行查询之前,我们创建一个王者荣耀英雄数据表。我们这里使用的是Mysql。

数据表中除了 id 以外的 24 个字段,分别代表的含义见下图。

hero-filed

建表的SQL如下:

DROP TABLE IF EXISTS `heros`;
CREATE TABLE `heros`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `hp_max` float NULL DEFAULT NULL,
  `hp_growth` float NULL DEFAULT NULL,
  `hp_start` float NULL DEFAULT NULL,
  `mp_max` float NULL DEFAULT NULL,
  `mp_growth` float NULL DEFAULT NULL,
  `mp_start` float NULL DEFAULT NULL,
  `attack_max` float NULL DEFAULT NULL,
  `attack_growth` float NULL DEFAULT NULL,
  `attack_start` float NULL DEFAULT NULL,
  `defense_max` float NULL DEFAULT NULL,
  `defense_growth` float NULL DEFAULT NULL,
  `defense_start` float NULL DEFAULT NULL,
  `hp_5s_max` float NULL DEFAULT NULL,
  `hp_5s_growth` float NULL DEFAULT NULL,
  `hp_5s_start` float NULL DEFAULT NULL,
  `mp_5s_max` float NULL DEFAULT NULL,
  `mp_5s_growth` float NULL DEFAULT NULL,
  `mp_5s_start` float NULL DEFAULT NULL,
  `attack_speed_max` float NULL DEFAULT NULL,
  `attack_range` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `role_main` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `role_assist` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `birthdate` date NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10069 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

然后,我们再插入一些英雄:

hero-datas

插入数据的SQL如下:

INSERT INTO `heros` VALUES (10000, '夏侯惇', 7350, 288.8, 3307, 1746, 94, 430, 321, 11.57, 159, 397, 21.14, 101, 98, 3.357, 51, 37, 1.571, 15, 0, '近战', '坦克', '战士', '2016-07-19');
INSERT INTO `heros` VALUES (10001, '钟无艳', 7000, 275, 3150, 1760, 95, 430, 318, 11, 164, 409, 22.07, 100, 92, 3.143, 48, 37, 1.571, 15, 0, '近战', '战士', '坦克', NULL);
INSERT INTO `heros` VALUES (10002, '张飞', 8341, 329.4, 3450, 100, 0, 100, 301, 10.57, 153, 504, 27.07, 125, 115, 4.143, 57, 5, 0, 5, 0, '近战', '坦克', '辅助', NULL);
INSERT INTO `heros` VALUES (10003, '牛魔', 8476, 352.8, 3537, 1926, 104, 470, 273, 8.357, 156, 394, 20.36, 109, 117, 4.214, 58, 42, 1.786, 17, 0, '近战', '坦克', '辅助', '2015-11-24');
INSERT INTO `heros` VALUES (10004, '吕布', 7344, 270, 3564, 0, 0, 0, 343, 12.36, 170, 390, 20.79, 99, 97, 3.071, 54, 0, 0, 0, 0, '近战', '战士', '坦克', '2015-12-22');
INSERT INTO `heros` VALUES (10005, '亚瑟', 8050, 316.3, 3622, 0, 0, 0, 346, 13, 164, 400, 21.57, 98, 106, 3.643, 55, 0, 0, 0, 0, '近战', '战士', '坦克', NULL);
INSERT INTO `heros` VALUES (10006, '芈月', 6164, 281.5, 3105, 100, 0, 100, 289, 9.786, 152, 361, 19.5, 88, 77, 2.357, 44, 0, 0, 0, 0, '远程', '法师', '坦克', '2015-12-08');
INSERT INTO `heros` VALUES (10007, '程咬金', 8611, 369.6, 3437, 0, 0, 0, 316, 11.07, 161, 504, 27.07, 125, 119, 4.429, 57, 0, 0, 0, 0, '近战', '坦克', '战士', NULL);
INSERT INTO `heros` VALUES (10008, '廉颇', 9328, 412.1, 3558, 1708, 92, 420, 286, 8.786, 163, 514, 27.29, 132, 128, 4.929, 59, 36, 1.5, 15, 0, '近战', '坦克', NULL, NULL);
INSERT INTO `heros` VALUES (10009, '东皇太一', 7669, 319.1, 3201, 1926, 104, 470, 286, 8.786, 163, 360, 18.64, 99, 106, 3.786, 53, 42, 1.786, 17, 0, '近战', '坦克', NULL, '2017-03-30');
INSERT INTO `heros` VALUES (10010, '庄周', 8149, 345.6, 3311, 1694, 91, 420, 297, 9.071, 170, 497, 24.79, 150, 113, 4.143, 55, 36, 1.5, 15, 0, '近战', '辅助', '坦克', NULL);
INSERT INTO `heros` VALUES (10011, '太乙真人', 6835, 242.3, 3443, 1680, 90, 420, 284, 9.286, 154, 396, 21.57, 94, 86, 2.643, 49, 35, 1.429, 15, 0, '近战', '辅助', '坦克', '2016-11-24');
INSERT INTO `heros` VALUES (10012, '白起', 8638, 366.3, 3510, 1666, 89, 420, 288, 9.286, 158, 430, 22.14, 120, 119, 4.357, 58, 34, 1.429, 14, 0, '近战', '坦克', NULL, NULL);
INSERT INTO `heros` VALUES (10013, '雅典娜', 6264, 243, 2862, 1732, 93, 430, 327, 11.79, 162, 418, 22.29, 106, 83, 2.786, 44, 36, 1.5, 15, 0, '近战', '战士', '坦克', NULL);
INSERT INTO `heros` VALUES (10014, '刘邦', 8073, 336, 3369, 1940, 105, 470, 302, 10.29, 158, 504, 27.07, 125, 117, 4.214, 58, 42, 1.786, 17, 0, '近战', '坦克', '辅助', '2016-04-26');
INSERT INTO `heros` VALUES (10015, '刘禅', 8581, 372.6, 3364, 1694, 91, 420, 295, 8.357, 178, 459, 22.86, 139, 118, 4.429, 56, 36, 1.5, 15, 0, '近战', '坦克', NULL, NULL);
INSERT INTO `heros` VALUES (10016, '墨子', 7176, 292.4, 3083, 1722, 93, 420, 328, 10.5, 181, 475, 26.64, 102, 100, 3.5, 51, 37, 1.571, 15, 0, '近战', '法师', '坦克', NULL);
INSERT INTO `heros` VALUES (10017, '项羽', 8057, 380.1, 3535, 1694, 91, 420, 306, 10.64, 157, 494, 26.5, 123, 121, 4.5, 58, 36, 1.5, 15, 0, '近战', '坦克', NULL, NULL);
INSERT INTO `heros` VALUES (10018, '关羽', 7107, 270.4, 3322, 10, 0, 10, 343, 12.36, 170, 386, 20.36, 101, 94, 3.071, 51, 0, 0, 0, 0, '近战', '战士', '坦克', '2016-06-28');
INSERT INTO `heros` VALUES (10019, '后羿', 5986, 200.3, 3182, 1784, 96, 440, 396, 16.79, 161, 336, 17.86, 86, 71, 2.143, 41, 38, 1.571, 16, 0, '远程', '射手', NULL, NULL);
INSERT INTO `heros` VALUES (10020, '马可波罗', 5584, 181.6, 3041, 200, 0, 200, 362, 13.36, 175, 344, 18.07, 91, 75, 2.071, 46, 50, 0, 50, 0, '远程', '射手', NULL, '2016-08-23');
INSERT INTO `heros` VALUES (10021, '鲁班七号', 5989, 184.9, 3401, 1756, 94, 440, 400, 16.14, 174, 323, 16.79, 88, 69, 1.929, 42, 36, 1.5, 15, 0, '远程', '射手', NULL, NULL);
INSERT INTO `heros` VALUES (10022, '李元芳', 5725, 194.1, 3007, 1770, 95, 440, 396, 16.79, 161, 340, 18.07, 87, 66, 2.071, 37, 37, 1.571, 15, 0, '远程', '射手', NULL, '2016-04-12');
INSERT INTO `heros` VALUES (10023, '孙尚香', 6014, 198.5, 3235, 1756, 94, 440, 411, 17.07, 172, 346, 18.36, 89, 69, 2.071, 40, 36, 1.5, 15, 0, '远程', '射手', NULL, NULL);
INSERT INTO `heros` VALUES (10024, '黄忠', 5898, 194.6, 3173, 1784, 96, 440, 403, 16.5, 172, 319, 16.29, 91, 68, 2.071, 39, 38, 1.571, 16, 0, '远程', '射手', NULL, NULL);
INSERT INTO `heros` VALUES (10025, '狄仁杰', 5710, 176.3, 3242, 1770, 95, 440, 376, 14.79, 169, 338, 17.36, 95, 66, 1.857, 40, 37, 1.571, 15, 0, '远程', '射手', NULL, NULL);
INSERT INTO `heros` VALUES (10026, '虞姬', 5669, 192.3, 2977, 1770, 95, 440, 407, 17.29, 165, 329, 17.5, 84, 63, 1.929, 36, 38, 1.571, 16, 0, '远程', '射手', NULL, '2016-05-24');
INSERT INTO `heros` VALUES (10027, '成吉思汗', 5799, 198, 3027, 1742, 93, 440, 394, 15, 184, 329, 16.79, 94, 66, 2.071, 37, 36, 1.5, 15, 0, '远程', '射手', NULL, '2016-09-27');
INSERT INTO `heros` VALUES (10028, '嬴政', 5471, 167.6, 3125, 1946, 104, 490, 309, 10.86, 157, 295, 14.57, 91, 70, 1.857, 44, 41, 1.714, 17, 0, '远程', '法师', NULL, NULL);
INSERT INTO `heros` VALUES (10029, '武则天', 5037, 155.5, 2860, 1988, 107, 490, 297, 8.857, 173, 348, 18.07, 95, 64, 1.714, 40, 43, 1.786, 18, 0, '远程', '法师', NULL, NULL);
INSERT INTO `heros` VALUES (10030, '露娜', 6612, 256.5, 3021, 1836, 99, 450, 335, 12.29, 163, 375, 19.5, 102, 99, 3.357, 52, 39, 1.643, 16, 0, '近战', '战士', '法师', NULL);
INSERT INTO `heros` VALUES (10031, '甄姬', 5584, 181.6, 3041, 2002, 108, 490, 296, 9.357, 165, 330, 17.36, 87, 71, 2, 43, 44, 1.857, 18, 0, '远程', '法师', NULL, NULL);
INSERT INTO `heros` VALUES (10032, '妲己', 5824, 185.4, 3229, 2016, 109, 490, 293, 8.786, 170, 326, 17.14, 86, 74, 2, 46, 44, 1.857, 18, 0, '远程', '法师', NULL, NULL);
INSERT INTO `heros` VALUES (10033, '干将莫邪', 5583, 171, 3189, 1946, 104, 490, 292, 9.5, 159, 323, 16.5, 92, 71, 1.857, 45, 41, 1.714, 17, 0, '远程', '法师', NULL, '2017-05-22');
INSERT INTO `heros` VALUES (10034, '姜子牙', 5399, 174.4, 2958, 2002, 108, 490, 317, 10.64, 168, 342, 16.86, 106, 69, 1.929, 42, 44, 1.857, 18, 0, '远程', '法师', '辅助', NULL);
INSERT INTO `heros` VALUES (10035, '王昭君', 5429, 167.6, 3083, 1960, 105, 490, 296, 9.357, 165, 305, 15.29, 91, 70, 1.857, 44, 41, 1.714, 17, 0, '远程', '法师', NULL, NULL);
INSERT INTO `heros` VALUES (10036, '诸葛亮', 5655, 180, 3135, 1988, 107, 490, 287, 9.357, 156, 330, 17.36, 87, 72, 2, 44, 43, 1.786, 18, 0, '远程', '法师', NULL, NULL);
INSERT INTO `heros` VALUES (10037, '不知火舞', 6014, 198.5, 3235, 200, 0, 200, 293, 8.786, 170, 336, 17.64, 89, 76, 2.143, 46, 50, 0, 50, 0, '近战', '法师', '刺客', '2016-05-12');
INSERT INTO `heros` VALUES (10038, '貂蝉', 5611, 185.1, 3019, 1960, 105, 490, 287, 8.571, 167, 330, 17.36, 87, 71, 2, 43, 41, 1.714, 17, 0, '近战', '法师', '刺客', '2015-12-15');
INSERT INTO `heros` VALUES (10039, '孙膑', 6811, 257.4, 3208, 1926, 104, 470, 328, 10.86, 176, 413, 22.5, 98, 85, 2.857, 45, 42, 1.786, 17, 0, '远程', '辅助', '法师', NULL);
INSERT INTO `heros` VALUES (10040, '安琪拉', 5994, 190.8, 3323, 1960, 105, 490, 293, 8.786, 170, 315, 16.57, 83, 76, 2.071, 47, 41, 1.714, 17, 0, '远程', '法师', NULL, NULL);
INSERT INTO `heros` VALUES (10041, '小乔', 5916, 202, 3088, 1988, 107, 490, 263, 7.857, 153, 309, 15.29, 95, 75, 2.214, 44, 43, 1.786, 18, 0, '远程', '法师', NULL, NULL);
INSERT INTO `heros` VALUES (10042, '周瑜', 5513, 172.9, 3093, 1974, 106, 490, 298, 9.857, 160, 320, 16.64, 87, 70, 1.857, 44, 42, 1.786, 17, 0, '远程', '法师', NULL, '2015-11-10');
INSERT INTO `heros` VALUES (10043, '张良', 5799, 198, 3027, 1988, 107, 490, 293, 8.786, 170, 320, 16.64, 87, 73, 2.143, 43, 43, 1.786, 18, 0, '远程', '法师', NULL, '2015-10-26');
INSERT INTO `heros` VALUES (10044, '高渐离', 6165, 217.4, 3122, 1988, 107, 490, 290, 9.071, 163, 343, 17.5, 98, 77, 2.357, 44, 42, 1.786, 17, 0, '远程', '法师', NULL, NULL);
INSERT INTO `heros` VALUES (10045, '扁鹊', 6703, 249.9, 3205, 2016, 109, 490, 309, 10.07, 168, 374, 20.5, 87, 83, 2.714, 45, 44, 1.857, 18, 0, '远程', '法师', '辅助', NULL);
INSERT INTO `heros` VALUES (10046, '钟馗', 6280, 204.3, 3420, 1988, 107, 490, 278, 8.286, 162, 390, 20.79, 99, 91, 2.429, 57, 42, 1.786, 17, 0, '近战', '法师', '战士', '2016-03-24');
INSERT INTO `heros` VALUES (10047, '大乔', 5399, 174.4, 2958, 1926, 104, 470, 305, 9.786, 168, 340, 17.64, 93, 69, 1.929, 42, 42, 1.786, 17, 0, '近战', '辅助', NULL, '2017-02-28');
INSERT INTO `heros` VALUES (10048, '鬼谷子', 7107, 270.4, 3322, 1808, 97, 450, 297, 9.643, 162, 394, 21.57, 92, 89, 3, 47, 38, 1.571, 16, 0, '近战', '辅助', NULL, '2017-06-29');
INSERT INTO `heros` VALUES (10049, '蔡文姬', 5910, 190.9, 3238, 1898, 102, 470, 292, 9.5, 159, 350, 18.14, 96, 66, 2, 38, 41, 1.714, 17, 0, '远程', '辅助', NULL, '2016-07-08');
INSERT INTO `heros` VALUES (10050, '花木兰', 5397, 179.4, 2886, 100, 0, 100, 362, 14, 166, 349, 18.79, 86, 72, 2, 44, 0, 0, 0, 0, '近战', '战士', '刺客', '2016-01-01');
INSERT INTO `heros` VALUES (10051, '赵云', 6732, 247.5, 3267, 1760, 95, 430, 380, 14.79, 173, 394, 22.14, 84, 89, 2.786, 50, 37, 1.571, 15, 0, '近战', '战士', '刺客', NULL);
INSERT INTO `heros` VALUES (10052, '橘石京', 7000, 275, 3150, 0, 0, 0, 347, 13, 165, 392, 21.14, 96, 92, 3.143, 48, 0, 0, 0, 0, '近战', '刺客', '战士', NULL);
INSERT INTO `heros` VALUES (10053, '李白', 5483, 179.6, 2968, 1808, 97, 450, 330, 11.5, 169, 358, 18.57, 98, 80, 2.214, 49, 38, 1.571, 16, 0, '近战', '刺客', '战士', '2016-03-01');
INSERT INTO `heros` VALUES (10054, '韩信', 5655, 190.5, 2988, 1704, 91, 430, 386, 15.29, 172, 323, 16.5, 92, 75, 2.143, 45, 36, 1.5, 15, 0, '近战', '刺客', '战士', NULL);
INSERT INTO `heros` VALUES (10055, '杨戬', 7420, 291.5, 3339, 1694, 91, 420, 325, 11.36, 166, 428, 23.57, 98, 98, 3.357, 51, 36, 1.5, 15, 0, '近战', '战士', NULL, '2016-10-11');
INSERT INTO `heros` VALUES (10056, '达摩', 7140, 280.5, 3213, 1694, 91, 420, 355, 13.14, 171, 415, 22.86, 95, 98, 3.357, 51, 36, 1.5, 15, 0, '近战', '战士', NULL, NULL);
INSERT INTO `heros` VALUES (10057, '孙悟空', 6585, 235.1, 3293, 1760, 95, 430, 349, 13, 167, 385, 20.79, 94, 87, 2.643, 50, 37, 1.571, 15, 0, '近战', '战士', '刺客', NULL);
INSERT INTO `heros` VALUES (10058, '刘备', 6900, 262.5, 3225, 1742, 93, 440, 363, 14.29, 163, 381, 19.14, 113, 79, 2.786, 40, 36, 1.5, 15, 0, '远程', '战士', NULL, '2016-02-02');
INSERT INTO `heros` VALUES (10059, '曹操', 7473, 286.1, 3467, 0, 0, 0, 361, 13.36, 174, 371, 19.07, 104, 101, 3.357, 54, 0, 0, 0, 0, '近战', '战士', NULL, NULL);
INSERT INTO `heros` VALUES (10060, '典韦', 7516, 291.6, 3434, 1774, 96, 430, 345, 12.64, 168, 402, 22.36, 89, 99, 3.357, 52, 39, 1.643, 16, 0, '近战', '战士', NULL, NULL);
INSERT INTO `heros` VALUES (10061, '宫本武藏', 6210, 236.3, 2902, 0, 0, 0, 330, 12.36, 157, 391, 20.36, 106, 85, 2.714, 47, 0, 0, 0, 0, '近战', '战士', NULL, '2015-10-30');
INSERT INTO `heros` VALUES (10062, '老夫子', 7155, 270.4, 3370, 5, 0, 5, 329, 11.5, 168, 409, 22.07, 100, 94, 3.071, 51, 0, 0, 0, 1, '近战', '战士', NULL, NULL);
INSERT INTO `heros` VALUES (10063, '哪吒', 7268, 270.4, 3483, 1808, 97, 450, 320, 11.5, 159, 408, 22.07, 99, 98, 3.214, 53, 38, 1.571, 16, 0, '近战', '战士', NULL, '2017-01-12');
INSERT INTO `heros` VALUES (10064, '阿轲', 5968, 192.8, 3269, 0, 0, 0, 427, 17.86, 177, 349, 18.57, 89, 81, 2.214, 50, 0, 0, 0, 0, '近战', '刺客', NULL, NULL);
INSERT INTO `heros` VALUES (10065, '娜可露露', 6205, 211.9, 3239, 1808, 97, 450, 385, 15.14, 173, 359, 19.5, 86, 79, 2.286, 47, 38, 1.571, 16, 0, '近战', '刺客', NULL, '2016-02-22');
INSERT INTO `heros` VALUES (10066, '兰陵王', 6232, 210, 3292, 1822, 98, 450, 388, 15.5, 171, 342, 18.36, 85, 99, 3.357, 52, 46, 1.929, 19, 0, '近战', '刺客', NULL, NULL);
INSERT INTO `heros` VALUES (10067, '铠', 6700, 237.5, 3375, 1784, 96, 440, 328, 10.86, 176, 388, 20.07, 107, 81, 2.643, 44, 38, 1.571, 16, 0, '近战', '战士', '坦克', NULL);
INSERT INTO `heros` VALUES (10068, '百里守约', 5611, 185.1, 3019, 1784, 96, 440, 410, 15.86, 188, 329, 16.79, 94, 68, 2.071, 39, 38, 1.571, 16, 0, '远程', '射手', '刺客', '2017-08-08');

3. SELECT 查询的基础语法

SELECT 可以帮助我们从一个表或多个表中进行数据查询。我们知道一个数据表是由列(字段名)和行(数据行)组成的,我们要返回满足条件的数据行,就需要在 SELECT 后面加上我们想要查询的列名,可以是一列,也可以是多个列。如果你不知道所有列名都有什么,也可以检索所有列。

3.1 查询列

如果我们想要对数据表中的某一列进行检索,在 SELECT 后面加上这个列的字段名即可。

比如我们想要检索数据表中都有哪些英雄。

SELECT name FROM heros

运行结果如下,你可以看到这样就等于单独输出了 name 这一列。

heros-select-name

我们也可以对多个列进行检索,在列名之间用逗号 (,) 分割即可。

比如我们想要检索有哪些英雄,他们的最大生命、最大法力、最大物攻和最大物防分别是多少。

SELECT name, hp_max, mp_max, attack_max, defense_max FROM heros

运行结果如下:

heros-select-more-filed

这个表中一共有 25 个字段,除了 id 和英雄名 name 以外,还存在 23 个属性值,如果我们记不住所有的字段名称,可以使用 SELECT * 帮我们检索出所有的列:

SELECT * FROM heros

运行结果如下:

heros-select-full-filed

我们在做数据检索的时候,SELECT *还是很有用的,这样我们就不需要写很长的 SELECT 语句了。

但是在生产环境时要尽量避免使用SELECT*,具体原因会在后面讲。

3.2 起别名

我们在使用 SELECT 查询的时候,还有一些技巧可以使用,比如你可以给列名起别名。

我们在进行检索的时候,可以给英雄名、最大生命、最大法力、最大物攻和最大物防等取别名:

SELECT name AS n, hp_max AS hm, mp_max AS mm, attack_max AS am, defense_max AS dm FROM heros

运行结果和上面多列检索的运行结果是一样的,只是将列名改成了 n、hm、mm、am、dm

当然这里的列别名只是举例,一般来说起别名的作用是对原有名称进行简化,从而让 SQL 语句看起来更精简。

同样我们也可以对表名称起别名,这个在多表连接查询的时候会用到。

3.3 查询常数

SELECT 查询还可以对常数进行查询,就是在 SELECT 查询结果中增加一列固定的常数列。这列的值是我们指定的,而不是从数据表中动态取出的。

你可能会问为什么我们还要对常数进行查询呢?SQL 中的 SELECT 语法的确提供了这个功能,一般来说我们只从一个表中查询数据,通常不需要增加一个固定的常数列,但如果我们想整合不同的数据源,用常数列作为这个表的标记,就需要查询常数。

比如、我们想对 heros 数据表中的英雄名进行查询,同时增加一列字段platform,这个字段固定值为“王者荣耀”,可以这样写:

SELECT '王者荣耀' as platform, name FROM heros

运行结果如下:

heros-select-platform

在这个 SQL 语句中,我们虚构了一个platform字段,并且把它设置为固定值“王者荣耀”。

需要说明的是,如果常数是个字符串,那么使用**单引号(‘’)**就非常重要了,比如‘王者荣耀’。

单引号说明引号中的字符串是个常数,否则 SQL 会把王者荣耀当成列名进行查询,但实际上数据表里没有这个列名,就会引起错误。

如果常数是英文字母,比如'WZRY'也需要加引号。

如果常数是个数字,就可以直接写数字,不需要单引号,比如:

SELECT 123 as platform, name FROM heros

运行结果如下:

heros-select-platform-123

3.4 去除重复行

关于单个表的 SELECT 查询,还有一个非常实用的操作,就是从结果中去掉重复的行。使用的关键字是 DISTINCT

比如我们想要看下 heros 表中关于攻击范围的取值都有哪些:

SELECT DISTINCT attack_range FROM heros

去除重复后,可以直观地看到攻击范围其实只有两个值,那就是近战和远程。

heros-select-DISTINCT

如果我们带上英雄名称,会是怎样呢:

SELECT DISTINCT attack_range, name FROM heros

运行结果如下:

heros-select-DISTINCT-with-name

这里有两点需要注意:

  • DISTINCT 需要放到所有列名的前面,如果写成SELECT name, DISTINCT attack_range FROM heros会报错。
  • DISTINCT 是对后面所有列名的组合进行去重,你能看到最后的结果是 69 条,因为这 69 个英雄名称不同,都有攻击范围(attack_range)这个属性值。如果你想要看都有哪些不同的攻击范围(attack_range),只需要写DISTINCT attack_range即可,后面不需要再加其他的列名了。

4. 如何排序检索数据

当我们检索数据的时候,有时候需要按照某种顺序进行结果的返回,比如我们想要查询所有的英雄,按照最大生命从高到底的顺序进行排列,就需要使用 ORDER BY 子句。使用 ORDER BY 子句有以下几个点需要掌握:

  1. 排序的列名:ORDER BY 后面可以有一个或多个列名,如果是多个列名进行排序,会按照后面第一个列先进行排序,当第一列的值相同的时候,再按照第二列进行排序,以此类推。
  2. 排序的顺序:ORDER BY 后面可以注明排序规则,ASC 代表递增排序,DESC 代表递减排序。如果没有注明排序规则,**默认情况下是按照 ASC 递增排序。**我们很容易理解 ORDER BY 对数值类型字段的排序规则,但如果排序字段类型为文本数据,就需要参考数据库的设置方式了,这样才能判断 A 是在 B 之前,还是在 B 之后。比如使用 MySQL 在创建字段的时候设置为 BINARY 属性,就代表区分大小写。
  3. 非选择列排序:ORDER BY 可以使用非选择列进行排序,所以即使在 SELECT 后面没有这个列名,你同样可以放到 ORDER BY 后面进行排序。
  4. ORDER BY 的位置:ORDER BY 通常位于 SELECT 语句的最后一条子句,否则会报错。

在了解了 ORDER BY 的使用语法之后,我们来看下如何对 heros 数据表进行排序。

假设我们想要显示英雄名称及最大生命值,按照最大生命值从高到低的方式进行排序:

SELECT name, hp_max FROM heros ORDER BY hp_max DESC 

运行结果如下:

heros-select-order-by-one

如果想要显示英雄名称及最大生命值,按照第一排序最大法力从低到高,当最大法力值相等的时候则按照第二排序进行,即最大生命值从高到低的方式进行排序:

SELECT name, hp_max FROM heros ORDER BY mp_max, hp_max DESC  

运行结果如下:

heros-select-order-by-two

5. 约束返回结果的数量

另外在查询过程中,我们可以约束返回结果的数量,使用 LIMIT 关键字。

比如我们想返回英雄名称及最大生命值,按照最大生命值从高到低排序,返回 5 条记录即可。

SELECT name, hp_max FROM heros ORDER BY hp_max DESC LIMIT 5

运行结果如下:

heros-select-LIMIT

有一点需要注意,约束返回结果的数量,在不同的 DBMS 中使用的关键字可能不同。

在 MySQL、PostgreSQL、MariaDB 和 SQLite 中使用 LIMIT 关键字,而且需要放到 SELECT 语句的最后面。

如果是 SQL Server 和 Access,需要使用 TOP 关键字,比如:

SELECT TOP 5 name, hp_max FROM heros ORDER BY hp_max DESC

如果是 DB2,使用FETCH FIRST 5 ROWS ONLY这样的关键字:

SELECT name, hp_max FROM heros ORDER BY hp_max DESC FETCH FIRST 5 ROWS ONLY

如果是 Oracle,你需要基于 ROWNUM 来统计行数:

SELECT name, hp_max FROM heros WHERE ROWNUM <=5 ORDER BY hp_max DESC

需要说明的是,Oracle的这条语句是先取出来前 5 行数据,然后再按照 hp_max 从高到低的顺序进行排序。但这样产生的结果和上述方法的并不一样。我会在后面讲到子查询,你可以使用SELECT name, hp_max FROM (SELECT name, hp_max FROM heros ORDER BY hp_max) WHERE ROWNUM <=5得到与上述方法一致的结果。

约束返回结果的数量可以减少数据表的网络传输量,也可以提升查询效率。如果我们知道返回结果只有 1 条,就可以使用LIMIT 1,告诉 SELECT 语句只需要返回一条记录即可。这样的好处就是 SELECT 不需要扫描完整的表,只需要检索到一条符合条件的记录即可返回。

6. SELECT 的执行顺序

查询是 RDBMS 中最频繁的操作。我们在理解 SELECT 语法的时候,还需要了解 SELECT 执行时的底层原理。只有这样,才能让我们对 SQL 有更深刻的认识。

其中你需要记住 SELECT 查询时的两个顺序:

  1. 关键字的顺序是不能颠倒的:

    SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ...
    
  2. SELECT 语句的执行顺序(在 MySQL 和 Oracle 中,SELECT 执行顺序基本相同):

    FROM > WHERE > GROUP BY > HAVING > SELECT 的字段 > DISTINCT > ORDER BY > LIMIT
    

比如你写了一个 SQL 语句,那么它的关键字顺序和执行顺序是下面这样的:

SELECT DISTINCT player_id, player_name, count(*) as num -- 顺序 5
FROM player JOIN team ON player.team_id = team.team_id -- 顺序 1
WHERE height > 1.80 -- 顺序 2
GROUP BY player.team_id -- 顺序 3
HAVING num > 2 -- 顺序 4
ORDER BY num DESC -- 顺序 6
LIMIT 2 -- 顺序 7

在 SELECT 语句执行这些步骤的时候,每个步骤都会产生一个虚拟表,然后将这个虚拟表传入下一个步骤中作为输入。需要注意的是,这些步骤隐含在 SQL 的执行过程中,对于我们来说是不可见的。

来详细解释一下 SQL 的执行原理。

首先,你可以注意到,SELECT 是先执行 FROM 这一步的。在这个阶段,如果是多张表联查,还会经历下面的几个步骤:

  1. 首先先通过 CROSS JOIN 求笛卡尔积,相当于得到虚拟表 vt1-1(virtual table);
  2. 通过 ON 进行筛选,在虚拟表 vt1-1 的基础上进行筛选,得到虚拟表 vt1-2
  3. 添加外部行。如果我们使用的是左连接、右链接或者全连接,就会涉及到外部行,也就是在虚拟表 vt1-2 的基础上增加外部行,得到虚拟表 vt1-3

当然如果我们操作的是两张以上的表,还会重复上面的步骤,直到所有表都被处理完为止。这个过程得到是我们的原始数据。

当我们拿到了查询数据表的原始数据,也就是最终的虚拟表 vt1,就可以在此基础上再进行 WHERE 阶段。在这个阶段中,会根据 vt1 表的结果进行筛选过滤,得到虚拟表 vt2

然后进入第三步和第四步,也就是 GROUP 和 HAVING 阶段。在这个阶段中,实际上是在虚拟表 vt2 的基础上进行分组和分组过滤,得到中间的虚拟表 vt3vt4

当我们完成了条件筛选部分之后,就可以筛选表中提取的字段,也就是进入到 SELECT 和 DISTINCT 阶段。

首先在 SELECT 阶段会提取想要的字段,然后在 DISTINCT 阶段过滤掉重复的行,分别得到中间的虚拟表 vt5-1vt5-2

当我们提取了想要的字段数据之后,就可以按照指定的字段进行排序,也就是 ORDER BY 阶段,得到虚拟表 vt6

最后在 vt6 的基础上,取出指定行的记录,也就是 LIMIT 阶段,得到最终的结果,对应的是虚拟表 vt7

当然我们在写 SELECT 语句的时候,不一定存在所有的关键字,相应的阶段就会省略。

同时因为 SQL 是一门类似英语的结构化查询语言,所以我们在写 SELECT 语句的时候,还要注意相应的关键字顺序,所谓底层运行的原理,就是我们刚才讲到的执行顺序。

7. 什么情况下用 SELECT*,如何提升 SELECT 查询效率?

当我们初学 SELECT 语法的时候,经常会使用SELECT *,因为使用方便。实际上这样也增加了数据库的负担。所以如果我们不需要把所有列都检索出来,还是先指定出所需的列名,因为写清列名,可以减少数据表查询的网络传输量,而且考虑到在实际的工作中,我们往往不需要全部的列名,因此你需要养成良好的习惯,写出所需的列名。

如果我们只是练习,或者对数据表进行探索,那么是可以使用SELECT *的。它的查询效率和把所有列名都写出来再进行查询的效率相差并不大。这样可以方便你对数据表有个整体的认知。但是在生产环境下,不推荐你直接使用SELECT *进行查询。

8. 总结

今天对 SELECT 的基础语法进行了讲解,SELECT 是 SQL 的基础。但不同阶段看 SELECT 都会有新的体会。当你第一次学习的时候,关注的往往是如何使用它,或者语法是否正确。再看的时候,可能就会更关注 SELECT 的查询效率,以及不同 DBMS 之间的差别。

在我们的日常工作中,很多人都可以写出 SELECT 语句,但是执行的效率却相差很大。

产生这种情况的原因主要有两个,一个是习惯的培养,比如大部分初学者会经常使用SELECT *,而好的习惯则是只查询所需要的列;另一个对 SQL 查询的执行顺序及查询效率的关注,比如当你知道只有 1 条记录的时候,就可以使用LIMIT 1来进行约束,从而提升查询效率。