继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

数据专业人士的高级SQL技巧

跃然一笑
关注TA
已关注
手记 314
粉丝 40
获赞 164

要开始与数据打交道,了解像SQL这样的工具是非常重要的。SQL是一种编程语言,用于存储、处理和管理关系数据库中的数据,不过,它的应用现在已经超出了关系数据库的范围,比如在Big Data(大数据)处理方面。

自20世纪70年代以来引入的SQL在数据工程、数据分析和数据科学等领域中仍是核心工具。该语言可以在多种平台上运行,例如SQL Server、Oracle、PostgreSQL、Snowflake、Google BigQuery以及基于Spark的平台,例如Databricks。

即使你只打算用Python、R或其他编程语言来做数据工程师或数据科学家,不学SQL也几乎是不可能的。

公用表表达式 (CTE)

通用表表达式(CTE)被引入SQL以简化SQL查询过程。CTE是一个临时的结果集,可以在SELECTINSERTUPDATEDELETE语句中引用它。它允许我们将复杂的查询拆分为更小、更易读的部分。

递归CTE是CTE一个非常实用的功能,它可以帮助您解决诸如层级数据查询等问题,例如组织中的员工层级结构,或生成像日历这样的序列数据。

下面是一个创建表并插入记录的SQL代码的例子。

    创建表 Employees 如下,  
    (  
      EmployeeID int,  
      名字 string,  
      姓氏 string,  
      经理ID int   
    )  

    插入 INTO Employees 值 (1, '哈珀', '韦斯特布鲁克', 空);  
    插入 INTO Employees 值 (2, '莱姆', '卡林顿', 1);  
    插入 INTO Employees 值 (3, '埃弗琳', '拉德克利夫', 1);  
    插入 INTO Employees 值 (4, '梅森', '艾尔布赖特', 2);  
    插入 INTO Employees 值 (5, '伊莎拉', '惠特曼', 2);  
    插入 INTO Employees 值 (6, '诺亚', '斯廷格尔', 3);  
    插入 INTO Employees 值 (7, '鲁比', '伦诺克斯', 3);  
    插入 INTO Employees 值 (8, '卡莱布', '温斯洛', 5);  
    插入 INTO Employees 值 (9, '艾弗莉', '辛克莱尔', 6);  
    插入 INTO Employees 值 (10, '奥利弗', '贝克特', 6).  

以下代码将生成一个包含员工及其对应经理信息的结果。通过使用 自连接,我们将根据 “MgrID” 将每个员工与其对应的经理匹配起来。

    WITH RECURSIVE   
      cteReports (EmpID, FirstName, LastName, MgrID, EmpLevel)  
      AS  
      (  
        SELECT EmployeeID, FirstName, LastName, ManagerID, 1  
        FROM Employees  
        WHERE ManagerID IS NULL  
        UNION ALL  
        SELECT e.EmployeeID, e.FirstName, e.LastName, e.ManagerID,   
          r.EmpLevel + 1  
        FROM Employees e  
          INNER JOIN cteReports r  
            ON e.ManagerID = r.EmpID  
      )  
    SELECT  
      FirstName || ' ' || LastName AS FullName,   
      EmpLevel,  
      (SELECT FirstName || ' ' || LastName FROM Employees   
        WHERE EmployeeID = cteReports.MgrID) AS Manager  
    FROM cteReports   
    ORDER BY EmpLevel, MgrID

结果是这样的:

比如,在数据仓库中进行基于日期的分析时,创建日期维度表很有用。日期维度表以一种结构化的方式来处理日期范围、节假日、财政年度及其他时间相关的属性。

以下是一个示例,演示如何使用递归CTE(通用表表达式)来生成日期维度表。递归CTE是一种可以在SQL中用来解决需要递归查询的数据问题的技术。日期维度表是一种包含多个日期维度信息的表格。

WITH RECURSIVE dates (Date) AS (  
    SELECT cast('2024-01-01' AS date) AS Date, extract('year' FROM cast('2024-01-01' AS date)) AS year, extract('month' FROM cast('2024-01-01' AS date)) AS month  

    UNION ALL   

    SELECT Date + INTERVAL '1' DAY, extract('year' FROM Date + INTERVAL '1' DAY) AS year, extract('month' FROM Date + INTERVAL '1' DAY) AS month  
    FROM dates  
    WHERE Date <= '2025-12-31' -- 请在这里填入结束日期   
)  

SELECT *  
FROM dates;

在Databricks SQL里,不支持递归公用表表达式。但是,你可以使用**EXPLODE**函数达到相同的效果。

删除重复的行

当我们处理原始数据时,一个常见的场景是需要去除重复项或仅获取最新的版本。在 SQL 中,使用 ROW_NUMBER() 窗口函数是一种高效处理这种情况的方法。该函数允许你根据指定的排序方式为数据分区内的每一行分配一个唯一的行号。

以下是一个使用 ROW_NUMBER() 来选择记录最新版本的示例。

查询会把记录编号,最新的记录会被编号为1。这样我们就能从表中筛选出最新的记录。

    -- 以下是一些示例数据  
    with dane (id, name, age, date) as  
    (  
    select 1, 'John Smit', 19, '2020-01-01'   
    UNION ALL   
    select 2, 'Eva Nowak', 21, '2021-01-01'  
    UNION ALL   
    select 3, 'Danny Clark', 24, '2021-01-01'  
    UNION ALL   
    select 4, 'Alicia Kaiser', 25, '2021-01-01'  
    UNION ALL   
    select 5, 'John Smit', 19, '2021-01-01'  
    UNION ALL   
    select 6, 'Eva Nowak', 21, '2022-01-01'  
    )  

    select *  
    from (  
     select   
     row_number() over (partition by name order by date) rn,  

*  
     from  
     dane  
     )  
    where   
    rn = 1  
    ;

使用子查询删除重复行

在SQL里,有许多方法可以达到相同的结果。我们可以使用一个(子查询)来找出每个组里的最新记录,然后用一个INNER JOIN来排除重复记录。

WITH dane (id, name, 年龄, 日期) AS  
(  
SELECT 1, 'John Smit', 19, '2020-01-01'   
UNION ALL -- 合并重复记录  
SELECT 2, 'Eva Nowak', 21, '2021-01-01'  
UNION ALL -- 合并重复记录  
SELECT 3, 'Danny Clark', 24, '2021-01-01'  
UNION ALL -- 合并重复记录  
SELECT 4, 'Alicia Kaiser', 25, '2021-01-01'  
UNION ALL -- 合并重复记录  
SELECT 5, 'John Smit', 19, '2021-01-01'  
UNION ALL -- 合并重复记录  
SELECT 6, 'Eva Nowak', 21, '2022-01-01'  
)  

SELECT   
a.*  
FROM  
dane a  
INNER JOIN ( -- 内连接,选择每个名字的最新日期  
SELECT   
name, MAX(日期) 最后日期 FROM dane GROUP BY name -- 按名称分组  
) b ON a.name = b.name AND a.日期 = b.最后日期 -- 连接条件  
;  

子查询**b****** 找出每个名称的最大日期。然后我们根据子查询的结果,只保留最近日期的记录。

例:

查找新条目或不存在的条目

当从源表加载数据时,一个常见的任务是加载仅新的记录,即之前不在目标表中的记录。一个有效的方法是使用 SQL 中的 **EXISTS** 命令,这个命令可以检查源表中的记录是否已在目标表中存在。如果不存在,就可以插入或处理该记录。

用于练习的测试数据 (yóu yú liànxiàn de cèshī shùjù)

假设 raw_Employees 是原始的表格,Employees 是我们要转换的目标表格。

    创建表 Employees  
    (  
      EmployeeID int,  
      FirstName string,  
      LastName string,  
      ManagerID int   
    );  

    INSERT INTO Employees VALUES (1, 'Harper', 'Westbrook', NULL);  
    INSERT INTO Employees VALUES (2, 'Liam', 'Carrington', 1);  
    INSERT INTO Employees VALUES (3, 'Evelyn', 'Radcliffe', 1);  
    INSERT INTO Employees VALUES (4, 'Mason', 'Albright', 2);  
    INSERT INTO Employees VALUES (5, 'Isla', 'Whitman', 2);  
    INSERT INTO Employees VALUES (6, 'Noah', 'Sterling', 3);  
    INSERT INTO Employees VALUES (7, 'Ruby', 'Lennox', 3);  
    INSERT INTO Employees VALUES (8, 'Caleb', 'Winslow', 5);  
    INSERT INTO Employees VALUES (9, 'Avery', 'Sinclair', 6);  
    INSERT INTO Employees VALUES (10, 'Oliver', 'Beckett', 6);  

    创建表 raw_Employees  
    (  
      EmployeeID int,  
      FirstName string,  
      LastName string,  
      ManagerID int   
    );  
    INSERT INTO raw_Employees VALUES (1, 'Harper', 'Westbrook', NULL);  
    INSERT INTO raw_Employees VALUES (2, 'Liam', 'Carrington', 1);  
    INSERT INTO raw_Employees VALUES (3, 'Evelyn', 'Radcliffe', 1);  
    INSERT INTO raw_Employees VALUES (4, 'Mason', 'Albright', 2);  
    INSERT INTO raw_Employees VALUES (5, 'Isla', 'Whitman', 2);  
    INSERT INTO raw_Employees VALUES (6, 'Noah', 'Sterling', 3);  
    INSERT INTO raw_Employees VALUES (7, 'Ruby', 'Lennox', 3);  
    INSERT INTO raw_Employees VALUES (8, 'Caleb', 'Winslow', 5);  
    INSERT INTO raw_Employees VALUES (9, 'Avery', 'Sinclair', 6);  
    INSERT INTO raw_Employees VALUES (10, 'Oliver', 'Beckett', 6);  
    INSERT INTO raw_Employees VALUES (11, 'Avery', 'Sinclair', 6);  
    INSERT INTO raw_Employees VALUES (12, 'Oliver', 'Beckett', 6);

我们可以用 EXISTS 命令来过滤掉源数据中已经在目标中存在的情况。

    SELECT *  

    FROM  
    raw_Employees a   
    WHERE  
    NOT EXISTS (  
      SELECT 1   
      FROM  
       Employees b  
      WHERE  
       a.EmployeeID = b.EmployeeID  

    )

/ 此查询从 raw_Employees 表中选择所有行,其中 EmployeeID 不在 Employees 表中。/

注意:SQL 术语如 "SELECT", "FROM", "WHERE" 保持不变,表名和列名 "raw_Employees", "Employees", "EmployeeID" 也不翻译,因为它们是特定的标识符。

这可能在报告目的时或仅当向事实表或维度表添加新记录时很有帮助。

另一种方式是使用 LEFT JOINWHERE 条件。这个查询会筛选出不在目标表中的记录。

    SELECT a.*  

    FROM  
    raw_Employees a   
    left join   
     Employees b on a.EmployeeID = b.EmployeeID  
    WHERE  
    b.EmployeeID is null;

/ 从raw_Employees表中选择所有在Employees表中没有对应记录的员工信息 /
/ 从raw_Employees表中选择所有在Employees表中没有对应记录的员工信息,这里的查询目的是找出那些在Employees表中没有匹配记录的员工数据。 /

检查是否存在其他表中的活跃的客户:

当处理数据时,一个常见的任务是检查特定记录是否存在于另一个表中。你可能需要创建一个只显示活跃客户的报告。为了找出这些客户,你可以看看特定的 CustomerID 是否出现在另一个表中。

在这种情况下,可以使用 EXISTS 命令(即EXISTS命令)来满足这个需求。

    -- 示例数据如下  

    CREATE TABLE Customers  
    (  
      CustomerID int,  
      FirstName string,  
      LastName string  
    );  
    INSERT INTO Customers VALUES (1, 'Harper', 'Westbrook');  
    INSERT INTO Customers VALUES (2, 'Liam', 'Carrington');  
    INSERT INTO Customers VALUES (3, 'Evelyn', 'Radcliffe');  
    INSERT INTO Customers VALUES (4, 'Mason', 'Albright');  
    INSERT INTO Customers VALUES (5, 'Isla', 'Whitman');  
    INSERT INTO Customers VALUES (6, 'Noah', 'Sterling');  
    INSERT INTO Customers VALUES (7, 'Ruby', 'Lennox');  
    INSERT INTO Customers VALUES (8, 'Caleb', 'Winslow');  
    INSERT INTO Customers VALUES (9, 'Avery', 'Sinclair');  
    INSERT INTO Customers VALUES (10, 'Oliver', 'Beckett');  
    INSERT INTO Customers VALUES (11, 'Avery', 'Sinclair');  
    INSERT INTO Customers VALUES (12, 'Oliver', 'Beckett');  

    CREATE TABLE orders  
    (  
      OrderID int,  
      CustomerID int,  
      Quantity int,  
      Price decimal(10,2),  
      ProductID int  
    );  

    INSERT INTO orders VALUES (1,1, 10, 5.2, 1);  
    INSERT INTO orders VALUES (2,2, 5, 5.2, 1);  
    INSERT INTO orders VALUES (3,3, 2, 5.2, 1);  
    INSERT INTO orders VALUES (4,4, 4, 5.2, 1);  
    INSERT INTO orders VALUES (5,5, 11, 5.2, 1);  
    INSERT INTO orders VALUES (6,6, 1, 5.2, 1);  
    INSERT INTO orders VALUES (7,7, 1, 5.2, 1);  

    -- 查询活跃的客户信息  

    select *  
    from  
      Customers a   
    WHERE  
    EXISTS (  
      SELECT 1 from orders b   
      WHERE  
      a.CustomerID = b.CustomerID  
    )

结果应该是这样:

活跃用户

如果我们在这条 EXISTS 命令前加上 NOT,就能获取那些未下单的未活跃客户的信息。

SELECT * 
FROM 
客户 a 
WHERE 
NOT EXISTS ( 
SELECT 1 FROM 订单 b 
WHERE 
a.CustomerID = b.CustomerID 
)
/* 查询没有订单的客户 */

不活跃的客户

使用 SQL:填补数据空白

您可能会遇到的一个常见问题是数据中的缺失条目。例如,在导入有关货币的数据时,因为数据只在工作日发布,因此可能会出现空缺。这些空缺需要被填补,以确保我们的计算和报告正常进行。如下屏幕所示,在黄色标记的记录之间存在数据空白。

空档

我们该怎么解决这个问题呢?

为了处理这个问题,我们需要使用一个包含所有所需日期的日历表。我使用了一个递归的CTE来生成日历表。

WITH RECURSIVE 日期表 (日期) AS (  
    SELECT cast('2006-01-01' as date) as 日期  

    UNION ALL   

    SELECT date_add(日期, INTERVAL 1 day)  
    FROM 日期表  
    WHERE 日期 <= '2006-01-31' -- 将结束日期放在这里   
)  
, 货币表 (日期, 价格, 货币) AS (  
SELECT cast('2006-01-02' as date) ,3.2582, 'USD'  
UNION SELECT cast('2006-01-03' as date) ,3.2488  , 'USD'  
UNION SELECT cast('2006-01-04' as date) ,3.1858  , 'USD'  
UNION SELECT cast('2006-01-05' as date) ,3.1416  , 'USD'  
UNION SELECT cast('2006-01-06' as date) ,3.1507  , 'USD'  
-- 数据缺失  
UNION SELECT cast('2006-01-09' as date) ,3.1228  , 'USD'  
UNION SELECT cast('2006-01-10' as date) ,3.128   , 'USD'  
UNION SELECT cast('2006-01-11' as date) ,3.1353  , 'USD'  
UNION SELECT cast('2006-01-12' as date) ,3.1229  , 'USD'  
UNION SELECT cast('2006-01-13' as date) ,3.1542  , 'USD'  
-- 数据缺失  
UNION SELECT cast('2006-01-16' as date) ,3.1321  , 'USD'  
UNION SELECT cast('2006-01-17' as date) ,3.1521  , 'USD'  
UNION SELECT cast('2006-01-18' as date) ,3.1887  , 'USD'  
UNION SELECT cast('2006-01-19' as date) ,3.1772  , 'USD'  
UNION SELECT cast('2006-01-20' as date) ,3.1868  , 'USD'  
-- 数据缺失  
UNION SELECT cast('2006-01-23' as date) ,3.1397  , 'USD'  
UNION SELECT cast('2006-01-24' as date) ,3.1333  , 'USD'  
UNION SELECT cast('2006-01-25' as date) ,3.095   , 'USD'  
UNION SELECT cast('2006-01-26' as date) ,3.1253  , 'USD'  
UNION SELECT cast('2006-01-27' as date) ,3.1379  , 'USD'  
-- 数据缺失  
UNION SELECT cast('2006-01-30' as date) ,3.1559  , 'USD'  
UNION SELECT cast('2006-01-31' as date) ,3.163   , 'USD'  
)  

SELECT  
    日期,  
    价格,  
    货币  
FROM  
(  
SELECT  
    ROW_NUMBER() over (PARTITION BY 货币, a.日期 ORDER BY b.日期 DESC) lp,  
    a.日期,  
    b.日期 org_date,  
    b.价格,      
    货币  
FROM  
    日期表 a LEFT JOIN   
    货币表 b ON b.日期 BETWEEN a.日期 - INTERVAL 3 DAY AND a.日期   

)  
WHERE  
    lp = 1  
ORDER BY 日期;

通过这个查询,我们可以用前一天的数据填补空缺。下面就是查询的结果。

填上的空隙

找到薪水最高的员工

找到像最高薪资的员工或最高消费的客户这样的实体时,可以使用LIMITORDER BY命令。但是,如果有多个实体的值相同,我们可能在结果中看不到所有这些实体。为了解决这个问题,我们可以使用子查询,如下例所示。

WITH Employees (EmployeeID, Name, Salary) AS (  
    SELECT 1, 'John', 5000   
    UNION ALL SELECT 2, 'Jane', 7000  
    UNION ALL SELECT 3, 'Bob', 4500  
    UNION ALL SELECT 4, 'Alice', 9000  
    UNION ALL SELECT 5, 'Mike', 9000  
    UNION ALL SELECT 6, 'Sara', 8000  
    UNION ALL SELECT 7, 'Tom', 6000  
    UNION ALL SELECT 8, 'Lucy', 5500  
    UNION ALL SELECT 9, 'Mary', 5820  
    UNION ALL SELECT 10, 'Tom', 7890  
)  

SELECT  
    a.*  
FROM Employees a  
INNER JOIN (  
    SELECT DISTINCT Salary FROM Employees ORDER BY Salary DESC LIMIT 3  
) AS b  
ON a.Salary = b.Salary;

正如你所见,我们试图找出薪水最高的三位员工,然而由于有两个人的薪水相同,因此这两位员工都应该包含在结果中。

在子查询中使用 MAX 函数(功能)

要找出薪水最高的员工,可以先用 MAX 函数找到最高薪水,再用子查询找出拿这个薪水的员工。

    with Employees (EmployeeID, Name, Salary) as (  
    select 1, 'John', 5000   
    union all select 2, 'Jane', 7000  
    union all select 3, 'Bob', 4500  
    union all select 4, 'Alice', 9000  
    union all select 5, 'Mike', 9000  
    union all select 6, 'Sara', 8000  
    union all select 7, 'Tom', 6000  
    union all select 8, 'Lucy', 5500  
    union all select 9, 'Mary', 5820  
    union all select 10, 'Tom', 7890  
    )  

    select   
        a.*  
    from   
    Employees a  
    inner join (  
    select max(Salary) salary from Employees  
    ) b  
    on a.Salary = b.Salary;  

    -- 或  

    select   
        a.*  
    from   
    Employees a  
    where  
    Salary = (select max(Salary) salary from Employees)  

结果是:

取消透视

这个例子来自Databricks SQL,但也可以在其他SQL引擎上运行。当你接收到以以下格式的数据时,将列中的值转换到行中对于数据建模很有帮助。

以下是一个示例查询:

    WITH 数据 AS (productID, I2024, II2024, III2024, IV2024)  
    (  
    SELECT 1, 100, 123, 234, 4323  
    UNION ALL  
    SELECT 2, 123, 445, 33, 2212  
    UNION ALL  
    SELECT 3, 1222, 1223, 1232, 43232  
    UNION ALL  
    SELECT 4, 111, 223, 234, 213  
    UNION ALL  
    SELECT 5, 22332, 2323, 2334, 4342  
    )  

    SELECT *  
    FROM  
     数据 UNPIVOT INCLUDE NULLS  
        (  
            销售 FOR 季度 IN (I2024, II2024, III2024, IV2024)  
        );

结果是:

如果你的数据库不提供 UNPIVOT 功能,你可以通过使用 UNION 来达到同样的效果。

SELECT productID, I2024 AS 值(value), 'I2024' AS 季度(quarter) FROM 表 UNION ALL SELECT productID, II2024 AS 值(value), 'II2024' AS 季度(quarter) FROM 表 UNION ALL SELECT productID, III2024 AS 值(value), 'III2024' AS 季度(quarter) FROM 表 UNION ALL SELECT productID, IV2024 AS 值(value), 'IV2024' AS 季度(quarter) FROM 表

Replacing the SQL-specific terms and quarters as suggested:

SELECT productID, I2024 AS 值(value), '2024年第1季度' AS 季度(quarter) FROM 表 UNION ALL SELECT productID, II2024 AS 值(value), '2024年第2季度' AS 季度(quarter) FROM 表 UNION ALL SELECT productID, III2024 AS 值(value), '2024年第3季度' AS 季度(quarter) FROM 表 UNION ALL SELECT productID, IV2024 AS 值(value), '2024年第4季度' AS 季度(quarter) FROM 表

UNPIVOT 相反的操作是 PIVOT,它用于将行值转换为列的形式展示数据。这种技术在报表中展示数据时非常有用。

    WITH data (年份, 季度, 地区, 数值) AS (  
    VALUES (2018, 1, '东部', 100),  
              (2018, 2, '东部', 20),  
              (2018, 3, '东部', 40),  
              (2018, 4, '东部', 40),  
              (2019, 1, '东部', 120),  
              (2019, 2, '东部', 110),  
              (2019, 3, '东部', 80),  
              (2019, 4, '东部', 60),  
              (2018, 1, '西部', 105),  
              (2018, 2, '西部', 25),  
              (2018, 3, '西部', 45),  
              (2018, 4, '西部', 45),  
              (2019, 1, '西部', 125),  
              (2019, 2, '西部', 115),  
              (2019, 3, '西部', 85),  
              (2019, 4, '西部', 65)  
              )  

    SELECT 年份, 地区, q1, q2, q3, q4  
      FROM data  
      PIVOT (SUM(数值) AS 销售额  
        FOR 季度  
        IN (1 AS q1, 2 AS q2, 3 AS q3, 4 AS q4))

结果 :
结果是怎么样的?

结果

对于 PIVOT 操作,我们可以使用 CASE WHEN 语句与 SUM 函数一起使用。这种做法更易于阅读,并且可以在不支持 PIVOT 操作的 SQL 引擎上使用。

    SELECT 年份, 地区,  
             sum(case when 季度 = 1 then 值 end) AS 一季度,  
             sum(case when 季度 = 2 then 值 end) AS 二季度,  
             sum(case when 季度 = 3 then 值 end) AS 三季度,  
             sum(case when 季度 = 4 then 值 end) AS 四季度  
      FROM 数据  
      GROUP BY 年份, 地区;

结果为:

枢轴结果

行与行的比较:LAG 函数的使用

The **LAG** 函数是一个窗口函数,它允许比较当前行与数据集中的前一行数据。这在诸如年对年同比和月对月同比等计算中特别有用,等等。

with 货币表 (日期, 价格, 货币代码) as (  
    select cast('2006-01-02' as date) ,3.2582, '美元'  
    UNION select cast('2006-01-03' as date) ,3.2488  , '美元'  
    UNION select cast('2006-01-04' as date) ,3.1858  , '美元'  
    UNION select cast('2006-01-05' as date) ,3.1416  , '美元'  
    UNION select cast('2006-01-06' as date) ,3.1507  , '美元'  
    UNION select cast('2006-01-09' as date) ,3.1228  , '美元'  
    UNION select cast('2006-01-10' as date) ,3.128   , '美元'  
    UNION select cast('2006-01-11' as date) ,3.1353  , '美元'  
    UNION select cast('2006-01-12' as date) ,3.1229  , '美元'  
    UNION select cast('2006-01-13' as date) ,3.1542  , '美元'  
    )  

    select   
      日期,   
      货币代码,   
      价格,  
      lag(价格) over (order by 日期) 前一天的价格,  
      (价格 - lag(价格) over (order by 日期)) / lag(价格) over (order by 日期) 变化率  

    from  
    货币表

我们期望的结果是,

LEAD 函数简介

**LEAD** 函数的工作方式与 **LAG** 函数类似,但它将当前记录与下一条记录进行对比,而不是与前一条记录进行对比。此函数对于随时间变化的趋势分析或数据变化特别有用,例如识别趋势或数据中的变化。

    WITH currency (日期, 价格, 货币) AS (  
    SELECT CAST('2006-01-02' AS date), 3.2582, 'USD'  
    UNION SELECT CAST('2006-01-03' AS date), 3.2488, 'USD'  
    UNION SELECT CAST('2006-01-04' AS date), 3.1858, 'USD'  
    UNION SELECT CAST('2006-01-05' AS date), 3.1416, 'USD'  
    UNION SELECT CAST('2006-01-06' AS date), 3.1507, 'USD'  
    UNION SELECT CAST('2006-01-09' AS date), 3.1228, 'USD'  
    UNION SELECT CAST('2006-01-10' AS date), 3.128, 'USD'  
    UNION SELECT CAST('2006-01-11' AS date), 3.1353, 'USD'  
    UNION SELECT CAST('2006-01-12' AS date), 3.1229, 'USD'  
    UNION SELECT CAST('2006-01-13' AS date), 3.1542, 'USD'  
    )  

    SELECT   
    日期,   
    货币,   
    价格,  
    LEAD(价格) OVER (ORDER BY 日期) 前一日价格  -- LEAD函数用于获取当前行的下一行数据,在这里表示获取前一日的价格  

    FROM  
    currency

我们预计的结果如下:

将数组转换成行数据

在将数据存储为单个单元格的数组或需要特定转换时,例如 **EXPLODE** 函数可以非常有用,用于将数组元素拆分成单独的行。

在Databricks SQL中使用EXPLODE函数

由于 Databricks 不支持递归的公共表表达式 (CTEs),你可以通过结合 **SEQUENCE** 函数生成一系列值,并使用 **EXPLODE** 函数将数组拆分为行来创建一个日历表(calendar table)。

SELECT   
date,  
EXTRACT(YEAR FROM date) 年,  
EXTRACT(MONTH FROM date) 月  
FROM (  
生成   
EXPLODE(SEQUENCE(DATE '2024-01-01', DATE '2024-12-31', INTERVAL 1 DAY)) AS date  
) 日历表

结果如下:

UNNEST:DuckDB和BigQuery

在像DuckDB和BigQuery这样的数据库中,**UNNEST** 函数用于将数组或嵌套结构拆分成单独的行,类似于其他SQL引擎中的 **EXPLODE** 函数的功能。这种转换在处理复杂数据类型(如数组或嵌套的JSON结构)时尤其有用。

将数组 [1, 2, 3] 解构并选择为 qt 列

结果是:

NTILE (分组等级)
NTILE 或 "分组等级" (fēnzǔ děngjí)

The **NTILE** 函数是一个窗口函数,它将结果集中的行按照指定数量分成组或桶,确保每个组中的行数尽可能相等。此函数在各种分析任务中非常有用,例如将数据分成四分位数或获取特定的百分位数记录。

    WITH Employees (EmployeeID, Name, Salary) AS (  
    SELECT 1, 'John', 5000   
    UNION ALL SELECT 2, 'Jane', 7000  
    UNION ALL SELECT 3, 'Bob', 4500  
    UNION ALL SELECT 4, 'Alice', 9000  
    UNION ALL SELECT 5, 'Mike', 12000  
    UNION ALL SELECT 6, 'Sara', 8000  
    UNION ALL SELECT 7, 'Tom', 6000  
    UNION ALL SELECT 8, 'Lucy', 5500  
    UNION ALL SELECT 9, 'Mary', 5820  
    UNION ALL SELECT 10, 'Tom', 7890  
    )  

    SELECT   
        EmployeeID,  
        Name,  
        Salary,  
        NTILE(10) OVER (ORDER BY Salary) AS 薪资分位数  
    FROM Employees  
    ;

无具体内容,无需翻译。

并入

**MERGE INTO**命令允许你在目标表中根据源表的数据执行更新、插入和删除操作。这个命令很有用,因为它可以同步两个表,使目标表反映源表的更改。

    使用数据库 demo;  
    CREATE TABLE silver.Employees  
    (  
      员工ID int,  
      名字 string,  
      姓氏 string,  
      经理ID int   
    );  

    CREATE TABLE silver.raw_Employees  
    (  
      员工ID int,  
      名字 string,  
      姓氏 string,  
      经理ID int   
    );  

    INSERT INTO Employees VALUES (1, 'Harper', 'Westbrook', NULL);  
    INSERT INTO Employees VALUES (2, 'Liam', 'Carrington', 1);  
    INSERT INTO Employees VALUES (3, 'Evelyn', 'Radcliffe', 1);  
    INSERT INTO Employees VALUES (4, 'Mason', 'Albright', 2);  
    INSERT INTO Employees VALUES (5, 'Isla', 'Whitman', 2);  
    INSERT INTO Employees VALUES (6, 'Noah', 'Sterling', 3);  
    INSERT INTO Employees VALUES (7, 'Ruby', 'Lennox', 3);  
    INSERT INTO Employees VALUES (8, 'Caleb', 'Winslow', 5);  
    INSERT INTO Employees VALUES (9, 'Avery', 'Sinclair', 6);  
    INSERT INTO Employees VALUES (10, 'Oliver', 'Beckett', 6);  

    INSERT INTO raw_Employees VALUES (1, 'Harper', 'Westbrook', NULL);  
    INSERT INTO raw_Employees VALUES (2, 'Liam', 'Carrington', 1);  
    INSERT INTO raw_Employees VALUES (3, 'Evelyn', 'Radcliffe', 1);  
    INSERT INTO raw_Employees VALUES (4, 'Mason', 'Albright', 2);  
    INSERT INTO raw_Employees VALUES (5, 'Isla', 'Whitman', 2);  
    INSERT INTO raw_Employees VALUES (6, 'Noah', 'Sterling', 3);  
    INSERT INTO raw_Employees VALUES (7, 'Ruby', 'Lennox', 3);  
    INSERT INTO raw_Employees VALUES (8, 'Caleb', 'Winslow', 5);  
    INSERT INTO raw_Employees VALUES (9, 'Avery', 'Sinclair', 6);  
    INSERT INTO raw_Employees VALUES (10, 'Oliver', 'Beckett', 6);  
    INSERT INTO raw_Employees VALUES (11, 'Avery', 'Sinclair', 6);  
    INSERT INTO raw_Employees VALUES (12, 'Oliver', 'Beckett', 6);

为了将源表中但目标表中不存在的所有行插入,你可以用下面的 **MERGE INTO** 语句:

    MERGE INTO Employees AS target   
    USING raw_Employees as source  
    ON target.EmployeeID = source.EmployeeID  
    WHEN NOT MATCHED THEN INSERT *

要在目标表中插入新行,并根据源表更新现有行,您可以这样做:例如,您可以使用以下 **MERGE INTO** 语句:

    MERGE INTO Employees AS target USING raw_Employees as source  
      ON target.EmployeeID = source.EmployeeID  
      -- 当源表和目标表的EmployeeID匹配时, 更新目标表的所有字段
      WHEN MATCHED THEN UPDATE SET *  
      -- 当源表和目标表的EmployeeID不匹配时, 在目标表中插入新的记录
      WHEN NOT MATCHED THEN INSERT *
检查这些表是否一样

在处理数据的过程中,尤其是在测试或调试代码时,我们经常需要比较表格。虽然对于小表格来说这相对来说比较简单,但比较拥有数百列的表格可能会极具挑战性。在这种情况下,**EXCEPT** 命令可以成为一个很有帮助的工具。

    CREATE TABLE silver.Employees  
    (  
      EmployeeID int,  
      FirstName string,  
      LastName string,  
      ManagerID int   
    );  

    CREATE TABLE silver.raw_Employees  
    (  
      EmployeeID int,  
      FirstName string,  
      LastName string,  
      ManagerID int   
    );  

    INSERT INTO Employees VALUES (1, 'Harper', 'Westbrook', NULL);  
    INSERT INTO Employees VALUES (2, 'Liam', 'Carrington', 1);  
    INSERT INTO Employees VALUES (3, 'Evelyn', 'Radcliffe', 1);  
    INSERT INTO Employees VALUES (4, 'Mason', 'Albright', 2);  
    INSERT INTO Employees VALUES (5, 'Isla', 'Whitman', 2);  
    INSERT INTO Employees VALUES (6, 'Noah', 'Sterling', 3);  
    INSERT INTO Employees VALUES (7, 'Ruby', 'Lennox', 3);  
    INSERT INTO Employees VALUES (8, 'Caleb', 'Winslow', 5);  
    INSERT INTO Employees VALUES (9, 'Avery', 'Sinclair', 6);  
    INSERT INTO Employees VALUES (10, 'Oliver', 'Beckett', 6);  

    INSERT INTO raw_Employees VALUES (1, 'Harper', 'Westbrook', NULL);  
    INSERT INTO raw_Employees VALUES (2, 'Liam', 'Carrington', 1);  
    INSERT INTO raw_Employees VALUES (3, 'Evelyn', 'Radcliffe', 1);  
    INSERT INTO raw_Employees VALUES (4, 'Mason', 'Albright', 2);  
    INSERT INTO raw_Employees VALUES (5, 'Isla', 'Whitman', 2);  
    INSERT INTO raw_Employees VALUES (6, 'Noah', 'Sterling', 3);  
    INSERT INTO raw_Employees VALUES (7, 'Ruby', 'Lennox', NULL);  
    INSERT INTO raw_Employees VALUES (8, 'Caleb', 'Winslow', 5);  
    INSERT INTO raw_Employees VALUES (9, 'Avery', 'Sinclair', 6);  
    INSERT INTO raw_Employees VALUES (10, 'Oliver', 'Beckett', 6);

试试代码:

选择 silver.Employees 表中的所有列  
排除  
选择 silver.raw_Employees 表中的所有列

运行此命令后,结果将显示两个表之间不匹配的记录。例如,如果 raw_Employees 中某行的 ManagerIDNULL,这种差异会被显示出来。

结果应为:

谢谢您的阅读!

如果你觉得这篇文章有帮助或有启发,我邀请你通过在LinkedIn上点个赞并点个“赞”按钮来表达你的赞赏。你的支持非常宝贵。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP