跳至主要内容

数据科学家的 SQL 命令

通过示例和可参考的工作簿笔记,学习 SQL 的基础与常用命令。
· 12分钟 阅读

使用 AI 探索

作为数据科学家或数据分析师,数据自然是工作核心。获取数据的来源可能有很多。通常数据存放在 SQL 数据库中,这种情况下,理解 SQL 查询命令对于成功完成工作至关重要。本文将带您从一些基础命令入门,逐步覆盖更高级的操作,帮助您在担任数据分析师或数据科学家时得心应手。

为便于理解上述命令,我们将使用开源的 DVD 租赁数据库来演示这些命令的用法。您可以在此处查看实体关系图(ERD)。

本文中,我们在本地搭建了该数据库,并使用 pgAdmin4 完成所有查询,因此使用的是 PostgreSQL。许多(如果不是全部)命令在其他 SQL 服务器中也应可用。PostgreSQL 的安装和数据库的搭建超出了本文范围;关于这些流程,建议参考 DataCamp 的PostgreSQL 新手指南等资源。

简单的数据检索

SELECT FROM

最简单的检索示例是获取数据库中某张表的全部内容。如果我们想知道 “Category” 表中的所有影片类别,您可以输入如下命令:

SELECT *
FROM category

返回结果(仅显示前 10 行):

SELECT FROM

这里我们在 FROM 后指定了表名。由于我们想要表中的所有内容,因此在 SELECT 后使用 * 表示选择全部。

DISTINCT

有时我们只关心唯一值。例如,如果我们想知道 film 表中的所有 MPAA 分级,使用 DISTINCT 可以仅返回唯一值:

SELECT DISTINCT(rating)
FROM film

返回结果:

DISTINCT

带简单条件的数据检索

WHERE

还有一些情况,我们只想检索满足特定条件的记录。可以使用 WHERE 子句在返回数据前指定必须满足的条件。实质上,这是根据条件筛选记录。例如,如果我们只想知道分级为 PG-13 的电影:

SELECT title
FROM film
WHERE rating = 'PG-13'

返回结果(仅显示前 10 行):

Data Retrieval with Simple Conditions 1

另外,我们也可以找出租赁费率不低于 4.5 的电影列表:

SELECT title, rental_rate
FROM film
WHERE rental_rate >= 4.5

返回结果(仅显示前 10 行):

Data Retrieval with Simple Conditions 2

ORDER BY

对返回结果进行排序常常有助于我们更好地理解数据。比如,我们仍想查看 PG-13 电影列表,但希望按片长从长到短排序,并显示相应片长。使用 ORDER BY 就能轻松实现:

SELECT title, length
FROM film
WHERE rating = 'PG-13'
ORDER BY length DESC

返回结果(仅显示前 10 行):

ORDER BY

LIMIT

有时我们只关心有限数量的记录。这里我们只对片长最长的前 10 部 PG-13 电影感兴趣,因此可以使用 LIMIT 子句。

SELECT title, length
FROM film
WHERE rating = 'PG-13'
ORDER BY length DESC
LIMIT 10

返回结果:

LIMIT

聚合

GROUP BY & COUNT( )

聚合常用于对数据集进行汇总以获取洞见。聚合通常与 GROUP BY 子句一起使用。比如,我们需要知道每位顾客目前租了多少次影片,可以统计租赁次数,并按租赁次数从高到低排序。

SELECT customer_id, COUNT(rental_id)
FROM rental
GROUP BY customer_id
ORDER BY COUNT(rental_id) DESC

返回结果(仅显示前 10 行):

GROUP BY & COUNT( )

SUM( )

那么每位顾客总共花了多少钱呢?我们可以使用 SUM 聚合函数来完成。这里我们希望按总花费从高到低排序。

SELECT customer_id, SUM(amount)
FROM payment
GROUP BY customer_id
ORDER BY SUM(amount) DESC

返回结果(仅显示前 10 行):

SUM( )

AVG( ) & HAVING

在计算分组聚合后,可以使用 HAVING 添加额外条件。例如,我们想知道平均片长超过 115 分钟的 MPAA 分级类别?

SELECT rating, AVG(length)
FROM film
GROUP BY rating
HAVING AVG(length) > 115

返回结果:

AVG( ) & HAVING

MIN( ) & 别名

使用聚合并不总是需要分组。如果您想知道最短的影片片长,可以直接使用 MIN 函数。我们还希望该列显示为 “shortest_movie_length”,可以通过使用 “AS” 指定别名来实现。

SELECT MIN(length) AS shortest_movie_length
FROM film

返回结果(仅显示前 10 行):

MIN( ) & Alias

您也可以为表名使用别名。这在处理较长的表名时很有用,尤其是在进行表连接(见下文)时。我们也需要注意别名的命名方式,因为它会显著影响代码的可读性。

还有其他有用的聚合函数,您可以在SQL 入门课程中进一步了解。

连接

INNER JOIN(JOIN)

您可能已经注意到,仅查看单张表能做的事情很有限。我们经常需要同时查看多张表中的数据。这时就要使用 JOIN。我们可以基于一个公共键来连接表。例如,如果我们要查找非英语的影片:

SELECT film.title, language.name
FROM film
JOIN language
    ON film.language_id = language.language_id
WHERE language.name != 'English'

(请注意,此数据库中只有英语影片。运行上述查询将得到一个空表。)

您也可以连接多张表。比如,下面我们想知道各个类别及其对应的平均租赁费率。我们还按平均租赁费率从高到低返回结果。

SELECT category.name, AVG(film.rental_rate) AS average_rating
FROM film
JOIN film_category
    ON film.film_id = film_category.film_id
JOIN category
    ON film_category.category_id = category.category_id
GROUP BY category.name
ORDER BY average_rating DESC

返回结果(仅显示前 10 行):

INNER JOIN (JOIN)

连接有多种类型:INNER JOIN、LEFT JOIN、RIGHT JOIN、OUTER JOIN、CROSS JOIN。这里我们使用的是 JOIN,默认等同于 INNER JOIN,即只有同时出现在两张表中的记录才会被返回。若想进一步了解这些连接的区别,您可以查看在 SQL 中进行数据连接课程。

更改数据类型

CAST( )

在前面计算金额的示例中,您可能注意到 SQL 仅将这些值视为数字。我们可以使用 CAST 函数将其转换为金额类型:

SELECT customer_id, CAST(SUM(amount) AS money)
FROM payment
GROUP BY customer_id
ORDER BY SUM(amount) DESC

返回结果(仅显示前 10 行):

CAST( )

可转换的不仅是金额。我们也可以将数字转换为浮点数、文本,甚至日期和时间。

ROUND( )

同样,我们也可以对数字进行四舍五入。在上面计算平均租赁费率的例子中,结果包含很多小数位。我们可以使用 ROUND 函数保留两位小数:

SELECT category.name, ROUND(AVG(film.rental_rate), 2) AS average_rating
FROM film
JOIN film_category
    ON film.film_id = film_category.film_id
JOIN category
    ON film_category.category_id = category.category_id
GROUP BY category.name
ORDER BY average_rating DESC

返回结果(仅显示前 10 行):

复杂条件

CASE 语句

可以想见,通过组合上述诸多函数和子句,您可以执行相当复杂的查询。不过,有些场景中这些基础还不够用。比如,我们想新增一列表示影片时长类别:小于 50 分钟标记为 “Short Film”,大于 150 分钟标记为 “Long Film”,其余标记为 “Medium Film”。我们可以使用 CASE 语句实现:

SELECT title, length,
CASE
    WHEN length < 50 THEN 'Short Film'
    WHEN length > 150 THEN 'Long Film'
    ELSE 'Medium Film'
END AS Movie_Length_Category
FROM film

返回结果(仅显示前 10 行):

CASE Statement

如您所见,当满足指定条件时会返回相应结果。这类似于编程语言中的 “if-else” 语句。

子查询

现在我们可能想知道哪些电影的片长最长,以及它们的标题。为此,我们可以仍然使用上文介绍的 WHERE 子句,只是条件更复杂一些:

SELECT title, length
FROM film
WHERE length = (SELECT max(length)
                FROM film)

返回结果:

Subqueries

子查询可以构建复杂查询。在此示例中,我们想要找出租赁费率高于所有喜剧(Comedy)影片平均租赁费率的电影标题列表:

SELECT title, rental_rate
FROM film
WHERE rental_rate >
(SELECT AVG(film.rental_rate) AS Average_Rating
FROM film
JOIN film_category AS cat_id
    ON film.film_id=cat_id.film_id
JOIN category AS cat
    ON cat_id.category_id=cat.category_id
GROUP BY cat.name, cat_id.category_id
HAVING cat.name = 'Comedy')

返回结果(仅显示前 10 行):

Which returns (only the first 10 rows shown):

在这个示例中,我们首先计算喜剧影片的平均租赁费率。该查询得到的值(见上方括号内)随后被用作 WHERE 子句中的条件,以此查找电影标题。

公用表表达式(CTE)

在更复杂的示例中,我们可以使用公用表表达式(CTE)来保存一个临时表,然后从中提取所需信息。比如,我们想知道每位员工每周处理的平均租赁次数。

WITH weekly_rentals AS(
    SELECT
        staff_id,
        DATE_PART('week', payment_date) AS week,
        COUNT(rental_id) AS rental_numbers
    FROM payment
    GROUP BY staff_id, week)

SELECT staff_id, AVG(rental_numbers)
FROM weekly_rentals
GROUP BY staff_id

返回结果:

Common Table Expressions (CTEs)

上述情况下,我们将每周的租赁次数保存到名为 “weekly_rentals” 的临时表中,然后基于其再次查询以获得最终结果。

窗口函数

窗口函数是另一类重要的 SQL 技能,能帮助您更进一步。尽管窗口函数通常被视为中高级技能,许多数据分析师和数据科学家都认为它们对工作至关重要,因此花时间熟悉它们非常值得。

OVER( ) & PARTITION BY 假设我们想要查出每位员工每周的租赁数量以及整体总租赁数量,同时返回员工 ID 和周序号。我们可以复用上例生成的 CTE,如下所示:

WITH weekly_rentals AS(
    SELECT
        staff_id,
        DATE_PART('week', payment_date) AS week,
        COUNT(rental_id) AS rental_numbers
    FROM payment
    GROUP BY staff_id, week)

SELECT *, SUM(rental_numbers) OVER() AS total_rentals
FROM weekly_rentals

返回结果(仅显示前 10 行):

Window Functions

代码中的 OVER 表示这是一个窗口函数。这里我们让窗口函数(此处为 SUM)应用于每一行。我们也可以将窗口函数与 PARTITION BY 一起使用,以在指定的“分区”内应用窗口函数。比如,现在我们想要计算每周的总租赁次数(而非整体总数),同时保留其他列。下面的示例同样复用了之前生成的 CTE。

WITH weekly_rentals AS(
    SELECT
        staff_id,
        DATE_PART('week', payment_date) AS week,
        COUNT(rental_id) AS rental_numbers
    FROM payment
    GROUP BY staff_id, week)

SELECT *, SUM(rental_numbers) OVER(PARTITION BY week) AS total_weekly_rentals
FROM weekly_rentals

返回结果(仅显示前 10 行):

Window Functions 2

新增的这一列包含每周的总租赁次数,而不是累计总数。

本文仅介绍了几个对许多公司的数据分析师或数据科学家而言非常重要的窗口函数入门示例。我们鼓励您通过提供的链接进一步学习这些命令。您也可以查看SQL 进阶课程来学习或巩固 SQL 技能。此外,您可以选取一个开源数据库,围绕实际问题动手练习这些不同的命令。

主题