简体   繁体   中英

T-SQL Query to return the closest values

I've got some data in a SQL Server table as followings:

DateTime                Bid       Ask 
02/10/2017 09:59.323    123.111   123.894 
02/10/2017 10:01.432    123.321   124.001  
02/10/2017 10:03.132    123.421   124.121
02/10/2017 10:03.983    123.121   123.721 
02/10/2017 10:04.342    123.587   124.200

What I'd like to query is what the Bid and Ask values were at each second time period. For example at:

  • 10:00.000 the Bid was 123.111 and the Ask was 123.894
  • 10:01.000 the Bid was 123.111 and the Ask was 123.894
  • 10:02.000 the Bid was 123.321 and the Ask was 124.001
  • 10:03.000 the Bid was 123.321 and the Ask was 124.001
  • 10:04.000 the Bid was 123.121 and the Ask was 123.721

So the SQL needs to return the Bid and Ask values for the Date Time before each second value.

For example for:

  • 10:00.000 use 09:59.323
  • 10:01.000 use 09:59.323
  • 10:02.000 use 10:01.432
  • 10:03.000 use 10:01.432
  • 10:04.000 use 10:03.983

And my query will return values between a start and end date/time, so it'll return multiple rows.

Order the table by datetime decending, limit the return to 1 and then make sure that any values returned are lower than the supplied datetime.

[edit]

select top 1 bid, ask
from datatable
where itsdatetime <= '02/10/2017 10:00.000'
order by itsdatetime desc

You can get more creative and put that inside of condition subquery.

select *
from requiretimes rt
join datatable dt on dt.itsdatetime = (select top 1 itsdatetime
                    from datatable
                    where itsdatetime <= rt.requireddatetime
                    order by itsdatetime desc)

You can try this query:

if object_id('tempdb..#Table1') is not null
  drop table #Table1
go
create table #Table1(
DateTime datetime
,Bid float     
,Ask float)

insert into #Table1
select '02/10/2017 09:59.323', 123.111, 123.894 
union all select '02/10/2017 10:01.432', 123.321, 124.001  
union all select '02/10/2017 10:03.132', 123.421, 124.121
union all select '02/10/2017 10:03.983', 123.121, 123.721 
union all select '02/10/2017 10:04.342', 123.587, 124.200

declare @start_date datetime
      , @end_date datetime

select @start_date = dateadd(mi, datediff(mi, 0, min(DateTime)) + 1, 0)
  from #Table1

select @end_date = dateadd(mi, datediff(mi, 0, max(DateTime)), 0) 
  from #Table1

;with generates_dates as(
select @start_date as dt
union all
select dateadd(mi, 1, dt) as dt
  from generates_dates
  where dt < @end_date)

select t1.dt
     , t2.Bid
     , t2.Ask
  from generates_dates t1
    cross apply(select top 1 Bid, Ask 
                  from #Table1 t2 
                  where t2.DateTime < t1.dt 
                  order by t2.DateTime desc)t2(Bid, Ask)
  option (maxrecursion 0)

There are two parts to this:

  1. Create a projection holding your timestamps: 10:00.000, 10:01.000, 10:02.000 etc. This is hard to show in an answer here, because we don't know what criteria you're using to determine your start and end ranges, and because your question asks about seconds, but your timestamp values are actually showing minutes . If you need help with this, there are lots of results in Google and here on Stack Overflow for creating projections, number tables, or sequences.

  2. Use the OUTER APPLY operator to connect the projection back to your original data. OUTER APPLY makes it easy to show exactly the one right record for each item from your projection.

.

WITH times As (
    SELECT cast('2017-02-10 10:00.000' as datetime) "DateTime"
    UNION
    SELECT cast('2017-02-10 10:01.00' as datetime)
    UNION
    SELECT cast('2017-02-10 10:02.000' as datetime) 
    UNION
    SELECT cast('2017-02-10 10:03.000' as datetime)
    UNION
    SELECT cast('2017-02-10 10:04.000' as datetime)
    UNION
    SELECT cast('2017-02-10 10:05.000' as datetime)
)
SELECT t.[DateTime], u.[DateTime], u.Bid, u.Ask
FROM times t
CROSS APPLY ( 
      SELECT TOP 1 * 
      FROM [MyTable] 
      WHERE [DateTime] < t.[DateTime] 
      ORDER BY [DateTime] DESC
) u
ORDER BY t.[DateTime]

SQLFiddle

DECLARE @T TABLE 
(
    d   DateTime, 
    bid MONEY,
    ask MONEY
)
INSERT INTO @T VALUES
('02/10/2017 09:59.323',    123.111,   123.894),
('02/10/2017 10:01.432',    123.321,   124.001),  
('02/10/2017 10:03.132',    123.421,   124.121),
('02/10/2017 10:03.983',    123.121,   123.721), 
('02/10/2017 10:04.342',    123.587,   124.200),
('03/10/2017 10:04.342',    123.587,   124.200)

;WITH sec AS 
(
    SELECT TOP (SELECT 60*60*24) ROW_NUMBER() OVER (ORDER BY 1/0) as s
    FROM master..spt_values a,master..spt_values m
), dd as
(
    SELECT DISTINCT CAST(d as date ) as d
    FROM @t
), Tbl as 
(
    SELECT
        DATEADD(ss,b.s,CAST(a.d as datetime)) as dat
    FROM     
        dd  a
    CROSS JOIN 
        sec b   
)
SELECT
    dat 
    ,c.*
FROM     tbl
CROSS APPLY 
(
    SELECT TOP 1 *
    FROM @t a
    WHERE 
        a.d >= tbl.dat
    ORDER BY 
        a.d ASC
) as c
WHERE 
    c.d >= dat  AND 
ORDER BY dat

You'd generate the seconds with a recursive query. (As SQL Server doesn't support ANSI timestamp literals, you'll need CONVERT for this.) Then join via CROSS APPLY to get the last entry from the table per second.

with secs(sec) as
(
  select convert(datetime, '2017-10-02 10:00:00', 20) as sec
  union all
  select dateadd(second, 1, sec) as sec 
  from secs
  where sec <= convert(datetime, '2017-10-02 10:00:04', 20)
)
select secs.sec, data.bid, data.ask
from secs
cross apply
(
  select top(1) *
  from mytable
  where mytable.datetime <= secs.sec
  order by datetime desc
) data;

I am using seconds here as per your description, while your sample uses minutes instead. Decide which you actually need.

Try this:

declare @idate datetime
declare @fdate datetime

select @idate = min(gendate) from BidAsk
select @fdate = max(gendate) from BidAsk

create table #temp (bid float, ask float, Gendate datetime)

while (@idate <= @fdate)
begin
   insert into #temp
   select top 1 Bid, Ask, @idate from BidAsk where @idate > GenDate
   order by GenDate desc

   set @idate = DATEADD(second,1,@idate)
end

select * from #temp

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM