Je travaille avec des données actuellement stockées à des intervalles d'une minute qui ressemblent à ceci:
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;
Les valeurs suivent les taux de change, donc pour chaque intervalle de minute (barre), il y a le prix Open
au début de la minute et un prix Close
pour la fin de la minute. Les valeurs High
et Low
représentent le taux le plus élevé et le plus bas au cours de chaque minute individuelle.
Sortie souhaitée
Je cherche à reformater ces données en intervalles de 5 minutes pour produire la sortie suivante:
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
Cela prend la valeur Open
de la première minute du 5, la valeur Close
de la dernière minute du 5. Les valeurs High
et Low
représentent les taux high
et low
les plus élevés sur la période de 5 minutes.
Solution actuelle
J'ai une solution qui fait cela (ci-dessous), mais elle semble inélégante car elle repose sur les valeurs id
et les auto-jointures. En outre, j'ai l'intention de l'exécuter sur des ensembles de données beaucoup plus importants, donc je cherchais à le faire de manière plus efficace si possible:
-- 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
Tentative de reprise
Au lieu des requêtes ci-dessus, j'ai cherché à utiliser FIRST_VALUE et LAST_VALUE , car cela semble être ce que je recherche, mais je ne peux pas vraiment l'obtenir travailler avec le groupement que je fais. Il pourrait y avoir une meilleure solution que ce que j'essaie de faire, donc je suis ouvert aux suggestions. Actuellement j'essaye de faire ceci:
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
Cela me donne l'erreur ci-dessous, qui est liée au FIRST_VALUE
et LAST_VALUE
lorsque la requête s'exécute si je supprime ces lignes:
La colonne '# MinuteData.MinuteBar' n'est pas valide dans la liste de sélection car elle n'est contenue ni dans une fonction d'agrégation ni dans la clause GROUP BY.
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
Une solution proche de la vôtre. Il y a deux endroits où vous vous êtes trompé.
LAST_VALUE est la dernière valeur de la fenêtre actuelle, qui n'est pas spécifiée dans votre requête, et une fenêtre par défaut est des lignes de la première ligne de la partition actuelle à ligne actuelle. Vous pouvez utiliser FIRST_VALUE avec l'ordre de désensemencement ou spécifier une fenêtre
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,
Voici une façon de le faire sans tables temporaires:
;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
Résultats:
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
;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