Skip to content

13. SQL の実践(集計と可視化)

第 13 回では、前回学んだ SQL の基礎をもとに、データ分析でよく使う「集計(集約)」と「グループ化」、そしてその集計結果の可視化について学びます。

13.1 サンプルデータの準備

本章では、第 12 回に登場したおでん屋さんの、ある日の売上(値上げ後の価格)を記録した sales テーブルを例に進めます。1 行が 1 つの注文に対応しており、この日に「はんぺん」は 3 つ売れて完売しました。まずは前回学んだ CREATE TABLE と INSERT(Cursor.executemany())で、第 12 回と同じ oden.db にサンプルデータを用意しましょう。

import sqlite3

# データベースに接続
conn = sqlite3.connect("oden.db")
cursor = conn.cursor()

# 何度実行しても同じ状態になるよう、いったん削除してから作成
cursor.execute("DROP TABLE IF EXISTS sales")
cursor.execute("""
    CREATE TABLE sales (
        id INTEGER PRIMARY KEY,
        item TEXT,
        category TEXT,
        price INTEGER
    )
""")

# 売上データ(1 行 = 1 つの注文)
data = [
    ("ちくわ", "練り物", 130),
    ("ちくわ", "練り物", 130),
    ("はんぺん", "練り物", 120),
    ("はんぺん", "練り物", 120),
    ("はんぺん", "練り物", 120),
    ("つみれ", "練り物", 140),
    ("かまぼこ", "練り物", 150),
    ("かまぼこ", "練り物", 150),
    ("だいこん", "野菜", 100),
    ("だいこん", "野菜", 100),
    ("だいこん", "野菜", 100),
    ("だいこん", "野菜", 100),
    ("たまご", "たまご", 90),
    ("たまご", "たまご", 90),
    ("たまご", "たまご", 90),
]
cursor.executemany("INSERT INTO sales (item, category, price) VALUES (?, ?, ?)", data)
conn.commit()

これで、15 件の売上データを持つ sales テーブルが用意できました。以降のクエリは、この cursor を使って実行していきます。

13.2 集約関数

集約関数 (aggregate function) は、複数の行の値をまとめて 1 つの値にする関数です。代表的なものは以下のとおりです。

関数 説明
COUNT() 行数
SUM() 合計
AVG() 平均
MAX() 最大値
MIN() 最小値

例えば、売上全体の「件数」「売上合計」「平均価格」「最高価格」「最低価格」をまとめて求めるには、以下のように書きます。

SELECT COUNT(*), SUM(price), AVG(price), MAX(price), MIN(price) FROM sales

Python では、これまでと同様に Cursor.execute() で実行し、結果を取得します。1 行だけの結果なので Cursor.fetchone() を使います。

cursor.execute("SELECT COUNT(*), SUM(price), AVG(price), MAX(price), MIN(price) FROM sales")
print(cursor.fetchone())
Output
(15, 1730, 115.33333333333333, 150, 90)

全部で 15 件、売上合計 1730 円、平均価格 約 115.3 円、最高 150 円、最低 90 円であることがわかります。

別名 (AS) と丸め (ROUND)

集約した列には AS で別名を付けると、結果が読みやすくなります。また、平均などで桁数が多くなる場合は ROUND(値, 桁数) で丸めることができます。

SELECT ROUND(AVG(price), 1) AS average FROM sales

13.3 グループ化(GROUP BY)

GROUP BY を使うと、ある列の値ごとにグループに分けて集約することができます。例えば、item(品目)ごとに「販売数」を求めるには、以下のように書きます。

SELECT item, COUNT(*) FROM sales GROUP BY item
cursor.execute("SELECT item, COUNT(*) FROM sales GROUP BY item")
for row in cursor.fetchall():
    print(row)
Output
('かまぼこ', 2)
('たまご', 3)
('だいこん', 4)
('ちくわ', 2)
('つみれ', 1)
('はんぺん', 3)

品目ごとに、販売数が集計されました。

集約結果の並べ替え(ORDER BY)

集約した結果も、ORDER BY で並べ替えることができます。品目ごとの売上合計(別名 total)の多い順に並べるには、以下のようにします。

SELECT item, SUM(price) AS total FROM sales GROUP BY item ORDER BY total DESC
Output
('だいこん', 400)
('はんぺん', 360)
('かまぼこ', 300)
('たまご', 270)
('ちくわ', 260)
('つみれ', 140)

この日は「だいこん」が最も売上が多かったことがわかります。さらに LIMIT を付ければ、上位だけを取り出すこともできます(例:... ORDER BY total DESC LIMIT 1 で 1 位のみ)。

集約結果への条件(HAVING)

WHERE は「集約する前の各行」に対する条件でしたが、「集約した後の結果」に対して条件を付けたい場合は HAVING を使います。例えば、3 つ以上売れた品目だけを取り出すには、以下のようにします。

SELECT item, COUNT(*) AS cnt FROM sales GROUP BY item HAVING cnt >= 3
Output
('たまご', 3)
('だいこん', 4)
('はんぺん', 3)

WHERE と HAVING の違い

  • WHERE:グループ化の 前 に、個々の行を絞り込む(例:WHERE price >= 100)
  • HAVING:グループ化の 後 に、集約結果を絞り込む(例:HAVING COUNT(*) >= 3)

両方を組み合わせて、「WHERE で行を絞り → GROUP BY で集約 → HAVING で結果を絞る」という流れで使うこともできます。

13.4 集計結果の可視化

集計(GROUP BY)の結果は、これまで学んできた Matplotlib で可視化することができます。Cursor.fetchall() で取得した結果から、品目のリストと売上合計のリストを作り、棒グラフにしてみましょう。

import sqlite3
import matplotlib.pyplot as plt

# データベースに接続
conn = sqlite3.connect("oden.db")
cursor = conn.cursor()

# 品目別の売上合計を取得
cursor.execute("""
    SELECT item, SUM(price) AS total
    FROM sales
    GROUP BY item
    ORDER BY total DESC
""")
rows = cursor.fetchall()

# 取得結果を「品目」と「売上合計」のリストに分ける
items = [row[0] for row in rows]
totals = [row[1] for row in rows]

# 棒グラフで可視化
fig, ax = plt.subplots(figsize=(5, 3))
ax.bar(items, totals)
ax.set_ylabel("売上合計(円)")
ax.set_title("品目別の売上合計")
plt.tight_layout()
plt.show()

conn.close()

品目別の売上合計

SQL で集計した結果を、グラフとして直感的に把握できるようになりました。このように、SQL でデータを集計し、Python で可視化するという流れは、実際のデータ分析でも頻繁に用いられます。

pandas との連携

pandas を使うと、pandas.read_sql_query() 関数で SQL の実行結果をそのまま DataFrame として取得することができ、これまで学んだ pandas の集計・可視化の機能を活用できます。

import pandas as pd

df = pd.read_sql_query("SELECT category, SUM(price) AS total FROM sales GROUP BY category", conn)

演習

以下の演習では、「13.1 サンプルデータの準備」で作成した sales テーブル(oden.db)を使用します。先にサンプルデータを作成してから取り組んでください。

演習 13-1(初級):集約関数と絞り込み

おでん屋さんの売上データベース(sales テーブル)から、100 円以上の具材に限定して売れ行きを調べます。価格が 100 円以上の売上データの「件数(COUNT)」と「平均価格(AVG)」を求めて表示してください。

  1. WHERE 句(プレースホルダ ? を使用)で価格が 100 円以上の行に絞り込む
  2. 件数と平均価格を集約関数で求めて表示する
テンプレートコード
import sqlite3

conn = sqlite3.connect("oden.db")
cursor = conn.cursor()

# 1〜2. 100 円以上の売上の件数と平均価格を求めて表示する

conn.close()

演習 13-2(中級):カテゴリ別の売上ランキング

おでん屋さんの売上データベース(sales テーブル)から、カテゴリ(練り物・野菜・たまご)単位での売れ行きを調べます。GROUP BY を使ってカテゴリ別の「件数(COUNT)」と「売上合計(SUM)」を求め、売上合計の高い順に並べて表示してください。

  1. GROUP BY でカテゴリごとの件数と売上合計を求める
  2. 売上合計に AS で別名を付け、高い順(降順)に並べ替える
テンプレートコード
import sqlite3

conn = sqlite3.connect("oden.db")
cursor = conn.cursor()

# カテゴリ別の件数と売上合計を、売上合計の高い順に並べて取得・表示する

conn.close()

演習 13-3(中級):カテゴリ別件数の可視化

おでんのカテゴリごとの売れ行きを、ひと目で把握できるようにします。おでん屋さんの売上データベース(sales テーブル)から、カテゴリ別の 件数 を GROUP BY で集計し、その結果を棒グラフで可視化してください。

  1. カテゴリ別の件数を集計する
  2. 取得結果をカテゴリのリストと件数のリストに分ける
  3. 棒グラフで可視化する
テンプレートコード
import sqlite3
import matplotlib.pyplot as plt

conn = sqlite3.connect("oden.db")
cursor = conn.cursor()

# 1. カテゴリ別の件数を集計して取得する

# 2. カテゴリのリストと件数のリストに分ける

# 3. 棒グラフで可視化する
fig, ax = plt.subplots(figsize=(5, 3))

plt.tight_layout()
plt.show()

conn.close()