Conte onde duas ou mais colunas seguidas estão acima de um determinado valor [basquete, duplo duplo, triplo duplo]

20

Eu jogo um jogo de basquete que permite exibir suas estatísticas como um arquivo de banco de dados, para que possamos calcular estatísticas que não foram implementadas no jogo. Até agora, não tive nenhum problema em calcular as estatísticas que queria, mas agora me deparei com um problema: contar o número de duplos duplos e / ou triplos duplos que um jogador fez ao longo da temporada a partir de suas estatísticas de jogo.

A definição de um duplo duplo e um triplo duplo é a seguinte:

Dobro dobro:

Um duplo duplo é definido como um desempenho no qual um jogador acumula um total de dois dígitos em duas das cinco categorias estatísticas - pontos, rebotes, assistências, roubadas de bola e bloqueios - em um jogo.

Triplo duplo:

Um triplo-duplo é definido como um desempenho no qual um jogador acumula um total de dois dígitos em três das cinco categorias estatísticas - pontos, rebotes, assistências, roubadas de bola e bloqueios - em um jogo.

Quádruplo-duplo (adicionado para esclarecimento)

Um quádruplo-duplo é definido como um desempenho no qual um jogador acumula um total de dois dígitos em quatro das cinco categorias estatísticas - pontos, rebotes, assistências, roubadas de bola e bloqueios - em um jogo.

A tabela "PlayerGameStats" armazena estatísticas para cada jogo que um jogador joga e tem a seguinte aparência:

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

A saída que eu quero alcançar é assim:

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

A única solução que encontrei até agora é tão terrível que me faz vomitar ...; o) ... É assim:

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

... e agora você provavelmente também está vomitando (ou rindo muito) depois de ler isso. Eu nem escrevi tudo o que seria necessário para obter todas as combinações de duplo duplo e omiti a declaração de caso dos duplos triplos porque é ainda mais ridículo.

Existe uma maneira melhor de fazer isso? Com a estrutura da tabela que eu tenho ou com uma nova estrutura da tabela (eu poderia escrever um script para converter a tabela).

Eu posso usar o MySQL 5.5 ou PostgreSQL 9.2.

Aqui está um link para o SqlFiddle com dados de exemplo e minha péssima solução que eu publiquei acima: http://sqlfiddle.com/#!2/af6101/3

Observe que não estou realmente interessado em duplos quádruplos (veja acima), pois eles não ocorrem no jogo que eu jogo até onde eu sei, mas seria uma vantagem se a consulta fosse facilmente expansível sem muita reescrita na conta para quádruplos-duplos.

keth
fonte

Respostas:

10

Não sei se esse é o melhor caminho. Primeiro, fiz uma seleção para descobrir se uma estatística é de dois dígitos e atribui um 1, se for. Resumiu tudo isso para descobrir o número total de dois dígitos por jogo. A partir daí, basta resumir todos os duplos e triplos. 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
fonte
Oi, obrigado por sua solução. Eu realmente gosto. Faz exatamente o que eu quero e é facilmente extensível para incluir duplos quádruplos e quíntuplos sem muita escrita. Isso fará a resposta aceita por enquanto. :)
keth
Eu gosto do seu código, mas você pode cortá-lo para ser ainda mais curto. Não há necessidade de usar CASEinstruções, pois as expressões booleanas avaliam 1 quando verdadeiro e 0 quando falso. Adicionei-o à minha resposta abaixo com um grito para você, pois não é possível postar o bloco de código SQL completo no comentário aqui.
Joshua Huber
Obrigado Joshua. Totalmente esquecido isso e parece muito melhor.
precisa saber é o seguinte
11
@ JoshuaHuber Certo, mas a consulta funcionará apenas no MySQL. Usando CASEe SUM/COUNTpermite que ele funcione no Postgres também.
precisa saber é o seguinte
@ypercube: Na verdade, adicionar booleanos também funciona no Postgres. Você só precisa transmitir explicitamente. Mas CASEnormalmente é um pouco mais rápido. Eu adicionei uma demonstração com algumas outras pequenas melhorias.
Erwin Brandstetter
7

Experimente isso (funcionou para mim no 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

Ou ainda mais, rasgando descaradamente o código de JChao de sua resposta, mas retirando as CASEdeclarações desnecessárias , já que o boolean expr é avaliado em {1,0} quando {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

Com base nos comentários de que o código acima não será executado no PostgreSQL, pois não gosta de fazer booleano + booleano. Eu ainda não gosto CASE. Aqui está uma saída do PostgreSQL (9.3), lançando para 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
fonte
@ypercube, bom argumento e obrigado. Acabara de pedir esse esclarecimento exato como comentário sobre a pergunta acima. Semântica. Acredito que quatro gols no hóquei ainda são considerados "fazer um hat trick", mas quatro ataques consecutivos no boliche podem não ser considerados um "peru" propriamente dito, mas sim um "quad". Não sou especialista na semântica de cada jogo. Você toma a decisão e escolhe =ou >=como adequado.
Joshua Huber
Obrigado pela sua solução. Definitivamente faz o que eu quero. Também como a versão reduzida do JChao que você forneceu.
keth
11
Adicionar booleanos não funcionará no PostgreSQL, lembre-se disso.
Craig Ringer
@ CraigRinger - obrigado por apontar isso. Como ainda estou verde atrás dos ouvidos quando se trata de SQL em geral e do PostgreSQl em particular, essas são informações valiosas para mim. :)
keth
11
@ Craigrainger Nice, mas não acho que o MySQL suporte CAST(... AS int) ( stackoverflow.com/questions/12126991/… ). O MySQL pode fazer CAST(... AS UNSIGNED), o que funciona nesta consulta, mas o PostgreSQL não. Não tenho certeza se existe algo comum CASTque ambos possam fazer pela portabilidade. Pior CASE, pode ser travado CASEno final se a portabilidade for primordial.
Joshua Huber
6

Aqui está outra opinião sobre o problema.

Na minha opinião, você está essencialmente trabalhando com dados dinâmicos para o problema atual; portanto, a primeira coisa a fazer é descompactá-lo. Infelizmente, o PostgreSQL não fornece boas ferramentas para fazer isso; portanto, sem entrar na geração dinâmica de SQL no PL / PgSQL, podemos pelo menos:

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

Isso coloca os dados em uma forma mais maleável, embora certamente não seja bonito. Aqui, eu suponho que (player_id, seasonday) é suficiente para identificar jogadores de forma exclusiva, ou seja, o ID do jogador é único entre os times. Caso contrário, você precisará incluir outras informações suficientes para fornecer uma chave exclusiva.

Com esses dados não dinâmicos, agora é possível filtrá-los e agregá-los de maneiras úteis, 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;

Isso está longe de ser bonito e provavelmente não é tão rápido assim. Porém, é sustentável, exigindo alterações mínimas para lidar com novos tipos de estatísticas, novas colunas etc.

Portanto, é mais um "ei, você pensou" do que uma sugestão séria. O objetivo era modelar o SQL para corresponder à declaração do problema o mais diretamente possível, em vez de torná-lo rápido.


Isso ficou muito mais fácil com o uso de inserções com vários valores e ANSI no SQL orientado ao MySQL. Obrigado; é bom não ver backticks pela primeira vez. Tudo o que tive que mudar foi a geração de chaves sintéticas.

Craig Ringer
fonte
Isso é o que eu tinha em mente.
Colin 'Hart
11
Obrigado por postar esta solução. Tive meus problemas implementando algo assim como o @ Colin'tHart sugeriu acima (nunca fiz algo assim antes, mas parece muito útil para algumas outras estatísticas que talvez eu queira calcular no futuro). É interessante quantas maneiras existem para alcançar minha saída desejada. Definitivamente aprendi muito hoje.
Keth
11
Para saber mais, explain analyzeos planos de consulta (ou MySQL equivalentes) e descobrir o que tudo o que fazem e como :)
Craig Ringer
@ CraigRinger - Obrigado. Bom conselho. Na verdade, fiz isso com todas as soluções fornecidas até agora (usei o SqlFiddles "visualizar plano de execução"). Mas eu definitivamente preciso trabalhar na parte "descubra o que todos eles fazem e como" ao ler a saída. = O
keth
6

O que o @Joshua exibe para o MySQL , também funciona no Postgres. Booleanvalores podem ser convertidos integere adicionados. O elenco precisa ser explícito, no entanto. Cria um código muito curto:

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;

No entanto, CASEembora seja mais detalhado, normalmente é um pouco mais rápido. E mais portátil, se isso 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
fonte
11
TIL: O PostgreSQL, como o MySQL, permite referenciar colunas no GROUP BY por alias ou números ordinais .
Andriy M
2

Usando divisão inteira e conversão binária

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
fonte
1

Só quero deixar uma variação da versão do @Craig Ringers aqui que encontrei por acidente, talvez seja útil para alguém no futuro.

Em vez de vários UNION ALL, ele usa unnest e array. Fonte de inspiração: /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
fonte