Как выбрать первый ряд каждой группы?

57

У меня есть такая таблица:

 ID |  Val   |  Kind
----------------------
 1  |  1337  |   2
 2  |  1337  |   1
 3  |   3    |   4
 4  |   3    |   4

Я хочу сделать, SELECTчто будет возвращать только первый ряд для каждого Val, упорядочив по Kind.

Образец вывода:

 ID |  Val   |  Kind
----------------------
 2  |  1337  |   1
 3  |   3    |   4

Как я могу построить этот запрос?

BrunoLM
источник
почему 3 | 3 | 4 а не 4 | 3 | 4 - что такое тай-брейк или вас это не волнует?
Джек Дуглас
@JackDouglas На самом деле у меня есть ORDER BY ID DESC, но это не имеет отношения к вопросу. В этом примере мне все равно.
BrunoLM

Ответы:

38

Это решение также использует keep, но valи kindможет также быть просто для каждой группы без подзапроса:

select min(id) keep(dense_rank first order by kind) id
     , val
     , min(kind) kind
  from mytable
 group by val;
ID | VAL | ДОБРЫЙ
-: | ---: | ---:
 3 | 3 | 4
 2 | 1337 | 1

dbfiddle здесь

KEEP… FIRST и KEEP… LAST - это особенность Oracle для агрегатов, о которой вы можете прочитать здесь, в документации Oracle или на ORACLE_BASE :

Функции FIRST и LAST могут использоваться для возврата первого или последнего значения из упорядоченной последовательности

МИК
источник
62

Используйте общее табличное выражение (CTE) и функцию управления окнами / ранжирования / разбиения, например ROW_NUMBER .

Этот запрос создаст таблицу в памяти с именем ORDERED и добавит дополнительный столбец rn, представляющий собой последовательность чисел от 1 до N. Параметр PARTITION BY указывает, что он должен перезапускаться с 1 каждый раз, когда изменяется значение Val, и мы хотим упорядочить строки по наименьшему значению вида.

WITH ORDERED AS
(
SELECT
    ID
,   Val
,   kind
,   ROW_NUMBER() OVER (PARTITION BY Val ORDER BY Kind ASC) AS rn
FROM
    mytable
)
SELECT
    ID
,   Val
,   Kind
FROM
    ORDERED
WHERE
    rn = 1;

Приведенный выше подход должен работать с любой СУБД, в которой реализована функция ROW_NUMBER (). Oracle обладает элегантной функциональностью, выраженной в ответе Мика, которая обычно дает более высокую производительность, чем этот ответ.

billinkc
источник
25

Решение bilinkc работает отлично, но я решил, что тоже выброшу. Он имеет такую ​​же стоимость, но может быть быстрее (или медленнее, я его не тестировал). Разница в том, что он использует First_Value вместо Row_Number. Поскольку нас интересует только первое значение, на мой взгляд, оно более простое.

SELECT ID, Val, Kind FROM
(
   SELECT First_Value(ID) OVER (PARTITION BY Val ORDER BY Kind) First, ID, Val, Kind 
   FROM mytable
)
WHERE ID = First;

Тестовые данные.

--drop table mytable;
create table mytable (ID Number(5) Primary Key, Val Number(5), Kind Number(5));

insert into mytable values (1,1337,2);
insert into mytable values (2,1337,1);
insert into mytable values (3,3,4);
insert into mytable values (4,3,4);

Если вы предпочитаете, вот эквивалент CTE.

WITH FirstIDentified AS (
   SELECT First_Value(ID) OVER (PARTITION BY Val ORDER BY Kind) First, ID, Val, Kind 
   FROM mytable
   )
SELECT ID, Val, Kind FROM FirstIdentified
WHERE ID = First;
Ли Риффель
источник
1
+1, но я просто подумал, что стоит подчеркнуть, что ваш ответ и биллинк не логически одинаковы, если они idне уникальны.
Джек Дуглас
@ Джек Дуглас - Правда, я предполагал это.
Ли Риффель
14

Вы можете использовать keepдля выбора idиз каждой группы:

select *
from mytable
where id in ( select min(id) keep (dense_rank first order by kind, id)
              from mytable
              group by val );
ID | VAL | ДОБРЫЙ
-: | ---: | ---:
 2 | 1337 | 1
 3 | 3 | 4

dbfiddle здесь

Джек Дуглас
источник
2
SELECT MIN(MyTable01.Id) as Id,
       MyTable01.Val     as Val,
       MyTable01.Kind    as Kind 
  FROM MyTable MyTable01,                         
       (SELECT Val,MIN(Kind) as Kind
          FROM MyTable                   
      GROUP BY Val) MyTableGroup
WHERE MyTable01.Val  = MyTableGroup.Val
  AND MyTable01.Kind = MyTableGroup.Kind
GROUP BY MyTable01.Val,MyTable01.Kind
ORDER BY Id;
Фреди
источник
Это будет намного менее эффективно, чем другие ответы из-за того, что необходимы два сканирования MyTable.
a_horse_with_no_name
2
Это верно только в том случае, если оптимизатор воспринимает письменный запрос буквально. Более продвинутые оптимизаторы могут увидеть намерение (строка на группу) и создать план с доступом к одной таблице.
Пол Уайт