使用PL/SQL查找单表重复行失败,求解决方案与建议
嘿,我来帮你搞定用PL/SQL查找Oracle emp表重复行的需求!首先咱们得先明确“重复行”的定义——这里我默认是指除主键empno外,其他核心字段(ename、job、mgr、hiredate、sal、comm、deptno)完全一致的记录(你可以根据实际需求调整字段)。下面是几个可以直接用的PL/SQL实现方案:
方案1:PL/SQL匿名块(快速测试用)
这个匿名块直接查询并输出所有重复行,适合快速验证你的测试数据:
SET SERVEROUTPUT ON; DECLARE CURSOR c_duplicate_emp IS SELECT e.* FROM emp e JOIN ( -- 先找出存在重复的字段组合 SELECT ename, job, mgr, hiredate, sal, comm, deptno FROM emp GROUP BY ename, job, mgr, hiredate, sal, comm, deptno HAVING COUNT(*) > 1 ) dup ON e.ename = dup.ename AND e.job = dup.job AND e.mgr = dup.mgr AND e.hiredate = dup.hiredate AND NVL(e.sal, 0) = NVL(dup.sal, 0) -- 处理NULL值,避免因NULL不相等漏判 AND NVL(e.comm, 0) = NVL(dup.comm, 0) AND e.deptno = dup.deptno ORDER BY e.ename, e.empno; v_emp emp%ROWTYPE; BEGIN DBMS_OUTPUT.PUT_LINE('重复的员工记录:'); DBMS_OUTPUT.PUT_LINE('EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO'); DBMS_OUTPUT.PUT_LINE('------|--------|-----------|------|-----------|-------|-------|-------'); OPEN c_duplicate_emp; LOOP FETCH c_duplicate_emp INTO v_emp; EXIT WHEN c_duplicate_emp%NOTFOUND; -- 格式化输出,让结果更清晰 DBMS_OUTPUT.PUT_LINE( RPAD(v_emp.empno, 6) || '|' || RPAD(v_emp.ename, 8) || '|' || RPAD(v_emp.job, 11) || '|' || RPAD(NVL(v_emp.mgr, 0), 4) || '|' || TO_CHAR(v_emp.hiredate, 'YYYY-MM-DD') || '|' || RPAD(NVL(v_emp.sal, 0), 7) || '|' || RPAD(NVL(v_emp.comm, 0), 7) || '|' || v_emp.deptno ); END LOOP; CLOSE c_duplicate_emp; IF c_duplicate_emp%ROWCOUNT = 0 THEN DBMS_OUTPUT.PUT_LINE('未找到重复记录'); END IF; END; /
方案2:封装成存储过程(可复用)
如果需要多次使用,把逻辑封装成存储过程更方便:
CREATE OR REPLACE PROCEDURE find_duplicate_emp IS CURSOR c_duplicate_emp IS SELECT e.* FROM emp e JOIN ( SELECT ename, job, mgr, hiredate, sal, comm, deptno FROM emp GROUP BY ename, job, mgr, hiredate, sal, comm, deptno HAVING COUNT(*) > 1 ) dup ON e.ename = dup.ename AND e.job = dup.job AND e.mgr = dup.mgr AND e.hiredate = dup.hiredate AND NVL(e.sal, 0) = NVL(dup.sal, 0) AND NVL(e.comm, 0) = NVL(dup.comm, 0) AND e.deptno = dup.deptno ORDER BY e.ename, e.empno; v_emp emp%ROWTYPE; BEGIN DBMS_OUTPUT.PUT_LINE('重复的员工记录:'); DBMS_OUTPUT.PUT_LINE('EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO'); DBMS_OUTPUT.PUT_LINE('------|--------|-----------|------|-----------|-------|-------|-------'); OPEN c_duplicate_emp; LOOP FETCH c_duplicate_emp INTO v_emp; EXIT WHEN c_duplicate_emp%NOTFOUND; DBMS_OUTPUT.PUT_LINE( RPAD(v_emp.empno, 6) || '|' || RPAD(v_emp.ename, 8) || '|' || RPAD(v_emp.job, 11) || '|' || RPAD(NVL(v_emp.mgr, 0), 4) || '|' || TO_CHAR(v_emp.hiredate, 'YYYY-MM-DD') || '|' || RPAD(NVL(v_emp.sal, 0), 7) || '|' || RPAD(NVL(v_emp.comm, 0), 7) || '|' || v_emp.deptno ); END LOOP; CLOSE c_duplicate_emp; IF c_duplicate_emp%ROWCOUNT = 0 THEN DBMS_OUTPUT.PUT_LINE('未找到重复记录'); END IF; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('查询出错:' || SQLERRM); RAISE; -- 抛出异常,方便上层处理 END find_duplicate_emp; /
调用存储过程的方式很简单:
SET SERVEROUTPUT ON; EXEC find_duplicate_emp;
你之前代码失败的可能原因排查:
- 未处理NULL值:Oracle中
NULL = NULL返回的是未知,不是真,所以如果你的重复行中有comm或sal为NULL的情况,直接用等于判断会匹配失败——这也是我在代码里加NVL()的原因。 - 重复行定义不符:比如你只比较了部分字段,但复制的测试行在未比较的字段上有差异,导致没查出来。
- 游标/循环逻辑错误:比如忘记打开游标、循环终止条件写错,或者没有正确处理游标遍历。
你可以根据自己实际的重复判断需求,修改分组查询中的字段列表。比如如果只认为ename和deptno相同就算重复,就把GROUP BY和JOIN条件改成这两个字段即可。
内容的提问来源于stack exchange,提问作者Carlos Johnes
相关产品推荐
相关产品推荐

