ORACLE SQL извлекает n строк без подзапросов или производных таблиц

Я делаю свои упражнения по SQL, но я застрял в одном. Мне нужно получить сотрудников с двумя самыми высокими зарплатами, но я не могу использовать какой-либо подзапрос или производную таблицу. Я делаю это с помощью такого подзапроса:

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

Я также знаю, что этого можно добиться с помощью предложения WITH, но мне интересно, есть ли какая-либо альтернатива этому.

PS: я использую Oracle 11.


person Elias Garcia    schedule 29.03.2016    source источник
comment
Технически это не подзапрос, а производная таблица. Какую версию Oracle вы используете? С Oracle 12 вы можете использовать ANSI SQL fetch first 3 rows only   -  person a_horse_with_no_name    schedule 30.03.2016
comment
Вы используете встроенный вид. Когда у вас есть запрос в предложении where, это подзапрос.   -  person Avani    schedule 30.03.2016
comment
Да, я знаю, мы также не можем использовать производные таблицы (забыл упомянуть об этом, извините). Я использую Оракл 11   -  person Elias Garcia    schedule 30.03.2016
comment
Что произойдет, если более двух сотрудников имеют одинаковую (максимальную) заработную плату? Или у одного самый высокий, а у двух одинаковый следующий за самым высоким?   -  person eckes    schedule 30.03.2016


Ответы (5)


На самом деле это жалкий метод, на мой взгляд, но вы можете использовать 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(), поэтому, если есть ничьи, вы получите более двух строк. Это достаточно легко исправить (при условии, что у вас есть уникальный идентификатор для каждой строки), но исправление усложняет логику и скрывает основную идею.

person Gordon Linoff    schedule 29.03.2016
comment
Это то, что я искал! Я также попробовал функцию dense_rank() и предложение where, но они не работают вместе, так что это похоже на функцию dense_rank() и работает абсолютно нормально. Большое спасибо! - person Elias Garcia; 30.03.2016
comment
Хорошее решение и действительно единственное, соблюдающее ограничение. - person Marmite Bomber; 30.03.2016

Если у вас 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

person Chris Ghyzel    schedule 29.03.2016
comment
Привет, я использую Oracle 11 - person Elias Garcia; 30.03.2016
comment
Привет, Элиас, я добавил ответ, используя функцию аналитики. Дайте мне знать, если это сработает! - person Chris Ghyzel; 30.03.2016
comment
В вашем обновленном запросе нет предложения from, и если вы исправите это, вы получите ORA-00904: "SAL_RANK": invalid identifier, поскольку в Emp нет столбца sal_rank, а предложение WHERE оценивается до создания выбранных столбцов. - person MT0; 30.03.2016

Хорошее упражнение должно помочь подготовиться к решению практических задач. Таким образом, важным моментом в данном случае является не использование подзапроса, а понимание того, что две самые высокие зарплаты могут иметь сотни сотрудников.

При использовании @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 
person Marmite Bomber    schedule 29.03.2016
comment
Это использует подзапрос. - person Gordon Linoff; 30.03.2016
comment
@ Гордон, вы правы, но легкое нарушение - не очень серьезное - правил, если приводит к правильному ответу может быть приемлемым :) - person Marmite Bomber; 30.03.2016
comment
@Gordon Я решаю удалить подзапрос, чтобы выполнить требование - см. обновление - person Marmite Bomber; 30.03.2016

Это обман, но... вместо использования подзапроса вы можете определить представление:

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 ) );
person MT0    schedule 29.03.2016
comment
Да, это похоже на использование предложения WITH. Нам тоже так нельзя... - person Elias Garcia; 30.03.2016
comment
@EliasGarciaMariño добавил решение с использованием конвейерных функций... не уверен, что это запрещено или нет. - person MT0; 30.03.2016

Это решение не использует подзапрос, но требует трех шагов:

-- 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 запросов, чтобы получить emps с лучшими зарплатами.

person Marmite Bomber    schedule 29.03.2016