2012-04-27 10 views
5

Następujące zapytanie zwraca poprawny wynik, ale jak uzyskać ten sam wynik szybciej?Najszybszy sposób podsumowania sprzedaży w oparciu o dzisiaj, w tym tygodniu, w tym miesiącu, w tym kwartale bieżącego roku?

Celem jest wydrukowanie tabeli śledzenia postępów sprzedawców poprzez podsumowanie ich sprzedaży dzisiaj, w tym tygodniu, miesiącu i kwartale.

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

Moje zapytanie:

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 

Podczas wykonywania tego samego zapytania o większej tabeli to robi się dość powoli. Po prostu usunięcie instrukcji CASE skraca czas wykonania o prawie 50%.

Jak osiągnąć ten sam rezultat szybciej i efektywniej?

+2

Dopóki twoja kolumna '[Data]' jest indeksowana, myślę, że masz już najbardziej wydajne rozwiązanie do wysyłania zapytań o dane transakcyjne. Jeśli trafisz na problemy z wydajnością, możesz chcieć rozszerzyć magazynowanie danych. – GarethD

+1

Tak, jest indeksowany. Jesteśmy na SQL Azure, który niestety jest obecnie nieco ograniczony. Myślę też, że byłoby to lepsze podejście (+1). –

Odpowiedz

8

Ponieważ jest to piątkowe popołudnie, pomyślałem, że rozwinę mój komentarz dotyczący składowania. nawet jeśli nie możesz w pełni zbadać kostek z SSAS lub jakimkolwiek innym OLAP, możesz nadal tworzyć własne raporty dotyczące konkretnego magazynu. W twoim przypadku utworzę nową Bazę danych (zawsze nazywam moją DW, ale świat jest twoją ostrygą) i tworzę 2 schematy Fact i Dim (reprezentujące fakty i wymiary). W twoim przypadku potrzebowałoby to dwóch tabel, chociaż możesz chcieć dodać kolejny wymiar dla "ID sprzedawcy" w zależności od tego, czy wymaga to dalszego raportowania.

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 
) 

Zakładając, że dane nie zostaną antydatowała można skorzystać z procedury tak, aby wypełnić swój magazyn na zaplanowanej pracy:

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 

To zostawiają cię z poniższego zapytania produkować raport

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 

Wygląda, jeśli w ogóle, bardziej skomplikowane niż pierwotne zapytanie, ale im więcej baza danych online będzie rosła, tym więcej korzyści odniesie użytkownik. Zrobiłem SQL Fiddle, aby zademonstrować zalety, wypełnia on dane na żywo 10000 losowych rekordów sprzedaży, a następnie tworzy magazyn (tworzenie schematu może zająć kilka sekund). Należy zauważyć, że czas wykonania zapytania w magazynie jest znacznie szybszy (ok. 20x).Może nie być 20 razy szybszy przy pierwszym uruchomieniu, ale gdy plan zapytania zostanie zbuforowany dla obu zapytań, zapytanie magazynowe jest 20 razy szybsze (i tak było dla mnie).

+0

Niesamowita odpowiedź! Dzięki, Gareth! –

2

Czy zachowasz znormalizowaną wersję danych?

np 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 

następnie można uruchomić zapytań typu:

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

dostać porównanie pierwszych kwartałów całej lat

Oczywiście to oznacza, że ​​trzeba wymyślić harmonogram utrzymywania wersji znormalizowanej danych. możesz to zrobić za pomocą wyzwalacza przy aktualizacji lub co godzinę.

możesz także spróbować dodać najnowsze dane do zdeformalizowanych wyników. W ten sposób powolutkujesz tylko te wiersze, które zostały utworzone dzisiaj.

EDYCJA: Nie wiem, czy po prostu użycie funkcji DATENAME poprawiłoby wydajność przy użyciu istniejącej struktury.

+0

To dobre rozwiązanie. Obecnie tworzymy zdenormalizowane wersje niektórych danych. Te dane są często aktualizowane i dostępne, więc najprawdopodobniej najłatwiej jest użyć pamięci podręcznej na stronach internetowych, aby uniemożliwić jej wykonanie dla każdego żądania? –

+0

również prosta wersja buforowania żądań internetowych działa tylko dla tych samych parametrów .. zdenormalizowane dane będą działać lepiej we wszystkich zapytaniach. – gordatron

+0

Nie jestem ekspertem i nie stosuję tego w praktyce na dużych aplikacjach, więc może niektórzy ludzie z bardziej realnym doświadczeniem mogą podać kilka odpowiedzi. – 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