Аналитическая функция Oracle для минимального значения в группировке - PullRequest
7 голосов
/ 07 октября 2009

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

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 у меня есть (но, конечно, он не работает, так как он хочет, чтобы персонал в группе по выражению):

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

Ответы [ 4 ]

9 голосов
/ 08 октября 2009

Я думаю, что функция 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 () заключается в том, что он просто отвечает на неправильный вопрос. Вопрос не в том, «Какая запись имеет зарплату, которая является первой в рейтинге, когда зарплата по отделу упорядочена по возрастанию», а в том, «Какая запись имеет минимальную зарплату на отдел». Это имеет большое значение для меня, по крайней мере.

4 голосов
/ 09 октября 2009

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

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

3 голосов
/ 07 октября 2009

Вы можете использовать синтаксис 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
0 голосов
/ 07 октября 2009
select e2.dept, e2.emp, e2.salary
from employee e2
where e2.salary = (select min(e1.salary) from employee e1)
...