1. 项目概述
作为一名Oracle DBA,我经常遇到开发同事提出的各种基础查询问题。很多看似简单的SQL查询,其实藏着不少门道。今天我们就来聊聊那些Oracle基础查询中容易被忽略但极其重要的关键词用法。
在日常工作中,我发现很多开发人员虽然会写SELECT语句,但对一些基础关键词的理解往往停留在表面。比如你知道WHERE和HAVING的区别吗?GROUP BY和ORDER BY的优先级是怎样的?这些细节问题在实际项目中经常会导致性能问题甚至错误结果。
2. 核心查询关键词详解
2.1 WHERE与HAVING的深层区别
WHERE和HAVING都是用于筛选数据的子句,但它们的执行时机和作用对象完全不同。
WHERE是在数据分组前对原始数据进行过滤,它不能使用聚合函数。比如:
sql复制SELECT department_id, AVG(salary)
FROM employees
WHERE salary > 5000 -- 这里过滤的是单条记录
GROUP BY department_id;
而HAVING是在数据分组后对分组结果进行过滤,必须配合GROUP BY使用,可以包含聚合函数:
sql复制SELECT department_id, AVG(salary)
FROM employees
GROUP BY department_id
HAVING AVG(salary) > 5000; -- 这里过滤的是分组结果
重要提示:WHERE条件会显著减少参与分组计算的数据量,通常应该优先使用WHERE过滤掉不需要的数据,再使用HAVING对分组结果进行筛选。
2.2 GROUP BY的隐藏规则
GROUP BY看似简单,但有几个容易踩坑的地方:
- SELECT列表中的非聚合列必须出现在GROUP BY子句中:
sql复制-- 错误写法
SELECT department_id, employee_name, AVG(salary)
FROM employees
GROUP BY department_id;
-- 正确写法
SELECT department_id, employee_name, AVG(salary)
FROM employees
GROUP BY department_id, employee_name;
- GROUP BY的列顺序会影响性能。通常应该把选择性高的列放在前面:
sql复制-- 较好的写法
SELECT country, city, COUNT(*)
FROM locations
GROUP BY country, city; -- 假设country的选择性高于city
- Oracle特有的GROUP BY扩展:
sql复制-- 使用ROLLUP生成小计和总计
SELECT department_id, job_id, SUM(salary)
FROM employees
GROUP BY ROLLUP(department_id, job_id);
2.3 ORDER BY的特殊用法
ORDER BY不只是简单的排序,它还有一些高级用法:
- 可以使用列名、列别名、列位置或表达式排序:
sql复制SELECT employee_id, salary*12 annual_salary
FROM employees
ORDER BY annual_salary DESC; -- 使用列别名
SELECT employee_id, last_name, salary
FROM employees
ORDER BY 3 DESC; -- 使用列位置(第3列)
- NULL值的排序控制:
sql复制-- NULL值默认排在最后(ASC)或最前(DESC)
SELECT employee_id, commission_pct
FROM employees
ORDER BY commission_pct NULLS FIRST; -- 显式控制NULL值位置
- 多列排序时,可以指定每列不同的排序方向:
sql复制SELECT department_id, salary, hire_date
FROM employees
ORDER BY department_id ASC, salary DESC;
3. 容易被忽略的关键词组合
3.1 DISTINCT的陷阱
DISTINCT用于去除重复行,但需要注意:
- DISTINCT作用于SELECT列表的所有列,不是第一列:
sql复制-- 返回department_id和job_id的组合唯一值
SELECT DISTINCT department_id, job_id
FROM employees;
- DISTINCT会影响性能,特别是大数据量时。如果只需要特定列去重,考虑使用GROUP BY:
sql复制-- 比DISTINCT更高效的去重方式
SELECT department_id
FROM employees
GROUP BY department_id;
- DISTINCT可以和聚合函数一起使用:
sql复制-- 计算不同部门的数量
SELECT COUNT(DISTINCT department_id)
FROM employees;
3.2 WITH子句的妙用
WITH子句(又称公共表表达式CTE)可以显著提高复杂查询的可读性:
sql复制WITH dept_stats AS (
SELECT department_id, AVG(salary) avg_sal
FROM employees
GROUP BY department_id
),
high_sal_depts AS (
SELECT department_id
FROM dept_stats
WHERE avg_sal > 8000
)
SELECT e.employee_id, e.last_name
FROM employees e
JOIN high_sal_depts h ON e.department_id = h.department_id;
WITH子句的优点:
- 分解复杂查询
- 提高可读性和可维护性
- 可以被多次引用
- 某些情况下能提高性能
3.3 连接查询中的USING关键字
当连接列名相同时,USING比ON更简洁:
sql复制-- 传统ON语法
SELECT e.employee_id, e.last_name, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id;
-- 使用USING语法
SELECT employee_id, last_name, department_name
FROM employees
JOIN departments USING (department_id);
USING的特点:
- 连接列在结果集中只出现一次
- 只能用于等值连接
- 连接列名必须相同
4. 实用查询技巧与优化建议
4.1 使用FETCH FIRST实现分页
Oracle 12c引入了FETCH FIRST语法,比传统的ROWNUM分页更直观:
sql复制-- 获取前10条记录
SELECT employee_id, last_name, salary
FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY;
-- 分页查询(第2页,每页10条)
SELECT employee_id, last_name, salary
FROM employees
ORDER BY salary DESC
OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
4.2 使用CASE表达式实现条件逻辑
CASE表达式可以在查询中实现复杂的条件逻辑:
sql复制SELECT employee_id, last_name, salary,
CASE
WHEN salary < 5000 THEN '低'
WHEN salary BETWEEN 5000 AND 10000 THEN '中'
ELSE '高'
END AS salary_level,
CASE department_id
WHEN 10 THEN '行政部'
WHEN 20 THEN '财务部'
ELSE '其他部门'
END AS dept_name
FROM employees;
4.3 查询性能优化建议
- 尽量避免在WHERE条件中对列使用函数:
sql复制-- 不推荐的写法(无法使用索引)
SELECT * FROM employees
WHERE TO_CHAR(hire_date, 'YYYY') = '2020';
-- 推荐的写法
SELECT * FROM employees
WHERE hire_date >= TO_DATE('2020-01-01', 'YYYY-MM-DD')
AND hire_date < TO_DATE('2021-01-01', 'YYYY-MM-DD');
- 使用EXISTS代替IN处理大数据量子查询:
sql复制-- 当子查询结果集大时,EXISTS通常更高效
SELECT d.department_name
FROM departments d
WHERE EXISTS (
SELECT 1 FROM employees e
WHERE e.department_id = d.department_id
);
- 合理使用索引提示:
sql复制SELECT /*+ INDEX(employees emp_salary_idx) */ employee_id, last_name
FROM employees
WHERE salary > 10000;
5. 常见问题排查
5.1 ORA-00937错误:不是单组分组函数
这个错误通常发生在SELECT列表包含非聚合列但未在GROUP BY中指定:
sql复制-- 错误示例
SELECT department_id, AVG(salary), employee_name
FROM employees
GROUP BY department_id;
-- 修正方案1:将employee_name加入GROUP BY
SELECT department_id, AVG(salary), employee_name
FROM employees
GROUP BY department_id, employee_name;
-- 修正方案2:移除employee_name或使用聚合函数
SELECT department_id, AVG(salary), MAX(employee_name)
FROM employees
GROUP BY department_id;
5.2 ORA-00904错误:标识符无效
这个错误通常由以下原因引起:
- 列名拼写错误
- 表别名使用不当
- 引用了不存在的列
检查要点:
- 确认表和列名拼写正确
- 检查表别名是否正确使用
- 确保查询中引用的列确实存在于FROM子句指定的表中
5.3 查询性能突然下降
可能原因及解决方案:
- 统计信息过时:
sql复制EXEC DBMS_STATS.GATHER_TABLE_STATS('HR', 'EMPLOYEES');
- 索引失效:
sql复制ALTER INDEX emp_name_idx REBUILD;
- 执行计划改变:
sql复制-- 查看执行计划
EXPLAIN PLAN FOR SELECT * FROM employees WHERE salary > 10000;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
在实际项目中,我发现很多查询问题都源于对基础关键词理解不深。比如一个开发人员曾经因为混淆WHERE和HAVING导致报表数据严重错误,另一个项目因为不恰当的DISTINCT使用导致查询性能极差。掌握这些基础关键词的精髓,往往能避免很多潜在问题。