Gibt es eine bessere Möglichkeit, dynamische zeitgemittelte Reihen über eine dynamische Frequenz zu erstellen?

7

Ich habe eine Reihe von Tabellen mit vielen hochpräzisen Daten, die von verschiedenen Geräten gesammelt wurden. Die Intervalle, in denen sie gesammelt wurden, variieren und wandern sogar über die Zeitreihen. Meine Benutzer möchten die Möglichkeit haben, einen Datumsbereich auszuwählen und einen Durchschnitt / min / max über diese Variablen mit einer bestimmten Häufigkeit zu erhalten. Dies ist der zweite Versuch, den ich unternommen habe, und er funktioniert, aber ich frage mich, ob es einen besseren / schnelleren Weg gibt, dies zu erreichen.

declare @start datetime
declare @end datetime
set @start = '3/1/2012'
set @end = '3/3/2012'
declare @interval int
set @interval = 300

declare @tpart table(
dt datetime
);

with CTE_TimeTable
as
(
select @start as [date]
union all
select dateadd(ss,@interval, [date])
from CTE_TimeTable
where DateAdd(ss,@interval, [date]) <= @end
)
insert into @tpart
select [date] from CTE_TimeTable
OPTION (MAXRECURSION 0);


select t.dt, avg(c.x1), min(c.x1), max(c.x2), avg(c.x2), min(c.x2), max(c.x2)  from clean.data c ,
@tpart t 
where
ABS(DateDIFF(ss, t.dt , c.Date) ) <= @interval /2
and
Date >= @start 
and 
Date <= @end
group by t.dt

Derzeit dauert die Ausführung dieser Abfrage über 32721 Zeilen für diesen Zeitraum von 3 Tagen ungefähr 43 Sekunden und gibt mir die 577 Zeilen, die ich erwarte, aber ich möchte dies schneller erhalten. Der große Erfolg kommt von der verschachtelten Schleife, um die innere Verbindung herzustellen.

chris.w.mclean
quelle
Tolle erste Frage und willkommen bei DBA.SE. Welche Indizes (sowohl gruppiert als auch nicht gruppiert) haben Sie clean.data? Können Sie auch erklären, warum Sie Cross-Joining mit @tpart?
Nick Chammas
Clustered-Index für clean.data.Date. Ich habe versucht, einen inneren Join und Cross durchzuführen, und der Abfrageplan hat sich nicht geändert. Der Mistkerl hier ist diese abs (datierte (ss, t.dt, c.date)) Anweisung.
chris.w.mclean
Was ist der Zweck dieser DATEDIFFAussage?
Nick Chammas
2
Was ist mit der Verwendung einer berechneten Spalte für den Wert von ABS(DateDIFF(ss, t.dt , c.Date) )? Sie könnten es indizieren. Macht die Operation für die c.DateSpalte in der Spalte nach meinem Verständnis (was möglicherweise WAAAAY falsch sein kann) WHEREden Index nicht unbrauchbar?
Swasheck
2
@swasheck - Das Intervall hängt von Benutzereingaben ab, sodass eine berechnete Spalte nicht möglich ist. Außerdem sollte der Index weiterhin für die Bereichssuche im letzten Teil der WHEREKlausel verwendet werden können.
Nick Chammas

Antworten:

4

Ihre Verknüpfung zwischen den Tabellen ist in eine Funktion eingebettet, die es dem Optimierer wirklich schwer macht, etwas Kluges damit zu tun. Ich denke, es muss jede Zeile in einer Tabelle mit jeder anderen Zeile in der anderen Tabelle vergleichen.

Das Umschreiben Ihres Joins mit einer Bereichsprüfung sollte viel schneller erfolgen. Ich habe Ihrer Tabellenvariablen auch einen Primärschlüssel hinzugefügt, um eine Sortieroperation aus dem Abfrageplan zu entfernen, und stattdessen Ihre Tabellenvariable in eine temporäre Tabelle umgewandelt. Der Unterschied bei meinen Tests bestand darin, dass im Abfrageplan Parallelität verwendet wurde.

declare @start datetime;
declare @end datetime;
set @start = '20120301';
set @end = '20120303';
declare @interval int;
set @interval = 300;

create table #tpart
(
  dt datetime primary key
);

with CTE_TimeTable
as
(
  select @start as [date]
  union all
  select dateadd(second ,@interval, [date])
  from CTE_TimeTable
  where dateadd(second, @interval, [date]) <= @end
)
insert into #tpart
select [date]
from CTE_TimeTable
option (maxrecursion 0);

select t.dt, avg(c.x1), min(c.x1), max(c.x2), avg(c.x2), min(c.x2), max(c.x2)
from clean.data c
  inner join #tpart t 
    on c.Date >= t.dt and
       c.Date < dateadd(second, @interval, t.dt)
group by t.dt;

drop table #tpart;

Hinweis: Diese Abfrage gibt nicht genau die gleichen Intervalle zurück wie Ihre Abfrage. Der Datumsbereich wird in gleich große Teile unterteilt, in denen Ihre Abfrage zu Beginn ein halbes Intervall und am Ende des Bereichs ein halbes Intervall hatte. Es ist natürlich möglich, die Abfrage so zu ändern, dass sie Ihrer Abfrage entspricht, wenn dies gewünscht wird.

Aktualisieren

Ich habe an einer Tabelle mit insgesamt 1036801Zeilen und mit 34560im Intervall 2012-03-01bis getestet 2012-03-03. In meinen Tests dauert die ursprüngliche Abfrage 4,1 Sekunden. Die obige Abfrage dauert 0,1 Sekunden.

Skript zum Generieren der Testdaten:

create table clean.data
(
    Date datetime primary key,
    x1 int,
    x2 int
);

go

with C as
(
  select cast('20120201' as datetime) as D
  union all
  select dateadd(second, 5, D)
  from C
  where D < '20120401'
)
insert into clean.data(Date, x1, x2)
select D, checksum(newid()) % 1000, checksum(newid()) % 1000
from C
option (maxrecursion 0);
Mikael Eriksson
quelle
0

Die Antwort von Mikael Eriksson hat mich dazu inspiriert, meinen ursprünglichen Code etwas zu ändern. Das habe ich mir ausgedacht.

declare @tpart table(
dtStart datetime,
dtEnd datetime,
midTime datetime
);

with CTE_TimeTable
as
(
select Dateadd(ss,-(@interval/2), @start) as [sdate], Dateadd(ss,(@interval/2),@start) 
as [edate] , @start as [middate]
union all
select dateadd(ss,@interval, [sdate]) , dateadd(ss,@interval,[edate]) ,    
dateadd(ss,@interval, [middate])
from CTE_TimeTable
where DateAdd(ss,@interval, [middate]) < @end
)
insert into @tpart
select [sdate] , [edate] , [middate] from CTE_TimeTable
OPTION (MAXRECURSION 0);

Die Tabellenvariable @tpart hat nun sowohl die Start- und Endzeit der Abfrage als auch den Mittelpunkt für die Gruppierung vorberechnet. Jetzt sieht meine Anfrage so aus:

select t.midtime, avg(c.x1), min(c.x1), max(c.x2), avg(c.x2), min(c.x2), max(c.x2)  from     
clean.data c inner join
@tpart t 
on c.Date >= t.dtStart and c.Date < t.dtEnd
group by t.midtime

Dies gab mir eine Abfragezeit von ca. 9 Sekunden. Nicht ganz so gut wie bei Mikael, aber viel besser als das, womit ich angefangen hatte und gut genug für meine Benutzer. Dies waren über ca. 80.000 Zeilen für einen einzelnen Tag, durchschnittlich fünf Minuten lang.

chris.w.mclean
quelle