Cuente dónde dos o más columnas seguidas están sobre un cierto valor [baloncesto, doble doble, triple doble]

20

Juego un juego de baloncesto que permite generar sus estadísticas como un archivo de base de datos, para poder calcular estadísticas que no se implementan en el juego. Hasta ahora no he tenido problemas para calcular las estadísticas que quería, pero ahora me he encontrado con un problema: contar el número de dobles dobles y / o triples dobles que un jugador hizo durante la temporada a partir de las estadísticas de su juego.

La definición de doble doble y triple doble es la siguiente:

Doble doble:

Un doble doble se define como una actuación en la cual un jugador acumula un total de dos dígitos en dos de las cinco categorías estadísticas: puntos, rebotes, asistencias, robos y tiros bloqueados en un juego.

Triple doble:

Un triple-doble se define como una actuación en la que un jugador acumula un número total de dos dígitos en tres de las cinco categorías estadísticas (puntos, rebotes, asistencias, robos y tiros bloqueados) en un juego.

Cuádruple-doble (agregado para aclaración)

Un cuádruple-doble se define como una actuación en la que un jugador acumula un número total de dos dígitos en cuatro de cinco categorías estadísticas (puntos, rebotes, asistencias, robos y tiros bloqueados) en un juego.

La tabla "PlayerGameStats" almacena estadísticas de cada juego que juega un jugador y tiene el siguiente aspecto:

CREATE TABLE PlayerGameStats AS SELECT * FROM ( VALUES
  ( 1, 1,  1, 'Nuggets',    'Cavaliers',  6,  8,  2, 2,  0 ),
  ( 2, 1,  2, 'Nuggets',     'Clippers', 15,  7,  0, 1,  3 ),
  ( 3, 1,  6, 'Nuggets', 'Trailblazers', 11, 11,  1, 2,  1 ),
  ( 4, 1, 10, 'Nuggets',    'Mavericks',  8, 10,  2, 2, 12 ),
  ( 5, 1, 11, 'Nuggets',       'Knicks', 23, 12,  1, 0,  0 ),
  ( 6, 1, 12, 'Nuggets',         'Jazz',  8,  8, 11, 1,  0 ),
  ( 7, 1, 13, 'Nuggets',         'Suns',  7, 11,  2, 2,  1 ),
  ( 8, 1, 14, 'Nuggets',        'Kings', 10, 15,  0, 3,  1 ),
  ( 9, 1, 15, 'Nuggets',        'Kings',  9,  7,  5, 0,  4 ),
  (10, 1, 17, 'Nuggets',      'Thunder', 13, 10, 10, 1,  0 )
) AS t(id,player_id,seasonday,team,opponent,points,rebounds,assists,steals,blocks);

El resultado que quiero lograr se ve así:

| player_id |    team | doubleDoubles | tripleDoubles |
|-----------|---------|---------------|---------------|
|         1 | Nuggets |             4 |             1 |

La única solución que encontré hasta ahora es tan horrible que me hace vomitar ...; o) ... Se ve así:

SELECT 
  player_id,
  team,
  SUM(CASE WHEN(points >= 10 AND rebounds >= 10) OR
               (points >= 10 AND assists  >= 10) OR
               (points >= 10 AND steals   >= 10) 
                THEN 1 
                ELSE 0 
      END) AS doubleDoubles
FROM PlayerGameStats
GROUP BY player_id

... y ahora probablemente también estás vomitando (o riéndote fuerte) después de leer esto. Ni siquiera escribí todo lo que se necesitaría para obtener todas las combinaciones dobles dobles, y omití la declaración del caso para los dobles triples porque es aún más ridículo.

¿Hay una mejor manera de hacer esto? Ya sea con la estructura de tabla que tengo o con una nueva estructura de tabla (podría escribir un script para convertir la tabla).

Puedo usar MySQL 5.5 o PostgreSQL 9.2.

Aquí hay un enlace a SqlFiddle con datos de ejemplo y mi horrible solución que publiqué anteriormente: http://sqlfiddle.com/#!2/af6101/3

Tenga en cuenta que no estoy realmente interesado en los dobles cuádruples (ver arriba) ya que no ocurren en el juego que juego hasta donde yo sé, pero sería una ventaja si la consulta se puede expandir fácilmente sin tener que volver a escribir en la cuenta para dobles cuádruples.

keth
fuente

Respuestas:

10

No sé si esta es la mejor manera. Primero hice una selección para averiguar si una estadística es de dos dígitos y asignarle un 1 si lo es. Resumió todos esos para descubrir el número total de dobles dígitos por juego. A partir de ahí, resumir todos los dobles y triples. Parece funcionar

select a.player_id, 
a.team, 
sum(case when a.doubles = 2 then 1 else 0 end) as doubleDoubles, 
sum(case when a.doubles = 3 then 1 else 0 end) as tripleDoubles
from
(select *, 
(case when points > 9 then 1 else 0 end) +
(case when rebounds > 9 then 1 else 0 end) +
(case when assists > 9 then 1 else 0 end) +
(case when steals > 9 then 1 else 0 end) +
(case when blocks > 9 then 1 else 0  end) as Doubles
from PlayerGameStats) a
group by a.player_id, a.team
SQLChao
fuente
Hola, gracias por tu solución. Me gusta mucho. Hace exactamente lo que quiero y es fácilmente ampliable para incluir cuádruple-doble y quíntuple-doble sin mucha escritura. Hará de esto la respuesta aceptada por ahora. :)
keth
Me gusta tu código, pero puedes hackearlo para que sea aún más corto. No es necesario usar CASEsentencias ya que las expresiones booleanas evalúan a 1 cuando es verdadero y 0 cuando es falso. Lo agregué a mi respuesta a continuación con un saludo, ya que no puedo publicar el bloque de código SQL completo en el comentario aquí.
Joshua Huber
Gracias Joshua Totalmente pasó por alto eso y se ve mucho mejor.
SQLChao
1
@JoshuaHuber Correcto, pero la consulta solo funcionará en MySQL. Usando CASEy SUM/COUNTpermite que funcione en Postgres también.
ypercubeᵀᴹ
@ypercube: En realidad, agregar booleanos también funciona en Postgres. Solo necesitas emitir explícitamente. Pero CASEgeneralmente es un poco más rápido. Agregué una demostración con algunas otras mejoras menores.
Erwin Brandstetter
7

Pruebe esto (funcionó para mí en MySQL 5.5):

SELECT 
  player_id,
  team,
  SUM(
    (   (points   >= 10)
      + (rebounds >= 10)
      + (assists  >= 10)
      + (steals   >= 10)
      + (blocks   >= 10) 
    ) = 2
  ) double_doubles,
  SUM(
    (   (points   >= 10)
      + (rebounds >= 10)
      + (assists  >= 10)
      + (steals   >= 10)
      + (blocks   >= 10) 
    ) = 3
  ) triple_doubles
FROM PlayerGameStats
GROUP BY player_id, team

O incluso más corto, arrancando descaradamente el código de JChao de su respuesta, pero sacando las CASEdeclaraciones innecesarias ya que boolean expr se evalúa en {1,0} cuando {True, False}:

select a.player_id, 
a.team, 
sum(a.doubles = 2) as doubleDoubles, 
sum(a.doubles = 3) as tripleDoubles
from
(select *, 
(points > 9) +
(rebounds > 9) +
(assists > 9) +
(steals > 9) +
(blocks > 9) as Doubles
from PlayerGameStats) a
group by a.player_id, a.team

Según los comentarios, el código anterior no se ejecutará en PostgreSQL ya que no le gusta hacer boolean + boolean. Aún no me gusta CASE. Aquí hay una salida en PostgreSQL (9.3), enviando a int:

select a.player_id, 
a.team, 
sum((a.doubles = 2)::int) as doubleDoubles, 
sum((a.doubles = 3)::int) as tripleDoubles
from
(select *, 
(points > 9)::int +
(rebounds > 9)::int +
(assists > 9)::int +
(steals > 9)::int +
(blocks > 9)::int as Doubles
from PlayerGameStats) a
group by a.player_id, a.team
Joshua Huber
fuente
@ypercube, buen punto y gracias. Acababa de pedir esa aclaración exacta como comentario sobre la pregunta anterior. Semántica. Creo que cuatro goles en el hockey todavía se consideran "hacer un hat trick", pero cuatro golpes consecutivos en los bolos podrían no considerarse un "pavo" propiamente dicho, sino más bien un "quad". No soy un experto en la semántica de cada juego. Tomas la decisión y eliges =o >=según te convenga.
Joshua Huber
Gracias por tu solución. Definitivamente hace lo que quiero. También me gusta la versión abreviada de JChao que proporcionó.
keth
1
Sin embargo, agregar booleanos no funcionará en PostgreSQL, tenlo en cuenta.
Craig Ringer
@CraigRinger: gracias por señalarlo. Como todavía soy verde detrás de las orejas cuando se trata de SQL en general y PostgreSQl en particular, esta es una información realmente valiosa para mí. :)
keth
1
@CraigRinger Nice, pero no creo que MySQL sea compatible CAST(... AS int) ( stackoverflow.com/questions/12126991/… ). MySQL puede hacerlo CAST(... AS UNSIGNED), lo que funciona en esta consulta, pero PostgreSQL no. No estoy seguro de que haya algo común CASTque ambos puedan hacer para la portabilidad. En el peor de los casos, CASEal final puede quedar atrapado si la portabilidad es primordial.
Joshua Huber
6

Aquí hay otra versión del problema.

A mi modo de ver, básicamente estás trabajando con datos pivotados para el problema actual, por lo que lo primero que debes hacer es desconectarlo. Lamentablemente, PostgreSQL no proporciona buenas herramientas para hacerlo, por lo que sin entrar en la generación dinámica de SQL en PL / PgSQL, al menos podemos hacer:

SELECT player_id, seasonday, 'points' AS scoretype, points AS score FROM playergamestats
UNION ALL
SELECT player_id, seasonday, 'rebounds' AS scoretype, rebounds FROM playergamestats
UNION ALL
SELECT player_id, seasonday, 'assists' AS scoretype, assists FROM playergamestats
UNION ALL
SELECT player_id, seasonday, 'steals' AS scoretype, steals FROM playergamestats
UNION ALL
SELECT player_id, seasonday, 'blocks' AS scoretype, blocks FROM playergamestats

Esto pone los datos en una forma más maleable, aunque seguramente no es bonita. Aquí supongo que (player_id, seasonday) es suficiente para identificar de forma única a los jugadores, es decir, la ID del jugador es única en todos los equipos. Si no es así, deberá incluir suficiente información adicional para proporcionar una clave única.

Con esos datos no divididos ahora es posible filtrarlos y agregarlos de maneras útiles, como:

SELECT
  player_id,
  count(CASE WHEN doubles = 2 THEN 1 END) AS doubledoubles,
  count(CASE WHEN doubles = 3 THEN 1 END) AS tripledoubles
FROM (
    SELECT
      player_id, seasonday, count(*) AS doubles
    FROM
    (
        SELECT player_id, seasonday, 'points' AS scoretype, points AS score FROM playergamestats
        UNION ALL
        SELECT player_id, seasonday, 'rebounds' AS scoretype, rebounds FROM playergamestats
        UNION ALL
        SELECT player_id, seasonday, 'assists' AS scoretype, assists FROM playergamestats
        UNION ALL
        SELECT player_id, seasonday, 'steals' AS scoretype, steals FROM playergamestats
        UNION ALL
        SELECT player_id, seasonday, 'blocks' AS scoretype, blocks FROM playergamestats
    ) stats
    WHERE score >= 10
    GROUP BY player_id, seasonday
) doublestats
GROUP BY player_id;

Esto está lejos de ser bonito, y probablemente no sea tan rápido. Sin embargo, es mantenible y requiere cambios mínimos para manejar nuevos tipos de estadísticas, nuevas columnas, etc.

Por lo tanto, es más un "oye, ¿pensaste?" Que una sugerencia seria. El objetivo era modelar el SQL para que se correspondiera con la declaración del problema lo más directamente posible, en lugar de hacerlo rápido.


Esto se hizo mucho más fácil gracias al uso de inserciones sanas de valores múltiples y las citas ANSI en su SQL orientado a MySQL. Gracias; Es bueno no ver backticks por una vez. Todo lo que tuve que cambiar fue la generación de claves sintéticas.

Craig Ringer
fuente
Esto es algo de lo que tenía en mente.
Colin 't Hart
1
Gracias por publicar esta solución. Tenía mis problemas para implementar algo como esto como @ Colin'tHart sugirió anteriormente (nunca antes había hecho algo así, pero parece realmente útil para algunas otras estadísticas que podría querer incluir en el futuro). Es interesante la cantidad de formas que hay para lograr mi resultado deseado. Definitivamente aprendí mucho hoy.
keth
1
Para obtener más información, explain analyzelos planes de consulta (o el equivalente de MySQL) y averiguar qué hacen todos y cómo :)
Craig Ringer
@CraigRinger - Gracias. Buen consejo. En realidad, lo hice con todas las soluciones proporcionadas hasta ahora (usé SqlFiddles "ver plan de ejecución"). Pero definitivamente necesito trabajar en la parte de "averiguar qué hacen todos y cómo" al leer el resultado. = O
keth
6

Lo que @Joshua muestra para MySQL también funciona en Postgres. Booleanlos valores se pueden convertir integery sumar. Sin embargo, el elenco debe ser explícito. Hace para código muy corto:

SELECT player_id, team
     , count(doubles = 2 OR NULL) AS doubledoubles
     , count(doubles = 3 OR NULL) AS tripledoubles
FROM  (
   SELECT player_id, team,
          (points   > 9)::int +
          (rebounds > 9)::int +
          (assists  > 9)::int +
          (steals   > 9)::int +
          (blocks   > 9)::int AS doubles
   FROM playergamestats
   ) a
GROUP  BY 1, 2;

Sin embargo, CASEaunque es más detallado, generalmente es un poco más rápido. Y más portátil, si eso importa:

SELECT player_id, team
     , count(doubles = 2 OR NULL) AS doubledoubles
     , count(doubles = 3 OR NULL) AS tripledoubles
FROM  (
   SELECT player_id, team,
          CASE WHEN points   > 9 THEN 1 ELSE 0 END +
          CASE WHEN rebounds > 9 THEN 1 ELSE 0 END +
          CASE WHEN assists  > 9 THEN 1 ELSE 0 END +
          CASE WHEN steals   > 9 THEN 1 ELSE 0 END +
          CASE WHEN blocks   > 9 THEN 1 ELSE 0 END AS doubles
   FROM playergamestats
   ) a
GROUP  BY 1, 2;

SQL Fiddle.

Erwin Brandstetter
fuente
2

Usando división entera y conversión binaria

SELECT player_id
     , team
     , SUM(CASE WHEN Doubles = 2 THEN 1 ELSE 0 END) DoubleDouble
     , SUM(CASE WHEN Doubles = 3 THEN 1 ELSE 0 END) TripleDouble
FROM   (SELECT player_id
             , team
             , (BINARY (points DIV 10) > 0)
             + (BINARY (rebounds DIV 10) > 0)
             + (BINARY (assists DIV 10) > 0)
             + (BINARY (steals DIV 10) > 0)
             + (BINARY (blocks DIV 10) > 0)
             AS Doubles
        FROM   PlayerGameStats) d
GROUP BY player_id, team
Serpiton
fuente
1

Solo quiero dejar una variación de la versión de @Craig Ringers aquí que encontré por accidente, tal vez sea útil para alguien en el futuro.

En lugar de múltiples UNION ALL, usa unnest y array. Fuente de inspiración: /programming/1128737/unpivot-and-postgresql


SELECT
  player_id,
  count(CASE WHEN doubles = 2 THEN 1 END) AS doubledoubles,
  count(CASE WHEN doubles = 3 THEN 1 END) AS tripledoubles
FROM (
    SELECT
      player_id, seasonday, count(*) AS doubles
    FROM
    (
        SELECT 
          player_id, 
          seasonday,
          unnest(array['Points', 'Rebounds', 'Assists', 'Steals', 'Blocks']) AS scoretype,
          unnest(array[Points, Rebounds, Assists, Steals, Blocks]) AS score
        FROM PlayerGameStats
    ) stats
    WHERE score >= 10
    GROUP BY player_id, seasonday
) doublestats
GROUP BY player_id;

SQL Fiddle: http://sqlfiddle.com/#!12/4980b/3

keth
fuente