めもちょー

メモ帳代わりに使っています。

生成AIを活用して複雑なスタッツを分析する

概要

この記事では、生成AI(Chat GPT無料枠)を用いてNBAのスタッツを分析した際の知見を記しています。

NBAスタッツ分析について

NBAでは、トラッキング技術により試合のあらゆるイベントが記録されています。
例えば、八村塁選手のデータに関して下記のような情報にアクセスすることができます。

数年間NBAに在籍していた渡邊雄太選手曰く、試合前にチームから分厚いルールブックが渡され、
そこには「相手選手Aは、左手のシュートの成功率が低いため、左手でシュートを打たせるように右側にディフェンスせよ」などスタッツをもとにした戦術がいくつも書かれており、それを頭に叩き込んで試合に臨んでいたようです。

チームがスタッツに注目する一方で、メディアやファンもスタッツに注目をしています。
例えば「ステフィン・カリーが3P成功数でレイ・アレンの2973本を越して歴代1位に」(キャリア総合ランキング)や「ルカ・ドンチッチが73得点をとり1試合の得点数で歴代4位に」(1試合における最高得点のランキング)などは王道スタッツとして、これらが更新されると大いに盛り上がります。
www.youtube.com
www.youtube.com

その一方で、王道スタッツほど単純なスタッツではないですが、選手やチームが「なにかスゴイ活躍をした」際に「どうすごいのか?」を、複数のスタッツや期間などを組合せて説明するニッチなスタッツもNBAを楽しむ要素となります。
例えば、

  • クリスマスの日に40得点以上、15リバウンド以上、5回以上の3Pを決めたのはウェンバンヤマのみ

  • 1シーズンに60得点を3回以上達成した選手は歴代、リラードとチェンバレンのみ

  • ヨキッチとマレーは共に30得点のトリプルダブル(得点・アシスト・リバウンドが2桁)をした史上初のチームメイト

  • CavsはWarriors以来初めて開幕から無敗で10連勝した


などが挙げられます。

分析の動機

王道スタッツであれば、スタッツサイトで簡単に確認できます。
しかし、ニッチなスタッツの場合、複数の条件を組み合わせたり、グルーピングが必要になるため、スタッツサイトでは確認が困難です。
例えば、「1シーズンで60得点を3回以上達成した選手は3人だが、50得点の場合はどれくらいいるのか?」や「同じチームでトリプルダブルを3人が達成した試合はあるのだろうか?」といった疑問は、発表されたスタッツだけでは答えが得られません。
そこで、自分自身でスタッツ収集基盤を構築し、いつでも計算できる環境を整えました。

こうしたニッチなスタッツを計算するには、複雑なSQLが必要です。
今回は効率化を図るために、生成AIを活用してSQLを作成することにしました。
そのためには、まず対象となるテーブルの情報を生成AIに提供する必要があります。

スタッツの計算

ChatGPTへの事前入力内容 前提

今回、検証するのはleaguegamelog_playerleaguegamelog_teamというテーブルです。
このテーブルには、選手単位のスタッツ、チーム単位のスタッツが1960年から現在にかけて全試合分入っています。

ChatGPTでクエリを書いてもらう上で、まずテーブルについての事前情報を説明します。
説明内容は2つで

  • メタデータ
  • データ

になります。

メタデータ

  • カラム名
  • カラムの型
  • NULL許容
  • カラムの説明

などを言います。
データとは、実際にテーブルに入っているレコードを言います。すべてのレコードを入力するわけには行かないので、入力するのは数行で問題ないと思っています。

本来、メタデータの情報が充実していれば、事前知識として与えるのはメタデータのみで十分だと考えられています。
現にGemini in BigQueryなどのクエリジェネレータのための生成AIはメタデータのみしか参照しないようです。

ただし、メタデータの情報だけでは、AIに事前知識を十分に与えられない場合があります。
それは、実際に入る値がどのようなものか?という情報についてです。
例えば、「SEASON_ID」というカラムには「22022」や「42022」などの値が入っています。先頭の2はレギュラーシーズンを、4はプレーオフを表し、2022は2022-23シーズンを表しています。(NBAのシーズンは10月から始まり年を跨ぎます。)
このような値に対する事前知識がないと、「2022-23シーズンのレコードを抽出するクエリを作って」と指示しても、「WHERE SEASON_ID=”2022-23”」などと適当に予測した値の条件句を提案してくる場合があります。

そんなときは、データそのものを事前知識として入力してあげると、実際のデータの様子を伝えることが可能となります。
ただし、データそのものの入力はセキュリティやプライバシーの問題、トークンを必要以上に消費してしまう等の問題を抱えているため、業務において実施するのが難しい場合があります。
今回は、趣味の延長線上のため、メタデータに加えてデータ例も事前知識として入力し、生成AIでクエリを作成しました。

ChatGPTへの事前入力内容

まず、メタデータの知識を共有します。
まず事前知識として提供します。

現在、BigQueryにleaguegamelog_player、 leaguegamelog_teamの2つのテーブルがあります。
カラムのメタデータについて情報提供します。

```python
from google.cloud import bigquery

client = bigquery.Client()
table_id = "sample.nba.leaguegamelog_player"
table = client.get_table(table_id)
for schema_field in table.schema:
    print(f"{schema_field.name}\t{schema_field.field_type}\t{schema_field.mode}\t{schema_field.description}")
```

上記のPythonスクリプトを叩くと、下記の結果が出ます。


```
AST	INTEGER	NULLABLE	選手の合計アシスト数(例: 23)
BLK	INTEGER	NULLABLE	選手の合計ブロックショット数(例: 4)
DREB	INTEGER	NULLABLE	選手の合計ディフェンシブリバウンド数(例: 31)
FANTASY_PTS	FLOAT	NULLABLE	ファンタジーポイント - 各種統計を weighted sum した値(例: 245.5)
FG3A	INTEGER	NULLABLE	選手の3ポイントショット試投数(例: 29)
FG3M	INTEGER	NULLABLE	選手の3ポイントショット成功数(例: 10)
FG3_PCT	FLOAT	NULLABLE	選手の3ポイントショット成功率 - FG3M/FG3A(例: 0.345)
FGA	INTEGER	NULLABLE	選手のフィールドゴール試投数(例: 90)
FGM	INTEGER	NULLABLE	選手のフィールドゴール成功数(例: 41)
FG_PCT	FLOAT	NULLABLE	選手のフィールドゴール成功率 - FGM/FGA(例: 0.456)
FTA	INTEGER	NULLABLE	選手のフリースロー試投数(例: 20)
FTM	INTEGER	NULLABLE	選手のフリースロー成功数(例: 15)
FT_PCT	FLOAT	NULLABLE	選手のフリースロー成功率 - FTM/FTA(例: 0.75)
GAME_DATE	DATE	REQUIRED	試合日 YYYY-MM-DD形式(例: 2023-10-24)
GAME_ID	STRING	REQUIRED	試合を一意に識別するID(例: 0022300061)
MATCHUP	STRING	NULLABLE	対戦カード(例: LAL vs. DEN)
MIN	INTEGER	NULLABLE	選手の総プレイ時間(例: 240分)
OREB	INTEGER	NULLABLE	選手の合計オフェンシブリバウンド数(例: 13)
PF	INTEGER	NULLABLE	選手の合計ファウル数(例: 18)
PLAYER_ID	INTEGER	NULLABLE	選手を一意に識別するID(例: 2544)
PLAYER_NAME	STRING	NULLABLE	選手名(例: LeBron James)
PLUS_MINUS	INTEGER	NULLABLE	プラスマイナス(得点差)(例: -12)
PTS	INTEGER	NULLABLE	選手の合計得点(例: 107)
REB	INTEGER	NULLABLE	選手の合計リバウンド数 - OREB + DREB(例: 44)
SEASON_ID	STRING	NULLABLE	シーズンを示すID、先頭の2は2000年代を表す(例: 22023は2023-24シーズン)
STL	INTEGER	NULLABLE	選手の合計スティール数(例: 5)
TEAM_ABBREVIATION	STRING	NULLABLE	選手の略称(例: LAL)
TEAM_ID	INTEGER	NULLABLE	選手を一意に識別するID(例: 1610612747)
TEAM_NAME	STRING	NULLABLE	選手の正式名称(例: Los Angeles Lakers)
TOV	INTEGER	NULLABLE	選手の合計ターンオーバー数(例: 12)
VIDEO_AVAILABLE	BOOLEAN	NULLABLE	ビデオハイライトが利用可能かどうか(例: true)
WL	STRING	NULLABLE	試合の勝敗(例: L [W:勝利、L:敗北])
_SEASON_TYPE	STRING	REQUIRED	シーズンタイプ(例: Regular Season)
```

同様にsample.nba.leaguegamelog_teamテーブルに対して同じスクリプトを叩くと下記の結果になります。
```
AST	INTEGER	NULLABLE	チームが記録したアシスト数
BLK	INTEGER	NULLABLE	チームが記録したブロックショット数
DREB	INTEGER	NULLABLE	チームが記録したディフェンスリバウンド数
FG3A	INTEGER	NULLABLE	チームが試投した3ポイントシュートの本数
FG3M	INTEGER	NULLABLE	チームが成功させた3ポイントシュートの本数
FG3_PCT	FLOAT	NULLABLE	チームの3ポイントシュート成功率(%)
FGA	INTEGER	NULLABLE	チームが試投したフィールドゴールの本数
FGM	INTEGER	NULLABLE	ゲーム中に選手が成功させたフィールドゴールの本数
FG_PCT	FLOAT	NULLABLE	チームのフィールドゴール成功率(%)
FTA	INTEGER	NULLABLE	チームが試投したフリースローの本数
FTM	INTEGER	NULLABLE	チームが成功させたフリースローの本数
FT_PCT	FLOAT	NULLABLE	チームのフリースロー成功率(%)
GAME_DATE	DATE	REQUIRED	ゲームの日付
GAME_ID	STRING	REQUIRED	ゲームを識別する一意のID
MATCHUP	STRING	NULLABLE	試合の対戦カード(例:'LAL vs BOS')
MIN	INTEGER	NULLABLE	チームがプレーした分数
OREB	INTEGER	NULLABLE	チームが記録したオフェンスリバウンド数
PF	INTEGER	NULLABLE	チームが犯した個人ファウル数
PLUS_MINUS	INTEGER	NULLABLE	チームの得失点差
PTS	INTEGER	NULLABLE	チームが記録した得点
REB	INTEGER	NULLABLE	チームが記録したリバウンド総数
SEASON_ID	STRING	NULLABLE	シーズンを識別するID(例:'22023')
STL	INTEGER	NULLABLE	ゲーム中に選手が記録したスティール数
TEAM_ABBREVIATION	STRING	NULLABLE	チームの略称(例:'LAL')
TEAM_ID	INTEGER	NULLABLE	チームを識別する一意のID
TEAM_NAME	STRING	NULLABLE	チーム名(例:'Los Angeles Lakers')
TOV	INTEGER	NULLABLE	チームが記録したターンオーバー数
VIDEO_AVAILABLE	BOOLEAN	NULLABLE	ゲーム映像が利用可能かどうか(true/false)
WL	STRING	NULLABLE	ゲーム結果(勝利:'W'、敗北:'L')
_SEASON_TYPE	STRING	REQUIRED	シーズンの種類(例:'Regular Season', 'Playoffs')
```


次にデータを知識として入力します。
ここでは数行与えればよいため、なるべくスキャン量は減らしたいです。
データ量を絞るにはLIMIT句を使えば良さそうに見えます。
ただし、LIMIT句は出力レコード数を抑えているつもりでもフルスキャンをしてしまい破産してしまう恐れがあるため、SAMPLINGによるレコード数制限が鍵となります。
BQストレージはレコードが1GB単位でブロックとしてまとめられており、SAMPLINGはブロック全体に対してN%のブロックを選択する仕組みになっています。
特定のブロックのみをスキャンするため、フルスキャンの心配はありません。(全レコード数が1ブロック以下であればフルスキャンとなりますが、その場合はスキャン量が小さいため問題となりません。)

次にデータ例を提供します。 
```sql
SELECT * FROM nba.leaguegamelog_player TABLESAMPLE SYSTEM (0.01 PERCENT) WHERE rand() < 0.1 LIMIT 10
```
上記のBigQueryのSQLを叩くと下記の結果が出ます。
```
AST	BLK	DREB	FANTASY_PTS	FG3A	FG3M	FG3_PCT	FGA	FGM	FG_PCT	FTA	FTM	FT_PCT	GAME_DATE	GAME_ID	MATCHUP	MIN	OREB	PF	PLAYER_ID	PLAYER_NAME	PLUS_MINUS	PTS	REB	SEASON_ID	STL	TEAM_ABBREVIATION	TEAM_ID	TEAM_NAME	TOV	VIDEO_AVAILABLE	WL	_SEASON_TYPE
0	0	4	21.8	0	0		10	4	0.4	0	0		2013-03-04	0021200894	CHA @ POR	26	5	1	202687	Bismack Biyombo	-9	8	9	22012	1	CHA	1610612766	Charlotte Bobcats	0	true	L	Regular Season
0	0	0	0.0	0	0		0	0		0	0		2013-03-04	0021200892	MIA @ MIN	2	0	0	2034	Mike Miller	0	0	0	22012	0	MIA	1610612748	Miami Heat	0	false	W	Regular Season
5	0	0	8.5	1	0	0.0	4	1	0.25	0	0		2013-03-04	0021200891	ORL @ NOH	12	0	1	2757	Beno Udrih	5	2	0	22012	0	ORL	1610612753	Orlando Magic	1	true	W	Regular Season
0	0	0	0.0	1	0	0.0	4	0	0.0	0	0		2013-03-04	0021200895	TOR @ GSW	8	0	1	203082	Terrence Ross	-7	0	0	22012	0	TOR	1610612761	Toronto Raptors	0	true	L	Regular Season
1	0	1	2.7	0	0		1	0	0.0	0	0		2013-03-04	0021200895	TOR @ GSW	7	0	1	202361	Landry Fields	-13	0	1	22012	0	TOR	1610612761	Toronto Raptors	0	true	L	Regular Season
1	0	4	12.5	2	1	0.5	6	3	0.5	0	0		2013-03-04	0021200893	DEN vs. ATL	27	1	0	201568	Danilo Gallinari	9	7	5	22012	0	DEN	1610612743	Denver Nuggets	2	true	W	Regular Season
0	0	9	25.6	0	0		11	5	0.455	0	0		2013-03-04	0021200893	DEN vs. ATL	26	4	5	202702	Kenneth Faried	3	10	13	22012	1	DEN	1610612743	Denver Nuggets	3	true	W	Regular Season
0	0	1	2.2	0	0		3	1	0.333	0	0		2013-03-04	0021200892	MIN vs. MIA	6	0	2	202419	Chris Johnson	-1	2	1	22012	0	MIN	1610612750	Minnesota Timberwolves	1	false	L	Regular Season
1	0	1	2.7	0	0		1	0	0.0	0	0		2013-03-04	0021200892	MIN vs. MIA	9	0	2	201880	Greg Stiemsma	-6	0	1	22012	0	MIN	1610612750	Minnesota Timberwolves	0	false	L	Regular Season
1	0	2	5.1	0	0		0	0		0	0		2013-03-04	0021200891	NOH vs. ORL	7	1	0	202498	Lance Thomas	-2	0	3	22012	0	NOH	1610612740	New Orleans Hornets	0	true	L	Regular Season
```

同じようにチーム単位のスタッツのデータは次のクエリで確認できます。
```sql
SELECT * FROM nba.leaguegamelog_team TABLESAMPLE SYSTEM (1 PERCENT) WHERE rand() < 0.1 LIMIT 10
```

```
AST	BLK	DREB	FG3A	FG3M	FG3_PCT	FGA	FGM	FG_PCT	FTA	FTM	FT_PCT	GAME_DATE	GAME_ID	MATCHUP	MIN	OREB	PF	PLUS_MINUS	PTS	REB	SEASON_ID	STL	TEAM_ABBREVIATION	TEAM_ID	TEAM_NAME	TOV	VIDEO_AVAILABLE	WL	_SEASON_TYPE
19	2	31	27	9	0.333	72	31	0.431	27	24	0.889	2014-11-17	0021400149	MIA @ BKN	240	10	24	12	95	41	22014	6	MIA	1610612748	Miami Heat	15	true	W	Regular Season
27	8	46	26	10	0.385	81	36	0.444	21	18	0.857	2014-11-17	0021400152	SAS vs. PHI	240	6	17	25	100	52	22014	9	SAS	1610612759	San Antonio Spurs	18	true	W	Regular Season
22	4	33	21	7	0.333	81	42	0.519	18	11	0.611	2017-04-05	0021601166	DET vs. TOR	240	12	21	-3	102	45	22016	5	DET	1610612765	Detroit Pistons	14	true	L	Regular Season
24	3	33	31	11	0.355	86	38	0.442	18	14	0.778	2017-04-05	0021601172	DAL @ LAC	240	11	26	-11	101	44	22016	5	DAL	1610612742	Dallas Mavericks	16	true	L	Regular Season
22	0	35	31	12	0.387	75	38	0.507	31	24	0.774	2017-04-05	0021601172	LAC vs. DAL	240	5	19	11	112	40	22016	11	LAC	1610612746	LA Clippers	15	true	W	Regular Season
22	5	28	24	7	0.292	85	38	0.447	23	17	0.739	2015-11-02	0021500046	PHI vs. CLE	240	9	18	-7	100	37	22015	7	PHI	1610612755	Philadelphia 76ers	14	true	L	Regular Season
32	13	55	25	11	0.44	84	43	0.512	30	22	0.733	2015-11-02	0021500051	GSW vs. MEM	240	10	15	50	119	65	22015	8	GSW	1610612744	Golden State Warriors	14	true	W	Regular Season
16	1	30	17	4	0.235	70	25	0.357	19	10	0.526	2007-03-03	0020600886	IND @ LAC	240	7	20	-23	64	37	22006	4	IND	1610612754	Indiana Pacers	15	false	L	Regular Season
14	4	23	10	4	0.4	74	38	0.514	25	18	0.72	2007-03-03	0020600884	ORL @ DAL	240	10	22	-5	98	33	22006	4	ORL	1610612753	Orlando Magic	12	false	L	Regular Season
24	6	36	4	2	0.5	70	31	0.443	32	23	0.719	2007-03-03	0020600886	LAC vs. IND	240	11	26	23	87	47	22006	13	LAC	1610612746	Los Angeles Clippers	12	false	W	Regular Season
```

スタッツの計算1

クリスマスの日に40得点以上、15リバウンド以上、5回以上の3Pを決めたのはウェンバンヤマのみ

まず、簡単なクエリ生成から始めました。

クリスマスに40得点以上、15リバウンド以上、5回以上の3Pを決めた選手一覧を抽出するクエリを作ってください。

単純なWHERE程度のクエリは問題なく作れるようです。
「クリスマス」という言葉は、12/24と12/25で曖昧ですが、NBAでは歴史的に12/24は試合がなく、12/25にビッグなマッチアップが組まれ、これをクリスマスゲームと言います。
この知識がAI側にあったためWHERE句の指定を12/25と指定できたのかと思います。

SELECT
   GAME_DATE,
   MATCHUP,
   PLAYER_NAME,
   PTS AS POINTS,
   REB AS REBOUNDS,
   FG3M AS THREE_POINTERS_MADE
FROM
   `nba.leaguegamelog_player`
WHERE
   EXTRACT(MONTH FROM GAME_DATE) = 12
   AND EXTRACT(DAY FROM GAME_DATE) = 25
   AND PTS >= 40
   AND REB >= 15
   AND FG3M >= 5
ORDER BY
   GAME_DATE DESC;

結果は次のようになりました。

GAME_DATE MATCHUP PLAYER_NAME POINTS REBOUNDS THREE_POINTERS_MADE
2024-12-25 SAS @ NYK Victor Wembanyama 42 18 6

スタッツの計算2

1シーズンに60得点を3回以上成した選手は歴代、リラードとチェンバレンのみ

上記のスタッツを求めてもらうクエリを教えてもらいました。
事前知識を与えた上で、次の質問をしたら1回で正解のクエリが返ってきました。

60得点以上を1シーズンで3回以上達成した選手を抽出してください。 出力は、選手名、シーズンID、そのシーズンに60得点以上を何回達成したかの3カラムにしてください。

多少厳密さは欠くものの上出来なクエリを提案してもらえました。
PLAYER_NAMEでなく、PLAYER_IDでグルーピングして欲して欲しかったです。
AIが提案するクエリは、このように結果的に正しいものであっても、一般化できない場合があるため現時点ではやはり参考程度にすべきだと思います。

SELECT
   PLAYER_NAME,
   SEASON_ID,
   COUNT(*) AS games_with_60_plus_points
FROM
   `nba.leaguegamelog_player`
WHERE
   PTS >= 60
GROUP BY
   PLAYER_NAME, SEASON_ID
HAVING
   COUNT(*) >= 3
ORDER BY
   games_with_60_plus_points DESC;

クエリの結果は下記のようになりました。
Xのポストだと「60得点以上を1シーズンに3回以上取った選手は史上2人しかいない」ということしか分かりませんでしたが、そのうちの1人は2シーズンも達成していることや、3回を大きく超え15回、9回と達成しているというニュースでは分からない追加の情報を得ることもできました。

PLAYER_NAME SEASON_ID games_with_60_plus_points
Wilt Chamberlain 21961 15
Wilt Chamberlain 21962 9
Damian Lillard 22019 3

スタッツの計算3

ヨキッチとマレーは共に30得点とトリプルダブル(得点・アシスト・リバウンドが2桁)をした史上初のチームメイト

次に少し難しいスタッツを試しました。
1人単位のスタッツではなく、同チームで2人以上という単位のスタッツを求めました。

30得点以上のトリプルダブルを1試合で、同チームの2人が達成した試合を抽出してください。

生成されたクエリは次のようになりました。
TEAM_IDでグルーピングされていないため、一見クエリに「同一チーム」という制約が入っていないように思えましたが、MATCHUPは team Aとteam Bの試合で、teamAがHOMEの場合はteamAのレコードには「teamA vs. teamB」teamBのレコードには「teamB @ teamA」、team AがAWAYの場合はteamAのレコードには「teamA @ teamB」teamBのレコードには「teamB vs. teamA」となるため、MATHUPでチーム単位のグルーピングが出来ていることがクエリから学ぶことができました。
この制約はメタデータでは説明しきれていなかったため、データを渡したことによるメリットと捉えることができます。

SELECT
   GAME_DATE,
   MATCHUP,
   ARRAY_AGG(STRUCT(PLAYER_NAME AS name, PTS AS points, AST AS assists, REB AS rebounds)) AS players_stats
FROM
   nba.leaguegamelog_player
WHERE
   PTS >= 30
   AND AST >= 10
   AND REB >= 10
GROUP BY
   GAME_DATE, MATCHUP
HAVING
   COUNT(PLAYER_NAME) >= 2

クエリの結果は下記となります。
3列目はBQの関数でJSON型にまとめてくれたため、見やすくなっています。
クエリを生成してもらうことのメリットの一つはこのように、自身の想定以上の出力を見ることにより学びを得ることができることにあります。

GAME_DATE MATCHUP players_stats
2023-06-07 DEN @ MIA {players_stats: [{name: Nikola Jokić,points: 32,assists: 10,rebounds: 21}, {name: Jamal Murray,points: 34,assists: 10,rebounds: 10}]}

スタッツの計算4

Cavsは1シーズン73勝したWarriors以来初めて開幕から無敗で10連勝した

次に選手単位ではなくチーム単位のスタッツについてクエリを考えてもらいました。

leaguegamelog_teamテーブルから、(シーズン、チーム)ごとに開幕から一敗もせずに連勝した数を算出するクエリを作って下さい。

下記のクエリが提案されました。
Window関数を含むクエリの提案も問題なくできるようです。
「無敗のままの連勝数」をどう捉えるかが提案クエリを読むことで理解することができました。
複雑だったので、コメントを私が入れています。

-- (シーズン, チーム)ごとに試合日に順番を付ける
WITH GameResults AS (
 SELECT
   SEASON_ID,
   TEAM_ID,
   TEAM_NAME,
   GAME_DATE,
   WL,
   ROW_NUMBER() OVER (PARTITION BY SEASON_ID, TEAM_ID ORDER BY GAME_DATE) AS game_order
 FROM
   `nba.leaguegamelog_team`
 WHERE
   _SEASON_TYPE = "Regular Season"
),
-- 負けの数を計算
-- (シーズン, チーム)で一番最初の行から和を計算していく
-- 1回でも以前に負けたことがあると, その行は1以上になる
WinStreaks AS (
 SELECT
   SEASON_ID,
   TEAM_ID,
   TEAM_NAME,
   GAME_DATE,
   game_order,
   WL,
   CASE WHEN WL = 'L' THEN 0 ELSE 1 END AS win_flag,
   SUM(CASE WHEN WL = 'L' THEN 1 ELSE 0 END) OVER (
     PARTITION BY SEASON_ID, TEAM_ID ORDER BY game_order ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
   ) AS loss_count
 FROM
   GameResults
),
-- それまでの試合で負けが無い試合をフィルタ
FilteredStreaks AS (
 SELECT
   SEASON_ID,
   TEAM_ID,
   TEAM_NAME,
   game_order,
   win_flag,
   loss_count
 FROM
   WinStreaks
 WHERE
   loss_count = 0
),
-- それまでに1敗も無い試合数の行数をカウントすると, シーズン開幕からの連勝数と解釈できる
FinalResult AS (
 SELECT
   SEASON_ID,
   TEAM_ID,
   TEAM_NAME,
   COUNT(*) AS opening_win_streak
 FROM
   FilteredStreaks
 GROUP BY
   SEASON_ID, TEAM_ID, TEAM_NAME
)
SELECT
 SEASON_ID,
 TEAM_ID,
 TEAM_NAME,
 opening_win_streak
FROM
 FinalResult
ORDER BY
 opening_win_streak DESC,
 SEASON_ID
LIMIT 10;

結果は下記のようになりました。
今シーズン話題になった、Cleveland Cavaliersの開幕から15連勝は歴代で2位タイの記録のようです。
1位は2015-16シーズンのGolden State Warriorsでした。
この年のWarriorsは73勝9敗という驚異的な強さを誇っていたので、開幕24戦無敗というのも頷けます。
また、1996-97シーズンのChicago BullsはMichael Jordanのピーク時のものなので、それよりも今シーズンのCavaliersが高いということが分かり、歴史における今回の記録がどれだけ偉大であるかということが分かりました。

SEASON_ID TEAM_ID TEAM_NAME opening_win_streak
22015 1610612744 Golden State Warriors 24
21948 1610610036 Washington Capitols 15
21993 1610612745 Houston Rockets 15
22024 1610612739 Cleveland Cavaliers 15
21957 1610612738 Boston Celtics 14
22002 1610612742 Dallas Mavericks 14
21982 1610612760 Seattle SuperSonics 12
21996 1610612741 Chicago Bulls 12
21964 1610612738 Boston Celtics 11
21990 1610612757 Portland Trail Blazers 11