/ / Seleccione un valor de un grupo según el orden de otras columnas: mysql, sql, mariadb

Seleccione un valor de un grupo en función del orden de otras columnas: mysql, sql, mariadb

Supongamos que tengo esta mesa tab (violín disponible).

| g | a | b |     v |
---------------------
| 1 | 3 | 5 |   foo |
| 1 | 4 | 7 |   bar |
| 1 | 2 | 9 |   baz |
| 2 | 1 | 1 |   dog |
| 2 | 5 | 2 |   cat |
| 2 | 5 | 3 | horse |
| 2 | 3 | 8 |   pig |

Estoy agrupando filas por g, y para cada grupo quiero un valor de la columna v. Sin embargo, no quiero alguna valor, pero quiero el valor de la fila con el máximo a, y de todos ellos, el que tiene el máximo b. En otras palabras, mi resultado debería ser

| 1 |   bar |
| 2 | horse |

Solución actual

Conozco una consulta para lograr esto:

SELECT grps.g,
(SELECT v FROM tab
WHERE g = grps.g
ORDER BY a DESC, b DESC
LIMIT 1) AS r
FROM (SELECT DISTINCT g FROM tab) grps

Pregunta

Pero considero esta consulta más bien feo. Principalmente porque usa un subconsulta dependiente, que se siente como un verdadero asesino de rendimiento. Entonces me pregunto si existe una solución más fácil a este problema.

Respuestas esperadas

La respuesta más probable que espero a esta pregunta.sería algún tipo de complemento o parche para MySQL (o MariaDB) que proporciona una función para esto. Pero también daré la bienvenida a otras inspiraciones útiles. Cualquier cosa que funcione sin una subconsulta dependiente calificaría como una respuesta.

Si su solución solo funciona para una sola columna de pedido, es decir, no podría distinguir entre cat y horse, siéntase libre de sugerir esa respuesta tan bien como espero que siga siendo útil para la mayoría de los casos de uso. Por ejemplo, 100*a+b sería una forma probable de ordenar los datos anteriores por ambas columnas sin dejar de usar una sola expresión.

Tengo algunas soluciones bastante complicadas en mente, y podría agregarlas después de un tiempo, pero primero miraré y veré si aparecen algunas nuevas y agradables.


Resultados de referencia

Como es bastante difícil comparar los distintosrespuestas con sólo mirarlas, he ejecutado algunos puntos de referencia en ellas. Esto se ejecutó en mi propio escritorio, usando MySQL 5.1. Los números no se compararán con ningún otro sistema, solo entre sí. Probablemente debería hacer sus propias pruebas con sus datos de la vida real si el rendimiento es crucial para su aplicación. Cuando lleguen nuevas respuestas, podría agregarlas a mi script y volver a ejecutar todas las pruebas.

Así que parece que mi propia solución hasta ahora no es todoasí de malo, incluso con la subconsulta dependiente. Sorprendentemente, la solución de acatt, que también usa una subconsulta dependiente y que, por lo tanto, habría considerado aproximadamente lo mismo, funciona mucho peor. Probablemente algo con lo que el optimizador de MySQL "no puede hacer frente. La solución que propuso RichardTheKiwi también parece tener un buen rendimiento general. Las otras dos soluciones dependen en gran medida de la estructura de los datos. Con muchos grupos de grupos pequeños, el enfoque xdazz" supera a todos los demás, mientras que la solución de los demócratas funciona mejor (aunque no excepcionalmente bien) para unos pocos grupos grandes.

Respuestas

4 para la respuesta № 1
SELECT g, a, b, v
FROM (
SELECT *,
@rn := IF(g = @g, @rn + 1, 1) rn,
@g := g
FROM (select @g := null, @rn := 0) x,
tab
ORDER BY g, a desc, b desc, v
) X
WHERE rn = 1;

Pase único. Todas las otras soluciones me parecen O (n ^ 2).


5 para la respuesta № 2

De esta forma no se utilizan subconsultas.

SELECT t1.g, t1.v
FROM tab t1
LEFT JOIN tab t2 ON t1.g = t2.g AND (t1.a < t2.a OR (t1.a = t2.a AND t1.b < t2.b))
WHERE t2.g IS NULL

Explicación:</ strong>

El LEFT JOIN funciona sobre la base de que cuando t1.a está en su valor máximo, no hay s2.a con un valor mayor y los valores de las filas de s2 serán NULL.


1 para la respuesta № 3

Esto se puede resolver mediante una consulta correlacionada:

SELECT g, v
FROM tab t
WHERE NOT EXISTS (
SELECT 1
FROM tab
WHERE g = t.g
AND a > t.a
OR (a = t.a AND b > t.b)
)

1 para la respuesta № 4

Muchos RDBMS tienen construcciones que se adaptan particularmente a este problema. MySQL no es uno de ellos.

Esto lo lleva a tres enfoques básicos.

  • Verifique cada registro para ver si es el que desea, utilizando EXISTS y una subconsulta correlacionada en una cláusula EXISTS. (La respuesta de @acatt, pero entiendo que MySQL no siempre optimiza esto muy bien. Asegúrese de tener un índice compuesto en (g,a,b) antes de asumir que MySQL no hará esto muy bien).

  • Haga un producto medio cartesiano para completar el mismo cheque. Cualquier registro que no se una es un registro de destino. Cuando cada grupo ("g") es grande, esto puede degradar rápidamente el rendimiento (Si hay 10 registros para cada valor único de g, esto producirá ~ 50 registros y descartará 49. Para un tamaño de grupo de 100, producirá ~ 5000 registros y descartará 4999), pero es ideal para grupos pequeños. (La respuesta de @xdazz.)

  • O use múltiples subconsultas para determinar el MAX (a) y luego el MAX (b) ...

Varias subconsultas secuenciales ...

SELECT
yourTable.*
FROM
(SELECT g,    MAX(a) AS a FROM yourTable GROUP BY g   ) AS searchA
INNER JOIN
(SELECT g, a, MAX(b) AS b FROM yourTable GROUP BY g, a) AS searchB
ON  searchA.g = searchB.g
AND searchA.a = searchB.a
INNER JOIN
yourTable
ON  yourTable.g = searchB.g
AND yourTable.a = searchB.a
AND yourTable.b = searchB.b

Dependiendo de cómo MySQL optimice la segunda subconsulta, esta puede o no ser más eficiente que las otras opciones. Sin embargo, es el más largo (y potencialmente menos mantenible) código para la tarea dada.

Suponiendo un índice compuesto en los tres campos de búsqueda (g, a, b), Supongo que es mejor para grupos grandes de g. Pero eso debería probarse.

Para grupos pequeños de g, Iría con la respuesta de @xdazz.

EDITAR

También hay un enfoque de fuerza bruta.

  • Cree una tabla idéntica, pero con una columna AUTO_INCREMENT como identificación.
  • Inserte su tabla en este clon, ordenado por g, a, b.
  • Las identificaciones se pueden encontrar con SELECT g, MAX(id).
  • Este resultado se puede utilizar para buscar el v valores que necesita.

Es poco probable que este sea el mejor enfoque. Si es así, es efectivamente una condimentación de la capacidad del optimizador de MySQL para lidiar con este tipo de problema.

Dicho esto, cada motor tiene sus puntos débiles. Así que, personalmente, lo intento todo hasta que pensar Entiendo cómo se está comportando el RDBMS y puedo hacer mi elección :)

EDITAR

Ejemplo usando ROW_NUMBER(). (Oracle, SQL Server, PostGreSQL, etc.)

SELECT
*
FROM
(
SELECT
ROW_NUMBER() OVER (PARTITION BY g ORDER BY a DESC, b DESC) AS sequence_id,
*
FROM
yourTable
)
AS data
WHERE
sequence_id = 1