#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开始,逐步学习复杂的查询技巧。

相关推荐
Leon-Ning Liu21 小时前
SQL Server在ldf文件误删的情况下恢复数据库
数据库·sqlserver
姜太小白2 天前
【SQLServer】SQL Server 2022 连接证书错误解决
网络·数据库·sqlserver
gaozhiyong08133 天前
SpringBoot连接多数据源MySQL、SqlServer等(MyBatisPlus测试)
spring boot·mysql·sqlserver
源远流长jerry6 天前
dpdk19.08编译问题解决方案
数据库·postgresql·sqlserver
全栈小56 天前
【数据库】Sql Server 安装教程,一键到底,沉浸式下载安装MSSQL和SSMS
数据库·sqlserver
Msshu1239 天前
多协议快充取电芯片 支持与主板MCU共用D+D-网络可取电可与电脑传输数据
elasticsearch·sqlserver·flink·rabbitmq·storm
夏光芒9 天前
SQLSERVER数据库常用语句
数据库·sqlserver
北京_小杰子23 天前
Windows10本地安装SQLserver数据库连接的过程
数据库·windows·sqlserver·php
山岚的运维笔记24 天前
SQL Server笔记 -- 第86章:查询存储
笔记·python·sql·microsoft·sqlserver·flask
汇智信科24 天前
汇智信科网络考试系统:以技术赋能,重构在线测评新范式
linux·数据库·mysql·oracle·sqlserver·java技术