売上の前年比較、去年の同じ月のデータ抽出、年次レポート作成――SQLで前年のデータを取得する処理は実務で頻繁に登場します。「昨年の売上と今年を比べたい」「前年同月比を出したい」という場面も多いですが、RDBMS(データベース)ごとに日付関数の書き方が異なるため、環境が変わると戸惑うことも多いのではないでしょうか。
この記事では、MySQL・PostgreSQL・SQL Server・Oracleの4つのRDBMSに対応した前年データ取得の構文を体系的に整理し、さらに前年同月比やLAG関数を使った実務パターンまで解説します。
この記事で分かること
- 前年のデータを取得する基本構文(MySQL・PostgreSQL・SQL Server・Oracle)
- YEAR関数・DATE_SUB・EXTRACTの使い分け
- 前年同月・前年同期など実務でよく使うパターン
- LAG関数を使った前年比(前年同月比)の算出
- 条件付き集計(CASE式)で今年と前年を横並びにする方法
- うるう年・年度ずれなどの注意点とエッジケース
サンプルテーブル
本記事では、以下の売上テーブルを使ってSQL文を解説します。
【SQL基本】前年1年分のデータを取得する
最もよく使うパターンは「前年のすべてのデータ」を取得する方法です。RDBMS別に構文を紹介します。
方法1:YEAR関数で年を比較する(MySQL)
MySQL — YEAR関数で前年のデータを取得
SELECT * FROM sales WHERE YEAR(sale_date) = YEAR(CURDATE()) - 1;
YEAR()関数は日付から年だけを整数で取り出します。CURDATE()は今日の日付を返すので、YEAR(CURDATE()) - 1で前年の年(例:2024)になります。
▼ 実行結果(2025年に実行した場合)
方法2:EXTRACT関数を使う(PostgreSQL / MySQL / Oracle)
PostgreSQL / MySQL / Oracle — EXTRACT関数
SELECT * FROM sales WHERE EXTRACT(YEAR FROM sale_date) = EXTRACT(YEAR FROM CURRENT_DATE) - 1;
EXTRACT(YEAR FROM ...)はSQL標準の構文で、PostgreSQL・MySQL・Oracleで使えます。可搬性の高い書き方です。
方法3:DATEPART関数を使う(SQL Server)
SQL Server — DATEPART / YEAR関数
-- 方法A:YEAR関数 SELECT * FROM sales WHERE YEAR(sale_date) = YEAR(GETDATE()) - 1; -- 方法B:DATEPART関数 SELECT * FROM sales WHERE DATEPART(YEAR, sale_date) = DATEPART(YEAR, GETDATE()) - 1;
RDBMS別 構文比較表
SQLで前年の「特定期間」を取得する
「前年1年分」ではなく、前年の特定の月や期間を取得したい場合は、日付の範囲指定と組み合わせます。
前年の同じ月のデータ(前年同月)
MySQL — 前年同月のデータ
SELECT * FROM sales WHERE YEAR(sale_date) = YEAR(CURDATE()) - 1 AND MONTH(sale_date) = MONTH(CURDATE());
PostgreSQL — 前年同月のデータ
SELECT * FROM sales WHERE EXTRACT(YEAR FROM sale_date) = EXTRACT(YEAR FROM CURRENT_DATE) - 1 AND EXTRACT(MONTH FROM sale_date) = EXTRACT(MONTH FROM CURRENT_DATE);
前年の今日から1年前まで(過去1年間)
「前年」ではなく「今日からちょうど1年前までのデータ」を取得したい場合は、DATE_SUB / DATEADD / INTERVALを使います。
RDBMS別 — 過去1年間のデータ取得
-- MySQL SELECT * FROM sales WHERE sale_date >= DATE_SUB(CURDATE(), INTERVAL 1 YEAR); -- PostgreSQL SELECT * FROM sales WHERE sale_date >= CURRENT_DATE - INTERVAL '1 year'; -- SQL Server SELECT * FROM sales WHERE sale_date >= DATEADD(YEAR, -1, GETDATE()); -- Oracle SELECT * FROM sales WHERE sale_date >= ADD_MONTHS(SYSDATE, -12);
「前年」と「過去1年間」の違い
YEAR(sale_date) = YEAR(CURDATE()) - 1 は前年(2024年1月1日〜12月31日)のデータを取得します。
sale_date >= DATE_SUB(CURDATE(), INTERVAL 1 YEAR) は今日から1年前〜今日までのデータを取得します。
レポートの要件に応じて使い分けてください。日付の範囲指定について詳しくは「日付の範囲指定を行う方法」で解説しています。
前年の特定日付範囲(例:前年4月〜9月)
MySQL — 前年の上半期(4月〜9月)のデータ
SELECT * FROM sales WHERE sale_date BETWEEN CONCAT(YEAR(CURDATE()) - 1, '-04-01') AND CONCAT(YEAR(CURDATE()) - 1, '-09-30');
SQLで前年同月比を算出する(CASE式による条件付き集計)
売上レポートでは、今年と前年の売上を横並びで比較したいことがよくあります。CASE式とSUM関数を組み合わせると、1回のクエリで実現できます。
MySQL — 月別の今年・前年売上を横並びで表示
SELECT MONTH(sale_date) AS sale_month, SUM(CASE WHEN YEAR(sale_date) = YEAR(CURDATE()) THEN amount ELSE 0 END) AS this_year, SUM(CASE WHEN YEAR(sale_date) = YEAR(CURDATE()) - 1 THEN amount ELSE 0 END) AS last_year, -- 前年同月比(%) ROUND( SUM(CASE WHEN YEAR(sale_date) = YEAR(CURDATE()) THEN amount ELSE 0 END) / NULLIF(SUM(CASE WHEN YEAR(sale_date) = YEAR(CURDATE()) - 1 THEN amount ELSE 0 END), 0) * 100, 1 ) AS yoy_ratio FROM sales WHERE YEAR(sale_date) IN (YEAR(CURDATE()), YEAR(CURDATE()) - 1) GROUP BY MONTH(sale_date) ORDER BY sale_month;
▼ 実行結果(サンプルデータで実行した場合)
NULLIF でゼロ除算を防ぐ
前年のデータが存在しない月では、前年売上が0になりゼロ除算エラーが発生します。NULLIF(値, 0)を使うと、値が0の場合にNULLを返し、結果もNULLになるためエラーを回避できます。
SQL LAG関数で前年比を算出する(ウィンドウ関数)
月次データが連続している場合、LAG関数(ウィンドウ関数)を使うとシンプルに前年比を算出できます。LAG関数はN行前のデータを参照できるため、月次集計なら12行前 = 前年同月のデータを取得できます。
MySQL 8.0+ — LAG関数で前年同月比(※PostgreSQL/SQL Server/OracleはEXTRACTやDATEPARTに置き換え)
WITH monthly_sales AS ( SELECT YEAR(sale_date) AS sale_year, MONTH(sale_date) AS sale_month, SUM(amount) AS total_amount FROM sales GROUP BY YEAR(sale_date), MONTH(sale_date) ) SELECT sale_year, sale_month, total_amount, -- 12行前(前年同月)の売上を取得 LAG(total_amount, 12) OVER ( ORDER BY sale_year, sale_month ) AS prev_year_amount, -- 前年同月比(%) ROUND( total_amount * 100.0 / NULLIF(LAG(total_amount, 12) OVER ( ORDER BY sale_year, sale_month ), 0), 1 ) AS yoy_ratio FROM monthly_sales ORDER BY sale_year, sale_month;
LAG関数を使う際の注意点
LAG関数は行の物理的な位置で参照するため、月のデータが欠けていると正確な前年同月にならない場合があります。データに歯抜けがある場合は、CASE式による条件付き集計のほうが確実です。
WITH句の使い方は「WITH句を使って副問合せを再利用する方法」で詳しく解説しています。
応用:商品別の前年同月比(PARTITION BY)
実務では、全体の前年比だけでなく商品別・カテゴリ別の前年同月比を算出するケースが非常に多いです。PARTITION BYを加えるだけで実現できます。
MySQL 8.0+ — 商品別の前年同月比をLAG関数で算出
WITH product_monthly AS ( SELECT product_name, YEAR(sale_date) AS sale_year, MONTH(sale_date) AS sale_month, SUM(amount) AS total_amount FROM sales GROUP BY product_name, YEAR(sale_date), MONTH(sale_date) ) SELECT product_name, sale_year, sale_month, total_amount, LAG(total_amount, 12) OVER ( PARTITION BY product_name ORDER BY sale_year, sale_month ) AS prev_year_amount FROM product_monthly ORDER BY product_name, sale_year, sale_month;
PARTITION BY product_nameを指定することで、商品ごとに独立してLAG関数が適用されます。カテゴリ別ならPARTITION BY categoryに変えるだけです。
SQL自己結合(SELF JOIN)で前年データと比較する
テーブルを自分自身と結合し、今年と前年のデータを行単位で対応づける方法もあります。
MySQL — 自己結合で今年と前年の月別売上を比較
SELECT cur.sale_month, cur.total_amount AS this_year, prev.total_amount AS last_year, ROUND(cur.total_amount / NULLIF(prev.total_amount, 0) * 100, 1) AS yoy_ratio FROM ( -- 今年の月別集計 SELECT MONTH(sale_date) AS sale_month, SUM(amount) AS total_amount FROM sales WHERE YEAR(sale_date) = YEAR(CURDATE()) GROUP BY MONTH(sale_date) ) cur LEFT JOIN ( -- 前年の月別集計 SELECT MONTH(sale_date) AS sale_month, SUM(amount) AS total_amount FROM sales WHERE YEAR(sale_date) = YEAR(CURDATE()) - 1 GROUP BY MONTH(sale_date) ) prev ON cur.sale_month = prev.sale_month ORDER BY cur.sale_month;
LEFT JOIN を使うことで、前年にデータがない月も結果に含まれます(last_yearがNULLになる)。INNER JOINにすると前年と今年の両方にデータがある月だけが返ります。テーブルの結合について詳しくは「INNER JOIN、LEFT JOIN、RIGHT JOINの使い方」を参照してください。
SQL前年データ取得 方法の選び方ガイド
SQL前年データ取得の注意点とエッジケース
うるう年と月末日の扱い
DATE_SUB('2024-02-29', INTERVAL 1 YEAR)の結果は2023-02-28になります。2024年はうるう年ですが、2023年は違うため、自動的に月末日に調整されます。
うるう年のテスト
-- MySQL SELECT DATE_SUB('2024-02-29', INTERVAL 1 YEAR); -- 結果: 2023-02-28(自動で月末に調整される) -- 逆パターン SELECT DATE_SUB('2023-02-28', INTERVAL -1 YEAR); -- 結果: 2024-02-28(2/29にはならない)
YEAR関数をWHERE句で使うとインデックスが効かない
WHERE YEAR(sale_date) = 2024のように日付列に関数を適用すると、インデックスが使われずテーブル全体をスキャンするため、大量データで遅くなります。
パフォーマンスを意識した書き方
-- ❌ インデックスが効かない SELECT * FROM sales WHERE YEAR(sale_date) = 2024; -- ✅ インデックスが効く(範囲検索) SELECT * FROM sales WHERE sale_date >= '2024-01-01' AND sale_date < '2025-01-01';
データ量が多い場合は、BETWEEN や不等号による範囲指定を使いましょう。日付の比較について詳しくは「日付の比較方法」で解説しています。
年度(4月始まり)の場合
会計年度が4月始まりの場合、単純な「前年」ではなく前年度のデータを取得する必要があります。
MySQL — 前年度のデータを取得(4月始まり)
-- 年度を算出するヘルパー式 -- 1月〜3月 → 前年が年度 4月〜12月 → 当年が年度 SELECT * FROM sales WHERE -- 年度を計算(月が3以下なら年-1) YEAR(sale_date) - (MONTH(sale_date) <= 3) = YEAR(CURDATE()) - (MONTH(CURDATE()) <= 3) - 1;
MONTH(sale_date) <= 3はMySQLでTRUE = 1、FALSE = 0として扱われるため、1〜3月なら年から1を引いて年度を算出します。
時刻部分の影響
日付列がDATETIME型の場合、2024-12-31 23:59:59のようなデータはYEAR()で正しく2024年と判定されるため問題ありません。ただし、BETWEENで範囲指定する場合は時刻部分に注意が必要です。時間を無視して日付だけで比較する方法は「時間を無視して日付のみで比較する方法」を参照してください。
まとめ
前年データ取得のポイント
YEAR(col) = YEAR(現在) - 1 で前年を取得
DATE_SUB / DATEADD / INTERVAL を使う
SQLで去年(昨年)のデータを取得する方法は、使うRDBMSや取得したいデータのパターンによって最適な方法が変わります。基本をマスターしたら、前年同月比やLAG関数を使った応用パターンにも挑戦してみてください。
関連記事として、前月のデータ取得は「前月のデータを取得する方法」、前日のデータ取得は「前日のデータを取得する方法」、BETWEEN演算子での範囲指定は「between演算子で抽出する範囲を指定する方法」も参考にしてください。

