¿Cómo seleccionar la primera fila de cada grupo?

57

Tengo una mesa como esta:

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

Quiero hacer un SELECTque devuelva solo la primera fila para cada uno Val, ordenando por Kind.

Salida de muestra:

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

¿Cómo puedo construir esta consulta?

BrunoLM
fuente
¿Por qué 3 | 3 | 4 y no 4 | 3 | 4? ¿Cuál es el desempate o no te importa?
Jack Douglas
@JackDouglas En realidad tengo un ORDER BY ID DESC, pero eso no es relevante para la pregunta. En este ejemplo no me importa.
BrunoLM

Respuestas:

38

Esta solución también utiliza keep, pero valy kindtambién puede ser simplemente calculado para cada grupo sin una subconsulta:

select min(id) keep(dense_rank first order by kind) id
     , val
     , min(kind) kind
  from mytable
 group by val;
ID | VAL | TIPO
-: | ---: | ---:
 3 | 3 | 4 4
 2 | 1337 1

dbfiddle aquí

MANTENER ... PRIMERO y MANTENER ... ÚLTIMO son una característica específica de Oracle de los agregados; puede leer sobre esto aquí en los documentos de Oracle, o en ORACLE_BASE :

Las funciones FIRST y LAST pueden usarse para devolver el primer o el último valor de una secuencia ordenada

mik
fuente
62

Use una expresión de tabla común (CTE) y una función de ventana / clasificación / partición como ROW_NUMBER .

Esta consulta creará una tabla en memoria llamada ORDERED y agregará una columna adicional de rn que es una secuencia de números del 1 al N. PARTITION BY indica que debe reiniciarse en 1 cada vez que el valor de Val cambia y queremos ordenar filas por el valor más pequeño de Kind.

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;

El enfoque anterior debería funcionar con cualquier RDBMS que haya implementado la función ROW_NUMBER (). Oracle tiene una funcionalidad elegante como se expresa en la respuesta de mik que generalmente rendirá mejor rendimiento que esta respuesta.

billinkc
fuente
25

La solución de bilinkc funciona bien, pero pensé en tirar la mía también. Tiene el mismo costo, pero podría ser más rápido (o más lento, no lo he probado). La diferencia es que usa First_Value en lugar de Row_Number. Como solo estamos interesados ​​en el primer valor, en mi opinión es más sencillo.

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;

Datos de prueba.

--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);

Si lo prefiere, aquí está el equivalente 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;
Leigh Riffel
fuente
1
+1 pero pensé que valía la pena enfatizar que su respuesta y las de billinkc no son lógicamente las mismas a menos que idsean únicas.
Jack Douglas
@Jack Douglas - Cierto, supuse eso.
Leigh Riffel
14

Puede usar keeppara seleccionar uno idde cada grupo:

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

dbfiddle aquí

Jack Douglas
fuente
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;
fredy
fuente
Eso será mucho menos eficiente que las otras respuestas debido al hecho de que se necesitan dos escaneos sobre MyTable.
a_horse_with_no_name
2
Eso solo es cierto si el optimizador toma la consulta escrita literalmente. Los optimizadores más avanzados pueden ver la intención (fila por grupo) y producir un plan con un solo acceso a la tabla.
Paul White