找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1538

积分

0

好友

193

主题
发表于 2025-12-31 04:10:41 | 查看: 25| 回复: 0

随着数据规模不断膨胀,市场对于精通SQL的数据从业者的需求已不仅限于基础层面。掌握中级乃至高级的SQL概念,对于提升数据分析效率与通过技术面试都至关重要。在云栈社区的技术讨论中,高效的SQL查询也是经久不衰的话题。本文将深入探讨10个关键的高级SQL查询技巧,助你从容应对复杂的数据处理场景。

1. 公共表表达式(CTEs)

当你需要频繁使用子查询时,公共表表达式(CTEs)便能大显身手——它本质上是创建一个临时的命名结果集。

使用CTEs是模块化与分解代码的绝佳方式,就像你将一篇文章拆分成多个段落。试想以下在WHERE子句中嵌套了子查询的语句:

SELECT
    name,
    salary
FROM
    People
WHERE
        NAME IN ( SELECT DISTINCT NAME FROM population WHERE country = "Canada" AND city = "Toronto" )
  AND salary >= (
    SELECT
        AVG( salary )
    FROM
        salaries
    WHERE
        gender = "Female")

这看起来已经有些复杂了。如果查询中需要更多的子查询,代码会变得多么难以维护?此时,CTEs的优势就体现出来了。

with toronto_ppl as (
    SELECT DISTINCT name
    FROM population
    WHERE country = "Canada"
      AND city = "Toronto"
)
   , avg_female_salary as (
        SELECT AVG(salary) as avgSalary
        FROM salaries
        WHERE gender = "Female"
    )
SELECT name
     , salary
FROM People
WHERE name in (SELECT DISTINCT FROM toronto_ppl)
  AND salary >= (SELECT avgSalary FROM avg_female_salary)

现在逻辑清晰多了:WHERE子句用于筛选出在多伦多的人。CTEs不仅允许你将代码分解为更小的、可管理的块,还能为每个CTE分配一个变量名(如toronto_pplavg_female_salary),极大地增强了可读性。

此外,CTEs还支持实现更高级的技术,例如创建递归查询。

2. 递归CTEs

递归CTE是一种能够自我引用的CTE,类似于编程中的递归函数。它在处理具有层次结构的数据时特别有用,例如组织架构图、文件系统或网页链接图。

一个递归CTE通常包含三个部分:

  • 锚定成员:返回CTE基本结果的初始查询。
  • 递归成员:引用CTE自身的递归查询。该部分通过UNION ALL与锚定成员的结果集合并。
  • 终止条件:确保递归能够停止。

以下是一个用于获取每位员工及其经理ID的递归CTE示例:

with org_structure as (
    SELECT id
         , manager_id
    FROM staff_members
    WHERE manager_id IS NULL
    UNION ALL
    SELECT sm.id
         , sm.manager_id
    FROM staff_members sm
             INNER JOIN org_structure os
                        ON os.id = sm.manager_id
)

3. 临时函数

临时函数是提升代码质量的重要工具,原因如下:

  • 它允许你将代码逻辑封装成更小的、可复用的单元。
  • 它有助于编写更清晰、更易读的代码。
  • 它可以防止代码重复,类似于在Python中定义和使用函数。

考虑以下示例,它使用CASE语句根据工作年限划分职级:

SELECT name
     , CASE WHEN tenure < 1 THEN "analyst"
            WHEN tenure BETWEEN 1 and 3 THEN "associate"
            WHEN tenure BETWEEN 3 and 5 THEN "senior"
            WHEN tenure > 5 THEN "vp"
            ELSE "n/a"
        END AS seniority
FROM employees

相反,你可以创建一个临时函数来封装这个职级判断逻辑:

CREATE TEMPORARY FUNCTION get_seniority(tenure INT64) AS (
   CASE WHEN tenure < 1 THEN "analyst"
        WHEN tenure BETWEEN 1 and 3 THEN "associate"
        WHEN tenure BETWEEN 3 and 5 THEN "senior"
        WHEN tenure > 5 THEN "vp"
        ELSE "n/a"
   END
);
SELECT name
     , get_seniority(tenure) as seniority
FROM employees

通过使用临时函数,主查询变得简洁且易于理解,并且get_seniority函数可以在其他查询中被重复使用。

4. 使用CASE WHEN进行数据透视

CASE WHEN语句因其强大的条件逻辑能力,在SQL问题中极为常见。它允许你根据其他变量的值来分配特定的值或类别。

一个不太为人熟知但极其有用的功能是,CASE WHEN可以用来“透视”数据。例如,如果你有一个“月份”列,并且希望为每个月份创建一个单独的列,你可以使用一系列CASE WHEN语句来实现。

示例问题:编写一个SQL查询来重新格式化表格,使每个月都有一个对应的收入列。

Initial table:
+------+---------+-------+
| id   | revenue | month |
+------+---------+-------+
| 1    | 8000    | Jan   |
| 2    | 9000    | Jan   |
| 3    | 10000   | Feb   |
| 1    | 7000    | Feb   |
| 1    | 6000    | Mar   |
+------+---------+-------+

Result table:
+------+-------------+-------------+-------------+-----+-----------+
| id   | Jan_Revenue | Feb_Revenue | Mar_Revenue | ... | Dec_Revenue |
+------+-------------+-------------+-------------+-----+-----------+
| 1    | 8000        | 7000        | 6000        | ... | null        |
| 2    | 9000        | null        | null        | ... | null        |
| 3    | null        | 10000       | null        | ... | null        |
+------+-------------+-------------+-------------+-----+-----------+

5. EXCEPT vs NOT IN

EXCEPT和NOT IN操作符的作用几乎是相同的,它们都用于比较两个查询/表之间的行。然而,两者之间存在一些细微但重要的差别。

首先,EXCEPT会自动过滤掉重复的行,并返回唯一的结果集,而NOT IN则不会。
其次,EXCEPT要求两个查询/表具有相同数量的列,而NOT IN通常用于比较单个列。

6. 自联结

自联结是指一个表与自身进行连接。你或许认为这没什么用处,但实际上它在实际场景中非常普遍。在许多情况下,数据往往存储在一个大表中,而非多个小表中。这时,自联结就成为解决特定问题的关键。

让我们看一个例子。

示例问题:给定下面的员工表,编写一个SQL查询,找出薪水高于其经理的员工。对于下表来说,Joe是唯一一个薪水高于经理的员工。

+----+-------+--------+-----------+
| Id | Name  | Salary | ManagerId |
+----+-------+--------+-----------+
| 1  | Joe   | 70000  | 3         |
| 2  | Henry | 80000  | 4         |
| 3  | Sam   | 60000  | NULL      |
| 4  | Max   | 90000  | NULL      |
+----+-------+--------+-----------+

Answer:
SELECT
    a.Name as Employee
FROM
    Employee as a
        JOIN Employee as b on a.ManagerID = b.Id
WHERE a.Salary > b.Salary

7. Rank vs Dense Rank vs Row Number

对行或数值进行排名是一个非常常见的需求。以下是一些公司常用的排名场景:

  • 按消费额、利润等对客户进行排名。
  • 对销量最高的产品进行排名。
  • 对销售额最高的国家进行排名。
  • 按观看时长、独立观众数等对视频进行排名。

在SQL中,有几种方法可以为行分配“排名”,我们将通过示例来探索它们。考虑以下查询及其结果:

SELECT Name
     , GPA
     , ROW_NUMBER() OVER (ORDER BY GPA desc)
  , RANK() OVER (ORDER BY GPA desc)
  , DENSE_RANK() OVER (ORDER BY GPA desc)
FROM student_grades

学生GPA排名对比
图1:ROW_NUMBER、RANK和DENSE_RANK函数排名结果对比

  • ROW_NUMBER() 为每一行分配一个从1开始的唯一序号。当出现并列情况时(例如Bob和Carrie),如果没有定义进一步的排序规则,ROW_NUMBER() 会任意分配数字。
  • RANK() 同样从1开始排名,但当出现并列时,RANK() 会分配相同的名次,并且后续名次会出现跳跃(即留下空位)。
  • DENSE_RANK()RANK() 类似,但在出现并列名次后不会留下空位。请注意,使用 DENSE_RANK(),Daniel排名第3,而不是第4。

8. 计算差值

另一个常见需求是比较不同时间段的值。例如,本月销售额与上月销售额的差值(Delta)是多少?或者本月销售额与去年同期本月销售额的差值是多少?

当需要比较不同时间段的值以计算差异时,LEAD()LAG() 窗口函数就派上用场了。

以下是一些示例:

-- 将每月的销售额与上月进行比较
SELECT month
       , sales
       , sales - LAG(sales, 1) OVER (ORDER BY month)
FROM monthly_sales

-- 将每月的销售额与去年同月进行比较
SELECT month
        , sales
        , sales - LAG(sales, 12) OVER (ORDER BY month)
FROM monthly_sales

9. 计算累计总数

如果你已经了解了 ROW_NUMBER()LAG()/LEAD(),那么这个技巧可能不会让你感到意外。但如果你还不了解,那么这可能是最有用的窗口函数之一,尤其是在你需要可视化增长趋势时!

结合 SUM() 函数与窗口函数,我们可以轻松计算累计总数。参考以下示例:

SELECT Month
        , Revenue
        , SUM(Revenue) OVER (ORDER BY Month) AS Cumulative
FROM monthly_revenue

月度收入累计表
图2:使用窗口函数计算月度累计收入

10. 日期时间处理

数据科学面试中,你几乎肯定会遇到涉及日期时间数据的SQL问题。例如,你可能需要按时间维度对数据分组,或者将日期格式从“DD-MM-YYYY”转换为“YYYY-MM-DD”。

你需要熟悉的一些常用日期时间函数包括:

  • EXTRACT
  • DATE_TRUNC
  • DATE_ADD, DATE_SUB
  • DATEDIFF

示例问题:给定天气表,编写一个SQL查询,找出所有温度高于前一天(昨天)的日期的ID。

+---------+------------------+------------------+
| Id(INT) | RecordDate(DATE) | Temperature(INT) |
+---------+------------------+------------------+
|       1 |       2015-01-01 |               10 |
|       2 |       2015-01-02 |               25 |
|       3 |       2015-01-03 |               20 |
|       4 |       2015-01-04 |               30 |
+---------+------------------+------------------+

Answer:
SELECT
    a.Id
FROM
    Weather a,
    Weather b
WHERE
    a.Temperature > b.Temperature
  AND DATEDIFF(a.RecordDate, b.RecordDate) = 1

熟练掌握这些SQL高级查询技巧,不仅能让你在处理复杂数据时游刃有余,也能在技术面试中脱颖而出。




上一篇:Vue3 + WebRTC:实现仿微信扫一扫的前端拍照功能
下一篇:Spring依赖注入深度解析:从构造器注入最佳实践到云原生演进
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 18:40 , Processed in 0.340755 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表