2016-06-01 16 views
2

Wir bauen ein Data Warehouse, in dem wir den Preis jedes einzelnen Produkts erfassen und diese Daten am Tag aufbewahren möchten. Es gibt eine Tabelle mit den FromDate und ToDate, die beide NULL haben können.Eine bessere Alternative zu ROW_NUMBER, um einen einzelnen Datensatz pro Gruppe abzurufen

Die Logik der heutigen Preis zu finden ist:

  1. alle Datensätze ignorieren, wo die FromDate in der Zukunft.
  2. Ignorieren Sie alle Datensätze, in denen die ToDate in der Vergangenheit ist.
  3. Wenn mehrere Datensätze die obigen Anforderungen erfüllen, möchten wir den Preis ermitteln, bei dem FromDate der neueste Wert ist und ToDate dem aktuellen Datum am nächsten liegt.
  4. Das System erlaubt Duplikate für die gleichen FromDate und ToDate, so dass wir dann durch den Primärschlüssel absteigend sortieren (neueste zuerst).

Um dieses Problem zu bewältigen, habe ich eine ROW_NUMBER mit einem PARTITION verwendet, um die Datensätze zu bestellen, und erhalten nur die erste. Das funktioniert, aber es dauert ungefähr 20 Minuten pro Tag (und trifft ziemlich hart auf tempdb), da wir ungefähr 2 Millionen Datensätze haben, die aus dieser Abfrage erstellt werden.

Gibt es bessere Alternativen, die die Leistung der Abfrage erhöhen können?

SQL Fiddle here

Beachten Sie, dass die Geige ein allzu vereinfachte Beispiel der Daten und enthält nur relevante Teile für diese Frage.

+0

Haben Sie Index auf dem Tisch ? –

+0

Ja, Indizes sind nicht mein Problem. Ich bin wandernder, wenn es einen Weg gibt, dies ohne analytische Funktionen zu tun. Etwas, das vielleicht 'tempdb' freundlicher ist. Es ist möglich, dass dies der beste Weg ist, und aufgrund der Datenmenge, die ich habe, muss ich damit leben. – Lock

+1

[** This **] (http://dba.stackexchange.com/questions/86415/retrieving-n-rows-per-group) könnte helfen. –

Antwort

1

Das ist Ihre Abfrage:

SELECT ProductId, @PriceDate AS PriceDate, Price 
FROM (SELECT fp.*, 
      ROW_NUMBER() OVER (PARTITION BY ProductId 
           ORDER BY COALESCE(FromDate, '19000101') DESC, COALESCE(ToDate, '21000101') ASC) AS RowNumber 
     FROM FactPrices fp 
     WHERE (FactPrices.FromDate IS NULL OR FactPrices.FromDate <= @PriceDate) AND 
      (FactPrices.ToDate IS NULL OR FactPrices.ToDate >= @PriceDate) 
    ) A 
WHERE A.RowNumber = 1; 

Die where Klausel eine große Zeit-Performance-Killer ist. Ich werde vorschlagen, zwei berechnete Spalten und dann zwei weitere Indizes hinzuzufügen.

Die berechneten Spalten sind:

FromDateNotNull as (coalesce(FromDate, '19000101')) 
ToDateNotNull as (coalesce(ToDate, '21000101')) 

Dann erstellen Indizes auf:

(Prices, FromDateNotNull desc, ToDateNotNull asc) 
(FromDateNotNull, ToDateNotNull) 

Dann schreiben Sie die Abfrage wie:

SELECT ProductId, @PriceDate AS PriceDate, Price 
FROM (SELECT fp.*, 
      ROW_NUMBER() OVER (PARTITION BY ProductId 
           ORDER BY FromDateNotNull DESC, ToDateNotNull 
          ) AS RowNumber 
     FROM FactPrices fp 
     WHERE FromDateNotNull <= @PriceDate AND 
      ToDateNotNull >= @PriceDate 
    ) A 
WHERE A.RowNumber = 1; 
+0

Werde es versuchen! Die Quelldaten werden über SSIS ausgegeben, sodass ich die NULLs auf dem Weg entfernen konnte. – Lock

+0

@Lock. . . Durch das Vermeiden von 'or' in der' where'-Klausel kann die Abfrageabfrage Indizes für die Abfrage verwenden. –

+0

Ich rejigged meine Abfrage, um alle 'OR's zu entfernen und es ist jetzt 33 Sekunden. Vielen Dank!! – Lock

Verwandte Themen