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%短縮されます。

どうすれば同じ結果をより速く、より効率的な方法で達成できますか?

4

3 に答える 3

9

金曜日の午後なので、倉庫についてのコメントを広げたいと思いました。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

どちらかといえば、元のクエリよりも複雑に見えますが、オンラインデータベースが大きくなるほど、メリットがわかります。利点を示すためにSQLFiddleを実行しました。これは、ライブデータに10000のランダムな販売レコードを入力してから、ウェアハウスを作成します(スキーマの構築には数秒かかる場合があります)。ウェアハウスでのクエリの実行時間が大幅に高速化されていることに注意してください(c.20x)。最初の実行では20倍速くはならないかもしれませんが、両方のクエリのクエリプランがキャッシュされると、ウェアハウスクエリは一貫して20倍速くなります(とにかく私にとってはそうです)。

于 2012-04-27T16:53:28.810 に答える
2

データの非正規化バージョンを保持しますか?

例: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

次に、次のようなクエリを実行できます。

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

数年にわたる第1四半期の比較を取得するには

明らかに、これは、データの非正規化バージョンを維持するためのスケジュールを考え出す必要があることを意味します。更新時または1時間ごとにトリガーを使用してこれを行うことができます。

非正規化された結果に最新のデータを追加してみることもできます。そうすれば、今日作成された行に対してのみ低速の処理を実行できます。

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

于 2012-04-27T15:10:57.083 に答える
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
于 2016-11-01T21:45:14.063 に答える