一
在LeetCode中有这么一道题(176. 第二高的薪水):
Employee
表:
Column Name | Type |
---|---|
id | int |
salary | int |
id 是这个表的主键。
表的每一行包含员工的工资信息。
查询并返回 Employee
表中第二高的 不同 薪水 。如果不存在第二高的薪水,查询应该返回 null(Pandas 则返回 None)
。
查询结果如下例所示。
示例 1:
输入:
Employee 表:
id | salary |
---|---|
1 | 100 |
2 | 200 |
3 | 300 |
输出:
SecondHighestSalary |
---|
200 |
示例 2:
输入:
Employee 表:
id | salary |
---|---|
1 | 100 |
输出:
SecondHighestSalary |
---|
null |
二
基于问题,很容易做出以下解答:
select distinct if(e.salary is null, 'null', e.salary ) as SecondHighestSalary
from Employee e
order by e.salary desc
limit 1,1
对Employee表中salary字段进行排序,选择其中排名第2的结果,并且使用if语句当结果为空时返回字符串null。
但是遇到了一个问题:
可以看到,200被转换成了字符串类型。
这个问题涉及到了一个知识点:
为了让 if
函数能够返回两种可能类型的值(数字或字符串),数据库系统通常会将数字类型隐式转换为字符串类型,以使两个分支的返回类型一致。
三
查阅答案,发现有以下解法:
SELECT
(
SELECT DISTINCT Salary
FROM Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET 1
) AS SecondHighestSalary;
其中并没有提到null的处理方法,为什么能够返回null?
在 SQL 中,当一个子查询被用在期望返回单个值的上下文中(例如在 SELECT
列表的列、WHERE
子句的比较操作符的一侧,或者作为另一个表达式的一部分),并且该子查询实际执行后没有返回任何行时,它的值会被解析为 NULL
。
子查询 (select distinct e.salary as SecondHighestSalary from Employee e order by e.salary desc limit 1,1)
被直接放在外层 SELECT
语句的选择列表中,并且没有关联任何外层查询的表(外层 SELECT
语句没有 FROM
子句)。在这种情况下,这个子查询被视为一个标量子查询。
标量子查询的定义和特性:
- 期望返回最多一行一列: 标量子查询的设计和使用场景决定了它应该返回零行或一行,并且只有一列。
- 用作单个值: 它的结果被当作一个单独的值来使用,就像一个常量或者一个列的值一样。
查询类型
1. 简单 SELECT 查询 (Simple SELECT Queries):
这是最基本的查询类型,用于从一个或多个表中检索数据。
SQL
SELECT column1, column2 FROM table_name;
2. 带 WHERE 子句的查询 (Queries with WHERE Clause):
使用 WHERE
子句来过滤数据,只返回满足特定条件的行。
SQL
SELECT column1 FROM table_name WHERE column2 = 'value';
3. 带 ORDER BY 子句的查询 (Queries with ORDER BY Clause):
使用 ORDER BY
子句对查询结果进行排序,可以按升序 (ASC
) 或降序 (DESC
) 排序。
SQL
SELECT column1 FROM table_name ORDER BY column1 ASC;
4. 带 GROUP BY 子句的查询 (Queries with GROUP BY Clause):
使用 GROUP BY
子句将具有相同值的行分组到一起,通常与聚合函数一起使用。
SQL
SELECT column1, COUNT(*) FROM table_name GROUP BY column1;
5. 使用聚合函数的查询 (Queries with Aggregate Functions):
聚合函数(如 COUNT
, SUM
, AVG
, MIN
, MAX
)用于对一组值进行计算并返回单个汇总值。
SQL
SELECT COUNT(*) FROM table_name WHERE condition;
SELECT AVG(salary) FROM employees;
6. 带 HAVING 子句的查询 (Queries with HAVING Clause):
HAVING
子句用于过滤 GROUP BY
子句分组后的结果,类似于 WHERE
子句用于过滤单个行。
SQL
SELECT department, COUNT(*) FROM employees GROUP BY department HAVING COUNT(*) > 5;
7. 连接查询 (JOIN Queries):
用于从两个或多个相关的表中检索数据。常见的连接类型包括:
- INNER JOIN (或 JOIN): 返回两个表中都匹配的行。
- LEFT JOIN (或 LEFT OUTER JOIN): 返回左表的所有行以及右表中匹配的行。如果右表中没有匹配的行,则右表的列值为
NULL
。 - RIGHT JOIN (或 RIGHT OUTER JOIN): 返回右表的所有行以及左表中匹配的行。如果左表中没有匹配的行,则左表的列值为
NULL
。 - FULL OUTER JOIN (或 FULL JOIN): 返回左表和右表中的所有行。如果一个表中没有匹配的行,则对应表的列值为
NULL
。(并非所有数据库都支持FULL OUTER JOIN
) - CROSS JOIN: 返回两个表中所有可能的行组合(笛卡尔积)。
SQL
SELECT e.name, d.department_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.id;
8. 子查询 (Subqueries) - 除了标量子查询:
子查询是嵌套在另一个 SQL 查询内部的查询。除了标量子查询(返回单个值)外,还有:
行子查询 (Row Subqueries): 返回一行中的多个列。
SQL
SELECT * FROM products WHERE (category, price) = (SELECT 'Electronics', MAX(price) FROM products);
列子查询 (Column Subqueries): 返回一列中的多个值。通常与
IN
,NOT IN
,ANY
,SOME
,ALL
等谓词一起使用。SQL
SELECT * FROM employees WHERE department_id IN (SELECT id FROM departments WHERE location = 'New York');
表子查询 (Table Subqueries): 返回一个或多个列的多行,可以像一个普通的表一样在
FROM
子句中使用,通常需要给它一个别名。SQL
SELECT avg(salary) FROM (SELECT department_id, avg(salary) as salary FROM employees GROUP BY department_id) as dept_avg_salaries;
相关子查询 (Correlated Subqueries): 子查询的执行依赖于外部查询的某些值。对于外部查询的每一行,相关子查询都会执行一次。
SQL
SELECT e1.name FROM employees e1 WHERE e1.salary > (SELECT AVG(e2.salary) FROM employees e2 WHERE e2.department_id = e1.department_id);
非相关子查询 (Non-Correlated Subqueries): 子查询的执行不依赖于外部查询,可以独立运行。
9. 集合操作查询 (Set Operation Queries):
用于组合多个 SELECT
语句的结果集。常见的集合操作包括:
- UNION: 合并两个或多个结果集,并去除重复的行。
- UNION ALL: 合并两个或多个结果集,保留所有行(包括重复的行)。
- INTERSECT: 返回两个结果集中都存在的行。(并非所有数据库都直接支持
INTERSECT
,可能需要使用其他方法模拟) - EXCEPT (或 MINUS): 返回存在于第一个结果集但不存在于第二个结果集中的行。(并非所有数据库都直接支持
EXCEPT
或MINUS
)
SQL
SELECT city FROM customers UNION SELECT city FROM suppliers;
10. 数据操作语言查询 (Data Manipulation Language - DML Queries):
虽然通常被称为语句而不是查询,但它们用于修改数据库中的数据:
- INSERT: 向表中添加新的行。
- UPDATE: 修改表中已有的行。
- DELETE: 从表中删除行。
SQL
INSERT INTO employees (name, salary) VALUES ('John Doe', 50000);
UPDATE employees SET salary = 55000 WHERE name = 'John Doe';
DELETE FROM employees WHERE name = 'John Doe';
11. 数据定义语言查询 (Data Definition Language - DDL Queries):
同样通常被称为语句,但用于定义和管理数据库结构:
- CREATE TABLE: 创建新的表。
- ALTER TABLE: 修改已有的表结构。
- DROP TABLE: 删除表。
- CREATE INDEX: 在表上创建索引以提高查询性能。
- DROP INDEX: 删除索引。
SQL
CREATE TABLE users (id INT PRIMARY KEY, username VARCHAR(50));
12. 公用表表达式 (Common Table Expressions - CTEs):
使用 WITH
子句创建临时的、命名的结果集,可以在单个查询中多次引用,通常用于简化复杂的查询或进行递归查询。
SQL
WITH RECURSIVE EmployeeHierarchy AS (
SELECT id, name, manager_id FROM employees WHERE id = 1
UNION ALL
SELECT e.id, e.name, e.manager_id FROM employees e INNER JOIN EmployeeHierarchy eh ON e.manager_id = eh.id
)
SELECT * FROM EmployeeHierarchy;
13. 窗口函数 (Window Functions):
窗口函数对一组与当前行相关的表行执行计算,这组行称为“窗口”。与聚合函数不同,窗口函数不会将多行结果聚合为一行,而是为查询结果集的每一行都返回一个值。
SQL
SELECT name, department, salary, RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rank_within_department
FROM employees;
14. 递归查询 (Recursive Queries):
通常与 CTE 结合使用,用于查询具有层级结构的数据,例如组织结构图或树状结构。上面的 CTE 示例中就包含了一个递归查询。
WITH RECURSIVE Subordinates AS (
-- 锚成员:选择起始员工
SELECT id, name, manager_id
FROM Employees
WHERE id = 2
UNION ALL
-- 递归成员:选择当前层级员工的下属
SELECT e.id, e.name, e.manager_id
FROM Employees e
INNER JOIN Subordinates s ON e.manager_id = s.id
)
-- 从 CTE 中选择结果
SELECT id, name
FROM Subordinates;
其他概念
与DML、DDL类似的还有:
DCL (Data Control Language): 数据控制语言。它用于控制数据库的访问权限和安全性。DCL 主要包含以下两个命令:
- GRANT: 授予用户或角色特定的权限。
- REVOKE: 撤销用户或角色已授予的权限。