ВЫБЕРИТЕ верхние N строк без ROWNUM? - PullRequest
4 голосов
/ 12 апреля 2011

Я надеюсь, что вы можете помочь мне с моей домашней работой:)

Нам нужно создать запрос, который выведет лучших N самых высокооплачиваемых сотрудников.

Моя версия работает отлично.
Например, верхние 3:

SELECT name, salary
FROM staff
WHERE salary IN ( SELECT * 
                  FROM ( SELECT salary
                         FROM staff 
                         ORDER BY salary DESC ) 
                  WHERE ROWNUM <= 3 )
ORDER BY salary DESC
;

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

1: Майк, 4080
2: Стив, 2800
2: Сьюзен, 2800
2: Джек, 2800
3: Хлоя, 1400


Но теперь наш учитель не позволяетнам использовать ROWNUM.
Я искал повсюду и не нашел ничего полезного.


Мое второе решение благодаря подсказке Джастина Кейвса.

Сначала я попробовал это:

SELECT name, salary, ( rank() OVER ( ORDER BY salary DESC ) ) as myorder
FROM staff
WHERE myorder <= 3
;

Сообщение об ошибке: "myorder: неверный идентификатор"

Благодаря DCookie теперь ясно:

«[...] Аналитика применяется ПОСЛЕ того, как вычисляется предложение where, поэтому вы получаете ошибку, что myorder является недействительным идентификатором.»

Оборачивание SELECT вокругрешает это:

SELECT *
FROM ( SELECT name, salary, rank() OVER ( ORDER BY salary DESC ) as myorder FROM staff )
WHERE myorder <= 3
;

Мой учитель снова наносит удар и не разрешает такие экзотические аналитические функции.

3-е решение из @Justin Caves.

"Если аналитические функции также запрещены, другой вариант, который я мог бы себе представить - тот, который вы никогда и никогда не будете писать на практике, был бы что-то вроде"

SELECT name, salary
  FROM staff s1
 WHERE (SELECT COUNT(*)
          FROM staff s2
         WHERE s1.salary < s2.salary) <= 3

Ответы [ 5 ]

10 голосов
/ 12 апреля 2011

Поскольку это домашнее задание, скорее подсказка, чем ответ. Вы хотите использовать аналитические функции. ROW_NUMBER, RANK или DENSE_RANK могут работать в зависимости от того, как вы хотите обрабатывать связи.

Если аналитические функции также запрещены, другой вариант, который я мог бы себе представить, - тот, который вы никогда и никогда не напишите на практике, будет что-то вроде

SELECT name, salary
  FROM staff s1
 WHERE (SELECT COUNT(*)
          FROM staff s2
         WHERE s1.salary < s2.salary) <= 3

Что касается производительности, я бы не стал полагаться на номер COST из плана запроса - это только оценка, и, как правило, невозможно сравнить стоимость между планами для различных операторов SQL. Вам гораздо лучше взглянуть на что-то вроде числа последовательных запросов, которые фактически выполняет запрос, и подумать о том, как производительность запроса будет масштабироваться по мере увеличения числа строк в таблице. Третий вариант будет радикально менее эффективным, чем два других, просто потому, что ему нужно дважды просмотреть таблицу STAFF.

У меня нет вашей таблицы STAFF, поэтому я буду использовать таблицу EMP из схемы SCOTT

Решение для аналитической функции на самом деле выполняет 7 последовательных вычислений, как и решение ROWNUM

Wrote file afiedt.buf

  1  select ename, sal
  2    from( select ename,
  3                 sal,
  4                 rank() over (order by sal) rnk
  5            from emp )
  6*  where rnk <= 3
SQL> /

ENAME             SAL
---------- ----------
smith             800
SM0               950
ADAMS            1110


Execution Plan
----------------------------------------------------------
Plan hash value: 3291446077

--------------------------------------------------------------------------------
-
| Id  | Operation                | Name | Rows  | Bytes | Cost (%CPU)| Time
|
--------------------------------------------------------------------------------
-
|   0 | SELECT STATEMENT         |      |    14 |   672 |     4  (25)| 00:00:01
|*  1 |  VIEW                    |      |    14 |   672 |     4  (25)| 00:00:01
|*  2 |   WINDOW SORT PUSHED RANK|      |    14 |   140 |     4  (25)| 00:00:01
|   3 |    TABLE ACCESS FULL     | EMP  |    14 |   140 |     3   (0)| 00:00:01
--------------------------------------------------------------------------------
-

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("RNK"<=3)
   2 - filter(RANK() OVER ( ORDER BY "SAL")<=3)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          7  consistent gets
          0  physical reads
          0  redo size
        668  bytes sent via SQL*Net to client
        524  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          3  rows processed

SQL> select ename, sal
  2    from( select ename, sal
  3            from emp
  4           order by sal )
  5   where rownum <= 3;

ENAME             SAL
---------- ----------
smith             800
SM0               950
ADAMS            1110


Execution Plan
----------------------------------------------------------
Plan hash value: 1744961472

--------------------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |     3 |   105 |     4  (25)| 00:00:01 |
|*  1 |  COUNT STOPKEY          |      |       |       |            |          |
|   2 |   VIEW                  |      |    14 |   490 |     4  (25)| 00:00:01 |
|*  3 |    SORT ORDER BY STOPKEY|      |    14 |   140 |     4  (25)| 00:00:01 |
|   4 |     TABLE ACCESS FULL   | EMP  |    14 |   140 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------


Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(ROWNUM<=3)
   3 - filter(ROWNUM<=3)


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          7  consistent gets
          0  physical reads
          0  redo size
        668  bytes sent via SQL*Net to client
        524  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          3  rows processed

Решение COUNT (*), однако, на самом деле выполняет 99 последовательных операций получения и должно дважды выполнить полное сканирование таблицы, поэтому оно более чем в 10 раз менее эффективно. И это будет масштабироваться гораздо хуже, так как количество строк в таблице увеличивается

SQL> select ename, sal
  2    from emp e1
  3   where (select count(*) from emp e2 where e1.sal < e2.sal) <= 3;

ENAME             SAL
---------- ----------
JONES            2975
SCOTT            3000
KING             5000
FORD             3000
FOO


Execution Plan
----------------------------------------------------------
Plan hash value: 2649664444

----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |    14 |   140 |    24   (0)| 00:00:01 |
|*  1 |  FILTER             |      |       |       |            |          |
|   2 |   TABLE ACCESS FULL | EMP  |    14 |   140 |     3   (0)| 00:00:01 |
|   3 |   SORT AGGREGATE    |      |     1 |     4 |            |          |
|*  4 |    TABLE ACCESS FULL| EMP  |     1 |     4 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter( (SELECT COUNT(*) FROM "EMP" "E2" WHERE
              "E2"."SAL">:B1)<=3)
   4 - filter("E2"."SAL">:B1)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         99  consistent gets
          0  physical reads
          0  redo size
        691  bytes sent via SQL*Net to client
        524  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          5  rows processed
3 голосов
/ 12 апреля 2011

Причина, по которой вы должны обернуть оператор другим выбором, заключается в том, что внешний оператор выбора ограничивает ваш результирующий набор требуемыми номерами строк.Вот полезная ссылка по аналитике .Если вы запустите внутренний выбор самостоятельно, вы поймете, почему вы должны это сделать.Аналитика применяется ПОСЛЕ того, как вычисляется предложение where, поэтому вы получаете сообщение о том, что myorder является недопустимым идентификатором.

1 голос
/ 12 апреля 2011

Oracle? Как насчет оконных функций?

select * from 
(SELECT s.*, row_number over (order by salary desc ) as rn FROM staff s )
where rn <=3
0 голосов
/ 25 апреля 2014

Вы можете решить эту проблему в Oracle 12c

select NAME, SALARY
from  STAFF
order by SALARY DESC
FETCH FIRST 3 ROWS ONLY

(синтаксис FETCH FIRST впервые введен в Oracle 12c)

0 голосов
/ 16 февраля 2014

Когда вы используете count(distinct <exp>), равные верхние зарплаты будут рассматриваться как ничьи.

select NAME, SALARY
from  STAFF STAFF1
where 3 >= ( select count(distinct STAFF2.SALARY) RANK
               from STAFF STAFF2
              where STAFF2.SALARY >= STAFF1.SALARY)
...