基于“第二高的薪水”衍生出的子查询问题

简介: 从LeetCode第176题答案中衍生的标量子查询特性及其它子查询的特性

在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。

但是遇到了一个问题:

解答错误1.png

可以看到,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): 返回存在于第一个结果集但不存在于第二个结果集中的行。(并非所有数据库都直接支持 EXCEPTMINUS

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: 撤销用户或角色已授予的权限。
目录
相关文章
Vue3 子/父组件相互调用
Vue3 子/父组件相互调用
479 0
|
4月前
|
机器学习/深度学习 人工智能 运维
运维的未来,不是加班修Bug,而是AI自愈
运维的未来,不是加班修Bug,而是AI自愈
175 7
|
4月前
|
机器学习/深度学习 自然语言处理 运维
“日志别再只会翻了,教它自己说话”——聊聊用 NLP 玩转日志分析
“日志别再只会翻了,教它自己说话”——聊聊用 NLP 玩转日志分析
391 0
|
3月前
|
前端开发 安全 Java
基于springboot+vue开发的会议预约管理系统
一个完整的会议预约管理系统,包含前端用户界面、管理后台和后端API服务。 ### 后端 - **框架**: Spring Boot 2.7.18 - **数据库**: MySQL 5.6+ - **ORM**: MyBatis Plus 3.5.3.1 - **安全**: Spring Security + JWT - **Java版本**: Java 11 ### 前端 - **框架**: Vue 3.3.4 - **UI组件**: Element Plus 2.3.8 - **构建工具**: Vite 4.4.5 - **状态管理**: Pinia 2.1.6 - **HTTP客户端
423 4
基于springboot+vue开发的会议预约管理系统
|
8月前
|
安全 API 数据安全/隐私保护
12种API认证全场景解析:从Basic到OAuth2.0,哪个认证最适合你的业务?
在API认证领域,从简单的Key-Value到高级的OAuth2.0和JWT,共有12种主流认证方式。本文详解了每种方式的意义、适用场景及优劣,并通过认证方式矩阵对比常见工具(如Postman、Apifox)的支持情况。此外,还介绍了企业级安全功能,如密钥保险箱、动态令牌和合规审计。选择合适的认证方式不仅能提升安全性,还能大幅提高开发效率。未来,自动化认证矩阵或将成为API调试的核心趋势。
|
5月前
|
供应链 BI
OA、CRM、ERP,到底有啥区别?
本文深入解析了企业在数字化过程中常见的OA、CRM、ERP三大系统的功能与选型逻辑。OA系统聚焦内部流程与员工管理,适用于考勤、报销等行政事务;CRM专注客户与销售管理,适合需要跟进客户、提升成交率的企业;ERP则侧重企业核心业务,如采购、库存、财务等。文章结合企业不同发展阶段和关注重点,给出了系统的优先级选择建议,帮助企业明确数字化转型的路径。
|
缓存 JavaScript 前端开发
vue2进阶篇:vue-router之嵌套(多级)路由
vue2进阶篇:vue-router之嵌套(多级)路由
440 3
|
JSON 移动开发 监控
快速上手|HTTP 接口功能自动化测试
HTTP接口功能测试对于确保Web应用和H5应用的数据正确性至关重要。这类测试主要针对后台HTTP接口,通过构造不同参数输入值并获取JSON格式的输出结果来进行验证。HTTP协议基于TCP连接,包括请求与响应模式。请求由请求行、消息报头和请求正文组成,响应则包含状态行、消息报头及响应正文。常用的请求方法有GET、POST等,而响应状态码如2xx代表成功。测试过程使用Python语言和pycurl模块调用接口,并通过断言机制比对实际与预期结果,确保功能正确性。
647 3
快速上手|HTTP 接口功能自动化测试
|
Kubernetes 容器
搭建K8S环境单机K8S集群
搭建K8S环境单机K8S集群
791 0
|
JavaScript NoSQL Java
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
425 0