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

简介: 从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: 撤销用户或角色已授予的权限。
目录
相关文章
|
24天前
|
机器学习/深度学习 人工智能 运维
运维的未来,不是加班修Bug,而是AI自愈
运维的未来,不是加班修Bug,而是AI自愈
80 7
|
18天前
|
存储 测试技术 开发者
NVFP4量化技术深度解析:4位精度下实现2.3倍推理加速
本文深入解析NVIDIA推出的NVFP4量化技术,探讨其在Blackwell GPU架构下的性能优势。通过对比主流4位量化方法,分析NVFP4在精度、内存和推理吞吐量方面的表现,结合LLM-Compressor与vLLM框架展示量化与部署实践,验证其在消费级与企业级应用中的高效性与实用性。
134 15
NVFP4量化技术深度解析:4位精度下实现2.3倍推理加速
|
19天前
|
缓存 NoSQL 前端开发
如何开发工程项目部管理系统中的物资管理板块(附架构图+流程图+代码参考)
工程项目部物资管理需构建“申请→审批→采购→入库→领用→盘点→预警”闭环流程,实现库存可视、差异可控、现场高效。本文详解落地思路、架构设计、关键表结构、业务流程及开发技巧,并提供核心代码示例与FAQ,助力企业实现数据驱动的物资管理体系。
|
6天前
|
JSON 监控 API
掌握使用 requests 库发送各种 HTTP 请求和处理 API 响应
本课程全面讲解了使用 Python 的 requests 库进行 API 请求与响应处理,内容涵盖环境搭建、GET 与 POST 请求、参数传递、错误处理、请求头设置及实战项目开发。通过实例教学,学员可掌握基础到高级技巧,并完成天气查询应用等实际项目,适合初学者快速上手网络编程与 API 调用。
209 130
|
6天前
|
人工智能 自然语言处理 机器人
向量化与嵌入模型:RAG系统背后的隐形英雄
传统搜索只懂字面不懂含义,向量化技术让AI真正理解语言。从日常类比到实际案例,揭秘为何向量化技术是RAG的灵魂,以及如何用最少的努力构建最聪明的AI应用。
100 10
|
17天前
|
XML JSON 数据库
大模型不听话?试试提示词微调
想象一下,你向大型语言模型抛出问题,满心期待精准回答,得到的却是答非所问,是不是让人抓狂?在复杂分类场景下,这种“大模型不听话”的情况更是常见。
116 9
|
16天前
|
缓存 Java Spring
Spring循环依赖:当两个Bean陷入鸡生蛋死循环时...
Spring中循环依赖问题常见于Bean相互依赖时,尤其在单例模式下。文章深入解析了循环依赖的成因及Spring的三级缓存解决方案,帮助理解Bean生命周期与依赖管理。
|
23天前
|
人工智能 程序员 定位技术
和AI谈恋爱指南:从尬聊到心有灵犀
想让AI理解你的需求?本文用最轻松有趣的方式教你掌握提示词工程,从小白到高手,让ChatGPT成为你最得力的助手。通过生动的类比和实战案例,轻松掌握与AI对话的艺术!
|
21天前
|
安全 Windows
修改Windows鼠标滚轮方向
本文介绍了如何在Windows系统中自定义鼠标滚轮方向。通过设备管理器识别鼠标硬件信息,找到对应的注册表项,修改`FlipFlopWheel`键值即可实现滚轮方向反转。操作简单,适用于单/多鼠标用户,提升操作体验。
313 5
|
19天前
|
负载均衡 网络协议 Linux
网络ping不通到底有多少原因?一文搞明白!
网络ping不通是网络中出现频率最高的故障之一,同时也是最让人抓狂的故障,谁没遇到过?今天就和你细说下ping不通的原因,看看能不能和你遇到的情况对上号。
263 0