5

Ich bin mit einigen Daten arbeiten, die zur Zeit in Intervallen von 1 Minute gespeichert ist, die wie folgt aussehen:GROUP BY mit FIRST_VALUE und LAST_VALUE

CREATE TABLE #MinuteData 
    (
     [Id] INT , 
     [MinuteBar] DATETIME , 
     [Open] NUMERIC(12, 6) , 
     [High] NUMERIC(12, 6) , 
     [Low] NUMERIC(12, 6) , 
     [Close] NUMERIC(12, 6) 
    ); 

INSERT INTO #MinuteData 
     ([Id], [MinuteBar], [Open], [High], [Low], [Close]) 
VALUES (1, '2015-01-01 17:00:00', 1.557870, 1.557880, 1.557870, 1.557880), 
     (2, '2015-01-01 17:01:00', 1.557900, 1.557900, 1.557880, 1.557880), 
     (3, '2015-01-01 17:02:00', 1.557960, 1.558070, 1.557960, 1.558040), 
     (4, '2015-01-01 17:03:00', 1.558080, 1.558100, 1.558040, 1.558050), 
     (5, '2015-01-01 17:04:00', 1.558050, 1.558100, 1.558020, 1.558030), 
     (6, '2015-01-01 17:05:00', 1.558580, 1.558710, 1.557870, 1.557950), 
     (7, '2015-01-01 17:06:00', 1.557910, 1.558120, 1.557910, 1.557990), 
     (8, '2015-01-01 17:07:00', 1.557940, 1.558250, 1.557940, 1.558170), 
     (9, '2015-01-01 17:08:00', 1.558140, 1.558200, 1.558080, 1.558120), 
     (10, '2015-01-01 17:09:00', 1.558110, 1.558140, 1.557970, 1.557970); 

SELECT * 
FROM #MinuteData; 

DROP TABLE #MinuteData; 

Die Werte Wechselkurse zu verfolgen, so dass für jedes Minuten-Intervall (bar), gibt es den Open Preis als Minute gestartet und einen Close Preis für das Minutenende. Die Werte High und Low repräsentieren die höchste und niedrigste Rate während jeder einzelnen Minute.

gewünschte Ausgabe

Ich suche diese Daten in 5-Minuten-Intervallen zu formatieren Sie die folgende Ausgabe zu erzeugen:

MinuteBar    Open  Close  Low   High 
2015-01-01 17:00:00.000 1.557870 1.558030 1.557870 1.558100 
2015-01-01 17:05:00.000 1.558580 1.557970 1.557870 1.558710 

Dies nimmt den Open Wert von der ersten Minute der 5 , Close Wert von der letzten Minute der 5. Die High und Low Werte repräsentieren die höchsten high und niedrigsten low Raten über den 5-Minuten-Zeitraum.

Aktuelle Lösung

Ich habe eine Lösung, die dies tut (siehe unten), aber es fühlt sich unelegant wie es auf id Werten beruht und sich selbst verbindet. Außerdem will ich es auf viel größere Datensätze laufen, so dass ich es in einer effizienteren Weise, wenn möglich zu tun war auf der Suche:

-- Create a column to allow grouping in 5 minute Intervals 
SELECT Id, MinuteBar, [Open], High, Low, [Close], 
DATEDIFF(MINUTE, '2015-01-01T00:00:00', MinuteBar)/5 AS Interval 
INTO #5MinuteData 
FROM #MinuteData 
ORDER BY minutebar 

-- Group by inteval and aggregate prior to self join 
SELECT Interval , 
     MIN(MinuteBar) AS MinuteBar , 
     MIN(Id) AS OpenId , 
     MAX(Id) AS CloseId , 
     MIN(Low) AS Low , 
     MAX(High) AS High 
INTO #DataMinMax 
FROM #5MinuteData 
GROUP BY Interval; 

-- Self join to get the Open and Close values 
SELECT t1.Interval , 
     t1.MinuteBar , 
     tOpen.[Open] , 
     tClose.[Close] , 
     t1.Low , 
     t1.High 
FROM #DataMinMax t1 
     INNER JOIN #5MinuteData tOpen ON tOpen.Id = OpenId 
     INNER JOIN #5MinuteData tClose ON tClose.Id = CloseId; 

DROP TABLE #DataMinMax 
DROP TABLE #5MinuteData 

Nacharbeits Versuch

Anstelle der obigen Abfragen, ich habe Ich benutze FIRST_VALUE und LAST_VALUE, da es scheint, was ich suche, aber ich kann nicht ganz mit der Gruppierung arbeiten, die ich mache. Es könnte eine bessere Lösung geben als das, was ich versuche, also bin ich offen für Vorschläge. Derzeit versuche ich, dies zu tun:

SELECT MIN(MinuteBar) MinuteBar5 , 
     FIRST_VALUE([Open]) OVER (ORDER BY MinuteBar) AS Opening, 
     MAX(High) AS High , 
     MIN(Low) AS Low , 
     LAST_VALUE([Close]) OVER (ORDER BY MinuteBar) AS Closing , 
     DATEDIFF(MINUTE, '2015-01-01 00:00:00', MinuteBar)/5 AS Interval 
FROM #MinuteData 
GROUP BY DATEDIFF(MINUTE, '2015-01-01 00:00:00', MinuteBar)/5 

Das gibt mir die folgenden Fehler, die die FIRST_VALUE verwendet ist und LAST_VALUE wie die Abfrage ausgeführt wird, wenn ich diese Zeilen entfernen:

Spalte ‚# MinuteData.MinuteBar 'ist in der Auswahlliste ungültig, da sie weder in einer Aggregatfunktion noch in der GROUP BY-Klausel enthalten ist.

+1

Die FIRST_VALUE angeben und last_value sind nicht wirklich Aggregatfunktionen wie Sie vielleicht denken. Sie sind eher wie row_number, wo sie über einen vollständigen Datensatz gefenstert sind. Problem ist, dass Sie versuchen, sie wie Aggregate zu benutzen, und deshalb schreit es Sie an. Ich muss jetzt sofort los, aber mein erster Gedanke war, das Datum auf eine Saite zu übertragen, die winzige Komponente zu teilen und sie in ihrer abgerundeten Form wieder zusammen zu kleben. – Xedni

+0

danke für die Antwort, ich habe First_value nicht zuvor in Wut verwendet. Die Daten sind nicht wirklich mein Problem hier, ich habe eine Lösung für das, was zu funktionieren scheint, obwohl es bessere Wege geben könnte, das zu tun. Das Hauptproblem besteht darin, die Werte für Öffnen und Schließen für die Zeiträume von 5 Minuten zu erhalten. – Tanner

+0

also Hoch und Niedrig ist einfach das höchste Hoch und das niedrigste niedrig, aber "offen" und "nah" sind das, was Sie Probleme haben, weil diese nur die ersten und letzten in dem Intervall sein sollten, unabhängig von ihrem Wert? Habe ich das richtig? – Xedni

Antwort

2
SELECT 
    MIN(MinuteBar) AS MinuteBar5, 
    Opening, 
    MAX(High) AS High, 
    MIN(Low) AS Low, 
    Closing, 
    Interval 
FROM 
(
    SELECT FIRST_VALUE([Open]) OVER (PARTITION BY DATEDIFF(MINUTE, '2015-01-01 00:00:00', MinuteBar)/5 ORDER BY MinuteBar) AS Opening, 
      FIRST_VALUE([Close]) OVER (PARTITION BY DATEDIFF(MINUTE, '2015-01-01 00:00:00', MinuteBar)/5 ORDER BY MinuteBar DESC) AS Closing, 
      DATEDIFF(MINUTE, '2015-01-01 00:00:00', MinuteBar)/5 AS Interval, 
      * 
    FROM #MinuteData 
) AS T 
GROUP BY Interval, Opening, Closing 

Eine Lösung der Nähe Ihrer aktuellen. Es gibt zwei Orte, die du falsch gemacht hast.

  1. FIRST_VALUE UND LAST_VALUE sind Analytic Funktionen, die auf einem Fenster oder einer Partition arbeiten, anstelle einer Gruppe.Sie können die verschachtelte Abfrage alleine ausführen und das Ergebnis sehen.
  2. LAST_VALUE ist der letzte Wert des aktuellen Fensters, der in Ihrer Abfrage nicht angegeben ist, und ein Standardfenster besteht aus Zeilen von der ersten Zeile der aktuellen Partition bis aktuelle Zeile. Sie können entweder FIRST_VALUE mit geriffelt, um verwenden oder ein Fenster

    LAST_VALUE([Close]) OVER (PARTITION BY DATEDIFF(MINUTE, '2015-01-01 00:00:00', MinuteBar)/5 
          ORDER BY MinuteBar 
          ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS Closing, 
    
+0

Danke, ich werde das in Kürze versuchen und zu dir zurückkommen. Ich vermutete, dass ich so etwas tun musste. – Tanner

+0

Schön. Ich habe nicht darüber nachgedacht, Opening und Closing der Gruppenklausel hinzuzufügen. –

+0

Dies scheint die sauberste Lösung zu sein, die nicht so viele Schritte erfordert wie die anderen und am nächsten an dem liegt, was ich erreichen wollte, obwohl ich immer noch darauf schaue, warum der LAST_VALUE-Teil nicht so funktioniert, wie ich es erwartet habe. Trotzdem funktioniert das so, danke. – Tanner

1

Hier ist eine Möglichkeit, es ohne temporäre Tabellen zu tun:

;WITH CTEInterval AS 
( -- This replaces your first temporary table (#5MinuteData) 
    SELECT [Id], 
      [MinuteBar], 
      [Open], 
      [High], 
      [Low], 
      [Close], 
      DATEPART(MINUTE, MinuteBar)/5 AS Interval 
    FROM #MinuteData 
), CTEOpenClose as 
(-- this is instead of your second temporary table (#DataMinMax) 
    SELECT [Id], 
      [MinuteBar], 
      FIRST_VALUE([Open]) OVER (PARTITION BY Interval ORDER BY MinuteBar) As [Open], 
      [High], 
      [Low], 
      FIRST_VALUE([Close]) OVER (PARTITION BY Interval ORDER BY MinuteBar DESC) As [Close], 
      Interval 
    FROM CTEInterval 
) 

-- This is the final select 
SELECT MIN([MinuteBar]) as [MinuteBar], 
     AVG([Open]) as [Open], -- All values of [Open] in the same interval are the same... 
     AVG([Close]) as [Close], -- All values of [Close] in the same interval are the same... 
     MIN([Low]) as [Low], 
     MAX([High]) as [High] 
FROM CTEOpenClose 
GROUP BY Interval 

Ergebnisse:

MinuteBar    Open  Close  Low   High 
2015-01-01 17:00:00.000 1.557870 1.558030 1.557870 1.558100 
2015-01-01 17:05:00.000 1.558580 1.557970 1.557870 1.558710 
+0

Danke Ich werde das in Kürze testen, derzeit in einer Besprechung. Ich hatte gehofft, die Anzahl der Schritte zu reduzieren, und ich möchte etwas, das über 600.000 Datensätze weit übertrifft. Werde später antworten, sobald ich getestet habe – Tanner

1

Demo here

;with cte 
as 
(--this can be your permanent table with intervals ,rather than generating on fly 
select cast('2015-01-01 17:00:00.000' as datetime) as interval,dateadd(mi,5,'2015-01-01 17:00:00.000') as nxtinterval 
union all 
select dateadd(mi,5,interval),dateadd(mi,5,nxtinterval) from cte 
where interval<='2015-01-01 17:45:00.000' 

) 
,finalcte 
as 
(select minutebar, 
low,high, 
dense_rank() over (order by interval,nxtinterval) as grpd, 
last_value([close]) over (partition by interval,nxtinterval order by interval,nxtinterval) as [close], 
first_value([open]) over (partition by interval,nxtinterval order by interval,nxtinterval) as [open] 
from cte c 
join 
#minutedata m 
on m.minutebar between interval and nxtinterval 
) 
select 
min(minutebar) as minutebar, 
min(low) as 'low', 
max(high) as 'High', 
max([open]) as 'open', 
max([close]) as 'close' 
from finalcte 
group by grpd 
+0

Wie haben Sie die Schaltfläche "Demo hier" erhalten? – Xedni

+1

@Xedni: Verwenden Sie behalten Sie etwas Text TheGameiswar