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

StackOverflow https://stackoverflow.com/questions/1533240

Вопрос

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

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

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

SELECT dept, 
  emp,
  MIN(salary) KEEP (DENSE_RANK FIRST ORDER BY salary)
FROM mytable
GROUP BY dept
Это было полезно?

Решение

Я думаю, что функция 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)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top