5

Pracuję z niektórych danych, które są obecnie przechowywane w odstępach 1-minutowych, który wygląda tak:Korzystanie z GROUP BY FIRST_VALUE i 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; 

Wartości śledzić kursów walutowych, więc za każdym przedziale minut (bar), cena zaczyna się od minuty Open a cena za minutę. Wartości High i Low reprezentują najwyższą i najniższą stawkę podczas każdej minuty.

Pożądany Wyjście

szukam formatowanie tych danych do 5-minutowych odstępach, aby następujący wynik:

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 

ta przyjmuje wartość Open od pierwszej minuty z 5 , wartość Close od ostatniej minuty 5. Wartości High i Low reprezentują najwyższe wartości high i najniższe wartości low w okresie 5 minut.

obecne rozwiązanie

Mam rozwiązanie, które wykonuje to (poniżej), ale czuje się nieeleganckie, gdyż opiera się na wartościach i samo id łączy. Również mam zamiar uruchomić go na znacznie większych zbiorów danych więc szukałem to zrobić w sposób bardziej efektywny, jeśli to możliwe:

-- 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 

Rework próba

Zamiast z powyższych pytań, mam patrzyłem na używanie FIRST_VALUE i LAST_VALUE, ponieważ wydaje mi się, że to jest to, o co prosiłem, ale nie mogę tego zrobić, pracując z grupą, którą robię. Może być lepsze rozwiązanie niż to, co próbuję zrobić, więc jestem otwarty na sugestie. Obecnie staram się to zrobić:

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 

To daje mi poniższy błąd, który jest związany z FIRST_VALUE i LAST_VALUE jak biegnie zapytanie czy mogę usunąć te linie:

kolumnie „# MinuteData.MinuteBar "jest niepoprawny na liście select, ponieważ nie jest zawarty ani w funkcji agregującej, ani w klauzuli GROUP BY.

+1

FIRST_VALUE i last_value rzeczywistości nie są zagregowane funkcje jak mogłoby się wydawać. Są bardziej podobne do row_number, gdzie są wyświetlane w pełnym zestawie danych. Problem polega na tym, że próbujesz używać ich jak agregatów, i dlatego to na ciebie krzyczy. Muszę teraz wyruszyć, ale moją pierwszą myślą było rzucić datę na napis, podciąć minutę i połączyć taśmę w zaokrągloną formę. – Xedni

+0

dzięki za odpowiedź, nie użyłem first_value w gniewie przed. daty nie są moim problemem, mam rozwiązanie tego, które wydaje się działać, chociaż mogą istnieć lepsze sposoby na to. głównym problemem jest uzyskiwanie wartości otwartych i zamkniętych dla okresów 5-minutowych. – Tanner

+0

tak wysokie i niskie to po prostu najwyższy najwyższy i najniższy poziom najniższy, ale "otwarte" i "bliskie" są tym, z czym masz problemy, ponieważ te powinny być po prostu pierwsze i ostatnie w przedziale, niezależnie od ich wartości? Czy mam to prawo? – Xedni

Odpowiedz

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 

Rozwiązanie blisko do obecnego. Są dwa miejsca, w których popełniłeś błąd.

  1. FIRST_VALUE I LAST_VALUE są funkcji analitycznych, które pracują na oknie lub partycji, a nie grupy.Możesz uruchomić zapytanie zagnieżdżone samodzielnie i zobaczyć jego wynik.
  2. LAST_VALUE to ostatnia wartość bieżącego okna, która nie została określona w zapytaniu, a domyślne okno to wiersze od pierwszego wiersza bieżącej partycji do bieżącego wiersza. Można użyć FIRST_VALUE z rzędu łuskania nasion lub podanie okna

    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

Dziękuję, spróbuję tego krótko i skontaktuję się z Tobą. Podejrzewałem, że być może potrzebowałem czegoś takiego. – Tanner

+0

Nice. Nie myślałem o dodawaniu Otwieranie i Zamykanie do klauzuli grupy przez. –

+0

To wydaje się być najłatwiejszym rozwiązaniem, które nie wymaga tylu kroków, co inne, i jest najbliższe temu, co próbowałem osiągnąć, chociaż wciąż zastanawiam się, dlaczego część LAST_VALUE nie działa tak, jak się spodziewałem. Mimo to działa to dzięki. – Tanner

1

Oto jeden ze sposobów, aby to zrobić bez tabel tymczasowych:

;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 

Wyniki:

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

Dzięki, przetestuję to wkrótce, obecnie na spotkaniu. Miałem nadzieję, że zmniejszę liczbę kroków i chcę czegoś, co przyniesie ponad 600 000 rekordów. Odpowiem później po przetestowaniu – 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

Skąd masz przycisk "Demo tutaj"? – Xedni

+1

@Xedni: użyj zachowaj trochę tekstu TheGameiswar