2012-04-27 20 views
5

次のクエリは正しい結果を返しますが、同じ結果をより速く得るにはどうすればよいですか?今年、今週、今月、今四半期に基づいて売上を集計する最速の方法は?

目標は、売り手の進捗状況を、今日、今週、月、および四半期の売上を要約して追跡するためのテーブルを出力することです。

SellerID Today     ThisWeek    ThisMonth    ThisQuarter 
----------- --------------------- --------------------- --------------------- --------------------- 
1   400,00    700,00    900,00    900,00 
2   950,00    1850,00    2650,00    2650,00 

マイクエリ:

CREATE TABLE #sales(
    [Price] MONEY, 
    [Date] DATE, 
    [SellerID] INT 
) 

INSERT INTO #sales VALUES 
(100, '2012-01-01', 1), 
(200, '2012-04-01',1), 
(300, '2012-04-23',1), 
(400, '2012-04-27',1), 
(700, '2012-01-01', 2), 
(700, '2012-01-02', 2), 
(800, '2012-04-01',2), 
(900, '2012-04-23',2), 
(950, '2012-04-27',2) 


SELECT 
SellerID AS SellerID, 

SUM(CASE WHEN [Date] >= DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()),0) THEN [Price] END) AS Today, 
SUM(CASE WHEN [Date] >= DATEADD(WEEK, DATEDIFF(WEEK, 0, GETDATE()), 0) THEN [Price] END) AS ThisWeek, 
SUM(CASE WHEN [Date] >= DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0) THEN [Price] END) AS ThisMonth, 
SUM(CASE WHEN [Date] >= DATEADD(QUARTER, DATEDIFF(QUARTER, 0, GETDATE()), 0) THEN [Price] END) AS ThisQuarter 

FROM #sales 
WHERE DATEPART(YEAR, [Date]) = DATEPART(YEAR, GETDATE()) 
GROUP BY SellerID 

大きなテーブルの上に同じクエリを実行すると、これは非常に遅くなります。 CASE文を削除するだけで実行時間が50%近く短縮されます。

私はどのようにしてより高速で効率的な方法で同じ結果を達成できますか?

+2

あなたの '[Date]'列がインデックスされている限り、トランザクションデータを照会するのに最も効率的なソリューションがあると思います。パフォーマンス上の問題を抱えている場合は、データウェアハウジングを増やしたいかもしれません。 – GarethD

+1

はい、インデックスに登録されています。残念ながら現時点では少し制限されているSQL Azureを使用しています。私はそれがより良いアプローチ(+1)だとも考えています。 –

答えて

8

金曜日の午後なので、倉庫に関する私のコメントを拡大したいと考えました。 SSASや他のOLAPを使用してキューブを完全に探索できない場合でも、独自のレポート固有のウェアハウスを実行できます。あなたの場合、私は新しいデータベース(私は常に私のDWと呼んでいますが、世界はあなたの牡蠣です)を設定し、2つのスキーマFactとDim(事実と次元を表す)を作成します。あなたのケースでは2つのテーブルが必要ですが、これ以上のレポートが必要な場合は、「SellerID」の別のディメンションを追加することもできます。データを仮定

CREATE TABLE Dim.Date 
(  DateKey  DATE NOT NULL, 
     DayOfWeek VARCHAR(20) NOT NULL, 
     Day   TINYINT NOT NULL, 
     Week  TINYINT NOT NULL, 
     Quarter  TINYINT NOT NULL, 
     Month  TINYINT NOT NULL, 
     Year  SMALLINT NOT NULL 
    CONSTRAINT PK_Dim_Date_DateKey PRIMARY KEY (DateKey) 
) 
CREATE TABLE Fact.Sales 
(  DateKey  DATE NOT NULL, 
     SellerID INT NOT NULL, 
     Sales  INT NOT NULL, 
     Amount  MONEY NOT NULL, 
    CONSTRAINT PK_Fact_Sales PRIMARY KEY (DateKey, SellerID), 
    CONSTRAINT FK_Fact_Sales_DateKey FOREIGN KEY (DateKey) REFERENCES Dim.Date 
) 

は、あなたがスケジュールされたジョブであなたの倉庫を埋めるために、次のように手順を使用することができますさかのぼっ得ることはありません:これは、レポートを生成するために次のクエリであなたを残して

DECLARE @MaxDate DATE 
SELECT @MaxDate = DATEADD(DAY, 1, MAX(DateKey)) 
FROM Fact.Sales 

INSERT INTO Dim.Date 
SELECT DATEADD(DAY, Increment, @MaxDate), 
     DATENAME(WEEKDAY, DATEADD(DAY, Increment, @MaxDate)), 
     DATEPART(DAY, DATEADD(DAY, Increment, @MaxDate)), 
     DATEPART(WEEK, DATEADD(DAY, Increment, @MaxDate)), 
     DATEPART(MONTH, DATEADD(DAY, Increment, @MaxDate)), 
     DATEPART(QUARTER, DATEADD(DAY, Increment, @MaxDate)), 
     DATEPART(YEAR, DATEADD(DAY, Increment, @MaxDate)) 
FROM ( SELECT ROW_NUMBER() OVER(ORDER BY Object_ID) - 1 [Increment] 
      FROM Sys.Objects 
     ) obj 
WHERE NOT EXISTS 
     ( SELECT 1 
      FROM Dim.Date 
      WHERE Date.DateKey = DATEADD(DAY, Increment, @MaxDate) 
     ) 


INSERT INTO Fact.Sales 
SELECT [Date], SellerID, COUNT(*), SUM(Price) 
FROM LiveDatabase..Sales 
WHERE [Date] >= @MaxDate 
GROUP BY [Date], SellerID 

SELECT SellerID, 
     SUM(CASE WHEN Today.DateKey = Date.DateKey THEN Amount ELSE O END) [Today], 
     SUM(CASE WHEN Today.Week = Date.Week THEN Amount ELSE O END) [ThisWeek], 
     SUM(CASE WHEN Today.Month = Date.Month THEN Amount ELSE O END) [ThisMonth], 
     SUM(CASE WHEN Today.Quarter = Date.Quarter THEN Amount ELSE O END) [ThisQuarter], 
     SUM(CASE WHEN Today.Year = Date.Year THEN Amount ELSE O END) [ThisYear] 
FROM Fact.Sales 
     INNER JOIN Dim.Date 
      ON Date.DateKey = Sales.DateKey 
     INNER JOIN Dim.Date Today 
      ON Today.DateKey = CAST(GETDATE() AS DATE) 
      AND Today.Year = Date.Year 
GROUP BY SellerID 

元のクエリよりも複雑に見えますが、オンラインデータベースが増えるほど効果が増えます。私はSQL Fiddleを使って利点を実証しました。ライブデータに10000個のランダムな販売記録を埋め込み、ウェアハウスを作成します(スキーマの構築には数秒かかる場合があります)。倉庫でのクエリの実行時間が大幅に短縮されていることに気付くはずです(c.20x)。最初の実行では20倍の速さではないかもしれませんが、クエリプランが両方のクエリに対してキャッシュされると、倉庫クエリは一貫して20倍高速になります。

+0

素晴らしい答え!ありがとうガレス! –

2

データの正規化されていないバージョンを保持していますか?

例:

select 
    year 
    ,sum(price) 
from 
    deNormalised 
where 
    quarter = 1 
group by 
    year 

は明らかに、これはあなたが思い付くしなければならないことを意味年を挟んで第1四半期の比較を取得する:http://sqlfiddle.com/#!3/300a5/2

select 
    * 
    ,DATENAME(day, [date]) as day 
    ,DATENAME(month, [date]) as month 
    , DATENAME(year, [date]) as year 
    ,DATENAME(quarter, [date]) as quarter 
into deNormalised 
from #sales 

は、次のようなクエリを実行することができますデータのバージョンを正規化解除するためのスケジュール。あなたは更新時または毎時のトリガーでそれを行うかもしれません。

また、正規化されていない結果に最新のデータを追加してみることもできます。この方法では、今日作成された行の処理が遅くなります。

EDIT:DATENAME関数を使用するだけで既存の構造体を使用してパフォーマンスを向上させるかどうかわかりません。

+0

これは良い解決策です。私たちは実際に今日いくつかのデータに対して非正規化バージョンを作成しています。このデータは頻繁に更新され、アクセスされるので、最も簡単なのは、ウェブフロントでキャッシュを使用して、リクエストごとにキャッシュが実行されないようにすることです。 –

+0

また、Webリクエストをキャッシュするシンプルなバージョンは、同じパラメータに対してのみ機能します。非正規化されたデータは、すべてのクエリでより優れたパフォーマンスを発揮します。 – gordatron

+0

私は専門家ではなく、大規模なアプリケーションでこれを実践することはしていないので、もっと実世界の経験を持つ人達が答えを出すかもしれません。 – gordatron

0
select 
    SellerID 
    ,sum(case when [Date]=getdate() then [Price] else 0 end) as Today 
    ,sum(case when datepart(week,[Date])=datepart(week,getdate()) then [Price] else 0 end) as ThisWeek 
    ,sum(case when datepart(MONTH,[Date])=datepart(month,getdate()) then [Price] else 0 end) as ThisMonth 
    ,sum(case when datepart(QUARTER,[Date])=datepart(QUARTER,getdate()) then [Price] else 0 end) as ThisQUARTER 
from #sales 
Group by SellerID 
関連する問題