SQL Server高级查询 #

一、子查询 #

1.1 标量子查询 #

sql
-- 返回单个值的子查询
SELECT 
    name,
    salary,
    (SELECT AVG(salary) FROM users) AS avg_salary
FROM users;

-- 在WHERE中使用
SELECT * FROM users
WHERE salary > (SELECT AVG(salary) FROM users);

1.2 列子查询 #

sql
-- 返回单列多行
SELECT * FROM users
WHERE department_id IN (SELECT id FROM departments WHERE location = 'Beijing');

-- 使用ANY/SOME
SELECT * FROM users
WHERE salary > ANY (SELECT salary FROM users WHERE department_id = 1);

-- 使用ALL
SELECT * FROM users
WHERE salary > ALL (SELECT salary FROM users WHERE department_id = 2);

1.3 表子查询 #

sql
-- 返回多行多列
SELECT u.name, o.order_date
FROM users u
INNER JOIN (
    SELECT user_id, order_date 
    FROM orders 
    WHERE status = 'completed'
) o ON u.id = o.user_id;

-- 使用EXISTS
SELECT * FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o 
    WHERE o.user_id = u.id 
    AND o.amount > 1000
);

-- 使用NOT EXISTS
SELECT * FROM users u
WHERE NOT EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.id
);

1.4 相关子查询 #

sql
-- 相关子查询(引用外部查询)
SELECT 
    name,
    salary,
    (SELECT AVG(salary) FROM users u2 WHERE u2.department_id = u1.department_id) AS dept_avg
FROM users u1;

-- 使用相关子查询更新
UPDATE users u1
SET salary = (
    SELECT AVG(salary) FROM users u2 
    WHERE u2.department_id = u1.department_id
)
WHERE salary < 3000;

1.5 派生表 #

sql
-- 使用派生表
SELECT dept_name, avg_salary
FROM (
    SELECT 
        d.name AS dept_name,
        AVG(u.salary) AS avg_salary
    FROM users u
    INNER JOIN departments d ON u.department_id = d.id
    GROUP BY d.name
) AS dept_stats
WHERE avg_salary > 5000;

二、CTE(公用表表达式) #

2.1 基本CTE #

sql
-- 简单CTE
WITH active_users AS (
    SELECT * FROM users WHERE status = 1
)
SELECT * FROM active_users;

-- 多个CTE
WITH 
active_users AS (
    SELECT * FROM users WHERE status = 1
),
high_salary AS (
    SELECT * FROM active_users WHERE salary > 5000
)
SELECT * FROM high_salary;

2.2 CTE与聚合 #

sql
-- 使用CTE进行聚合分析
WITH user_stats AS (
    SELECT 
        department_id,
        COUNT(*) AS employee_count,
        AVG(salary) AS avg_salary,
        MAX(salary) AS max_salary,
        MIN(salary) AS min_salary
    FROM users
    GROUP BY department_id
)
SELECT 
    d.name AS department,
    us.employee_count,
    us.avg_salary,
    us.max_salary,
    us.min_salary
FROM user_stats us
INNER JOIN departments d ON us.department_id = d.id
ORDER BY us.avg_salary DESC;

2.3 递归CTE #

sql
-- 组织架构递归查询
WITH org_hierarchy AS (
    -- 锚点:顶级员工
    SELECT 
        id,
        name,
        manager_id,
        1 AS level,
        CAST(name AS NVARCHAR(1000)) AS path
    FROM employees
    WHERE manager_id IS NULL
    
    UNION ALL
    
    -- 递归:下级员工
    SELECT 
        e.id,
        e.name,
        e.manager_id,
        h.level + 1,
        CAST(h.path + ' > ' + e.name AS NVARCHAR(1000))
    FROM employees e
    INNER JOIN org_hierarchy h ON e.manager_id = h.id
)
SELECT * FROM org_hierarchy
ORDER BY level, path;

-- 递归生成日期序列
WITH date_series AS (
    SELECT CAST('2024-01-01' AS DATE) AS date_value
    
    UNION ALL
    
    SELECT DATEADD(DAY, 1, date_value)
    FROM date_series
    WHERE date_value < '2024-01-31'
)
SELECT date_value FROM date_series
OPTION (MAXRECURSION 100);  -- 默认100,最大32767

2.4 CTE修改数据 #

sql
-- 使用CTE删除
WITH duplicate_users AS (
    SELECT 
        id,
        ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS row_num
    FROM users
)
DELETE FROM users
WHERE id IN (SELECT id FROM duplicate_users WHERE row_num > 1);

-- 使用CTE更新
WITH user_ranks AS (
    SELECT 
        id,
        salary,
        RANK() OVER (ORDER BY salary DESC) AS salary_rank
    FROM users
)
UPDATE user_ranks
SET salary = salary * 1.1
WHERE salary_rank <= 5;

三、窗口函数 #

3.1 ROW_NUMBER #

sql
-- 行号
SELECT 
    name,
    salary,
    ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num
FROM users;

-- 分组行号
SELECT 
    department_id,
    name,
    salary,
    ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS dept_rank
FROM users;

-- 分页
WITH numbered AS (
    SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS row_num
    FROM users
)
SELECT * FROM numbered
WHERE row_num BETWEEN 4 AND 6;

3.2 RANK和DENSE_RANK #

sql
-- RANK:并列跳过
SELECT 
    name,
    salary,
    RANK() OVER (ORDER BY salary DESC) AS rank_val,
    DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rank_val
FROM users;

-- 结果示例
-- name    | salary | rank_val | dense_rank_val
-- --------|--------|----------|----------------
-- Charlie | 7000   | 1        | 1
-- Jane    | 6000   | 2        | 2
-- David   | 5500   | 3        | 3
-- John    | 5000   | 4        | 4
-- Eve     | 4800   | 5        | 5
-- Bob     | 4500   | 6        | 6
-- Alice   | 4000   | 7        | 7

3.3 聚合窗口函数 #

sql
-- 累计聚合
SELECT 
    order_date,
    amount,
    SUM(amount) OVER (ORDER BY order_date) AS running_total,
    AVG(amount) OVER (ORDER BY order_date) AS running_avg,
    COUNT(*) OVER (ORDER BY order_date) AS running_count
FROM orders;

-- 分组聚合
SELECT 
    department_id,
    name,
    salary,
    SUM(salary) OVER (PARTITION BY department_id) AS dept_total,
    AVG(salary) OVER (PARTITION BY department_id) AS dept_avg
FROM users;

3.4 帧窗口 #

sql
-- 移动平均
SELECT 
    order_date,
    amount,
    AVG(amount) OVER (
        ORDER BY order_date
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS moving_avg_3
FROM orders;

-- 窗口帧类型
/*
ROWS:基于行数
RANGE:基于值范围

帧边界:
PRECEDING:之前
FOLLOWING:之后
CURRENT ROW:当前行
UNBOUNDED PRECEDING:窗口开始
UNBOUNDED FOLLOWING:窗口结束
*/

-- 常用帧定义
SELECT 
    order_date,
    amount,
    -- 从开始到当前
    SUM(amount) OVER (ORDER BY order_date ROWS UNBOUNDED PRECEDING) AS cum_sum,
    -- 前3行到后1行
    AVG(amount) OVER (ORDER BY order_date ROWS BETWEEN 3 PRECEDING AND 1 FOLLOWING) AS avg_5,
    -- 当前到结束
    SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS remaining
FROM orders;

3.5 LEAD和LAG #

sql
-- 访问前后行数据
SELECT 
    order_date,
    amount,
    LAG(amount, 1, 0) OVER (ORDER BY order_date) AS prev_amount,
    LEAD(amount, 1, 0) OVER (ORDER BY order_date) AS next_amount,
    amount - LAG(amount, 1, amount) OVER (ORDER BY order_date) AS diff
FROM orders;

-- 计算环比增长
SELECT 
    order_date,
    amount,
    LAG(amount) OVER (ORDER BY order_date) AS prev_amount,
    CAST((amount - LAG(amount) OVER (ORDER BY order_date)) * 100.0 / 
         LAG(amount) OVER (ORDER BY order_date) AS DECIMAL(10,2)) AS growth_rate
FROM orders;

3.6 FIRST_VALUE和LAST_VALUE #

sql
-- 首尾值
SELECT 
    department_id,
    name,
    salary,
    FIRST_VALUE(name) OVER (PARTITION BY department_id ORDER BY salary DESC) AS highest_paid,
    LAST_VALUE(name) OVER (
        PARTITION BY department_id 
        ORDER BY salary DESC 
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS lowest_paid
FROM users;

3.7 NTILE #

sql
-- 分组
SELECT 
    name,
    salary,
    NTILE(4) OVER (ORDER BY salary DESC) AS quartile  -- 分成4组
FROM users;

-- 结果
-- name    | salary | quartile
-- --------|--------|----------
-- Charlie | 7000   | 1
-- Jane    | 6000   | 1
-- David   | 5500   | 2
-- John    | 5000   | 2
-- Eve     | 4800   | 3
-- Bob     | 4500   | 3
-- Alice   | 4000   | 4

四、CASE表达式 #

4.1 简单CASE #

sql
-- 简单CASE表达式
SELECT 
    name,
    status,
    CASE status
        WHEN 1 THEN 'Active'
        WHEN 0 THEN 'Inactive'
        ELSE 'Unknown'
    END AS status_text
FROM users;

4.2 搜索CASE #

sql
-- 搜索CASE表达式
SELECT 
    name,
    salary,
    CASE 
        WHEN salary >= 6000 THEN 'High'
        WHEN salary >= 4000 THEN 'Medium'
        ELSE 'Low'
    END AS salary_level
FROM users;

4.3 CASE在聚合中 #

sql
-- 条件聚合
SELECT 
    department_id,
    COUNT(*) AS total,
    SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS active_count,
    SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) AS inactive_count,
    AVG(CASE WHEN status = 1 THEN salary END) AS avg_active_salary
FROM users
GROUP BY department_id;

4.4 CASE在ORDER BY中 #

sql
-- 自定义排序
SELECT * FROM users
ORDER BY 
    CASE status
        WHEN 1 THEN 1
        WHEN 0 THEN 2
        ELSE 3
    END,
    salary DESC;

五、PIVOT和UNPIVOT #

5.1 PIVOT #

sql
-- 创建示例数据
CREATE TABLE sales (
    year INT,
    quarter NVARCHAR(10),
    amount DECIMAL(10,2)
);

INSERT INTO sales VALUES
    (2023, 'Q1', 1000),
    (2023, 'Q2', 1500),
    (2023, 'Q3', 2000),
    (2023, 'Q4', 2500),
    (2024, 'Q1', 1200),
    (2024, 'Q2', 1800);

-- PIVOT转换
SELECT * FROM 
(
    SELECT year, quarter, amount
    FROM sales
) AS source
PIVOT
(
    SUM(amount) FOR quarter IN ([Q1], [Q2], [Q3], [Q4])
) AS pvt;

-- 结果
-- year | Q1    | Q2    | Q3    | Q4
-- -----|-------|-------|-------|------
-- 2023 | 1000  | 1500  | 2000  | 2500
-- 2024 | 1200  | 1800  | NULL  | NULL

5.2 动态PIVOT #

sql
-- 动态列
DECLARE @columns NVARCHAR(MAX);
DECLARE @sql NVARCHAR(MAX);

SELECT @columns = STRING_AGG(QUOTENAME(quarter), ',')
FROM (SELECT DISTINCT quarter FROM sales) t;

SET @sql = '
SELECT * FROM 
(
    SELECT year, quarter, amount
    FROM sales
) AS source
PIVOT
(
    SUM(amount) FOR quarter IN (' + @columns + ')
) AS pvt';

EXEC sp_executesql @sql;

5.3 UNPIVOT #

sql
-- 创建宽表
CREATE TABLE sales_wide (
    year INT,
    Q1 DECIMAL(10,2),
    Q2 DECIMAL(10,2),
    Q3 DECIMAL(10,2),
    Q4 DECIMAL(10,2)
);

INSERT INTO sales_wide VALUES
    (2023, 1000, 1500, 2000, 2500),
    (2024, 1200, 1800, NULL, NULL);

-- UNPIVOT转换
SELECT year, quarter, amount
FROM sales_wide
UNPIVOT
(
    amount FOR quarter IN (Q1, Q2, Q3, Q4)
) AS unpvt;

-- 结果
-- year | quarter | amount
-- -----|---------|-------
-- 2023 | Q1      | 1000
-- 2023 | Q2      | 1500
-- 2023 | Q3      | 2000
-- 2023 | Q4      | 2500
-- 2024 | Q1      | 1200
-- 2024 | Q2      | 1800

六、EXCEPT和INTERSECT #

6.1 EXCEPT #

sql
-- 返回第一个查询有但第二个查询没有的行
SELECT name, email FROM users
EXCEPT
SELECT name, email FROM archived_users;

-- 找出只在users中的用户

6.2 INTERSECT #

sql
-- 返回两个查询共有的行
SELECT name, email FROM users
INTERSECT
SELECT name, email FROM vip_users;

-- 找出既是普通用户又是VIP的用户

七、APPLY操作符 #

7.1 CROSS APPLY #

sql
-- 类似INNER JOIN,但可以应用表值函数
SELECT 
    u.name,
    t.top_order
FROM users u
CROSS APPLY (
    SELECT TOP 1 order_date AS top_order
    FROM orders o
    WHERE o.user_id = u.id
    ORDER BY amount DESC
) t;

-- 使用表值函数
SELECT 
    u.name,
    f.*
FROM users u
CROSS APPLY dbo.fn_GetUserOrders(u.id) f;

7.2 OUTER APPLY #

sql
-- 类似LEFT JOIN,保留左表所有行
SELECT 
    u.name,
    t.top_order
FROM users u
OUTER APPLY (
    SELECT TOP 1 order_date AS top_order
    FROM orders o
    WHERE o.user_id = u.id
    ORDER BY amount DESC
) t;

-- 没有订单的用户也会返回,top_order为NULL

八、总结 #

高级查询技术:

技术 说明
子查询 嵌套查询
CTE 公用表表达式
递归CTE 层次数据查询
窗口函数 排名、聚合、帧
CASE 条件表达式
PIVOT 行转列
UNPIVOT 列转行
APPLY 表值函数应用

最佳实践:

  1. 优先使用CTE替代子查询,提高可读性
  2. 窗口函数解决复杂聚合问题
  3. 递归CTE处理层次数据
  4. 使用CASE进行条件聚合
  5. PIVOT用于报表数据转换

下一步,让我们学习多表查询!

最后更新:2026-03-27