Hitung di mana dua atau lebih kolom berturut-turut berada di atas nilai tertentu [bola basket, dobel ganda, rangkap tiga]

20

Saya memainkan permainan bola basket yang memungkinkan untuk mengeluarkan statistiknya sebagai file basis data, jadi orang dapat menghitung statistik dari itu yang tidak diterapkan dalam permainan. Sejauh ini saya tidak punya masalah menghitung statistik yang saya inginkan, tetapi sekarang saya mengalami masalah: menghitung jumlah ganda ganda dan / atau tiga kali lipat ganda yang dibuat seorang pemain selama musim ini dari statistik permainannya.

Definisi ganda ganda dan ganda ganda adalah sebagai berikut:

Ganda-ganda:

Double-double didefinisikan sebagai kinerja di mana pemain mengakumulasi total angka dua digit dalam dua dari lima kategori statistik — poin, rebound, assist, mencuri, dan tembakan yang diblokir — dalam sebuah game.

Triple-double:

Triple-double didefinisikan sebagai kinerja di mana pemain mengakumulasi total angka dua digit dalam tiga dari lima kategori statistik — poin, rebound, assist, steal, dan tembakan yang diblokir — dalam sebuah permainan.

Quadruple-double (ditambahkan untuk klarifikasi)

Quadruple-double didefinisikan sebagai kinerja di mana pemain mengakumulasi total angka dua digit dalam empat dari lima kategori statistik — poin, rebound, assist, steal, dan tembakan yang diblokir — dalam sebuah permainan.

Tabel "PlayerGameStats" menyimpan statistik untuk setiap permainan yang dimainkan pemain dan terlihat sebagai berikut:

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

Output yang ingin saya capai terlihat seperti ini:

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

Satu-satunya solusi yang saya temukan sejauh ini sangat mengerikan sehingga membuat saya muntah ...; o) ... Sepertinya ini:

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

... dan sekarang Anda mungkin juga muntah (atau tertawa keras) setelah membaca ini. Saya bahkan tidak menulis semua yang diperlukan untuk mendapatkan semua kombinasi ganda ganda, dan menghilangkan pernyataan kasus untuk triple ganda karena itu bahkan lebih konyol.

Apakah ada cara yang lebih baik untuk melakukan ini? Baik dengan struktur tabel yang saya miliki atau dengan struktur tabel baru (saya bisa menulis skrip untuk mengonversi tabel).

Saya dapat menggunakan MySQL 5.5 atau PostgreSQL 9.2.

Berikut ini tautan ke SqlFiddle dengan contoh data dan solusi mengerikan yang saya posting di atas: http://sqlfiddle.com/#!2/af6101/3

Perhatikan bahwa saya tidak benar-benar tertarik pada quadruple-doubles (lihat di atas) karena mereka tidak terjadi dalam permainan yang saya mainkan sejauh yang saya tahu, tetapi itu akan menjadi nilai tambah jika kueri mudah diperluas dengan tanpa banyak menulis ulang ke akun untuk quadruple-dobel.

keth
sumber

Jawaban:

10

Tidak tahu apakah ini cara terbaik. Saya pertama kali melakukan seleksi untuk mengetahui apakah stat adalah dua digit dan menetapkannya 1 jika benar. Ringkas semuanya untuk mengetahui jumlah digit ganda per game. Dari sana, jumlahkan semua ganda dan tiga kali lipat. Tampaknya bekerja

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
sumber
Hai, terima kasih atas solusinya. Aku benar-benar menyukainya. Melakukan persis apa yang saya inginkan dan mudah diperluas untuk memasukkan Quadruple-double dan Quintuple-double tanpa banyak menulis. Akan menjadikan ini jawaban yang diterima untuk saat ini. :)
keth
Saya suka kode Anda, tetapi Anda dapat meretasnya menjadi lebih pendek. Tidak perlu menggunakan CASEpernyataan karena ekspresi boolean mengevaluasi ke 1 ketika benar dan 0 ketika salah. Saya telah menambahkannya ke jawaban saya di bawah ini dengan berteriak kepada Anda karena tidak dapat memposting blok kode SQL lengkap dalam komentar di sini.
Joshua Huber
Terima kasih Joshua. Benar-benar diabaikan itu dan terlihat jauh lebih baik.
SQLChao
1
@ JoshuaHuber Benar tapi kemudian kueri hanya akan berfungsi di MySQL. Menggunakan CASEdan SUM/COUNTmemungkinkannya untuk bekerja di Postgres juga.
ypercubeᵀᴹ
@ ypercube: Sebenarnya, menambahkan boolean juga berfungsi di Postgres. Anda hanya perlu melakukan casting secara eksplisit. Tetapi CASEbiasanya sedikit lebih cepat. Saya menambahkan demo dengan beberapa perbaikan kecil lainnya.
Erwin Brandstetter
7

Coba ini (berhasil untuk saya di 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

Atau bahkan lebih pendek, dengan membohongi kode JChao dari jawabannya, tetapi mengeluarkan CASEpernyataan yang tidak dibutuhkan karena boolean expr mengevaluasi menjadi {1,0} ketika {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

Berdasarkan komentar bahwa kode di atas tidak akan berjalan di PostgreSQL karena tidak suka melakukan boolean + boolean. Saya masih tidak suka CASE. Inilah jalan keluar di PostgreSQL (9.3), dengan melakukan casting ke 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
sumber
@ ypercube, poin bagus & terima kasih. Baru saja meminta klarifikasi yang tepat seperti komentar pada pertanyaan di atas. Semantik. Saya percaya empat gol dalam hoki masih dianggap "menarik hattrick", tetapi empat serangan berturut-turut di bowling mungkin tidak dianggap sebagai "kalkun" yang tepat, melainkan merupakan "quad". Saya bukan ahli dalam semantik setiap gim. Anda membuat keputusan dan memilih =atau >=sesuai.
Joshua Huber
Terima kasih atas solusinya. Pasti melakukan apa yang saya inginkan. Juga suka versi singkat dari JChao yang Anda berikan.
keth
1
Menambahkan booleans tidak akan berfungsi di PostgreSQL, ingatlah itu.
Craig Ringer
@CraigRinger - terima kasih sudah menunjukkannya. Karena saya masih hijau di belakang telinga ketika datang ke SQL secara umum dan PostgreSQl pada khususnya, ini adalah informasi berharga rellay bagi saya. :)
keth
1
@CraigRinger Bagus, tapi saya tidak berpikir MySQL mendukung CAST(... AS int) ( stackoverflow.com/questions/12126991/… ). MySQL dapat melakukannya CAST(... AS UNSIGNED), yang berfungsi dalam kueri ini, tetapi PostgreSQL tidak bisa. Tidak yakin ada kesamaan CASTyang keduanya bisa lakukan untuk portabilitas. KASUS Terburuk, mungkin terjebak dengan CASEpada akhirnya jika portabilitas adalah yang terpenting.
Joshua Huber
6

Inilah satu lagi masalah.

Menurut saya, Anda pada dasarnya bekerja dengan data yang diputar untuk masalah saat ini, jadi hal pertama yang harus dilakukan adalah menghapusnya. Sayangnya PostgreSQL tidak menyediakan alat yang bagus untuk melakukan itu, jadi tanpa masuk ke generasi SQL dinamis di PL / PgSQL, setidaknya kita bisa melakukan:

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

Ini menempatkan data dalam bentuk yang lebih mudah ditempa, meskipun itu pasti tidak cantik. Di sini saya berasumsi bahwa (player_id, seasonday) cukup untuk mengidentifikasi pemain secara unik, yaitu ID pemain unik di seluruh tim. Jika tidak, Anda harus memasukkan cukup info lain untuk memberikan kunci unik.

Dengan data yang tidak diproteksi itu sekarang mungkin untuk memfilter dan menggabungkannya dengan cara yang bermanfaat, seperti:

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;

Ini jauh dari cantik, dan mungkin tidak secepat itu. Ini dapat dipelihara, membutuhkan perubahan minimal untuk menangani tipe statistik baru, kolom baru, dll.

Jadi ini lebih merupakan "hei, apakah Anda memikirkan" daripada saran yang serius. Tujuannya adalah untuk memodelkan SQL agar sesuai dengan pernyataan masalah secara langsung, daripada membuatnya cepat.


Ini menjadi jauh lebih mudah dengan penggunaan sisipan bernilai multi-waras dan kutipan ANSI dalam SQL berorientasi MySQL Anda. Terima kasih; senang tidak melihat backticks sekali. Yang harus saya ubah adalah pembuatan kunci sintetis.

Craig Ringer
sumber
Ini semacam apa yang ada dalam pikiran saya.
Colin 't Hart
1
Terima kasih telah memposting solusi ini. Seandainya masalah saya mengimplementasikan sesuatu seperti ini seperti yang disarankan @ Colin'tHart di atas (tidak pernah melakukan hal seperti itu sebelumnya, tetapi tampaknya sangat berguna untuk beberapa statistik lain yang mungkin ingin saya hitung di masa depan). Sangat menarik berapa banyak cara untuk mencapai hasil yang saya inginkan. Pasti banyak belajar hari ini.
keth
1
Untuk mempelajari lebih lanjut, explain analyzekueri merencanakan (atau yang setara dengan MySQL) dan mencari tahu apa yang mereka semua lakukan dan caranya :)
Craig Ringer
@CraigRinger - Terima kasih. Saran yang bagus. Sebenarnya jenis melakukan itu dengan semua solusi yang disediakan sampai sekarang (saya menggunakan SqlFiddles "lihat rencana eksekusi"). Tapi saya jelas perlu bekerja pada bagian "mencari tahu apa yang mereka semua lakukan dan bagaimana" ketika membaca output. = O
keth
6

Apa yang ditampilkan oleh Yosua untuk MySQL , juga berfungsi di Postgres. Booleannilai dapat dilemparkan ke integerdan ditambahkan. Para pemerannya perlu eksplisit. Membuat kode yang sangat singkat:

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;

Namun, CASE- meskipun lebih bertele-tele - biasanya sedikit lebih cepat. Dan lebih portabel, jika itu penting:

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
sumber
2

Menggunakan pembagian integer dan pemeran biner

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

Hanya ingin meninggalkan variasi versi @Craig Ringers di sini yang saya temukan secara tidak sengaja, mungkin berguna bagi seseorang di masa depan.

Alih-alih beberapa UNION ALL itu menggunakan undest dan array. Sumber untuk inspirasi: /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
sumber