Аналитическая функция Oracle для минимального значения в группировке

Я новичок в работе с аналитическими функциями.

DEPT EMP   SALARY
---- ----- ------
  10 MARY  100000
  10 JOHN  200000
  10 SCOTT 300000
  20 BOB   100000
  20 BETTY 200000
  30 ALAN  100000
  30 TOM   200000
  30 JEFF  300000

Я хочу, чтобы отдел и сотрудник с минимальной зарплатой.

результат должен выглядеть так:

DEPT EMP   SALARY
---- ----- ------
  10 MARY  100000
  20 BOB   100000
  30 ALAN  100000

EDIT: вот SQL, который у меня есть (но, конечно, он не работает, поскольку он хочет, чтобы персонал в предложении group by также):

SELECT dept, 
  emp,
  MIN(salary) KEEP (DENSE_RANK FIRST ORDER BY salary)
FROM mytable
GROUP BY dept

4 ответов


Я думаю, что функция Rank () не подходит для этого по двум причинам.

во-первых, это, вероятно, менее эффективно, чем метод на основе Min ().

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

таким образом, производительность функции Rank() зависит от общего количества сканируемых элементов, и если этого количества достаточно, чтобы сортировка разлилась на диск, производительность рухнет.

Это, вероятно, более эффективно:

select dept,
       emp,
       salary
from
       (
       SELECT dept, 
              emp,
              salary,
              Min(salary) Over (Partition By dept) min_salary
       FROM   mytable
       )
where salary = min_salary
/

этот метод требует только, чтобы запрос поддерживал одно значение на отдел минимального значения, обнаруженного до сих пор. Если обнаружен новый минимум тогда существующее значение изменяется, в противном случае новое значение отбрасывается. Общее количество элементов, которые должны храниться в памяти, связано с количеством отделов, а не с количеством отсканированных строк.

возможно, у Oracle есть кодовый путь, чтобы признать, что ранг на самом деле не нужно вычислять в этом случае, но я бы на это не поставил.

вторая причина неприязни Rank() заключается в том, что он просто отвечает на неправильный вопрос. Вопрос не в том, " что записи имеют зарплату, которая является первым ранжированием, когда зарплаты на отдел по возрастанию упорядочены", это "какие записи имеют зарплату, которая является минимальной на отдел". По крайней мере, для меня это имеет большое значение.


Я думаю, что вы были довольно близки с вашим первоначальным запросом. Следующее будет выполняться и соответствовать вашему тестовому набору:

SELECT dept, 
  MIN(emp) KEEP(DENSE_RANK FIRST ORDER BY salary, ROWID) AS emp,
  MIN(salary) KEEP (DENSE_RANK FIRST ORDER BY salary, ROWID) AS salary
FROM mytable
GROUP BY dept

в отличие от решений RANK (), это гарантирует не более одной строки на отдел. Но это намекает на проблему: что происходит в отделе, где есть два сотрудника с самой низкой зарплатой? Решения RANK () вернут обоих сотрудников-более одной строки для отдела. Этот ответ будет выбирать один произвольно и убедитесь, что есть только один для департамента.


можно использовать RANK() синтаксис. Например, этот запрос покажет вам, где сотрудник занимает место в своем отделе в отношении того, насколько велика его зарплата:

SELECT
  dept,
  emp,
  salary,
  (RANK() OVER (PARTITION BY dept ORDER BY salary)) salary_rank_within_dept
FROM EMPLOYEES

затем вы можете запросить из этого, где salary_rank_within_dept = 1:

SELECT * FROM
  (
    SELECT
      dept,
      emp,
      salary,
      (RANK() OVER (PARTITION BY dept ORDER BY salary)) salary_rank_within_dept
    FROM EMPLOYEES
  )
WHERE salary_rank_within_dept = 1

select e2.dept, e2.emp, e2.salary
from employee e2
where e2.salary = (select min(e1.salary) from employee e1)