0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SQLで顧客ごとの直近の製品の購買データの検索を速くする方法(小ネタ)

Posted at

はじめに

データベースに大量の件数がある場合、ある検索では、インデックスやCPU数では大きく速度向上できない場合があります。その場合、いろいろなパフォーマンス・チューニングがあるのですが、今回は、SQLで「顧客ごとの直近の製品の購買データの検索を速くする方法」(小ネタ)をご紹介します。
サンプルのデータは、Oracle Database の SHスキーマのSALES表(約90万件)を使います。

DESC SH.SALES

名前            Nullかどうか タイプ          
------------- -------- ------------ 
PROD_ID       NOT NULL NUMBER       
CUST_ID       NOT NULL NUMBER       
TIME_ID       NOT NULL DATE         
CHANNEL_ID    NOT NULL NUMBER       
PROMO_ID      NOT NULL NUMBER       
QUANTITY_SOLD NOT NULL NUMBER(10,2) 
AMOUNT_SOLD   NOT NULL NUMBER(10,2) 

普通に検索するSQL

このデータから、顧客ごとの直近の製品の購買データを検索したい場合は、顧客ごと製品ごとの最大日時をGROUP BYしたサブクエリと、元の表を結合した次のようなSQLになります。

SELECT
 SALES.CUST_ID,
 SALES.PROD_ID,
 SALES.TIME_ID,
 SALES.CHANNEL_ID,
 SALES.PROMO_ID,
 SALES.QUANTITY_SOLD,
 SALES.AMOUNT_SOLD
FROM
 (SELECT CUST_ID, MAX(TIME_ID) TIME_ID
  FROM SH.SALES
  GROUP BY CUST_ID) RCNT,
  SH.SALES
WHERE RCNT.CUST_ID = SALES.CUST_ID
  AND RCNT.TIME_ID = SALES.TIME_ID;

このようなSQLは回数が少ないと、それほど問題にはならないのですが、実行回数が多くなると、全体の処理時間が長くなってしまう場合があります。

データマート(中間テーブル)を作る

この課題の解決方法は、単純にデータマート(中間デーブル)を作ることです。顧客ごとの直近の製品の購買データだけを持つ表を作ります。今回はマテリアライズド・ビューとして作ります。実体化した表にすることで、処理コストが大きい GROUP BY の実行を1回できます。レコード件数が少ない表を検索することで、処理全体の検索コストを小さくし、検索処理の実行回数が多い場合でも、全体の処理時間を短くすることができます。
(ここでは、CUST_ID と TIME_ID の組み合わせが、ユニークになると仮定しています)

CREATE MATERIALIZED VIEW SALES_RCNT_M_VIEW AS
SELECT
SALES.CUST_ID,
SALES.PROD_ID,
SALES.TIME_ID,
SALES.CHANNEL_ID,
SALES.PROMO_ID,
SALES.QUANTITY_SOLD,
SALES.AMOUNT_SOLD
FROM
(SELECT CUST_ID,  MAX(TIME_ID) TIME_ID
FROM SH.SALES
GROUP BY CUST_ID) RCNT,
SH.SALES
WHERE RCNT.CUST_ID = SALES.CUST_ID
AND RCNT.TIME_ID = SALES.TIME_ID;

このマテリアライズド・ビューはレコード件数が約5万件と少なくなりました。

分析関数(ウィンドウ関数)を使ったデータマート

上のSQLはMAX関数を使いましたが、分析関数(ウィンドウ関数)を使うこともできます。CUST_ID ごとに TIME_ID を降順にならべて番号を割り当てています。

CREATE MATERIALIZED VIEW SALES_RCNT_M_VIEW2 AS
SELECT
SALES.CUST_ID,
SALES.PROD_ID,
SALES.TIME_ID,
SALES.CHANNEL_ID,
SALES.PROMO_ID,
SALES.QUANTITY_SOLD,
SALES.AMOUNT_SOLD
FROM
(SELECT
 CUST_ID,
 TIME_ID,
 ROW_NUMBER() OVER(PARTITION BY CUST_ID ORDER BY TIME_ID DESC) RN
FROM SH.SALES
) RCNT,
SH.SALES
WHERE
RN = 1
AND RCNT.CUST_ID = SALES.CUST_ID
AND RCNT.TIME_ID = SALES.TIME_ID;

ウインドウ関数を使うと、上位何個までといった指定を「RN = 1」の条件式を変えることで実現できます。また、RANGE を使うと、顧客の最新購買日から遡って30日までのデータだけといった絞り込みもできます。

おわりに

この記事では、SQLのチューニングについて、マテリアライズド・ビューや分析関数を使ったデータマートによる、より実践的な方法をご紹介しました。データマートや分析関数などは、普段、あまり使われないものですが、業務によっては有益な場合がありますので、頭の片隅に置くと良いかもしれません。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?