Da sich meine Fähigkeiten zur Leistungsoptimierung nie ausreichend anfühlen, frage ich mich immer, ob ich bei einigen Abfragen mehr Optimierungen vornehmen kann. Die Situation, auf die sich diese Frage bezieht, ist eine Windowed MAX-Funktion, die in einer Unterabfrage verschachtelt ist.
Die Daten, die ich durchsuche, sind eine Reihe von Transaktionen für verschiedene Gruppen größerer Mengen. Ich habe 4 wichtige Felder, die eindeutige ID einer Transaktion, die Gruppen-ID eines Transaktionsstapels und Daten, die der jeweiligen eindeutigen Transaktion oder Gruppe von Transaktionen zugeordnet sind. In den meisten Fällen stimmt das Gruppendatum mit dem maximalen eindeutigen Transaktionsdatum für einen Stapel überein. Es gibt jedoch Zeiten, in denen manuelle Anpassungen über unser System vorgenommen werden und nach der Erfassung des Gruppentransaktionsdatums eine eindeutige Datumsoperation erfolgt. Diese manuelle Bearbeitung passt das Gruppendatum nicht an.
Was ich in dieser Abfrage identifiziere, sind die Datensätze, bei denen das eindeutige Datum nach dem Gruppendatum liegt. Die folgende Beispielabfrage liefert ein ungefähres Äquivalent zu meinem Szenario, und die SELECT-Anweisung gibt die gesuchten Datensätze zurück. Nähere ich mich dieser Lösung jedoch auf die effizienteste Weise? Es dauert eine Weile, bis meine Faktentabelle geladen ist, da mein Datensatz die Nummer in den oberen 9 Ziffern zählt, aber meistens frage ich mich, ob es hier einen besseren Ansatz gibt, wenn ich Unterabfragen verachte. Ich bin nicht so besorgt über Indizes, wie ich zuversichtlich bin, dass diese bereits vorhanden sind. Was ich suche, ist ein alternativer Abfrageansatz, der dasselbe erreicht, aber noch effizienter. Jedes Feedback ist willkommen.
CREATE TABLE #Example
(
UniqueID INT IDENTITY(1,1)
, GroupID INT
, GroupDate DATETIME
, UniqueDate DATETIME
)
CREATE CLUSTERED INDEX [CX_1] ON [#Example]
(
[UniqueID] ASC
)
SET NOCOUNT ON
--Populate some test data
DECLARE @i INT = 0, @j INT = 5, @UniqueDate DATETIME, @GroupDate DATETIME
WHILE @i < 10000
BEGIN
IF((@i + @j)%173 = 0)
BEGIN
SET @UniqueDate = GETDATE()+@i+5
END
ELSE
BEGIN
SET @UniqueDate = GETDATE()+@i
END
SET @GroupDate = GETDATE()+(@j-1)
INSERT INTO #Example (GroupID, GroupDate, UniqueDate)
VALUES (@j, @GroupDate, @UniqueDate)
SET @i = @i + 1
IF (@i % 5 = 0)
BEGIN
SET @j = @j+5
END
END
SET NOCOUNT OFF
CREATE NONCLUSTERED INDEX [IX_2_4_3] ON [#Example]
(
[GroupID] ASC,
[UniqueDate] ASC,
[GroupDate] ASC
)
INCLUDE ([UniqueID])
-- Identify any UniqueDates that are greater than the GroupDate within their GroupID
SELECT UniqueID
, GroupID
, GroupDate
, UniqueDate
FROM (
SELECT UniqueID
, GroupID
, GroupDate
, UniqueDate
, MAX(UniqueDate) OVER (PARTITION BY GroupID) AS maxUniqueDate
FROM #Example
) calc_maxUD
WHERE maxUniqueDate > GroupDate
AND maxUniqueDate = UniqueDate
DROP TABLE #Example
dbfiddle hier
quelle
Antworten:
Ich gehe davon aus, dass es keinen Index gibt, da Sie keinen angegeben haben.
Der folgende Index eliminiert auf Anhieb einen Sortieroperator in Ihrem Plan, der andernfalls möglicherweise viel Speicher verbrauchen würde:
Die Unterabfrage ist in diesem Fall kein Leistungsproblem. Wenn überhaupt, würde ich nach Möglichkeiten suchen, die Fensterfunktion (MAX ... OVER) zu entfernen, um das Konstrukt Nested Loop und Table Spool zu vermeiden.
Mit demselben Index sieht die folgende Abfrage auf den ersten Blick weniger effizient aus und führt zwar von zwei auf drei Scans in der Basistabelle, eliminiert jedoch eine große Anzahl interner Lesevorgänge, da Spool-Operatoren fehlen. Ich vermute, dass die Leistung immer noch besser ist, insbesondere wenn Sie über genügend CPU-Kerne und E / A-Leistung auf Ihrem Server verfügen:
(Hinweis: Ich habe einen
MERGE JOIN
Abfragehinweis hinzugefügt , dies sollte jedoch wahrscheinlich automatisch geschehen, wenn Ihre Statistiken in Ordnung sind. Es wird empfohlen, solche Hinweise wegzulassen, wenn Sie können.)quelle
Wann und wenn Sie ein Upgrade von SQL Server 2012 auf SQL Server 2016 durchführen können, können Sie möglicherweise die stark verbesserte Leistung (insbesondere für rahmenlose Fensteraggregate) nutzen, die der neue Fensteraggregatoperator im Stapelmodus bietet.
Fast alle großen Datenverarbeitungsszenarien funktionieren mit Columnstore-Speicher besser als mit Rowstore. Auch ohne für Ihre Basistabellen zum Spaltenspeicher zu wechseln, können Sie die Vorteile der neuen Ausführung im Operator- und Stapelmodus 2016 nutzen, indem Sie einen leeren, nicht gruppierten, durch den Spaltenspeicher gefilterten Index für eine der Basistabellen erstellen oder redundant eine äußere Verknüpfung mit einem von einem Spaltenspeicher organisierten Speicher herstellen Tabelle.
Mit der zweiten Option wird die Abfrage zu:
db <> Geige
Beachten Sie, dass die einzige Änderung an der ursprünglichen Abfrage darin besteht, eine leere temporäre Tabelle zu erstellen und den linken Join hinzuzufügen. Der Ausführungsplan lautet:
Weitere Informationen und Optionen finden Sie in der hervorragenden Serie von Itzik Ben-Gan, Was Sie über den Aggregatoperator für Stapelfenster in SQL Server 2016 wissen müssen (in drei Teilen).
quelle
Ich werde nur das alte Kreuz werfen.
Mit einigen Indizes funktioniert es ziemlich gut.
Die Statistikzeit und io sehen so aus (Ihre Anfrage ist das erste Ergebnis)
Abfragepläne sind hier (wieder ist Ihre zuerst):
https://www.brentozar.com/pastetheplan/?id=BJYJvqAal
Warum bevorzuge ich diese Version? Ich vermeide die Spulen. Wenn diese auf die Festplatte verschüttet werden, wird es hässlich.
Aber vielleicht möchten Sie das auch ausprobieren.
Wenn dies ein großer DW ist, bevorzugen Sie möglicherweise den Hash-Join und die Zeilenfilterung im Join, anstatt am Ende der
TOP 1
Abfrage als Filteroperator.Plan ist hier: https://www.brentozar.com/pastetheplan/?id=BkUF55ATx
Statistik Zeit und io hier:
Hoffe das hilft!
Eine Bearbeitung, basierend auf der Idee von @ ypercube, und ein neuer Index.
Hier ist die Statistik Zeit und io:
Hier ist der Plan:
https://www.brentozar.com/pastetheplan/?id=SJv8foR6g
quelle
Ich würde einen Blick darauf werfen
top with ties
Wenn
GroupDate
ist das gleiche proGroupId
dann:Sonst: Verwendung
top with ties
in einem gemeinsamen Tabellenausdruckdbfiddle: http://dbfiddle.uk/?rdbms=sqlserver_2016&fiddle=c058994c2f5f3d99b212f06e1dae9fd3
Ursprüngliche Abfrage
vs
top with ties
in einem gemeinsamen Tabellenausdruckquelle
Daher habe ich einige Analysen zu den verschiedenen bisher veröffentlichten Ansätzen durchgeführt, und in meiner Umgebung sieht es so aus, als würde Daniels Ansatz bei den Ausführungszeiten konsequent gewinnen. Überraschenderweise (für mich) war der dritte CROSS APPLY-Ansatz von sp_BlitzErik nicht so weit zurück. Hier sind die Ergebnisse, wenn jemand interessiert ist, aber danke einer TON für alle alternativen Ansätze. Ich habe mehr aus den Antworten auf diese Frage gelernt als seit einiger Zeit!
quelle
top with ties
Schnallen mit so vielen Reihen. dbfiddle.uk/…