Compter lorsque deux colonnes ou plus d'une rangée dépassent une certaine valeur [basket-ball, double double, triple double]


20

Je joue à un match de basket qui permet de sortir ses statistiques sous forme de fichier de base de données, donc on peut en calculer des statistiques qui ne sont pas implémentées dans le jeu. Jusqu'à présent, je n'ai eu aucun problème à calculer les statistiques que je voulais, mais maintenant j'ai rencontré un problème: compter le nombre de doubles doubles et / ou triples doubles réalisés par un joueur au cours de la saison à partir de ses statistiques de jeu.

La définition d'un double double et d'un triple double est la suivante:

Double double:

Un double-double est défini comme une performance dans laquelle un joueur accumule un nombre total à deux chiffres dans deux des cinq catégories statistiques - points, rebonds, passes décisives, interceptions et tirs bloqués - dans une partie.

Triple-double:

Un triple-double est défini comme une performance dans laquelle un joueur accumule un nombre total à deux chiffres dans trois des cinq catégories statistiques - points, rebonds, passes décisives, interceptions et tirs bloqués - dans une partie.

Quadruple-double (ajouté pour clarification)

Un quadruple double est défini comme une performance dans laquelle un joueur accumule un nombre total à deux chiffres dans quatre des cinq catégories statistiques - points, rebonds, passes décisives, interceptions et tirs bloqués - dans une partie.

Le tableau "PlayerGameStats" stocke les statistiques de chaque jeu joué par un joueur et se présente comme suit:

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

La sortie que je veux atteindre ressemble à ceci:

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

La seule solution que j'ai trouvée jusqu'à présent est si horrible qu'elle me fait vomir ...; o) ... Cela ressemble à ceci:

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

... et maintenant vous êtes probablement aussi en train de vomir (ou de rire fort) après avoir lu ceci. Je n'ai même pas écrit tout ce qui serait nécessaire pour obtenir toutes les combinaisons doubles doubles, et j'ai omis la déclaration de cas pour les doubles doubles parce que c'est encore plus ridicule.

Y a-t-il une meilleure manière de faire cela? Soit avec la structure de table que j'ai, soit avec une nouvelle structure de table (je pourrais écrire un script pour convertir la table).

Je peux utiliser MySQL 5.5 ou PostgreSQL 9.2.

Voici un lien vers SqlFiddle avec des exemples de données et ma terrible solution que j'ai publiée ci-dessus: http://sqlfiddle.com/#!2/af6101/3

Notez que je ne suis pas vraiment intéressé par les quadruples doubles (voir ci-dessus) car ils ne se produisent pas dans le jeu que je joue pour autant que je sache, mais ce serait un plus si la requête est facilement extensible sans trop de réécriture pour le compte pour quadruple-double.

Réponses:


10

Je ne sais pas si c'est la meilleure façon. J'ai d'abord fait une sélection pour savoir si une statistique est à deux chiffres et lui attribuer un 1 si c'est le cas. Récapitulez tous ces éléments pour connaître le nombre total de chiffres à deux chiffres par partie. De là, résumez tous les doubles et triples. Semble fonctionner

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

Salut, merci pour votre solution. J'aime vraiment ça. Fait exactement ce que je veux et est facilement extensible pour inclure le quadruple double et le quintuple double sans beaucoup d'écriture. En fera la réponse acceptée pour l'instant. :)
keth

J'aime votre code, mais vous pouvez le pirater pour être encore plus court. Pas besoin d'utiliser des CASEinstructions car les expressions booléennes sont évaluées à 1 lorsqu'elles sont vraies et à 0 lorsqu'elles sont fausses. Je l'ai ajouté à ma réponse ci-dessous avec un cri car vous ne pouvez pas poster le bloc de code SQL complet en commentaire ici.
Joshua Huber

Merci Joshua. Totalement ignoré cela et ça a l'air beaucoup mieux.
SQLChao

1
@JoshuaHuber Oui, mais la requête ne fonctionnera que dans MySQL. L'utiliser CASEet lui SUM/COUNTpermet également de fonctionner sur Postgres.
ypercubeᵀᴹ

@ypercube: En fait, l'ajout de booléens fonctionne également dans Postgres. Vous avez seulement besoin de caster explicitement. Mais CASEest généralement un tout petit peu plus rapide. J'ai ajouté une démo avec quelques autres améliorations mineures.
Erwin Brandstetter

7

Essayez ceci (travaillé pour moi sur 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 encore plus court, en arrachant ouvertement le code de JChao de sa réponse, mais en supprimant les CASEdéclarations inutiles puisque boolean expr est évalué à {1,0} lorsque {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

Sur la base des commentaires que le code ci-dessus ne fonctionnera pas dans PostgreSQL car n'aime pas faire booléen + booléen. Je n'aime toujours pas CASE. Voici un moyen de sortir sur PostgreSQL (9.3), en effectuant un cast vers 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

@ypercube, bon point et merci. Je venais de demander cette précision exacte comme commentaire sur la question ci-dessus. Sémantique. Je crois que quatre buts au hockey sont toujours considérés comme «tirer un coup du chapeau», mais quatre frappes consécutives au bowling pourraient ne pas être considérées comme une «dinde» proprement dite, mais plutôt comme un «quad». Je ne suis pas un expert de la sémantique de chaque jeu. Vous prenez la décision et choisissez =ou >=comme bon vous semble.
Joshua Huber

Merci pour votre solution. Fait définitivement ce que je veux. Comme la version abrégée de JChao que vous avez fournie.
keth

1
L'ajout de booléens ne fonctionnera pas dans PostgreSQL, gardez cela à l'esprit.
Craig Ringer

@CraigRinger - merci de l'avoir signalé. Étant donné que je suis toujours vert derrière les oreilles en ce qui concerne SQL en général et PostgreSQl en particulier, c'est une information très utile pour moi. :)
keth

1
@CraigRinger Nice, mais je ne pense pas que MySQL supporte CAST(... AS int) ( stackoverflow.com/questions/12126991/… ). MySQL peut le faire CAST(... AS UNSIGNED), ce qui fonctionne dans cette requête, mais pas PostgreSQL. Pas sûr qu'il y ait un commun CASTque les deux peuvent faire pour la portabilité. Le pire des cas, pourrait être coincé CASEà la fin si la portabilité est primordiale.
Joshua Huber

6

Voici une autre vision du problème.

À mon avis, vous travaillez essentiellement avec des données pivotées pour le problème actuel, donc la première chose à faire est de le débloquer. Malheureusement, PostgreSQL ne fournit pas de bons outils pour le faire, donc sans entrer dans la génération SQL dynamique en PL / PgSQL, nous pouvons au moins faire:

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

Cela met les données sous une forme plus malléable, mais ce n'est certainement pas joli. Ici, je suppose que (player_id, seasonday) est suffisant pour identifier de manière unique les joueurs, c'est-à-dire que l'ID du joueur est unique d'une équipe à l'autre. Si ce n'est pas le cas, vous devrez inclure suffisamment d'autres informations pour fournir une clé unique.

Avec ces données non pivotées, il est désormais possible de les filtrer et de les agréger de manière utile, comme:

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;

C'est loin d'être joli, et ce n'est probablement pas si rapide. Il est cependant maintenable, nécessitant des modifications minimales pour gérer de nouveaux types de statistiques, de nouvelles colonnes, etc.

C'est donc plus un "hé, avez-vous pensé" qu'une suggestion sérieuse. Le but était de modéliser le SQL pour correspondre à l'énoncé du problème aussi directement que possible, plutôt que de le rendre rapide.


Cela a été rendu beaucoup plus facile par votre utilisation d'inserts sensés à valeurs multiples et de cotations ANSI dans votre SQL orienté MySQL. Merci; c'est sympa de ne pas voir de backticks pour une fois. Tout ce que j'ai dû changer, c'est la génération de clés synthétiques.


C'est en quelque sorte ce que j'avais en tête.
Colin 't Hart

1
Merci d'avoir publié cette solution. J'ai eu des problèmes pour implémenter quelque chose comme ça, comme @ Colin'tHart l'a suggéré ci-dessus (jamais fait quelque chose comme ça auparavant, mais cela semble vraiment utile pour d'autres statistiques que je pourrais vouloir caluber à l'avenir). Il est intéressant de voir combien de façons il y a de réaliser ma sortie souhaitée. Certainement beaucoup appris aujourd'hui.
keth

1
Pour en savoir plus, explain analyzeles plans de requête (ou équivalent MySQL) et comprendre ce qu'ils font tous et comment :)
Craig Ringer

@CraigRinger - Merci. Bon conseil. En fait, cela a été fait avec toutes les solutions fournies jusqu'à présent (j'ai utilisé SqlFiddles "voir le plan d'exécution"). Mais je dois absolument travailler sur la partie "comprendre ce qu'ils font tous et comment" lors de la lecture de la sortie. = O
keth

6

Ce que @Joshua affiche pour MySQL , fonctionne également dans Postgres. Booleanles valeurs peuvent être converties integeret additionnées. Le casting doit cependant être explicite. Fait pour le code très court:

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;

Cependant, CASE- même si plus verbeux - est généralement un tout petit peu plus rapide. Et plus portable, si cela importe:

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.



2

Utilisation de la division entière et de la conversion binaire

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

1

Je veux juste laisser ici une variante de la version de @Craig Ringers que j'ai trouvée par accident, peut-être que c'est utile pour quelqu'un à l'avenir.

Au lieu de plusieurs UNION ALL, il utilise unnest et array. Source d'inspiration: /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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.