简体   繁体   中英

How to get specified number of prior rows from a specified row in SQL Server 2014?

In the following set of data in SQL Server 2014 how would I select prices >= 2/1/2014 and then the 7 rows prior to 2/1/2014?

Date        Price
2014-01-23  10269.389648
2014-01-24  10034.44043
2014-01-27  9981.349609
2014-01-28  10066.839844
2014-01-29  9962.919922
2014-01-30  10048.69043
2014-01-31  9967.650391
2014-02-03  9741.580078
2014-02-04  9816.969727
2014-02-05  9809.030273
2014-02-06  9940.219727
2014-02-07  10055.339844
2014-02-10  10050.400391

I can easily get the rows >= 2/1 in a WHERE clause, but I'm not sure how to select the 5 prior rows since the dates are not contiguous. I can't use a start date 5 days prior to 2/1 because I need a specified # of prices prior to 2/1. If I just subtract 7 days prior to 2/1 I don't get the correct # of prices/rows.

Here's a way using ROW_NUMBER to get the previous 7 records before 2014-02-01 :

;With Previous As
(
    Select  Date, Price, Row_Number() Over (Order By Date Desc) RN
    From    Table
    Where   Date < '2014-02-01'
),
Cur As
(
    Select  Date, Price
    From    Table
    Where   Date >= '2014-02-01'
)
Select  Date, Price
From    Cur
Union All
Select  Date, Price
From    Previous
Where   RN <= 7

Here is one more variant, based on what @Siyual suggested, but simplified a bit. You don't need to generate row numbers for the whole table in order to select only 7 rows. We can use simple TOP(7) here. In this case optimizer is smart enough to introduce the TOP operator into the plan with ROW_NUMBER , but still there is slight overhead.

WITH
CTE_Prev
AS
(
    SELECT TOP(7) [Date], Price
    FROM T
    WHERE [Date] < '2014-02-01'
    ORDER BY [Date] DESC
)
,CTE_Curr
AS
(
    SELECT [Date], Price
    FROM T
    WHERE [Date] >= '2014-02-01'
)
SELECT [Date], Price
FROM CTE_Prev

UNION ALL

SELECT [Date], Price
FROM CTE_Curr
;

If you create supporting index

CREATE NONCLUSTERED INDEX [IX_Date] ON [dbo].[T]
(
    [Date] ASC
)
INCLUDE ([Price])

Then the plan looks like this:

最佳

The plan for the variant with ROW_NUMBER looks like this:

ROW_NUMBER

You can see that in both variants only 7 previous rows were read, not the whole table.

Variants suggested by Juan Carlos Oropeza are significantly more complex and slower. They scan the whole table. His first variant does it twice. His second variant does it once, but with intermediate Sort.

娟

娟

I added 700,000 rows to the test table with dates before 2014-02-01 :

CREATE TABLE T
    ([Date] datetime, [Price] numeric)
;
GO

INSERT INTO T ([Date], [Price]) VALUES
('2014-01-23 00:00:00', 10269.389648),
('2014-01-24 00:00:00', 10034.44043),
('2014-01-27 00:00:00', 9981.349609),
('2014-01-28 00:00:00', 10066.839844),
('2014-01-29 00:00:00', 9962.919922),
('2014-01-30 00:00:00', 10048.69043),
('2014-01-31 00:00:00', 9967.650391),
('2014-02-03 00:00:00', 9741.580078),
('2014-02-04 00:00:00', 9816.969727),
('2014-02-05 00:00:00', 9809.030273),
('2014-02-06 00:00:00', 9940.219727),
('2014-02-07 00:00:00', 10055.339844),
('2014-02-10 00:00:00', 10050.400391);
GO

INSERT INTO T ([Date], [Price]) VALUES
('2013-01-23 00:00:00', 10269.389648),
('2013-01-24 00:00:00', 10034.44043),
('2013-01-27 00:00:00', 9981.349609),
('2013-01-28 00:00:00', 10066.839844),
('2013-01-29 00:00:00', 9962.919922),
('2013-01-30 00:00:00', 10048.69043),
('2013-01-31 00:00:00', 9967.650391);
GO 100000

I dont like the union. So I just calculate what is the min value for >= '2014-02-01'

WITH withID as 
(
  SELECT *, row_number() over (order by [Date]) rn
  FROM Prices
),
myDate as 
(
   SELECT min(rn) minRN
   FROM withID
   WHERE   [Date] >= '2014-02-01'
)
SELECT *
FROM withID, myDate
WHERE rn + 7 >=  myDate.minRN   

EDIT SQL Fiddle Demo

I found another cool way to do the same. Using partition by to split the date in BEFORE and AFTER . Then bring all AFTER with all rn <= 7 from BEFORE

WITH withID as 
(
  SELECT 
      *,
      case 
         when [Date] >= '2014-02-01' THEN 'AFTER'
         else  'BEFORE'
      END as dateRange
      , row_number() over (partition by 
                                  case 
                                     when [Date] >= '2014-02-01' THEN 'AFTER'
                                     else  'BEFORE'
                                  END
                           order by [Date] DESC) rn
  FROM Prices
)
SELECT *
FROM withID
WHERE 
    dateRange = 'AFTER'
OR  rn <= 7

OUTPUT

|       Date | price | dateRange | rn |
|------------|-------|-----------|----|
| 10/02/2014 | 10050 |     AFTER |  1 |
| 07/02/2014 | 10055 |     AFTER |  2 |
| 06/02/2014 |  9940 |     AFTER |  3 |
| 05/02/2014 |  9809 |     AFTER |  4 |
| 04/02/2014 |  9817 |     AFTER |  5 |
| 03/02/2014 |  9742 |     AFTER |  6 |
| 31/01/2014 |  9968 |    BEFORE |  1 |
| 30/01/2014 | 10049 |    BEFORE |  2 |
| 29/01/2014 |  9963 |    BEFORE |  3 |
| 28/01/2014 | 10067 |    BEFORE |  4 |
| 27/01/2014 |  9981 |    BEFORE |  5 |
| 24/01/2014 | 10034 |    BEFORE |  6 |
| 23/01/2014 | 10269 |    BEFORE |  7 |

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