Bir satırdaki iki veya daha fazla sütunun belirli bir değerin üzerinde olduğu sayma [basketbol, ​​çift çift, üçlü çift]


20

İstatistiklerini bir veritabanı dosyası olarak çıkarmaya izin veren bir basketbol oyunu oynuyorum, böylece oyunda uygulanmayan istatistikleri hesaplayabiliyorum. Şimdiye kadar istediğim istatistikleri hesaplarken hiç sorun yaşamadım, ama şimdi bir sorunla karşılaşıyorum: sezon boyunca bir oyuncunun oyun istatistiklerinden yaptığı iki kat ve / veya üç kat sayısını saymak.

Bir double double ve triple double tanımı şu şekildedir:

Çift-çift:

Double-double, bir oyuncunun bir oyunda beş istatistik kategorisinden ikisinde (puan, ribaunt, asist, top çalma ve engellenen atışlar) çift haneli sayı toplamı yaptığı bir performans olarak tanımlanır.

Üçlü çift:

Üçlü çift, bir oyuncunun bir oyunda beş istatistiksel kategoriden üçünde (puan, ribaunt, asist, top çalma ve engellenen atışlar) toplamda çift haneli bir sayı toplamış bir performans olarak tanımlanır.

Dörtlü çift (açıklama için eklendi)

Dörtlü çift, bir oyuncunun bir oyunda beş istatistik kategorisinden dördünde (puan, ribaunt, asist, top çalma ve engellenen atışlar) toplamda çift haneli bir sayı topladığı bir performans olarak tanımlanır.

"PlayerGameStats" tablosu, bir oyuncunun oynadığı ve oynadığı her oyun için istatistikleri depolar:

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

Elde etmek istediğim çıktı şu şekilde:

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

Şimdiye kadar bulduğum tek çözüm o kadar korkunç ki beni kusacak ...; o) ... Şöyle görünüyor:

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

... ve şimdi de bunu okuduktan sonra muhtemelen kusuyorsun (ya da çok gülüyorsun). Tüm çift çift kombinasyonları elde etmek için gereken her şeyi bile yazmadım ve üçlü çiftler için durum bildirimini atladım, çünkü daha saçma.

Bunu yapmanın daha iyi bir yolu var mı? Ya tablo yapısı ile ya da yeni bir tablo yapısı ile (tablo dönüştürmek için bir komut dosyası yazabilirim).

MySQL 5.5 veya PostgreSQL 9.2 kullanabilirim.

Örnek veriler ve yukarıda gönderdiğim korkunç çözüm ile SqlFiddle bağlantısı: http://sqlfiddle.com/#!2/af6101/3

Bildiğim kadarıyla oynadığım oyunda meydana gelmedikleri için dörtlü çiftlerle (yukarı bakın) gerçekten ilgilenmiyorum, ancak hesap çok fazla yeniden yazma olmadan kolayca genişletilebilirse, bir artı olur dörtlü çiftler için.

Yanıtlar:


10

Bunun en iyi yol olup olmadığını bilmiyorum. İlk önce bir stat çift haneli olup olmadığını öğrenmek için bir seçim yaptım ve varsa 1 atayım. Oyun başına toplam çift hane sayısını bulmak için hepsini topladı. Oradan sadece tüm çiftler ve üçlüleri özetlemek. Çalışıyor gibi görünüyor

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

Merhaba, çözümün için teşekkürler. Gerçekten beğendim. Tam olarak istediğimi yapar ve çok fazla yazmadan Dörtlü-Çift ve Beşli-iki katına dahil etmek için kolayca genişletilebilir. Bunu şimdilik kabul edilen cevap yapacak. :)
keth

Kodunuzu beğendim, ancak daha kısa olması için hackleyebilirsiniz. CASEBoole ifadeleri true olduğunda 1 ve false olduğunda 0 olarak değerlendirildiğinden, ifade kullanmaya gerek yoktur . Aşağıdaki cevabımı ekledim, çünkü burada tam SQL kod bloğunu gönderemiyorum.
Joshua Huber

Teşekkürler Joshua. Tamamen gözden kaçan ve çok daha iyi görünüyor.
SQLChao

1
@JoshuaHuber Doğru, ancak sorgu yalnızca MySQL'de çalışacaktır. Kullanarak CASEve SUM/COUNTPostgres üzerinde çalışmasına izin verir.
ypercubeᵀᴹ

@ypercube: Aslında, Postgres'de de booleans ekleniyor. Sadece açıkça yayınlamanız gerekir. Ancak CASEgenellikle biraz daha hızlıdır. Birkaç küçük geliştirmeyle bir demo ekledim.
Erwin Brandstetter

7

Bunu deneyin (benim için MySQL 5.5 üzerinde çalıştı):

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

Ya da daha da kısası, JChao'nun kodunu cevabından bilerek kopararak, ancak CASEboole ifadesinin {True, False} olduğunda {1,0} olarak değerlendirildiğinden beri gereksiz ifadeleri çıkararak :

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

Boolean + boolean yapmak istemediğinden yukarıdaki kodun PostgreSQL'de çalışmadığı yorumlarına dayanarak. Hala sevmiyorum CASE. İşte PostgreSQL'de (9.3) bir çıkış yolu 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, iyi bir nokta ve teşekkürler. Sadece yukarıdaki açıklamaya yorum olarak tam açıklamayı sormuştu. Semantik. Ben hokey dört gol hala "bir şapka hile çekmek" olarak kabul edilir, ancak bowling dört ardışık grev "türkiye" uygun olarak düşünülmeyebilir, daha ziyade bir "dört". Her oyunun anlambilimi konusunda uzman değilim. Kararı siz verirsiniz =veya >=uygun olarak seçersiniz .
Joshua Huber

Çözümünüz için teşekkürler. Kesinlikle istediğimi yapıyor. Sağladığınız JChao'nun kısa versiyonu gibi.
14'te keth

1
Boole eklemek PostgreSQL'de çalışmaz, bunu aklınızda bulundurun.
Craig Ringer

@CraigRinger - bunu işaret ettiğiniz için teşekkürler. Genelde SQL ve özellikle PostgreSQl söz konusu olduğunda hala kulakların arkasında yeşil olduğum için, bu benim için değerli bilgiler. :)
keth

1
@CraigRinger Güzel, ama MySQL'in desteklediğini sanmıyorum CAST(... AS int) ( stackoverflow.com/questions/12126991/… ). CAST(... AS UNSIGNED)Bu sorguda çalışan MySQL yapabilir , ancak PostgreSQL yapamaz. Her CASTikisinin de taşınabilirlik için yapabileceği bir ortak olduğundan emin değilim . En kötü durum, CASEtaşınabilirlik çok önemliyse sonunda sıkışmış olabilir .
Joshua Huber

6

İşte sorunla ilgili bir başka görüş.

Benim düşündüğüm gibi, aslında mevcut sorun için özet verilerle çalışıyorsunuz, bu yüzden yapılacak ilk şey onu açmak. Ne yazık ki PostgreSQL bunu yapmak için güzel araçlar sağlamaz, bu nedenle PL / PgSQL'de dinamik SQL nesline girmeden en azından yapabiliriz:

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

Bu, güzel olmadığından emin olsa da, verileri daha yumuşak bir biçime sokar. Burada (player_id, seasonday) oyuncuları benzersiz bir şekilde tanımlamak için yeterli olduğunu, yani oyuncu kimliğinin takımlar arasında benzersiz olduğunu varsayıyorum. Değilse, benzersiz bir anahtar sağlamak için yeterince başka bilgi eklemeniz gerekir.

Bu yayınlanmamış verilerle, şu şekilde yararlı şekillerde filtre uygulamak ve toplamak mümkündür:

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;

Bu güzel olmaktan uzak ve muhtemelen o kadar hızlı değil. Yine de, yeni istatistik türlerini, yeni sütunları vb. İşlemek için minimum değişiklikler gerektiren bakım yapılabilir.

Yani ciddi bir öneriden daha çok "hey, düşündün mü?" Amaç, SQL'i hızlı bir şekilde yapmak yerine sorun ifadesine mümkün olduğunca doğrudan karşılık verecek şekilde modellemekti.


Bu, MySQL odaklı SQL'inizde çok değerli ekler ve ANSI alıntıları kullanmanızla çok daha kolay hale getirildi. Teşekkür ederim; bir kez geri gitmemek güzel. Değiştirmem gereken tek şey sentetik anahtar üretimi idi.


Bu benim aklımda olan şeydi.
Colin 't Hart

1
Bu çözümü gönderdiğiniz için teşekkürler. @ Colin'tHart'ın yukarıda önerdiği gibi böyle bir şey uygulamakta problemlerim olsaydı (daha önce böyle bir şey yapmadım, ancak gelecekte hesaplamak isteyebileceğim diğer bazı istatistikler için relly yararlı görünüyor). İstediğim çıktıyı elde etmenin kaç yolu olduğu ilginç. Kesinlikle bugün çok şey öğrendim.
14'te keth

1
Daha fazla bilgi edinmek explain analyzeiçin sorgu planları (veya MySQL eşdeğeri) ve hepsinin ne yaptığını ve nasıl yaptığını
Craig Ringer

@CraigRinger - Teşekkürler. İyi tavsiye. Aslında bunu şimdiye kadar sağlanan tüm çözümlerle yaptım (SqlFiddles "görünüm uygulama planını" kullandım). Ama ben kesinlikle çıktılar okurken "hepsinin ne yaptığını ve nasıl" parçası üzerinde çalışmak gerekiyor. = O
keth

6

Ne MySQL için @Joshua görüntüler , sıra Postgres çalışır. Booleandeğerler kullanılabilir integerve eklenebilir. Yine de oyuncu kadrosunun açık olması gerekiyor. Çok kısa kod yapar:

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;

Bununla birlikte, CASE- daha ayrıntılı olmasına rağmen - genellikle biraz daha hızlıdır. Ve daha taşınabilir, eğer önemli olursa:

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

Tamsayı bölme ve ikili döküm kullanma

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

Sadece kazara bulduğum @Craig Ringers sürümünün bir varyasyonunu bırakmak istiyorum, belki de gelecekte biri için yararlıdır.

Birden fazla UNION ALL yerine, unnest ve array kullanır. İlham kaynağı: /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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.