【问题标题】:ORACLE SQL retrieve n rows without subqueries or derived tablesORACLE SQL 检索 n 行没有子查询或派生表
【发布时间】:2016-07-17 15:49:25
【问题描述】:

我正在做我的 SQL 练习,但我陷入了其中。我需要检索两个薪水最高的员工,但我不能使用任何类型的子查询或派生表。我用这样的子查询来做:

SELECT *
FROM (SELECT * FROM emp ORDER BY sal DESC) new_emp
WHERE ROWNUM < 3;

我也知道这可以使用WITH 子句来实现,但我想知道是否有任何替代方法。

PS:我使用的是 Oracle 11。

【问题讨论】:

  • 从技术上讲,这不是“子查询”,而是派生表。您使用的是哪个 Oracle 版本?在 Oracle 12 中,您可以使用 ANSI SQL fetch first 3 rows only
  • 您正在使用内联视图。当你在 where 子句中有查询时,它就是一个子查询。
  • 是的,我知道,我们也不能使用派生表(我忘了提这个,抱歉)。我正在使用 Oracle 11
  • 如果两个以上的员工拥有相同的(最高)工资应该怎么办?或者一个具有最高的,两个具有相同的次高?

标签: sql oracle oracle11g window-functions row-number


【解决方案1】:

如果您是 Oracle 12.1 或更高版本,则可以使用行限制子句。在您的情况下,您只需使用子查询加上行限制子句,如下所示:

SELECT * FROM emp 
ORDER BY sal DESC
FETCH FIRST 5 ROWS ONLY;

来源:https://oracle-base.com/articles/12c/row-limiting-clause-for-top-n-queries-12cr1#top-n

【讨论】:

  • 嗨,我使用的是 Oracle 11
  • 您好 Elias,我使用分析功能添加了答案。让我知道它是否有效!
  • 更新后的查询中没有 from 子句,如果修复它,您将获得 ORA-00904: "SAL_RANK": invalid identifier,因为在 Emp 中没有 sal_rank 列,并且之前评估了 WHERE 子句已生成选定的列。
【解决方案2】:

在我看来,这实际上是一种可悲的方法,但是您可以使用join

select e.col1, e.col2, . . .
from emp e left join
     emp e2
     on e2.salary >= e.salary
group by e.col1, e.col2, . . .
having count(distinct e2.salary) <= 2;

注意:这真的等同于dense_rank(),所以如果有平局,你会得到多于两行。解决这个问题很容易(假设您对每一行都有一个唯一标识符),但该修复使逻辑复杂化并隐藏了基本思想。

【讨论】:

  • 这就是我要找的!我还尝试了 dense_rank() 函数和 where 子句,但它不能一起工作,所以这就像 dense_rank() 函数并且工作得很好。非常感谢!
  • 不错的解决方案,真正符合限制的唯一解决方案。
【解决方案3】:

好的练习应该有助于准备解决实际问题。所以在这一项中重要的事情不是子查询的使用,而是要意识到这两个高薪可以拥有数百名员工。

使用@MT0 查看解决方法时,这是查询

CREATE VIEW sal_ordered_emps AS
  SELECT e.*,
  row_number() over (order by sal desc) as RN
  FROM   SCOTT.emp e
  ORDER BY sal DESC;

select e.* from scott.emp e join 
sal_ordered_emps soe on e.sal = soe.sal and rn <= 2
;

解释的结果可以超过 2 条记录

     EMPNO ENAME      JOB              MGR HIREDATE                   SAL       COMM     DEPTNO
---------- ---------- --------- ---------- ------------------- ---------- ---------- ----------
      7788 SCOTT      ANALYST         7566 19.04.1987 00:00:00       3000                    20 
      7839 KING       PRESIDENT            17.11.1981 00:00:00       5000                    10 
      7902 FORD       ANALYST         7566 03.12.1981 00:00:00       3000                    20 

【讨论】:

  • 这使用了一个子查询。
  • @Gordon 你是对的,但是如果导致正确答案可能是可以接受的:)
  • @Gordon 我决定删除子查询以符合要求 - 请参阅更新
【解决方案4】:

这是作弊,但是...您可以定义视图,而不是使用子查询:

CREATE VIEW sal_ordered_emps AS
  SELECT *
  FROM   emp
  ORDER BY sal DESC;

那么你可以这样做:

SELECT * FROM sal_ordered_emps WHERE ROWNUM < 3;

或者,您可以使用流水线函数...

CREATE OR REPLACE PACKAGE emp_pkg
AS
  TYPE emp_table IS TABLE OF EMP%ROWTYPE;

  FUNCTION get_max_sals(
    n INT
  ) RETURN emp_table PIPELINED;
END;
/

CREATE OR REPLACE PACKAGE BODY emp_pkg
AS
  FUNCTION get_max_sals(
    n INT
  ) RETURN emp_table PIPELINED
  AS
    cur SYS_REFCURSOR;
    in_rec EMP%ROWTYPE;
    i INT := 0;
  BEGIN
    OPEN cur FOR SELECT * FROM EMP ORDER BY SAL DESC;
    LOOP
      i := i + 1;
      FETCH cur INTO in_rec; 
      EXIT WHEN cur%NOTFOUND OR i > n;
      PIPE ROW(in_rec);
    END LOOP;
    CLOSE cur;
    RETURN;
  END;
END;
/

SELECT *
FROM   TABLE( emp_pkg.get_max_sals( 2 ) );

【讨论】:

  • 是的,这就像使用WITH 子句。我们也不允许这样做……
  • @EliasGarciaMariño 添加了一个使用流水线函数的解决方案...不确定这是否被禁止。
【解决方案5】:

此方案不使用子查询,但需要三个步骤:

-- Q1
select max(sal) from scott.emp;
-- Q2
select max(sal) from scott.emp where sal < {result of Q1}; 

select * from scott.emp where sal in ({result of Q1},{result of Q2});

一般情况下,您需要 N+1 个查询才能获得前 N 个薪水 emp。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-16
    • 1970-01-01
    • 2021-12-18
    • 1970-01-01
    • 1970-01-01
    • 2017-01-25
    • 2021-11-03
    • 2018-02-07
    相关资源
    最近更新 更多