Question

I have a set of data with a DateTime, say CalculatedOn what I would like is to get start at the current date getdate() and get an x amount of records from before the current date, and the same amount from after.

If x = 50 then 50 prior to now and 50 in front of now. I was thinking rownumber() would be perfect for this, however I cannot think of how to number the rows negative for prior and positive for future.

Also there is the issue of if there are not 50 prior or future what will happen, but that will come after.

Assume the table has just two columns :

create table MyTable
(
   Id int not null constraint pk_mytable primary key,
   SomeTextIWant nvarchar(50) not null,
   CalculateDate DateTime not null
);

Results :

If today is 25/04 12:54

then

Id, SomeTextIWant, CalculatedDate
-- 50 from before now--
-----now here-----
-- 50 from after now--
Was it helpful?

Solution 2

You can use two CTE's, one for past and one for future dates, then use ROW_NUMBER with ASC and DESC, multiply before now with -1 and concat all:

WITH dataBefore AS
(
    SELECT d.*, rn = (-1) * row_Number() over (Order By CalculatedOn DESC)
    FROM dbo.TableName d
    WHERE CalculatedOn < GetDate()
)
, dataAfter AS
(
    SELECT d.*, rn = row_Number() over (Order By CalculatedOn ASC)
    FROM dbo.TableName d
    WHERE CalculatedOn >= GetDate()
)
SELECT * FROM
(
    SELECT db.*
    FROM dataBefore db
    UNION ALL 
    SELECT da.*
    FROM dataAfter da
)x
WHERE x.rn >= -50 AND x.RN <= 50
ORDER BY x.RN

OTHER TIPS

If you want to get 50 rows before and after, perhaps this will do what you want:

with cte1 as (
      select top 50 t.*
      from table t
      where CalculatedDate <= getdate()
      order by CalculatedDate desc
     ),
     cte2 as (
      select top 50 t.*
      from table t
      where CalculatedDate > getdate()
      order by CalculatedDate
     )
select *
from (select * from cte1 union all select * from cte2) t

EDIT:

It is not clear to me from the context of the question whether a row number is actually needed. It is easy enough to add, thoug:

(select top 50 t.*,
        - row_number() over (order by CalculatedDate desc) as rownumber
 from table t
 where CalculatedDate <= getdate()
 order by CalculatedDate desc
)
union all
(select top 50 t.*,
        row_number() over (order by CalculatedDate) as rownumber
 from table t
 where CalculatedDate > getdate()
 order by CalculatedDate
)

You can actually combine these into one query:

select t.*,
       ((case when CalculatedDate < getdate() then -1 else 1 end) *
        (row_number() over (partition by (case when CalculatedDate < getdate() then 1 else 0 end)
                           order by (case when CalculatedDate < getdate() then CalculatedDate end) desc, 
                                     CalculatedDate asc
                           )
         )) as rn
from table t;

You can put this in a subquery and select where rn between -50 and 50.

However, I'm not sure what to do about row number 0 and the question provides no information on what to do with any records that match getdate() (as unlikely as that is). I think the first answer does what that OP needs.

try this...

With myCte
As
(
    Select top 2 column1,column2 from  YourTable where yourdate > '2014-04-23'
    union 
    Select top 2 column1,column2 from  YourTable where yourdate  < '2014-04-23'
) select ROW_NUMBER() over (order by column1) as RNO,* from myCte

With RowNumber

SELECT TOP 50 ROW_NUMBER() OVER (ORDER BY CalculateDate) AS RowNum,
    id, SomeTextIWant, CalculateDate
FROM MyTable
WHERE CalculateDate > @Date

UNION ALL

SELECT TOP 50 -ROW_NUMBER() OVER (ORDER BY CalculateDate DESC) AS RowNum,
    id, SomeTextIWant, CalculateDate
FROM MyTable
WHERE CalculateDate < @Date

Rather than splitting and UNIONing the tables back up, you could also compute an offset and minus that from the ROW_NUMBER() on the whole table:

WITH
    base AS (
        SELECT
            *,
            ROW_NUMBER() OVER(ORDER BY CalculatedDate) AS row_num
        FROM MyTable
    ),
    offst AS (
        SELECT MIN(row_num) AS row_num
        FROM base
        WHERE CalculatedDate >= CAST(GETDATE() AS DATE)
    )

SELECT
    base.CalculatedDate,
    base.row_num - offst.row_num AS row_num
FROM base
    CROSS JOIN offst
WHERE base.row_num - offst.row_num BETWEEN -50 AND 50

This approach has a few advantages:

  1. From my testing, this is about 33% less costly than the other split-then-UNION approaches and the same cost as Gordon Linoff's short query (tested in SQL Server on a small table without indexes)
  2. This method returns the 101 rows -- the 50 before, the current/now, and the 50 after, with the current/now identified by row_num = 0 in the output
    • The split-then-UNION approaches only return 100 rows because they're including the current/now row as 1 of the 50 before or the 50 after
    • Gordon Linoff's short query would need to be filtered on the computed rn column
  3. It's easy to adjust if you need the row numbers partitioned by another column(s), say an account ID -- just add a GROUP BY to the offst CTE and convert the CROSS JOIN to a LEFT JOIN on the partitioning column(s)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top