Oracle/SQL — использование функции ранжирования

То, что я пытаюсь сделать, это список лиц из таблицы, и в случае, если человек существует более одного раза, верните его запись, содержащую «код» с наивысшим рейтингом.

Ранжирование кода (сверху вниз): T, E, F

Итак, для данного набора данных

Person  Code
----------------    
Tom     F
Paul    E
Mark    F
Paul    T
Mark    E
Chris   T
Chris   E

Я бы получил следующее из моего запроса

Person  Code
----------------
Tom     F
Paul    T
Mark    E
Chris   T

Я предполагаю, что это будет использовать ранговые/аналитические функции, но я просто недостаточно знаком с ними.

Спасибо!


person dscl    schedule 12.09.2011    source источник


Ответы (4)


Самое короткое и эффективное решение для Oracle:

SQL> create table mytable(person,code)
  2  as
  3  select 'Tom', 'F' from dual union all
  4  select 'Paul', 'E' from dual union all
  5  select 'Mark', 'F' from dual union all
  6  select 'Paul', 'T' from dual union all
  7  select 'Mark', 'E' from dual union all
  8  select 'Chris', 'T' from dual union all
  9  select 'Chris', 'E' from dual
 10  /

Table created.

SQL> select person
  2       , max(code) keep (dense_rank first order by decode(code,'T',1,'E',2,'F',3,4)) code
  3    from mytable
  4   group by person
  5  /

PERSO C
----- -
Chris T
Mark  E
Paul  T
Tom   F

4 rows selected.

С уважением,
Роб.

person Rob van Wijk    schedule 13.09.2011

Вы можете использовать функцию RANK для ранжирования данных

SQL> ed
Wrote file afiedt.buf

  1  with data as (
  2    select 'Tom' person, 'F' code from dual union all
  3    select 'Paul', 'E' from dual union all
  4    select 'Paul', 'T' from dual union all
  5    select 'Mark', 'F' from dual union all
  6    select 'Mark', 'E' from dual
  7  )
  8  select *
  9    from (select person,
 10                 code,
 11                 rank() over (partition by person
 12                                  order by (case when code='T' then 1
 13                                                 when code='E' then 2
 14                                                 when code='F' then 3
 15                                                 else null
 16                                             end)) rnk
 17*           from data)
SQL> /

PERS C        RNK
---- - ----------
Mark E          1
Mark F          2
Paul T          1
Paul E          2
Tom  F          1

Elapsed: 00:00:00.00

Затем вам просто нужно выбрать строки с RNK из 1

SQL> ed
Wrote file afiedt.buf

  1  with data as (
  2    select 'Tom' person, 'F' code from dual union all
  3    select 'Paul', 'E' from dual union all
  4    select 'Paul', 'T' from dual union all
  5    select 'Mark', 'F' from dual union all
  6    select 'Mark', 'E' from dual
  7  )
  8  select *
  9    from (select person,
 10                 code,
 11                 rank() over (partition by person
 12                                  order by (case when code='T' then 1
 13                                                 when code='E' then 2
 14                                                 when code='F' then 3
 15                                                 else null
 16                                             end)) rnk
 17            from data)
 18*  where rnk = 1
SQL> /

PERS C        RNK
---- - ----------
Mark E          1
Paul T          1
Tom  F          1

Elapsed: 00:00:00.00
person Justin Cave    schedule 12.09.2011
comment
Используйте row_number() вместо rank(), если (человек, код) может быть продублирован, и вы хотите вернуть только одну (произвольно выбранную) строку. Если (человек, код) можно дублировать, и вам нужна одна строка, и вам важно, какая из нескольких строк, добавьте дополнительные критерии в order by. Возможно, вы захотите подумать о сохранении кода rank_value в таблице и объединении вместо того, чтобы кодировать его в своей хранимой процедуре. В некоторых случаях преимущества перекрывают недостатки дополнительной таблицы. - person Shannon Severance; 13.09.2011

Я не думаю, что RANK - это то, что вам нужно...

в основном ваше удаление будет выглядеть так: (псевдозапрос)

delete the rows from person
where that row is not in ( select the rows from person with the highest code )

изменить:

этот трюк может помочь вам тоже:

select person, code, decode( code, 'T', 1, 'E', 2, 'F', 3, 0 ) from mytable
person Randy    schedule 12.09.2011
comment
Удалить? Я думаю, он хочет запросить максимальный ранг, а не удалять данные. - person Anthony Accioly; 13.09.2011
comment
Правильно я не хочу удалять - просто нужно вернуть результат для каждого человека в таблице. Если человек указан в списке более одного раза, верните запись с наивысшим рейтингом «кода». Я вижу, как «обрезка» может вводить в заблуждение. - person dscl; 13.09.2011

Хм... Альтернативное предложение со стандартным SQL. Создайте таблицу CODE_WEIGHT, например:

CODE WEIGHT
T    3    
E    2
F    1

Затем сгруппируйте запрос по лицу (если это критерий группировки) и выберите отдельный код, содержащий max(weight).

Я опубликую запрос через пару минут.

ОБНОВЛЕНИЕ

Хорошо, извините за задержку.

Вот решение, использующее предыдущую указанную таблицу и трюк @Randy:

SELECT 
 pp.person, decode(max(c.weight), 3, 'T', 2, 'E', 1, 'F', '') code
FROM 
 person pp INNER JOIN code_weight c on (pp.code = c.code)
GROUP BY
 pp.person 
ORDER BY 
 person DESC;

Я почти уверен, что есть способ сбросить проприетарную функцию Oracle и сделать все на чистом SQL... В любом случае, поскольку вы просили решение Oracle, вот оно.

ОБНОВЛЕНИЕ 2

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

SELECT 
 p.person, c.code
FROM
(
 SELECT 
   pp.person, MAX(cc.weight) weight
 FROM 
  person pp INNER JOIN code_weight cc ON (pp.code = cc.code)
 GROUP BY
  pp.person 
) p INNER JOIN code_WEIGHT c ON (p.weight = c.weight)
ORDER BY
  p.person DESC;

Немного некрасиво с двумя соединениями... Но это работает без проприетарных расширений. Любой гуру SQL знает, как его оптимизировать?

Ваше здоровье,

person Anthony Accioly    schedule 12.09.2011