Подсчитайте, где два или более столбцов подряд превышают определенное значение [баскетбол, двойной, двойной, тройной двойной]

20

Я играю в баскетбольную игру, которая позволяет выводить статистику в виде файла базы данных, чтобы можно было рассчитать статистику, которая не реализована в игре. До сих пор у меня не было проблем с вычислением статистики, которую я хотел, но теперь я столкнулся с проблемой: подсчитать количество двойных и / или тройных удвоений, сделанных игроком за сезон, из его игровой статистики.

Определение двойного двойного и тройного двойного следующее:

Пятислойный:

Дабл-дабл определяется как исполнение, в котором игрок накапливает в игре двузначное число в двух из пяти статистических категорий - очки, отдачи, передачи, перехваты и заблокированные удары.

Трипл-дабл:

Тройной дабл определяется как исполнение, в котором игрок накапливает в игре двузначное число в трех из пяти статистических категорий - очки, отскоки, передачи, перехваты и заблокированные удары.

Четырехместный-двойной (добавлено для уточнения)

Четверной дубль определяется как исполнение, в котором игрок накапливает двузначное число в четырех из пяти статистических категорий - очки, отдачи, передачи, перехваты и заблокированные удары - в игре.

Таблица «PlayerGameStats» хранит статистику для каждой игры, в которую играет игрок, и выглядит следующим образом:

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

Результат, который я хочу получить, выглядит следующим образом:

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

Единственное решение, которое я нашел до сих пор, так ужасно, что меня тошнит ...; о) ... Это выглядит так:

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

... и теперь вы, вероятно, тоже рвете (или сильно смеетесь) после прочтения этого. Я даже не выписал все, что нужно для получения всех двойных двойных комбинаций, и пропустил оператор case для тройных двойных комбинаций, потому что это еще более нелепо.

Есть лучший способ это сделать? Либо со структурой таблицы, которая у меня есть, либо с новой структурой таблицы (я мог бы написать скрипт для преобразования таблицы).

Я могу использовать MySQL 5.5 или PostgreSQL 9.2.

Вот ссылка на SqlFiddle с примерами данных и моим ужасным решением, которое я разместил выше: http://sqlfiddle.com/#!2/af6101/3

Обратите внимание, что меня не очень интересуют четверные-двойные числа (см. Выше), поскольку они не встречаются в игре, в которую я играю, насколько я знаю, но было бы плюсом, если запрос легко расширяется без особой перезаписи в аккаунт. для четырехместных.

Кет
источник

Ответы:

10

Не знаю, если это лучший способ. Сначала я сделал выбор, чтобы выяснить, является ли статистика двузначной цифрой, и присвоить ей 1, если она есть. Подведем итоги, чтобы узнать общее количество двузначных чисел за игру. Оттуда просто суммируем все двойники и тройки. Кажется работать

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
источник
Привет, спасибо за решение. Мне это и вправду нравится. Делает именно то, что я хочу, и легко расширяется, чтобы включить Quadruple-double и Quintuple-doubles без особой записи. Сделаю это принятым ответом пока. :)
Кет
Мне нравится ваш код, но вы можете взломать его, чтобы быть еще короче. Нет необходимости использовать CASEоператоры, так как логические выражения оцениваются в 1, когда истина, и в 0, когда ложь. Я добавил его к своему ответу ниже с приветом, так как не могу опубликовать полный блок кода SQL в комментарии здесь.
Джошуа Хубер,
Спасибо, Джошуа. Полностью пропустил это и выглядит намного лучше.
SQLChao
1
@JoshuaHuber Правильно, но тогда запрос будет работать только в MySQL. Использование CASEи SUM/COUNTпозволяет работать на Postgres.
ypercubeᵀᴹ
@ypercube: На самом деле, сложение логических значений работает и в Postgres. Вам нужно только привести в явном виде. Но, CASEкак правило, немного быстрее. Я добавил демо с несколькими другими незначительными улучшениями.
Эрвин Брандштеттер
7

Попробуйте это (работал для меня на 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

Или даже короче, откровенно срывая код JChao из его ответа, но удаляя ненужные CASEутверждения, так как логическое выражение expr оценивается в {1,0}, когда {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

Судя по комментариям, приведенный выше код не будет работать в PostgreSQL, так как не любит делать boolean + boolean. Я до сих пор не люблю CASE. Вот выход на PostgreSQL (9.3), приведя к 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, хорошая мысль и спасибо. Только что попросил это точное разъяснение в качестве комментария на вопрос выше. Семантика. Я считаю, что четыре гола в хоккее до сих пор считаются «хитрым трюком», но четыре последовательных удара в боулинге не могут считаться «индейкой», а скорее «четверкой». Я не эксперт по семантике каждой игры. Вы принимаете решение и выбираете =или >=как хотите .
Джошуа Хубер
Спасибо за ваше решение. Определенно делает то, что я хочу. Также как краткая версия от JChao, которую вы предоставили.
Кет
1
Однако добавление логических значений не будет работать в PostgreSQL, имейте это в виду.
Крейг Рингер
@CraigRinger - спасибо за указание на это. Поскольку я до сих пор не слышу, когда речь заходит об SQL в целом и PostgreSQl в частности, для меня это ценная информация. :)
Кет
1
@CraigRinger Хорошо, но я не думаю, что MySQL поддерживает CAST(... AS int) ( stackoverflow.com/questions/12126991/… ). MySQL может сделать CAST(... AS UNSIGNED), что работает в этом запросе, но PostgreSQL не может. Не уверен, что есть что-то общее, CASTчто оба могут сделать для переносимости. В худшем случае, CASEв конце концов , может возникнуть проблема, если переносимость имеет первостепенное значение.
Джошуа Хубер
6

Вот еще один взгляд на проблему.

По моему мнению, вы по сути работаете с сводными данными для текущей проблемы, поэтому первое, что нужно сделать, - это отменить ее. К сожалению, PostgreSQL не предоставляет хороших инструментов для этого, поэтому, не вдаваясь в динамическую генерацию SQL в PL / PgSQL, мы можем по крайней мере сделать следующее:

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

Это помещает данные в более податливую форму, хотя это, конечно, не красиво. Здесь я предполагаю, что (player_id, seasonday) достаточно для однозначной идентификации игроков, т.е. идентификатор игрока уникален для разных команд. Если это не так, вам нужно будет включить достаточно информации для предоставления уникального ключа.

С этими неискаженными данными теперь можно фильтровать и агрегировать их полезными способами, такими как:

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;

Это далеко не красиво, и, вероятно, не так быстро. Тем не менее, его можно обслуживать, требуя минимальных изменений для обработки новых типов статистики, новых столбцов и т. Д.

Так что это скорее «эй, ты думал», чем серьезное предложение? Цель состояла в том, чтобы смоделировать SQL так, чтобы он максимально точно соответствовал формулировке задачи, а не делал это быстро.


Это стало намного проще благодаря использованию правильных многозначных вставок и цитирования ANSI в MySQL-ориентированном SQL. Спасибо; приятно не видеть галочки на этот раз. Все, что мне пришлось изменить, - это генерация синтетического ключа.

Крейг Рингер
источник
Это то, что я имел в виду.
Colin 't Hart
1
Спасибо за публикации этого решения. Если бы у меня были проблемы с реализацией чего-то подобного, как предложено выше @ Colin'tHart (никогда раньше такого не делалось, но, похоже, очень полезно для некоторых других статистических показателей, которые я мог бы рассчитывать в будущем). Интересно, как много способов достичь желаемого результата. Определенно многому научился сегодня.
Кет
1
Чтобы узнать больше, explain analyzeзапросите планы (или эквивалент MySQL) и выясните, что они все делают и как :)
Крейг Рингер,
@CraigRinger - Спасибо. Хороший совет. На самом деле вроде как и со всеми решениями, предоставленными до сих пор (я использовал SqlFiddles "просмотр плана выполнения"). Но мне определенно нужно работать над частью «выяснить, что они все делают и как» при чтении вывода. = O
Кет
6

То, что @Joshua отображает для MySQL , работает и в Postgres. Booleanзначения могут быть приведены integerи сложены. Приведение должно быть явным, хотя. Делает для очень короткого кода:

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;

Тем не менее, CASEхотя и более многословно, как правило, немного быстрее. И более портативный, если это имеет значение:

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

Использование целочисленного деления и двоичного приведения

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
источник
1

Просто хочу оставить вариант версии @Craig Ringers здесь, который я нашел случайно, возможно, он пригодится кому-то в будущем.

Вместо нескольких UNION ALL он использует unnest и array. Источник для вдохновения: /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

Кет
источник