作为一名Oracle DBA,我经常遇到开发人员对基础查询关键词理解不透彻导致性能问题的情况。今天我们就来系统梳理Oracle基础查询中的核心关键词,这些看似简单的语法元素实际上藏着不少门道。
Oracle的基础查询关键词可以归纳为五大类,它们构成了SELECT语句的骨架:
这些关键词组合起来,就能完成从简单到中等复杂度的数据查询任务。下面我将结合10年实战经验,详细解析每类关键词的使用技巧和避坑指南。
AS关键字用于给列或表指定别名,但很多人不知道它其实可以省略:
sql复制-- 两种写法等效
SELECT emp_id AS "员工编号" FROM emp;
SELECT emp_id "员工编号" FROM emp;
注意:当别名包含特殊字符或空格时,必须用双引号包裹。Oracle中双引号保持大小写敏感,而单引号用于字符串常量。
我在实际项目中见过一个典型错误案例:
sql复制-- 错误写法(缺少双引号)
SELECT emp_id AS 员工 编号 FROM emp;
-- 正确写法
SELECT emp_id AS "员工 编号" FROM emp;
DISTINCT用于去除重复行,但它的性能影响常被低估。当处理百万级数据时,DISTINCT会导致全表扫描和大量排序操作。我曾优化过一个查询,将:
sql复制SELECT DISTINCT dept_id FROM emp;
改写为:
sql复制SELECT dept_id FROM emp GROUP BY dept_id;
执行时间从3.2秒降至0.8秒,因为GROUP BY可以利用索引。
新手常犯的错误是滥用*通配符:
sql复制SELECT * FROM emp;
这会导致:
实际项目中,应该始终明确指定需要的列:
sql复制SELECT emp_id, emp_name, hire_date FROM emp;
IS NULL和IS NOT NULL是处理NULL值的正确方式,但很多人会错误地使用= NULL:
sql复制-- 错误写法(永远返回空结果集)
SELECT * FROM emp WHERE bonus = NULL;
-- 正确写法
SELECT * FROM emp WHERE bonus IS NULL;
这是因为NULL在Oracle中代表未知值,任何与NULL的比较都会返回UNKNOWN。我在一次数据迁移项目中就遇到过这个问题,导致漏掉了大量有效记录。
LIKE配合%和_通配符可以实现灵活查询,但要注意:
sql复制-- 无法使用索引的写法
SELECT * FROM emp WHERE emp_name LIKE '%张%';
-- 可以使用索引的写法
SELECT * FROM emp WHERE emp_name LIKE '张%';
如果必须使用前导通配符,可以考虑Oracle Text索引。我曾为一个客户系统优化姓名查询,通过建立函数索引:
sql复制CREATE INDEX idx_emp_name_reverse ON emp(REVERSE(emp_name));
然后使用:
sql复制SELECT * FROM emp WHERE REVERSE(emp_name) LIKE REVERSE('%张');
查询性能提升了20倍。
当需要查询包含%或_字符的数据时,ESCAPE就派上用场了:
sql复制-- 查询包含下划线的文件名
SELECT file_name FROM documents
WHERE file_name LIKE '%\_%' ESCAPE '\';
实际项目中,我建议统一使用不常见的字符作为转义符,比如ESCAPE '|',避免与数据内容冲突。
ORDER BY支持多列排序,但要注意排序顺序的影响:
sql复制-- 先按部门升序,同部门按薪资降序
SELECT emp_name, dept_id, salary FROM emp
ORDER BY dept_id ASC, salary DESC;
一个常见错误是忽略了数据类型的影响。有次我发现一个报表排序异常,原来是VARCHAR类型的ID列按字符串方式排序("1","10","2"),应该改为:
sql复制ORDER BY TO_NUMBER(dept_id) ASC;
Oracle特有的NULLS FIRST和NULLS LAST可以精确控制NULL值位置:
sql复制-- 奖金高的在前,NULL值排在最后
SELECT emp_name, bonus FROM emp
ORDER BY bonus DESC NULLS LAST;
在财务系统中,这特别有用。比如显示未发放奖金的员工单独列出:
sql复制ORDER BY NVL(bonus,0) DESC NULLS FIRST;
ROWNUM是Oracle传统的分页方式,但有很多坑:
sql复制-- 错误的分页写法(永远返回空结果)
SELECT * FROM emp WHERE ROWNUM BETWEEN 6 AND 10;
-- 正确的分页写法
SELECT * FROM (
SELECT e.*, ROWNUM rn FROM emp e
WHERE ROWNUM <= 10
) WHERE rn >= 6;
这是因为ROWNUM是在数据被获取后分配的。我在优化一个分页查询时,发现这种写法在10g版本会导致全表扫描,改为12c的FETCH语法后性能提升显著。
Oracle 12c引入的FETCH语法更直观:
sql复制-- 查询薪资最高的5名员工
SELECT emp_name, salary FROM emp
ORDER BY salary DESC
FETCH FIRST 5 ROWS ONLY;
它还支持百分比和WITH TIES选项:
sql复制-- 取前10%且允许并列
SELECT emp_name, salary FROM emp
ORDER BY salary DESC
FETCH FIRST 10 PERCENT ROWS WITH TIES;
在最近的数据分析项目中,这种语法让代码可读性提高了不少。
EXISTS和IN都可以用于子查询,但性能特征不同:
sql复制-- EXISTS通常在大表关联时更快
SELECT * FROM emp e
WHERE EXISTS (
SELECT 1 FROM dept d
WHERE d.dept_id=e.dept_id AND d.location='北京'
);
-- IN在子查询结果集小时更高效
SELECT * FROM emp
WHERE dept_id IN (SELECT dept_id FROM dept WHERE location='北京');
经验法则是:外层表大而子查询结果集小用IN,反之用EXISTS。我曾优化过一个查询,将IN改为EXISTS后执行时间从45秒降到2秒。
NOT EXISTS是查找不匹配记录的利器:
sql复制-- 查找没有下属的员工
SELECT e1.emp_name FROM emp e1
WHERE NOT EXISTS (
SELECT 1 FROM emp e2
WHERE e2.manager_id=e1.emp_id
);
但要注意NULL值的影响。有次查询结果异常,发现是因为manager_id字段存在NULL值,需要加上条件:
sql复制WHERE e2.manager_id IS NOT NULL AND e2.manager_id=e1.emp_id
这是最常见的错误之一。解决方案:
优化方案:
改进方法:
例如对大小写不敏感的姓名查询:
sql复制CREATE INDEX idx_emp_name_upper ON emp(UPPER(emp_name));
SELECT * FROM emp WHERE UPPER(emp_name) = UPPER('张三');
WITH子句(CTE)可以提高复杂查询的可读性和性能:
sql复制WITH dept_stats AS (
SELECT dept_id, AVG(salary) avg_sal
FROM emp GROUP BY dept_id
)
SELECT e.emp_name, e.salary, d.avg_sal
FROM emp e JOIN dept_stats d ON e.dept_id=d.dept_id
WHERE e.salary > d.avg_sal;
重点关注:
通过这些关键词的系统掌握和优化技巧的应用,我帮助多个客户将关键查询性能提升了10倍以上。记住,基础查询的优化是SQL调优的基石,值得投入时间深入理解。