#SQL Server 查询语句大全及实战应用指南

SQL Server 查询语句大全及实战应用指南

📚 一、SQL Server 查询语句分类

1. 基础查询语句(必须掌握!)

SELECT - 查询数据(最常用!)
sql 复制代码
-- ======================================================
-- 1. 查询所有列(最简单的查询)
-- ======================================================
-- 语法:SELECT * FROM 表名
-- * 表示所有列
SELECT * FROM Employees;

-- ⚠️ 注意:实际开发中不建议用 SELECT *
-- 原因:1. 查询不需要的列浪费资源 2. 表结构变化时程序可能出错

-- ======================================================
-- 2. 查询指定列(推荐做法)
-- ======================================================
-- 语法:SELECT 列1, 列2, 列3 FROM 表名
-- 只查询需要的列,提高性能
SELECT 
    EmployeeID,          -- 员工ID
    FirstName,           -- 名字
    LastName,            -- 姓氏
    Salary,              -- 工资
    HireDate             -- 入职日期
FROM Employees;

-- ======================================================
-- 3. 查询带别名(让列名更好理解)
-- ======================================================
-- 语法:SELECT 列名 AS 别名 FROM 表名
-- AS关键字可以省略,但建议加上
SELECT 
    EmployeeID AS 员工编号,        -- 使用AS关键字
    FirstName AS 名字,             -- 中文别名
    LastName 姓氏,                  -- 可以省略AS关键字
    Salary * 12 AS 年薪,           -- 计算列+别名
    Salary 月薪,                    -- 也可以这样写
    HireDate '入职时间'             -- 使用单引号的别名
FROM Employees;

-- 运行结果示例:
-- 员工编号 | 名字 | 姓氏 | 年薪 | 月薪 | 入职时间
-- ---------|------|------|------|------|----------
-- 1        | 张三 | 张   | 120000 | 10000 | 2020-01-01

-- ======================================================
-- 4. 查询带计算的列
-- ======================================================
SELECT 
    -- 字符串连接:把名字和姓氏连起来
    FirstName + ' ' + LastName AS 全名,
    
    -- 基础工资
    Salary AS 月薪,
    
    -- 计算年薪:月薪 * 12
    Salary * 12 AS 年薪,
    
    -- 计算奖金:月薪的10%
    Salary * 0.1 AS 奖金,
    
    -- 计算总收入:月薪 + 奖金
    Salary + (Salary * 0.1) AS 月总收入,
    
    -- 获取当前系统时间
    GETDATE() AS 当前时间,
    
    -- 计算工龄:当前年份 - 入职年份
    DATEDIFF(YEAR, HireDate, GETDATE()) AS 工龄
    
FROM Employees;

-- ======================================================
-- 5. 查询去重(去除重复值)
-- ======================================================
-- 语法:SELECT DISTINCT 列名 FROM 表名

-- 查询有哪些不同的部门(去掉重复的部门名)
SELECT DISTINCT Department FROM Employees;

-- 查询不同的部门和职位组合
-- DISTINCT作用于所有列,只有所有列的值都相同才会去重
SELECT DISTINCT Department, JobTitle FROM Employees;

-- 示例数据:
-- 原始数据:          去重后:
-- IT, 程序员        IT, 程序员
-- IT, 程序员        IT, 测试员  
-- IT, 测试员        HR, 专员
-- HR, 专员
-- HR, 专员
WHERE - 条件过滤(最常用!)
sql 复制代码
-- ======================================================
-- 1. 基本条件查询
-- ======================================================
-- 语法:SELECT * FROM 表名 WHERE 条件

-- 查询工资大于5000的员工
SELECT * FROM Employees WHERE Salary > 5000;

-- 查询姓"张"的员工
SELECT * FROM Employees WHERE LastName = '张';

-- 查询2020年入职的员工
SELECT * FROM Employees WHERE YEAR(HireDate) = 2020;

-- ======================================================
-- 2. 多个条件查询(AND/OR)
-- ======================================================
-- AND:两个条件都要满足
-- OR:两个条件满足一个就行

-- 查询IT部门且工资大于6000的员工
SELECT * FROM Employees 
WHERE Department = 'IT' AND Salary > 6000;

-- 查询IT部门或工资大于8000的员工
SELECT * FROM Employees 
WHERE Department = 'IT' OR Salary > 8000;

-- 复杂条件:IT部门工资>6000,或者HR部门工资>5000
SELECT * FROM Employees 
WHERE (Department = 'IT' AND Salary > 6000)
   OR (Department = 'HR' AND Salary > 5000);

-- ======================================================
-- 3. 范围查询(BETWEEN)
-- ======================================================
-- BETWEEN 值1 AND 值2:包括边界值

-- 查询工资在4000到8000之间的员工
SELECT * FROM Employees 
WHERE Salary BETWEEN 4000 AND 8000;
-- 等价于:Salary >= 4000 AND Salary <= 8000

-- 查询2020年1月到6月入职的员工
SELECT * FROM Employees 
WHERE HireDate BETWEEN '2020-01-01' AND '2020-06-30';

-- ======================================================
-- 4. 模糊查询(LIKE)
-- ======================================================
-- %:匹配任意多个字符(0个或多个)
-- _:匹配单个字符
-- []:匹配括号内的任意一个字符
-- [^]:不匹配括号内的字符

-- 查询姓"张"的员工(张开头)
SELECT * FROM Employees WHERE LastName LIKE '张%';
-- 匹配:张三、张三丰、张...

-- 查询名字以"明"结尾的员工
SELECT * FROM Employees WHERE FirstName LIKE '%明';
-- 匹配:小明、说明

-- 查询包含"文"字的员工
SELECT * FROM Employees WHERE FirstName LIKE '%文%';
-- 匹配:文、李文、文静、李文静

-- 查询姓"张"且名字是2个字的员工
SELECT * FROM Employees WHERE LastName LIKE '张_' AND LEN(FirstName) = 1;
-- 匹配:张+单个字(张三)

-- 查询邮箱是gmail.com结尾的员工
SELECT * FROM Employees 
WHERE Email LIKE '%@gmail.com';

-- ======================================================
-- 5. IN查询(多值匹配)
-- ======================================================
-- 查询部门是IT、HR或Sales的员工
SELECT * FROM Employees 
WHERE Department IN ('IT', 'HR', 'Sales');
-- 等价于:Department = 'IT' OR Department = 'HR' OR Department = 'Sales'

-- 查询员工ID为1,3,5,7的员工
SELECT * FROM Employees 
WHERE EmployeeID IN (1, 3, 5, 7);

-- ======================================================
-- 6. NULL值处理
-- ======================================================
-- NULL表示空值,要用IS NULL或IS NOT NULL判断

-- 查询邮箱为空的员工
SELECT * FROM Employees WHERE Email IS NULL;

-- 查询邮箱不为空的员工
SELECT * FROM Employees WHERE Email IS NOT NULL;

-- ❌ 错误写法:不能直接用 = NULL
-- SELECT * FROM Employees WHERE Email = NULL;  -- 错误!

-- ======================================================
-- 7. NOT运算符
-- ======================================================
-- 查询不是IT部门的员工
SELECT * FROM Employees WHERE NOT Department = 'IT';
-- 等价于:Department <> 'IT'

-- 查询工资不在4000-8000之间的员工
SELECT * FROM Employees WHERE Salary NOT BETWEEN 4000 AND 8000;

-- 查询不姓"张"的员工
SELECT * FROM Employees WHERE LastName NOT LIKE '张%';

-- 查询部门不是IT、HR、Sales的员工
SELECT * FROM Employees WHERE Department NOT IN ('IT', 'HR', 'Sales');
ORDER BY - 排序
sql 复制代码
-- ======================================================
-- 1. 单列排序
-- ======================================================
-- ASC:升序(从小到大,默认)
-- DESC:降序(从大到小)

-- 按工资升序排列
SELECT * FROM Employees ORDER BY Salary ASC;

-- 按入职日期降序排列(最新的在最前面)
SELECT * FROM Employees ORDER BY HireDate DESC;

-- 按名字拼音升序排列
SELECT * FROM Employees ORDER BY FirstName;

-- ======================================================
-- 2. 多列排序
-- ======================================================
-- 先按部门升序,部门相同的再按工资降序
SELECT * FROM Employees 
ORDER BY Department ASC, Salary DESC;

-- 三列排序:部门、职位、工资
SELECT * FROM Employees 
ORDER BY Department, JobTitle, Salary DESC;

-- 运行结果示例:
-- 部门  | 职位   | 工资
-- ------|--------|------
-- HR    | 经理   | 8000
-- HR    | 专员   | 5000
-- IT    | 工程师 | 9000
-- IT    | 工程师 | 7000

-- ======================================================
-- 3. 按表达式排序
-- ======================================================
-- 按年薪排序
SELECT 
    EmployeeID,
    FirstName,
    LastName,
    Salary,
    Salary * 12 AS 年薪
FROM Employees
ORDER BY Salary * 12 DESC;

-- 按名字长度排序
SELECT 
    FirstName,
    LEN(FirstName) AS 名字长度
FROM Employees
ORDER BY LEN(FirstName) DESC;

-- ======================================================
-- 4. 按列位置排序
-- ======================================================
-- 按SELECT中的列位置排序(不推荐,可读性差)
SELECT 
    FirstName,
    LastName,
    Salary
FROM Employees
ORDER BY 3 DESC, 1 ASC;  -- 3表示第3列(Salary),1表示第1列(FirstName)

2. 聚合查询(统计类)

聚合函数
sql 复制代码
-- ======================================================
-- 1. COUNT() - 计数
-- ======================================================
-- 统计员工总数
SELECT COUNT(*) AS 总人数 FROM Employees;

-- 统计有邮箱的员工数
SELECT COUNT(Email) AS 有邮箱人数 FROM Employees;
-- COUNT(列名)会忽略NULL值

-- 统计不同部门的数量
SELECT COUNT(DISTINCT Department) AS 部门数量 FROM Employees;

-- ======================================================
-- 2. SUM() - 求和
-- ======================================================
-- 计算所有员工的工资总和
SELECT SUM(Salary) AS 总工资 FROM Employees;

-- 计算IT部门的工资总和
SELECT SUM(Salary) AS IT部门总工资 
FROM Employees 
WHERE Department = 'IT';

-- ======================================================
-- 3. AVG() - 平均值
-- ======================================================
-- 计算平均工资
SELECT AVG(Salary) AS 平均工资 FROM Employees;

-- 计算有奖金的员工的平均奖金
SELECT AVG(Bonus) AS 平均奖金 FROM Employees WHERE Bonus IS NOT NULL;

-- 保留两位小数
SELECT ROUND(AVG(Salary), 2) AS 平均工资 FROM Employees;

-- ======================================================
-- 4. MAX() - 最大值
-- ======================================================
-- 查询最高工资
SELECT MAX(Salary) AS 最高工资 FROM Employees;

-- 查询最晚入职的日期
SELECT MAX(HireDate) AS 最近入职日期 FROM Employees;

-- 查询最长的名字
SELECT MAX(LEN(FirstName)) AS 最长名字长度 FROM Employees;

-- ======================================================
-- 5. MIN() - 最小值
-- ======================================================
-- 查询最低工资
SELECT MIN(Salary) AS 最低工资 FROM Employees;

-- 查询最早入职的日期
SELECT MIN(HireDate) AS 最早入职日期 FROM Employees;

-- ======================================================
-- 6. 聚合函数综合示例
-- ======================================================
SELECT 
    COUNT(*) AS 总人数,
    SUM(Salary) AS 工资总额,
    AVG(Salary) AS 平均工资,
    MAX(Salary) AS 最高工资,
    MIN(Salary) AS 最低工资,
    MAX(Salary) - MIN(Salary) AS 工资差距
FROM Employees;
GROUP BY - 分组统计
sql 复制代码
-- ======================================================
-- 1. 基础分组
-- ======================================================
-- 按部门分组,统计每个部门的人数
SELECT 
    Department AS 部门,
    COUNT(*) AS 人数
FROM Employees
GROUP BY Department;

-- 运行结果:
-- 部门 | 人数
-- -----|-----
-- IT   | 10
-- HR   | 5
-- Sales| 8

-- ======================================================
-- 2. 多列分组
-- ======================================================
-- 按部门和职位分组
SELECT 
    Department AS 部门,
    JobTitle AS 职位,
    COUNT(*) AS 人数
FROM Employees
GROUP BY Department, JobTitle;

-- ======================================================
-- 3. 分组后使用聚合函数
-- ======================================================
-- 按部门分组,统计每个部门的各项数据
SELECT 
    Department AS 部门,
    COUNT(*) AS 人数,
    AVG(Salary) AS 平均工资,
    SUM(Salary) AS 部门总工资,
    MAX(Salary) AS 最高工资,
    MIN(Salary) AS 最低工资
FROM Employees
GROUP BY Department;

-- ======================================================
-- 4. HAVING - 分组后筛选
-- ======================================================
-- WHERE:分组前筛选
-- HAVING:分组后筛选

-- 查询人数超过5人的部门
SELECT 
    Department,
    COUNT(*) AS 人数
FROM Employees
GROUP BY Department
HAVING COUNT(*) > 5;

-- 查询平均工资大于6000的部门
SELECT 
    Department,
    AVG(Salary) AS 平均工资
FROM Employees
GROUP BY Department
HAVING AVG(Salary) > 6000;

-- 查询人数超过3人且平均工资大于5000的部门
SELECT 
    Department,
    COUNT(*) AS 人数,
    AVG(Salary) AS 平均工资
FROM Employees
GROUP BY Department
HAVING COUNT(*) > 3 AND AVG(Salary) > 5000;

-- ======================================================
-- 5. 分组排序
-- ======================================================
-- 按部门平均工资从高到低排序
SELECT 
    Department,
    AVG(Salary) AS 平均工资
FROM Employees
GROUP BY Department
ORDER BY AVG(Salary) DESC;

-- ======================================================
-- 6. GROUP BY与WHERE结合
-- ======================================================
-- 先筛选2020年以后的员工,再按部门分组
SELECT 
    Department,
    COUNT(*) AS 2020年后入职人数,
    AVG(Salary) AS 平均工资
FROM Employees
WHERE HireDate >= '2020-01-01'  -- 先筛选
GROUP BY Department
HAVING COUNT(*) > 2             -- 再筛选分组结果
ORDER BY 平均工资 DESC;

3. 连接查询(多表查询)

INNER JOIN - 内连接(最常用!)
sql 复制代码
-- ======================================================
-- 基本INNER JOIN
-- ======================================================
-- 语法:SELECT ... FROM 表1 INNER JOIN 表2 ON 连接条件

-- 假设有两个表:
-- Employees(员工表):EmployeeID, EmployeeName, DepartmentID
-- Departments(部门表):DepartmentID, DepartmentName

-- 查询员工及其部门信息
SELECT 
    e.EmployeeID,          -- e表示Employees表
    e.EmployeeName,        -- 员工姓名
    d.DepartmentName,      -- d表示Departments表
    e.Salary,
    e.HireDate
FROM Employees e           -- 给Employees表起别名e
INNER JOIN Departments d   -- 给Departments表起别名d
    ON e.DepartmentID = d.DepartmentID;  -- 连接条件

-- 运行结果:只显示有部门的员工
-- 员工ID | 员工名 | 部门名 | 工资 | 入职日期
-- --------|--------|--------|------|----------
-- 1      | 张三   | IT部   | 8000 | 2020-01-01
-- 2      | 李四   | HR部   | 6000 | 2020-02-01

-- ======================================================
-- 多表INNER JOIN
-- ======================================================
-- 三个表连接:员工-部门-职位
SELECT 
    e.EmployeeName,
    d.DepartmentName,
    j.JobTitle,
    j.MinSalary,
    j.MaxSalary,
    e.Salary
FROM Employees e
INNER JOIN Departments d ON e.DepartmentID = d.DepartmentID
INNER JOIN Jobs j ON e.JobID = j.JobID
WHERE e.Salary BETWEEN j.MinSalary AND j.MaxSalary;

-- ======================================================
-- 带条件的INNER JOIN
-- ======================================================
-- 查询IT部门工资大于6000的员工
SELECT 
    e.EmployeeName,
    d.DepartmentName,
    e.Salary
FROM Employees e
INNER JOIN Departments d ON e.DepartmentID = d.DepartmentID
WHERE d.DepartmentName = 'IT' 
  AND e.Salary > 6000;
LEFT JOIN - 左连接
sql 复制代码
-- ======================================================
-- LEFT JOIN基本用法
-- ======================================================
-- 显示所有员工,包括没有部门的员工
-- 左表(Employees)的所有记录都会显示
-- 右表(Departments)没有匹配的显示NULL

SELECT 
    e.EmployeeID,
    e.EmployeeName,
    d.DepartmentName
FROM Employees e
LEFT JOIN Departments d ON e.DepartmentID = d.DepartmentID;

-- 运行结果:
-- 员工ID | 员工名 | 部门名
-- --------|--------|----------
-- 1      | 张三   | IT部
-- 2      | 李四   | HR部
-- 3      | 王五   | NULL      <-- 没有部门的员工

-- ======================================================
-- 查找没有部门的员工
-- ======================================================
SELECT 
    e.EmployeeID,
    e.EmployeeName
FROM Employees e
LEFT JOIN Departments d ON e.DepartmentID = d.DepartmentID
WHERE d.DepartmentID IS NULL;  -- 部门ID为NULL表示没有部门

-- ======================================================
-- 多表LEFT JOIN
-- ======================================================
-- 查询所有员工及其部门和职位信息
SELECT 
    e.EmployeeName,
    d.DepartmentName,
    j.JobTitle
FROM Employees e
LEFT JOIN Departments d ON e.DepartmentID = d.DepartmentID
LEFT JOIN Jobs j ON e.JobID = j.JobID;
RIGHT JOIN - 右连接
sql 复制代码
-- ======================================================
-- RIGHT JOIN基本用法
-- ======================================================
-- 显示所有部门,包括没有员工的部门
-- 右表(Departments)的所有记录都会显示
-- 左表(Employees)没有匹配的显示NULL

SELECT 
    d.DepartmentName,
    e.EmployeeName
FROM Employees e
RIGHT JOIN Departments d ON e.DepartmentID = d.DepartmentID;

-- 运行结果:
-- 部门名   | 员工名
-- ---------|----------
-- IT部     | 张三
-- HR部     | 李四
-- 财务部   | NULL      <-- 没有员工的部门

-- ======================================================
-- 查找没有员工的部门
-- ======================================================
SELECT 
    d.DepartmentName
FROM Employees e
RIGHT JOIN Departments d ON e.DepartmentID = d.DepartmentID
WHERE e.EmployeeID IS NULL;  -- 员工ID为NULL表示没有员工
FULL JOIN - 全连接
sql 复制代码
-- ======================================================
-- FULL JOIN基本用法
-- ======================================================
-- 显示所有员工和所有部门
-- 两个表的所有记录都会显示
-- 没有匹配的部分显示NULL

SELECT 
    e.EmployeeName,
    d.DepartmentName
FROM Employees e
FULL JOIN Departments d ON e.DepartmentID = d.DepartmentID;

-- 运行结果:
-- 员工名 | 部门名
-- --------|----------
-- 张三   | IT部
-- 李四   | HR部
-- 王五   | NULL      <-- 没有部门的员工
-- NULL   | 财务部    <-- 没有员工的部门

4. 子查询(查询嵌套查询)

WHERE子句中的子查询
sql 复制代码
-- ======================================================
-- 1. 标量子查询(返回单个值)
-- ======================================================
-- 查询工资高于平均工资的员工
SELECT * FROM Employees 
WHERE Salary > (
    SELECT AVG(Salary) FROM Employees  -- 子查询返回平均工资
);

-- 查询最后入职的员工
SELECT * FROM Employees 
WHERE HireDate = (
    SELECT MAX(HireDate) FROM Employees  -- 子查询返回最晚入职日期
);

-- ======================================================
-- 2. 列子查询(返回一列多行)
-- ======================================================
-- 使用IN关键字

-- 查询IT部门的员工
SELECT * FROM Employees 
WHERE DepartmentID IN (
    SELECT DepartmentID FROM Departments 
    WHERE DepartmentName = 'IT'
);

-- 查询工资高于所有经理工资的员工
SELECT * FROM Employees 
WHERE Salary > ALL (
    SELECT Salary FROM Employees 
    WHERE JobTitle = '经理'
);

-- 查询工资高于任意一个经理工资的员工
SELECT * FROM Employees 
WHERE Salary > ANY (
    SELECT Salary FROM Employees 
    WHERE JobTitle = '经理'
);

-- ======================================================
-- 3. 行子查询(返回一行多列)
-- ======================================================
-- 查询和张三在同一个部门同一个职位的员工
SELECT * FROM Employees 
WHERE (DepartmentID, JobTitle) = (
    SELECT DepartmentID, JobTitle 
    FROM Employees 
    WHERE EmployeeName = '张三'
)
AND EmployeeName <> '张三';  -- 排除张三自己

-- ======================================================
-- 4. EXISTS子查询(判断是否存在)
-- ======================================================
-- 查询有下属的经理
SELECT * FROM Employees e1
WHERE EXISTS (
    SELECT 1 FROM Employees e2
    WHERE e2.ManagerID = e1.EmployeeID
);

-- 查询没有订单的客户
SELECT * FROM Customers c
WHERE NOT EXISTS (
    SELECT 1 FROM Orders o
    WHERE o.CustomerID = c.CustomerID
);
FROM子句中的子查询
sql 复制代码
-- ======================================================
-- 把子查询结果当表使用
-- ======================================================
-- 查询每个部门的平均工资,并显示部门名称
SELECT 
    dept.DepartmentName AS 部门名,
    empStats.平均工资,
    empStats.员工数量
FROM (
    -- 子查询:计算每个部门的统计信息
    SELECT 
        DepartmentID,
        COUNT(*) AS 员工数量,
        AVG(Salary) AS 平均工资,
        SUM(Salary) AS 工资总额
    FROM Employees
    GROUP BY DepartmentID
) empStats  -- 子查询的别名
INNER JOIN Departments dept ON empStats.DepartmentID = dept.DepartmentID
ORDER BY empStats.平均工资 DESC;

-- ======================================================
-- 多层嵌套子查询
-- ======================================================
-- 查询工资超过部门平均工资的员工
SELECT 
    e.EmployeeName,
    e.Salary,
    deptAvg.部门平均工资
FROM Employees e
INNER JOIN (
    -- 子查询:计算每个部门的平均工资
    SELECT 
        DepartmentID,
        AVG(Salary) AS 部门平均工资
    FROM Employees
    GROUP BY DepartmentID
) deptAvg ON e.DepartmentID = deptAvg.DepartmentID
WHERE e.Salary > deptAvg.部门平均工资;

5. 分页查询(重要!)

ROW_NUMBER() 方式
sql 复制代码
-- ======================================================
-- ROW_NUMBER()分页(SQL Server 2005+)
-- ======================================================
-- 查询第2页,每页10条(第11-20条)

-- 方法1:使用子查询
SELECT * FROM (
    -- 内层查询:给每一行添加行号
    SELECT 
        ROW_NUMBER() OVER (ORDER BY EmployeeID) AS RowNum,
        EmployeeID,
        EmployeeName,
        Salary,
        HireDate
    FROM Employees
    WHERE Department = 'IT'  -- 可以加筛选条件
) AS Temp
WHERE RowNum BETWEEN 11 AND 20;  -- 第2页:跳过前10条,取10条

-- 方法2:使用CTE(更清晰)
WITH NumberedEmployees AS (
    SELECT 
        ROW_NUMBER() OVER (ORDER BY EmployeeID) AS RowNum,
        EmployeeID,
        EmployeeName,
        Salary,
        HireDate
    FROM Employees
    WHERE Department = 'IT'
)
SELECT * FROM NumberedEmployees
WHERE RowNum BETWEEN 11 AND 20;

-- ======================================================
-- 分页参数化
-- ======================================================
DECLARE @PageIndex INT = 2;     -- 第几页(从1开始)
DECLARE @PageSize INT = 10;     -- 每页条数
DECLARE @StartRow INT, @EndRow INT;

-- 计算开始和结束行号
SET @StartRow = (@PageIndex - 1) * @PageSize + 1;
SET @EndRow = @PageIndex * @PageSize;

SELECT * FROM (
    SELECT 
        ROW_NUMBER() OVER (ORDER BY EmployeeID) AS RowNum,
        EmployeeID,
        EmployeeName,
        Salary
    FROM Employees
) AS Temp
WHERE RowNum BETWEEN @StartRow AND @EndRow;
OFFSET FETCH 方式(推荐!)
sql 复制代码
-- ======================================================
-- OFFSET FETCH分页(SQL Server 2012+,更简单)
-- ======================================================
-- 语法:OFFSET 跳过行数 ROWS FETCH NEXT 取行数 ROWS ONLY

-- 查询第1页,每页10条(第1-10条)
SELECT * FROM Employees
ORDER BY EmployeeID
OFFSET 0 ROWS           -- 跳过0条
FETCH NEXT 10 ROWS ONLY; -- 取10条

-- 查询第2页,每页10条(第11-20条)
SELECT * FROM Employees
ORDER BY EmployeeID
OFFSET 10 ROWS          -- 跳过前10条
FETCH NEXT 10 ROWS ONLY; -- 取10条

-- 查询第3页,每页5条(第11-15条)
SELECT * FROM Employees
ORDER BY EmployeeID
OFFSET 10 ROWS          -- 跳过前10条
FETCH NEXT 5 ROWS ONLY;  -- 取5条

-- ======================================================
-- 带条件的分页查询
-- ======================================================
-- 查询IT部门员工,按工资降序分页
SELECT * FROM Employees
WHERE Department = 'IT'
ORDER BY Salary DESC
OFFSET 20 ROWS          -- 跳过前20条
FETCH NEXT 10 ROWS ONLY; -- 取10条

-- ======================================================
-- 分页查询优化:同时获取总数
-- ======================================================
-- 使用窗口函数获取总数
SELECT 
    EmployeeID,
    EmployeeName,
    Salary,
    Department,
    COUNT(*) OVER() AS 总记录数  -- 窗口函数获取总数
FROM Employees
WHERE Department = 'IT'
ORDER BY EmployeeID
OFFSET 10 ROWS
FETCH NEXT 10 ROWS ONLY;

6. 高级查询

CTE(公用表表达式)
sql 复制代码
-- ======================================================
-- 基本CTE语法
-- ======================================================
-- 语法:WITH CTE名称 AS (查询语句) SELECT * FROM CTE名称

-- 示例1:简单的CTE
WITH ITEmployees AS (
    SELECT * FROM Employees WHERE Department = 'IT'
)
SELECT * FROM ITEmployees
ORDER BY Salary DESC;

-- 示例2:多个CTE
WITH 
-- 第一个CTE:IT部门员工
IT_Dept AS (
    SELECT EmployeeID, EmployeeName, Salary 
    FROM Employees 
    WHERE Department = 'IT'
),
-- 第二个CTE:工资超过8000的员工
HighSalary AS (
    SELECT * FROM IT_Dept WHERE Salary > 8000
)
-- 主查询
SELECT 
    EmployeeName,
    Salary,
    CASE 
        WHEN Salary > 10000 THEN '高薪'
        WHEN Salary > 8000 THEN '中高薪'
        ELSE '一般'
    END AS 薪资等级
FROM HighSalary;

-- ======================================================
-- 递归CTE(查询树形结构)
-- ======================================================
-- 查询员工的管理层级(经理-下属关系)

-- 假设表结构:
-- EmployeeID, EmployeeName, ManagerID

WITH EmployeeHierarchy AS (
    -- 锚点成员:顶级经理(没有上级)
    SELECT 
        EmployeeID,
        EmployeeName,
        ManagerID,
        1 AS Level,  -- 层级
        CAST(EmployeeName AS VARCHAR(100)) AS Path  -- 路径
    FROM Employees
    WHERE ManagerID IS NULL
    
    UNION ALL  -- 联合所有
    
    -- 递归成员:下属员工
    SELECT 
        e.EmployeeID,
        e.EmployeeName,
        e.ManagerID,
        eh.Level + 1 AS Level,  -- 层级加1
        CAST(eh.Path + ' -> ' + e.EmployeeName AS VARCHAR(100)) AS Path
    FROM Employees e
    INNER JOIN EmployeeHierarchy eh ON e.ManagerID = eh.EmployeeID
)
SELECT 
    EmployeeID,
    EmployeeName,
    Level,
    Path
FROM EmployeeHierarchy
ORDER BY Path;
窗口函数
sql 复制代码
-- ======================================================
-- 1. 排名函数
-- ======================================================
-- ROW_NUMBER(): 连续不重复的序号(1,2,3,4...)
-- RANK(): 排名,相同值有相同排名,会跳过序号(1,2,2,4...)
-- DENSE_RANK(): 密集排名,相同值有相同排名,不跳过序号(1,2,2,3...)
-- NTILE(n): 将结果集分成n组,每组编号1-n

-- 按工资排名
SELECT 
    EmployeeName,
    Salary,
    Department,
    ROW_NUMBER() OVER (ORDER BY Salary DESC) AS 行号,
    RANK() OVER (ORDER BY Salary DESC) AS 排名,
    DENSE_RANK() OVER (ORDER BY Salary DESC) AS 密集排名,
    NTILE(4) OVER (ORDER BY Salary DESC) AS 四分位  -- 分成4组
FROM Employees;

-- ======================================================
-- 2. 分区窗口函数
-- ======================================================
-- PARTITION BY:按部门分区,在每个部门内单独排名

SELECT 
    EmployeeName,
    Department,
    Salary,
    -- 部门内工资排名
    ROW_NUMBER() OVER (
        PARTITION BY Department 
        ORDER BY Salary DESC
    ) AS 部门内排名,
    
    -- 部门内工资密集排名
    DENSE_RANK() OVER (
        PARTITION BY Department 
        ORDER BY Salary DESC
    ) AS 部门内密集排名,
    
    -- 部门内工资百分比排名
    PERCENT_RANK() OVER (
        PARTITION BY Department 
        ORDER BY Salary DESC
    ) AS 部门内百分比排名
FROM Employees;

-- ======================================================
-- 3. 聚合窗口函数
-- ======================================================
-- 可以同时显示明细和聚合值

SELECT 
    EmployeeName,
    Department,
    Salary,
    -- 部门平均工资
    AVG(Salary) OVER (PARTITION BY Department) AS 部门平均工资,
    
    -- 部门最高工资
    MAX(Salary) OVER (PARTITION BY Department) AS 部门最高工资,
    
    -- 部门最低工资
    MIN(Salary) OVER (PARTITION BY Department) AS 部门最低工资,
    
    -- 部门工资总和
    SUM(Salary) OVER (PARTITION BY Department) AS 部门工资总额,
    
    -- 累计工资(部门内按工资排序累计)
    SUM(Salary) OVER (
        PARTITION BY Department 
        ORDER BY Salary 
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS 部门累计工资,
    
    -- 与平均工资的差距
    Salary - AVG(Salary) OVER (PARTITION BY Department) AS 与平均工资差
FROM Employees
ORDER BY Department, Salary DESC;
PIVOT - 行转列
sql 复制代码
-- ======================================================
-- PIVOT基本用法
-- ======================================================
-- 将行数据转为列显示

-- 示例:统计每个部门男女员工的人数
SELECT * FROM (
    -- 源数据:每个员工的部门和性别
    SELECT 
        Department,
        Gender,
        EmployeeID
    FROM Employees
) AS SourceTable
PIVOT (
    COUNT(EmployeeID)        -- 聚合函数:计数
    FOR Gender IN ([男], [女]) -- 要转成列的字段值
) AS PivotTable
ORDER BY Department;

-- 运行结果:
-- Department | 男 | 女
-- ------------|----|----
-- IT         | 8  | 2
-- HR         | 3  | 7
-- Sales      | 6  | 4

-- ======================================================
-- 多列PIVOT
-- ======================================================
-- 统计每个部门男女员工的平均工资
SELECT * FROM (
    SELECT 
        Department,
        Gender,
        Salary
    FROM Employees
) AS SourceTable
PIVOT (
    AVG(Salary)            -- 聚合函数:平均值
    FOR Gender IN ([男], [女])
) AS PivotTable
ORDER BY Department;

-- ======================================================
-- 动态PIVOT(列值不固定时)
-- ======================================================
-- 当要转置的列值不确定时使用动态SQL

DECLARE @Columns NVARCHAR(MAX), @SQL NVARCHAR(MAX);

-- 获取所有不重复的部门
SELECT @Columns = COALESCE(@Columns + ',', '') + QUOTENAME(Department)
FROM (SELECT DISTINCT Department FROM Employees) AS Dept;

-- 构建动态SQL
SET @SQL = '
SELECT * FROM (
    SELECT 
        Gender,
        Department,
        COUNT(*) AS EmployeeCount
    FROM Employees
    GROUP BY Gender, Department
) AS SourceTable
PIVOT (
    SUM(EmployeeCount)
    FOR Department IN (' + @Columns + ')
) AS PivotTable
ORDER BY Gender;';

-- 执行动态SQL
EXEC sp_executesql @SQL;

🏢 二、项目中常用的查询语句(实战场景)

场景1:用户管理系统

1. 用户登录验证
sql 复制代码
-- ======================================================
-- 用户登录验证(带参数)
-- ======================================================
-- 实际项目中,参数来自应用程序(如C#)

-- 定义参数
DECLARE @UserName NVARCHAR(50) = 'zhangsan';
DECLARE @Password NVARCHAR(50) = '123456';  -- 密码应该是加密的

-- 登录验证查询
SELECT 
    UserID,              -- 用户ID
    UserName,            -- 用户名
    RealName,            -- 真实姓名
    UserType,            -- 用户类型:1管理员 2普通用户
    LastLoginTime,       -- 上次登录时间
    LoginCount           -- 登录次数
FROM Users 
WHERE UserName = @UserName 
  AND Password = @Password  -- 实际中密码应该加密存储和比较
  AND IsActive = 1          -- 账号是否激活
  AND IsLocked = 0          -- 账号是否被锁定
  AND IsDeleted = 0;        -- 账号是否被删除

-- ======================================================
-- 登录成功后的更新操作
-- ======================================================
BEGIN TRANSACTION;  -- 开始事务

-- 更新登录信息
UPDATE Users 
SET 
    LastLoginTime = GETDATE(),      -- 更新最后登录时间
    LoginCount = LoginCount + 1,    -- 登录次数加1
    UpdateTime = GETDATE()          -- 更新时间
WHERE UserID = @UserID;

-- 插入登录日志
INSERT INTO LoginLogs (
    UserID,
    LoginTime,
    IPAddress,
    UserAgent,
    LoginResult
) VALUES (
    @UserID,
    GETDATE(),
    @IPAddress,        -- IP地址参数
    @UserAgent,        -- 浏览器信息参数
    'Success'          -- 登录结果
);

COMMIT TRANSACTION;  -- 提交事务
2. 用户分页列表
sql 复制代码
-- ======================================================
-- 带搜索条件的分页查询(完整版)
-- ======================================================
-- 存储过程方式,方便调用

CREATE PROCEDURE sp_GetUsersPaged
    @PageIndex INT = 1,           -- 页码,默认第1页
    @PageSize INT = 10,           -- 每页条数,默认10条
    @SearchKeyword NVARCHAR(100) = NULL,  -- 搜索关键词
    @UserType INT = NULL,         -- 用户类型筛选
    @IsActive BIT = NULL,         -- 是否激活筛选
    @StartDate DATE = NULL,       -- 注册开始日期
    @EndDate DATE = NULL          -- 注册结束日期
AS
BEGIN
    -- 设置NOCOUNT ON以提高性能
    SET NOCOUNT ON;
    
    -- 分页数据查询
    SELECT * FROM (
        SELECT 
            ROW_NUMBER() OVER (
                ORDER BY 
                    CASE WHEN @SearchKeyword IS NOT NULL 
                         THEN (CASE WHEN UserName LIKE '%' + @SearchKeyword + '%' THEN 0 
                                    WHEN RealName LIKE '%' + @SearchKeyword + '%' THEN 1 
                                    ELSE 2 END)
                         ELSE 0 END,
                    CreateTime DESC
            ) AS RowNum,
            
            -- 用户基本信息
            UserID,
            UserName,
            RealName,
            UserType,
            
            -- 联系方式
            Phone,
            Email,
            
            -- 状态信息
            IsActive,
            IsLocked,
            IsDeleted,
            
            -- 时间信息
            CreateTime,
            LastLoginTime,
            UpdateTime,
            
            -- 统计信息(使用窗口函数)
            COUNT(*) OVER() AS TotalRecords  -- 总记录数
            
        FROM Users
        WHERE 
            -- 搜索条件
            (@SearchKeyword IS NULL OR 
             UserName LIKE '%' + @SearchKeyword + '%' OR
             RealName LIKE '%' + @SearchKeyword + '%' OR
             Phone LIKE '%' + @SearchKeyword + '%' OR
             Email LIKE '%' + @SearchKeyword + '%')
            
            -- 用户类型筛选
            AND (@UserType IS NULL OR UserType = @UserType)
            
            -- 激活状态筛选
            AND (@IsActive IS NULL OR IsActive = @IsActive)
            
            -- 删除状态(只查询未删除的)
            AND IsDeleted = 0
            
            -- 注册时间范围筛选
            AND (@StartDate IS NULL OR CreateTime >= @StartDate)
            AND (@EndDate IS NULL OR CreateTime <= DATEADD(DAY, 1, @EndDate))  -- 包含结束日期当天
    ) AS Temp
    WHERE RowNum > (@PageIndex - 1) * @PageSize
      AND RowNum <= @PageIndex * @PageSize
    
    -- 排序结果
    ORDER BY RowNum;
    
    -- 也可以单独返回总数(如果需要)
    -- SELECT COUNT(*) AS TotalCount FROM Users WHERE ...(相同条件)
END
GO

-- ======================================================
-- 调用分页存储过程
-- ======================================================
-- 示例1:查询第1页,搜索"张"
EXEC sp_GetUsersPaged 
    @PageIndex = 1,
    @PageSize = 10,
    @SearchKeyword = '张';

-- 示例2:查询管理员用户
EXEC sp_GetUsersPaged 
    @PageIndex = 1,
    @PageSize = 20,
    @UserType = 1,  -- 管理员
    @IsActive = 1;  -- 已激活

-- 示例3:查询某个时间段的用户
EXEC sp_GetUsersPaged 
    @PageIndex = 1,
    @PageSize = 15,
    @StartDate = '2024-01-01',
    @EndDate = '2024-01-31';

场景2:电商系统

1. 商品搜索
sql 复制代码
-- ======================================================
-- 商品搜索存储过程
-- ======================================================
CREATE PROCEDURE sp_SearchProducts
    @Keyword NVARCHAR(100) = NULL,         -- 搜索关键词
    @CategoryId INT = NULL,                -- 分类ID
    @BrandId INT = NULL,                   -- 品牌ID
    @MinPrice DECIMAL(18,2) = NULL,        -- 最低价格
    @MaxPrice DECIMAL(18,2) = NULL,        -- 最高价格
    @IsOnSale BIT = NULL,                  -- 是否在售
    @IsHot BIT = NULL,                     -- 是否热销
    @IsRecommend BIT = NULL,               -- 是否推荐
    @SortBy VARCHAR(50) = 'default',       -- 排序方式
    @PageIndex INT = 1,                    -- 页码
    @PageSize INT = 20                     -- 每页条数
AS
BEGIN
    SET NOCOUNT ON;
    
    -- 构建排序条件
    DECLARE @OrderBy NVARCHAR(1000);
    SET @OrderBy = CASE @SortBy
        WHEN 'price_asc' THEN 'Price ASC'
        WHEN 'price_desc' THEN 'Price DESC'
        WHEN 'sales' THEN 'SalesCount DESC'
        WHEN 'new' THEN 'CreateTime DESC'
        WHEN 'hot' THEN 'ClickCount DESC'
        ELSE 'SalesCount DESC, CreateTime DESC'  -- 默认排序
    END;
    
    -- 动态SQL构建
    DECLARE @SQL NVARCHAR(MAX);
    DECLARE @WhereSQL NVARCHAR(MAX) = '';
    DECLARE @Params NVARCHAR(1000);
    
    -- 构建WHERE条件
    IF @Keyword IS NOT NULL
        SET @WhereSQL = @WhereSQL + ' AND (ProductName LIKE ''%' + @Keyword + '%'' OR Description LIKE ''%' + @Keyword + '%'' OR Keywords LIKE ''%' + @Keyword + '%'')';
    
    IF @CategoryId IS NOT NULL
        SET @WhereSQL = @WhereSQL + ' AND CategoryID = ' + CAST(@CategoryId AS VARCHAR(10));
    
    IF @BrandId IS NOT NULL
        SET @WhereSQL = @WhereSQL + ' AND BrandID = ' + CAST(@BrandId AS VARCHAR(10));
    
    IF @MinPrice IS NOT NULL
        SET @WhereSQL = @WhereSQL + ' AND Price >= ' + CAST(@MinPrice AS VARCHAR(20));
    
    IF @MaxPrice IS NOT NULL
        SET @WhereSQL = @WhereSQL + ' AND Price <= ' + CAST(@MaxPrice AS VARCHAR(20));
    
    IF @IsOnSale IS NOT NULL
        SET @WhereSQL = @WhereSQL + ' AND IsOnSale = ' + CAST(@IsOnSale AS CHAR(1));
    
    IF @IsHot IS NOT NULL
        SET @WhereSQL = @WhereSQL + ' AND IsHot = ' + CAST(@IsHot AS CHAR(1));
    
    IF @IsRecommend IS NOT NULL
        SET @WhereSQL = @WhereSQL + ' AND IsRecommend = ' + CAST(@IsRecommend AS CHAR(1));
    
    -- 基础条件
    SET @WhereSQL = 'WHERE IsDeleted = 0 AND IsActive = 1' + @WhereSQL;
    
    -- 构建完整SQL
    SET @SQL = '
    WITH ProductList AS (
        SELECT 
            ROW_NUMBER() OVER (ORDER BY ' + @OrderBy + ') AS RowNum,
            ProductID,
            ProductCode,
            ProductName,
            CategoryID,
            BrandID,
            Price,
            MarketPrice,
            Stock,
            SalesCount,
            ClickCount,
            MainImage,
            IsOnSale,
            IsHot,
            IsRecommend,
            CreateTime,
            -- 总记录数
            COUNT(*) OVER() AS TotalRecords,
            -- 库存状态
            CASE 
                WHEN Stock <= 0 THEN ''缺货''
                WHEN Stock < 10 THEN ''紧张''
                ELSE ''充足''
            END AS StockStatus,
            -- 折扣率
            CASE 
                WHEN MarketPrice > 0 THEN CAST((1 - Price/MarketPrice) * 100 AS DECIMAL(5,1))
                ELSE 0
            END AS DiscountRate
        FROM Products
        ' + @WhereSQL + '
    )
    SELECT 
        ProductID,
        ProductCode,
        ProductName,
        CategoryID,
        BrandID,
        Price,
        MarketPrice,
        Stock,
        SalesCount,
        ClickCount,
        MainImage,
        IsOnSale,
        IsHot,
        IsRecommend,
        CreateTime,
        TotalRecords,
        StockStatus,
        DiscountRate
    FROM ProductList
    WHERE RowNum > ' + CAST((@PageIndex - 1) * @PageSize AS VARCHAR(10)) + '
      AND RowNum <= ' + CAST(@PageIndex * @PageSize AS VARCHAR(10)) + '
    ORDER BY RowNum;';
    
    -- 执行查询
    EXEC sp_executesql @SQL;
END
GO

-- ======================================================
-- 调用商品搜索
-- ======================================================
-- 搜索手机,价格在1000-5000之间,按销量排序
EXEC sp_SearchProducts 
    @Keyword = '手机',
    @MinPrice = 1000,
    @MaxPrice = 5000,
    @SortBy = 'sales',
    @PageIndex = 1,
    @PageSize = 20;

-- 搜索某个分类的热销商品
EXEC sp_SearchProducts 
    @CategoryId = 1,
    @IsHot = 1,
    @PageIndex = 1,
    @PageSize = 10;
2. 订单统计报表
sql 复制代码
-- ======================================================
-- 每日销售统计报表
-- ======================================================
WITH DailySales AS (
    SELECT 
        -- 日期(去掉时间部分)
        CONVERT(DATE, OrderTime) AS 销售日期,
        
        -- 订单统计
        COUNT(*) AS 订单数量,
        COUNT(DISTINCT CustomerID) AS 客户数量,
        
        -- 金额统计
        SUM(TotalAmount) AS 销售总额,
        AVG(TotalAmount) AS 平均订单金额,
        SUM(Freight) AS 运费总额,
        
        -- 商品统计
        SUM(Quantity) AS 商品总数量,
        COUNT(DISTINCT ProductID) AS 商品种类数,
        
        -- 状态统计
        SUM(CASE WHEN Status = 1 THEN 1 ELSE 0 END) AS 待付款订单数,
        SUM(CASE WHEN Status = 2 THEN 1 ELSE 0 END) AS 待发货订单数,
        SUM(CASE WHEN Status = 3 THEN 1 ELSE 0 END) AS 待收货订单数,
        SUM(CASE WHEN Status = 4 THEN 1 ELSE 0 END) AS 已完成订单数,
        SUM(CASE WHEN Status = 5 THEN 1 ELSE 0 END) AS 已取消订单数
    FROM Orders o
    INNER JOIN OrderDetails od ON o.OrderID = od.OrderID
    WHERE OrderTime >= DATEADD(DAY, -30, GETDATE())  -- 最近30天
      AND Status IN (3, 4)  -- 只统计已发货和已完成的订单
    GROUP BY CONVERT(DATE, OrderTime)
),
RunningTotals AS (
    SELECT 
        *,
        -- 累计销售额
        SUM(销售总额) OVER (ORDER BY 销售日期) AS 累计销售额,
        
        -- 移动平均(最近7天平均)
        AVG(销售总额) OVER (
            ORDER BY 销售日期 
            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
        ) AS 近7日平均销售额,
        
        -- 环比增长率
        LAG(销售总额, 1) OVER (ORDER BY 销售日期) AS 昨日销售额,
        
        -- 计算环比
        CASE 
            WHEN LAG(销售总额, 1) OVER (ORDER BY 销售日期) > 0 
            THEN (销售总额 - LAG(销售总额, 1) OVER (ORDER BY 销售日期)) * 100.0 / 
                 LAG(销售总额, 1) OVER (ORDER BY 销售日期)
            ELSE NULL
        END AS 环比增长率
    FROM DailySales
)
SELECT 
    销售日期,
    
    -- 基础统计
    订单数量,
    客户数量,
    销售总额,
    平均订单金额,
    
    -- 客户质量指标
    CAST(销售总额 AS FLOAT) / NULLIF(客户数量, 0) AS 客单价,
    CAST(订单数量 AS FLOAT) / NULLIF(客户数量, 0) AS 人均订单数,
    
    -- 商品相关
    商品总数量,
    商品种类数,
    CAST(商品总数量 AS FLOAT) / NULLIF(订单数量, 0) AS 平均每单商品数,
    
    -- 状态分布
    待付款订单数,
    待发货订单数,
    待收货订单数,
    已完成订单数,
    已取消订单数,
    
    -- 完成率
    CAST(已完成订单数 AS FLOAT) * 100.0 / NULLIF(订单数量, 0) AS 订单完成率,
    
    -- 时间分析
    DATENAME(WEEKDAY, 销售日期) AS 星期几,
    CASE 
        WHEN DATENAME(WEEKDAY, 销售日期) IN ('星期六', '星期日') 
        THEN '周末' 
        ELSE '工作日' 
    END AS 日期类型,
    
    -- 趋势分析
    累计销售额,
    近7日平均销售额,
    昨日销售额,
    环比增长率,
    
    -- 趋势判断
    CASE 
        WHEN 环比增长率 > 10 THEN '大幅增长'
        WHEN 环比增长率 > 0 THEN '增长'
        WHEN 环比增长率 = 0 THEN '持平'
        WHEN 环比增长率 > -10 THEN '小幅下降'
        ELSE '大幅下降'
    END AS 销售趋势
FROM RunningTotals
ORDER BY 销售日期 DESC;

场景3:企业管理系统

1. 员工考勤统计
sql 复制代码
-- ======================================================
-- 月度考勤统计(详细版)
-- ======================================================
DECLARE @Year INT = 2024;
DECLARE @Month INT = 1;

WITH DateRange AS (
    -- 生成当月的所有日期
    SELECT 
        DATEADD(DAY, number, DATEFROMPARTS(@Year, @Month, 1)) AS WorkDate
    FROM master..spt_values
    WHERE type = 'P' 
      AND number < DAY(EOMONTH(DATEFROMPARTS(@Year, @Month, 1)))
),
EmployeeWorkDays AS (
    -- 每个员工的工作日
    SELECT 
        e.EmployeeID,
        e.EmployeeName,
        d.DepartmentName,
        dr.WorkDate,
        -- 判断是否是工作日(排除周末)
        CASE 
            WHEN DATEPART(WEEKDAY, dr.WorkDate) IN (1, 7) 
            THEN 0 
            ELSE 1 
        END AS IsWorkday
    FROM Employees e
    CROSS JOIN DateRange dr
    LEFT JOIN Departments d ON e.DepartmentID = d.DepartmentID
    WHERE e.IsActive = 1
      AND e.HireDate <= dr.WorkDate
      AND (e.LeaveDate IS NULL OR e.LeaveDate >= dr.WorkDate)
),
AttendanceDetails AS (
    -- 考勤明细
    SELECT 
        ew.EmployeeID,
        ew.EmployeeName,
        ew.DepartmentName,
        ew.WorkDate,
        ew.IsWorkday,
        a.AttendanceID,
        a.CheckInTime,
        a.CheckOutTime,
        a.AttendanceType,  -- 1正常 2迟到 3早退 4请假 5缺勤 6加班
        a.Remark
    FROM EmployeeWorkDays ew
    LEFT JOIN Attendance a ON ew.EmployeeID = a.EmployeeID 
                           AND CONVERT(DATE, a.AttendanceDate) = ew.WorkDate
),
AttendanceSummary AS (
    -- 考勤统计
    SELECT 
        EmployeeID,
        EmployeeName,
        DepartmentName,
        
        -- 工作日天数
        SUM(IsWorkday) AS 应出勤天数,
        
        -- 实际考勤统计
        SUM(CASE WHEN AttendanceType = 1 THEN 1 ELSE 0 END) AS 正常出勤,
        SUM(CASE WHEN AttendanceType = 2 THEN 1 ELSE 0 END) AS 迟到次数,
        SUM(CASE WHEN AttendanceType = 3 THEN 1 ELSE 0 END) AS 早退次数,
        SUM(CASE WHEN AttendanceType = 4 THEN 1 ELSE 0 END) AS 请假天数,
        SUM(CASE WHEN AttendanceType = 5 THEN 1 ELSE 0 END) AS 缺勤天数,
        SUM(CASE WHEN AttendanceType = 6 THEN 1 ELSE 0 END) AS 加班天数,
        
        -- 未打卡天数
        SUM(CASE WHEN AttendanceID IS NULL AND IsWorkday = 1 THEN 1 ELSE 0 END) AS 未打卡天数,
        
        -- 工作时间统计
        AVG(CASE WHEN CheckInTime IS NOT NULL 
                 THEN DATEDIFF(MINUTE, '09:00:00', CONVERT(TIME, CheckInTime)) 
                 ELSE NULL END) AS 平均迟到分钟数,
        
        AVG(CASE WHEN CheckOutTime IS NOT NULL 
                 THEN DATEDIFF(MINUTE, CONVERT(TIME, CheckOutTime), '18:00:00') 
                 ELSE NULL END) AS 平均早退分钟数,
        
        -- 加班时间
        SUM(CASE WHEN AttendanceType = 6 
                 THEN DATEDIFF(HOUR, CheckOutTime, DATEADD(HOUR, 3, '18:00:00'))
                 ELSE 0 END) AS 加班总小时数
    FROM AttendanceDetails
    GROUP BY EmployeeID, EmployeeName, DepartmentName
)
SELECT 
    EmployeeID AS 员工编号,
    EmployeeName AS 员工姓名,
    DepartmentName AS 部门,
    
    -- 出勤统计
    应出勤天数,
    正常出勤,
    迟到次数,
    早退次数,
    请假天数,
    缺勤天数,
    加班天数,
    未打卡天数,
    
    -- 出勤率
    CAST(正常出勤 * 100.0 / NULLIF(应出勤天数, 0) AS DECIMAL(5,2)) AS 出勤率,
    
    -- 迟到早退统计
    平均迟到分钟数,
    平均早退分钟数,
    
    -- 加班统计
    加班总小时数,
    CAST(加班总小时数 AS DECIMAL(10,2)) * 50 AS 加班费估算,  -- 假设每小时50元
    
    -- 考勤评分(满分100分)
    CASE 
        WHEN 缺勤天数 > 3 THEN 60
        WHEN 迟到次数 > 5 OR 早退次数 > 5 THEN 70
        WHEN 未打卡天数 > 2 THEN 75
        WHEN 迟到次数 > 0 OR 早退次数 > 0 THEN 85
        WHEN 正常出勤 = 应出勤天数 THEN 100
        ELSE 90
    END AS 考勤评分,
    
    -- 考勤状态
    CASE 
        WHEN 缺勤天数 > 应出勤天数 * 0.2 THEN '差'
        WHEN 迟到次数 > 5 OR 早退次数 > 5 THEN '需改进'
        WHEN 出勤率 >= 95 THEN '优秀'
        WHEN 出勤率 >= 90 THEN '良好'
        WHEN 出勤率 >= 80 THEN '合格'
        ELSE '不合格'
    END AS 考勤状态
FROM AttendanceSummary
ORDER BY DepartmentName, 考勤评分 DESC;

由于文章篇幅限制,这里只展示了部分详细代码。实际项目中,SQL查询会更加复杂和多样化。关键是要理解每个查询的基本原理,然后根据具体需求进行组合和优化。

记住:多练习、多实践是掌握SQL查询的最好方法!从简单的SELECT开始,逐步学习复杂的查询技巧。

相关推荐
fen_fen1 天前
SqlServer新增schema和用户的命令
数据库·sqlserver
Clang's Blog7 天前
使用 SQL Server Management Studio 还原 .bak 备份文件的完整指南
数据库·sqlserver
雁凡彡9 天前
mybatis-plus中sqlserver 查询数组中指定位置前的数据
数据库·sqlserver·mybatis
dishugj10 天前
[SQLSERVER] Lock Waits/sec参数含义详解
数据库·oracle·sqlserver
l1t13 天前
使用docker安装sql server linux版
linux·sql·docker·容器·sqlserver
杨云龙UP14 天前
Windows环境下安装SQL Server 2016企业版+SP3补丁+SSMS连接操作手册_20251230
运维·服务器·数据库·sql·算法·sqlserver·哈希算法
杨云龙UP15 天前
SQL Server定时自动备份配置:使用SSMS维护计划向导配置数据库每日自动备份_20260101
运维·服务器·数据库·sql·sqlserver·桌面
牛魔王_118 天前
SqlServer 大数据量分页查询
数据库·sqlserver·分页·查询·翻页
banpu19 天前
Spring相关
数据库·spring·sqlserver