SqlServer的实用且高级玩法.md

1.常见表表达式(CTEs)

如果您想要查询子查询,那就是CTEs施展身手的时候 - CTEs基本上创建了一个临时表。

使用常用表表达式(CTEs)是模块化和分解代码的好方法,与您将文章分解为几个段落的方式相同。

请在Where子句中使用子查询进行以下查询。

1.1 在查询中有许多子查询,那么怎么样?这就是CTEs发挥作用的地方。
---示例---

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子句是在多伦多的名称中过滤。如果您注意到,CTE很有用,因为您可以将代码分解为较小的块,但它们也很有用,因为它允许您为每个CTE分配变量名称(即toronto_ppl和avg_female_salary) 同样,CTEs允许您完成更高级的技术,如创建递归表

2.临时函数

如果您想了解有关临时函数的更多信息,请检查此项,但知道如何编写临时功能是重要的原因:
  • 它允许您将代码的块分解为较小的代码块
  • 它适用于写入清洁代码
  • 它可以防止重复,并允许您重用类似于使用Python中的函数的代码。
2.1 标量函数返回单个值,可以用于 SELECT 语句中的计算
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 FUNCTION dbo.MyTenureName
(
    -- 参数列表
    @Input int
)
RETURNS Nvarchar(255)
AS
BEGIN
    -- 函数的逻辑
    DECLARE @Result nvarchar(255);
    -- 示例逻辑:将输入参数加倍
    -- 使用 CASE 语句
    SET @Result = 
        CASE 
            WHEN @Input < 1 THEN 'analyst'
            WHEN @Input BETWEEN 1 and 3 THEN 'associate'
            WHEN @Input BETWEEN 3 and 5 THEN 'senior'
            WHEN @Input > 5 THEN 'vp'
			 ELSE 'n/a'
        END;
    RETURN @Result;
END;

-- 使用标量函数
SELECT name,tenure
       ,dbo.MyTenureName(tenure) as tenureName 
FROM employees
2.2 创建表值函数(Table-Valued Function): 表值函数返回一个表,可以在 FROM 子句中用于查询。
-- 示例1
CREATE FUNCTION dbo.MyTableValuedFunction
(
    -- 参数列表
    @InputParameter INT
)
RETURNS TABLE
AS
RETURN (
    -- 返回的表结构
    SELECT
        Column1,
        Column2
    FROM
        YourTable
    WHERE
        SomeCondition = @InputParameter
);


-- 示例2
create function dbo.MyTableValuedFunction
(
    -- 参数列表
    @InputTopRow int = 10,
	@InputName nvarchar(50) = ''
)
Returns Table
as
return (
	SELECT top (@InputTopRow) 
	[name],
	tenure,
	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  where  [name] like '%'+ISNULL(@InputName,'')+'%')
)


-- 使用表值函数
select * from MyTableValuedFunction(5,'张三')
2.3 删除函数
-- 删除标量函数 
DROP FUNCTION dbo.MyScalarFunction;

-- 删除表值函数 
DROP FUNCTION dbo.MyTableValuedFunction;

3.使用CASE WHEN枢转数据

您很可能会看到许多要求在陈述时使用CASE WHEN的问题,这只是因为它是一种多功能的概念。如果要根据其他变量分配某个值或类,则允许您编写复杂的条件语句。较少众所周知,它还允许您枢转数据。例如,如果您有一个月列,并且您希望为每个月创建一个单个列,则可以使用语句追溯数据的情况。
示例问题:编写SQL查询以重新格式化表,以便每个月有一个收入列
-- 创建表
CREATE TABLE Case_Test_Table (
    id INT,
    revenue INT,
    month VARCHAR(3)
);

-- 插入数据
INSERT INTO Case_Test_Table (id, revenue, month)
VALUES
    (1, 8000, 'Jan'),
    (2, 9000, 'Jan'),
    (3, 10000, 'Feb'),
    (1, 7000, 'Feb'),
    (1, 6000, 'Mar');
    
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        |  
+------+-------------+-------------+-------------+-----+-----------+

-- 结果示例 Sql
SELECT
    id,
    MAX(CASE WHEN month = 'Jan' THEN revenue END) AS Jan_Revenue,
    MAX(CASE WHEN month = 'Feb' THEN revenue END) AS Feb_Revenue,
    MAX(CASE WHEN month = 'Mar' THEN revenue END) AS Mar_Revenue,
    MAX(CASE WHEN month = 'Apr' THEN revenue END) AS Apr_Revenue,
    MAX(CASE WHEN month = 'May' THEN revenue END) AS May_Revenue,
    MAX(CASE WHEN month = 'Jun' THEN revenue END) AS Jun_Revenue,
    MAX(CASE WHEN month = 'Jul' THEN revenue END) AS Jul_Revenue,
    MAX(CASE WHEN month = 'Aug' THEN revenue END) AS Aug_Revenue,
    MAX(CASE WHEN month = 'Sep' THEN revenue END) AS Sep_Revenue,
    MAX(CASE WHEN month = 'Oct' THEN revenue END) AS Oct_Revenue,
    MAX(CASE WHEN month = 'Nov' THEN revenue END) AS Nov_Revenue,
    MAX(CASE WHEN month = 'Dec' THEN revenue END) AS Dec_Revenue
FROM Case_Test_Table
GROUP BY id
ORDER BY id;

4.EXCEPT vs NOT IN 差异

EXCEPTNOT IN 都是用于从查询结果中排除特定值的 SQL 查询语句的部分。然而,它们有一些关键的差异:
  1. 语法结构:
    • EXCEPT 使用两个 SELECT 语句,它从第一个查询的结果中排除第二个查询的结果。
    • NOT IN 在单个 SELECT 语句中使用,并且通常结合子查询来排除特定值。
  2. 处理 NULL 值:
    • EXCEPT 会自动处理 NULL 值。如果两个查询中都有 NULL 值,它们将被视为相等,不会被排除。
    • NOT IN 在比较中对 NULL 值的处理可能不同,具体取决于数据库的实现。通常情况下,NOT IN 可能需要特殊处理 NULL。
  3. 性能:
    • 在某些情况下,数据库引擎可能对 EXCEPT 优化得更好,尤其是当查询中包含大量结果时。
    • NOT IN 的性能可能受到查询中值的数量和索引的影响。
  4. 查询结果:
    • EXCEPT 返回两个查询结果的差异,即从第一个结果中排除了第二个结果。
    • NOT IN 返回满足条件的所有行,除了子查询中指定的值。

以下是示例说明:

使用 EXCEPT 的示例:

SELECT column1 FROM table1
EXCEPT
SELECT column1 FROM table2;

使用 NOT IN 的示例:

SELECT column1 FROM table1
WHERE column1 NOT IN (SELECT column1 FROM table2);
总体而言,选择使用 EXCEPT 还是 NOT IN 取决于具体的查询需求和对 NULL 值的处理偏好。

5.自联结

一个SQL表自行连接自己。你可能会认为没有用,但你会感到惊讶的是这是多么常见。在许多现实生活中,数据存储在一个大型表中而不是许多较小的表中。在这种情况下,可能需要自我连接来解决独特的问题。
让我们来看看一个例子。
示例问题:给定下面的员工表,写出一个SQL查询,了解员工的工资,这些员工比其管理人员工资更多。对于上表来说,Joe是唯一一个比他的经理工资更多的员工。
CREATE TABLE YourTable (
    Id INT,
    Name VARCHAR(50),
    Salary INT,
    ManagerId INT
);

INSERT INTO YourTable (Id, Name, Salary, ManagerId)
VALUES
    (1, 'Joe', 70000, 3),
    (2, 'Henry', 80000, 4),
    (3, 'Sam', 60000, NULL),
    (4, 'Max', 90000, NULL);

+----+-------+--------+-----------+  
| 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

6. Rank vs Dense Rank vs Row Number

6.1.ROW_NUMBER() OVER (ORDER BY GPA desc)
6.2 RANK() OVER (ORDER BY GPA desc)
6.3 DENSE_RANK() OVER (ORDER BY GPA desc)

这些都是用于在 SQL 中进行排名(ranking)的窗口函数。它们通常与 OVER 子句一起使用,用于根据指定的排序条件对结果集中的行进行排名。

  1. ROW_NUMBER() OVER (ORDER BY GPA desc):
    • ROW_NUMBER() 分配唯一的整数值给结果集中的每一行,按照指定的排序条件(这里是 GPA 降序)进行排序。即,每行都有一个唯一的排名,不会有相同的排名。
    • 例如,如果有两个相同的 GPA,它们将被分配不同的 ROW_NUMBER()
  2. RANK() OVER (ORDER BY GPA desc):
    • RANK() 为每一行分配一个排名,但允许有相同的排名。如果两个行具有相同的排序条件(GPA),它们将被分配相同的排名,并且下一个排名将被跳过。
    • 例如,如果两个相同的 GPA,它们将被分配相同的 RANK(),下一个行将被跳过。
  3. DENSE_RANK() OVER (ORDER BY GPA desc):
    • DENSE_RANK() 类似于 RANK(),也为每一行分配一个排名。然而,不同之处在于它不会跳过排名。即,如果有两个行具有相同的排序条件(GPA),它们将被分配相同的排名,并且下一个排名不会被跳过。
    • 例如,如果两个相同的 GPA,它们将被分配相同的 DENSE_RANK(),下一个行将被继续分配下一个排名。

这些排名函数通常用于在查询结果中创建排名列,以便更容易了解每行在排序中的位置。

7. OVER 窗口函数

OVER 子句通常与窗口函数一起使用,用于定义窗口(window),指定在执行窗口函数时要考虑的行的范围。OVER 子句的主要作用是控制窗口函数的计算范围,从而提供更灵活的查询和分析能力。以下是一些常见的用法:

CREATE TABLE YourTable (
    column1 INT,
    column2 INT,
    column3 INT
);

INSERT INTO YourTable (column1, column2, column3)
VALUES
    (1, 10, 100),
    (2, 20, 200),
    (3, 30, 300),
    (4, 40, 400),
    (5, 50, 500);

  1. ORDER BY 子句:

    • OVER 子句通常包含 ORDER BY 子句,用于指定窗口函数计算时的排序条件。这可以确保在窗口函数中按照指定的顺序处理数据。
    sqlCopy codeSELECT 
        column1,
        column2,
        SUM(column3) OVER (ORDER BY column1) AS RunningTotal
    FROM 
        YourTable;
    
  2. PARTITION BY 子句:

    • OVER 子句还可以包含 PARTITION BY 子句,用于将数据分成逻辑上的分区,窗口函数在每个分区内进行计算。这允许在分组级别上执行窗口函数。
    sqlCopy codeSELECT 
        column1,
        column2,
        AVG(column3) OVER (PARTITION BY column1) AS AvgInPartition
    FROM 
        YourTable;
    
  3. ROWS 或 RANGE 子句:

    • OVER 子句还可以包含 ROWSRANGE 子句,用于指定窗口的实际行范围。这允许定义窗口函数计算时要考虑的具体行数或范围。
    sqlCopy codeSELECT 
        column1,
        column2,
        SUM(column3) OVER (ORDER BY column1 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS MovingSum
    FROM 
        YourTable;
    
  4. UNBOUNDED 子句:

    • OVER 子句还可以使用 UNBOUNDED 关键字,用于表示窗口的边界不受限制。
    sqlCopy codeSELECT 
        column1,
        column2,
        SUM(column3) OVER (ORDER BY column1 ROWS UNBOUNDED PRECEDING) AS CumulativeSum
    FROM 
        YourTable;
    

通过结合 OVER 子句和不同的窗口函数,可以实现各种复杂的分析和聚合操作

8.计算Delta值

另一个常见应用程序是将不同时期的值进行比较。例如,本月和上个月的销售之间的三角洲是什么?或者本月和本月去年这个月是什么?

在将不同时段的值进行比较以计算Deltas时,这是Lead()和LAG()发挥作用时。

这是一些例子:

# Comparing each month's sales to last month  
SELECT month  
       , sales  
       , sales - LAG(sales, 1) OVER (ORDER BY month)  
FROM monthly_sales  
# Comparing each month's sales to the same month last year  
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

10.日期时间操纵

您应该肯定会期望某种涉及日期时间数据的SQL问题。例如,您可能需要将数据分组组或将可变格式从DD-MM-Yyyy转换为简单的月份。

您应该知道的一些功能是:

  • 提炼
  • 日元
  • date_add,date_sub.
  • date_trunc.

示例问题:给定天气表,写一个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
posted @ 2024-01-22 17:25  雨太阳  阅读(95)  评论(0编辑  收藏  举报