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