2012-04-21 47 views
5

如果我有一個表的結構是這樣的:SQL服務器 - 查詢最近的日期範圍

ProductCode Date 
Foo   4/1/2012 
Foo   4/2/2012 
Foo   4/3/2012 
Foo   4/6/2012 
Foo   4/7/2012 
Foo   4/8/2012 
Foo   4/9/2012 
Foo   4/10/2012 
Foo   4/15/2012 
Foo   4/16/2012 
Foo   4/17/2012 

有沒有一種方法來查詢的日期範圍給定ProductCodeDate(假設範圍必須是連續的)?換句話說,對於這個表格,Foo存在3個日期範圍:4/1-4/3; 4/6-4/10;和4/15-4/17,我正在查找給定日期的日期範圍。

請注意,Foo沒有日期的4/44/54/114/124/134/14

示例:
ProductCode=Foo, Date=4/2將返回4/1-4/3,因爲條目是連續的。
ProductCode=Foo, Date=4/4將不會返回任何東西
ProductCode=Foo, Date=4/7將返回4/6-4/10,因爲條目是連續的。
ProductCode=Foo, Date=4/12將返回任何

+0

什麼版本的SQL Server? – 2012-04-21 13:50:58

+0

我認爲SQL Server 2005.他/她有兩個問題[sql-server-2005]標記。 – 2012-04-21 14:30:50

回答

0

可以用遞歸CTE做。

declare @target_date datetime = convert(datetime, '04/07/2012', 101); 

with source_table as (
    select ProductCode, convert(datetime, Date, 101) as Date 
    from (
    values 
    ('Foo', '4/1/2012') 
    ,('Foo', '4/2/2012') 
    ,('Foo', '4/3/2012') 
    ,('Foo', '4/6/2012') 
    ,('Foo', '4/7/2012') 
    ,('Foo', '4/8/2012') 
    ,('Foo', '4/9/2012') 
    ,('Foo', '4/10/2012') 
    ,('Foo', '4/15/2012') 
    ,('Foo', '4/16/2012') 
    ,('Foo', '4/17/2012') 
) foo(ProductCode, Date) 
), 
recursive_date_lower as (
    select Date from source_table where Date = @target_date 

    union all 

    select dateadd(d, -1, r.Date) from recursive_date_lower r where exists (select 0 from source_table s where s.Date = dateadd(d, -1, r.Date)) 
), 
recursive_date_upper as (
    select Date from source_table where Date = @target_date 

    union all 

    select dateadd(d, 1, r.Date) from recursive_date_upper r where exists (select 0 from source_table s where s.Date = dateadd(d, 1, r.Date)) 
) 
select 
    (select min(Date) from recursive_date_lower) as start, 
    (select max(Date) from recursive_date_upper) as finish 
+0

根本不在SQL 2005上運行。我不認爲你可以在2005年聲明一個類似於這樣的變量,並且'values'不會在沒有'insert'的情況下獨立存在,至少是afaik! – deutschZuid 2012-04-23 03:38:17

+0

@JamesJiao它是2008年引入的語法糖。與示例無關,您應該刪除'source_table' CTE,並將查詢中的名稱替換爲實際的表名。變量聲明可以分成兩行(聲明然後賦值)。 – GSerg 2012-04-23 09:16:03

1

新的範圍從前一天沒有行開始。如果您正在運行SQL Server 2012,則可以使用lag窗口函數來檢查行是否引入新範圍。一旦你知道哪些行引入了一個新的範圍,你可以計算頭行的數量來爲每個範圍分配一個唯一的數字。

有一個範圍編號,您可以使用minmax查找開始日期和結束日期。在此之後,它只是一個選擇行的問題:

; with IsHead as 
     (
     select ProductCode 
     ,  Date 
     ,  case when lag(Date) over (partition by ProductCode 
        order by Date) = dateadd(day, -1, Date) then 0 
        else 1 end as IsHead 
     from YourTable 
     ) 
,  RangeNumber as 
     (
     select ProductCode 
     ,  Date 
     ,  sum(IsHead) over (partition by ProductCode order by Date) 
        as RangeNr 
     from IsHead 
     ) 
,  Ranges as 
     (
     select * 
     ,  min(Date) over (partition by RangeNr) as RangeStart 
     ,  max(Date) over (partition by RangeNr) as RangeEnd 
     from RangeNumber 
     ) 
select * 
from Ranges 
where ProductCode = 'Bar' 
     and Date = '4/2/2012' 

Example at SQL Fiddle.

+0

不編譯。 'order'附近的語法不正確。''sum''不允許在'over'子句中用於彙總窗口函數,例如'sum'。 – GSerg 2012-04-21 13:44:35

+0

@GSerg:您可能正在使用舊的SQL Server版本。 SQL Fiddle示例有效。 – Andomar 2012-04-21 13:46:23

+0

@GSerg在SQL Server 2012中引入了用於聚合的「OVER」子句(適用於運行總計) – 2012-04-21 13:52:00

0

注:我已經添加了具有較少的邏輯讀取(更好的性能)的第二溶液(非遞歸)。

1)你可以使用一個recursive CTE(演示here):

DECLARE @Test TABLE 
(
    ID   INT IDENTITY NOT NULL UNIQUE, --ID is for insert order 
    ProductCode VARCHAR(10) NOT NULL, 
    [Date]  SMALLDATETIME NOT NULL, 
    PRIMARY KEY(ProductCode, [Date]) 
); 

INSERT @Test (ProductCode , [Date]) 
      SELECT 'Foo' , '20120401' 
UNION ALL SELECT 'Foo' , '20120402' 
UNION ALL SELECT 'Foo' , '20120403' 

UNION ALL SELECT 'Foo' , '20120404' 
--UNION ALL SELECT 'Foo' , '20120405' 

UNION ALL SELECT 'Foo' , '20120406' 
UNION ALL SELECT 'Foo' , '20120407' 
UNION ALL SELECT 'Foo' , '20120408' 
UNION ALL SELECT 'Foo' , '20120409' 
UNION ALL SELECT 'Foo' , '20120410' 
UNION ALL SELECT 'Foo' , '20120415' 
UNION ALL SELECT 'Foo' , '20120416' 
UNION ALL SELECT 'Foo' , '20120417'; 

DECLARE @MyProductCode VARCHAR(10), 
     @MyDate SMALLDATETIME; 


SELECT @MyProductCode = 'Foo', 
     @MyDate = '20120402'; 

WITH CteRecursive 
AS 
(
     --Starting row 
     SELECT t.ID, 
       t.ProductCode, 
       t.[Date], 
       1 AS RowType 
     FROM @Test t 
     WHERE t.ProductCode = @MyProductCode 
     AND  t.[Date] = @MyDate 
     UNION ALL 
     --Add the next days DATEADD(DAY, +1, ..) 
     SELECT crt.ID, 
       crt.ProductCode, 
       crt.[Date], 
       2 AS RowType 
     FROM CteRecursive prev 
     INNER JOIN @Test crt ON DATEADD(DAY, 1, prev.[Date]) = crt.[Date] AND prev.RowType IN (1,2) 
     UNION ALL 
     --Add the previous days DATEADD(DAY, -1, ..) 
     SELECT crt.ID, 
       crt.ProductCode, 
       crt.[Date], 
       0 AS RowType 
     FROM CteRecursive prev 
     INNER JOIN @Test crt ON DATEADD(DAY, -1, prev.[Date]) = crt.[Date] AND prev.RowType IN (0,1) 
) 
SELECT * 
FROM CteRecursive r 
ORDER BY r.[Date] 
/*--Or 
SELECT MIN(r.[Date]) AS BeginDate, MAX(r.[Date]) AS EndDate 
FROM CteRecursive r 
*/ 

結果:

ID   ProductCode Date     RowType 
----------- ----------- ----------------------- ------- 
1   Foo   2012-04-01 00:00:00  0 
2   Foo   2012-04-02 00:00:00  1 
3   Foo   2012-04-03 00:00:00  2 
4   Foo   2012-04-04 00:00:00  2 

2)非遞歸解決方案:

DECLARE @Test TABLE 
(
    ProductCode VARCHAR(10) NOT NULL, 
    [Date]  SMALLDATETIME NOT NULL, 
    PRIMARY KEY(ProductCode, [Date]) 
); 

INSERT @Test (ProductCode , [Date]) 
      SELECT 'Foo' , '20120401' 
UNION ALL SELECT 'Foo' , '20120402' 
UNION ALL SELECT 'Foo' , '20120403' 

UNION ALL SELECT 'Foo' , '20120404' 
--UNION ALL SELECT 'Foo' , '20120405' 

UNION ALL SELECT 'Foo' , '20120406' 
UNION ALL SELECT 'Foo' , '20120407' 
UNION ALL SELECT 'Foo' , '20120408' 
UNION ALL SELECT 'Foo' , '20120409' 
UNION ALL SELECT 'Foo' , '20120410' 
UNION ALL SELECT 'Foo' , '20120415' 
UNION ALL SELECT 'Foo' , '20120416' 
UNION ALL SELECT 'Foo' , '20120417'; 

DECLARE @MyProductCode VARCHAR(10), 
     @MyDate SMALLDATETIME; 


SELECT @MyProductCode = 'Foo', 
     @MyDate = '20120402'; 

DECLARE @StartDate SMALLDATETIME, 
     @EndDate SMALLDATETIME; 

SELECT @EndDate = MAX(b.[Date]) 
FROM  
(
     SELECT a.[Date], 
       ROW_NUMBER() OVER(ORDER BY a.Date ASC)-1 AS RowNum 
     FROM @Test a 
     WHERE a.ProductCode = @MyProductCode 
     AND  a.[Date] >= @MyDate 
) b 
WHERE b.[Date] = DATEADD(DAY, b.RowNum, @MyDate); 

SELECT @StartDate = MIN(b.[Date]) 
FROM  
(
     SELECT a.[Date], 
       ROW_NUMBER() OVER(ORDER BY a.Date DESC)-1 AS RowNum 
     FROM @Test a 
     WHERE a.ProductCode = @MyProductCode 
     AND  a.[Date] <= @MyDate 
) b 
WHERE b.[Date] = DATEADD(DAY, -b.RowNum, @MyDate); 

SELECT @StartDate [@StartDate], @EndDate [@EndDate]; 
SELECT LEFT(CONVERT(VARCHAR(10), @StartDate, 101),5) [@StartDate], LEFT(CONVERT(VARCHAR(10), @EndDate, 101),5) [@EndDate]; 

結果:

@StartDate    @EndDate 
----------------------- ----------------------- 
2012-04-01 00:00:00  2012-04-04 00:00:00 

@StartDate @EndDate 
---------- -------- 
04/01  04/04 
1

如果SQL Server 2005支持它,可以使用LAG。不幸的是LAG window function作品的SQL Server 2012只,並PostgreSQL 8.4 and above ;-)

工程SQL Server 2005中我應該上,SQLFiddle沒有SQL 2005的支持,試圖SQLFiddle的SQL Server 2008中唯一的,而不是2012:

with DetectLeaders as 
(
    select cr.ProductCode, CurRowDate = cr.Date, PrevRowDate = pr.Date 
    from tbl cr 
    left join tbl pr 
    on pr.ProductCode = cr.ProductCode AND cr.Date = DATEADD(DAY,1,pr.Date) 
), 
MembersLeaders as 
(
    select *, 
     MemberLeader = 
      (select top 1 CurRowDate 
      from DetectLeaders nearest 
      where nearest.PrevRowDate is null 
       and nearest.ProductCode = DetectLeaders.ProductCode 
       and DetectLeaders.CurRowDate >= nearest.CurRowDate 
      order by nearest.CurRowDate desc) 
    from DetectLeaders 
) 
select BeginDate = MIN(CurRowDate), EndDate = MAX(CurRowDate) 
from MembersLeaders 
where MemberLeader = 
    (select MemberLeader 
    from MembersLeaders 
    where ProductCode = 'Foo' and CurRowDate = '4/7/2012') 

現場試驗:http://sqlfiddle.com/#!3/3fd1f/1


基本上,這是它如何工作的:

PRODUCTCODE  CURROWDATE PREVROWDATE MEMBERLEADER 
Foo    2012-04-01    2012-04-01 
Foo    2012-04-02 2012-04-01 2012-04-01 
Foo    2012-04-03 2012-04-02 2012-04-01 
Foo    2012-04-06    2012-04-06 
Foo    2012-04-07 2012-04-06 2012-04-06 
Foo    2012-04-08 2012-04-07 2012-04-06 
Foo    2012-04-09 2012-04-08 2012-04-06 
Foo    2012-04-10 2012-04-09 2012-04-06 
Foo    2012-04-15    2012-04-15 
Foo    2012-04-16 2012-04-15 2012-04-15 
Foo    2012-04-17 2012-04-16 2012-04-15 
Bar    2012-05-01    2012-05-01 
Bar    2012-05-02 2012-05-01 2012-05-01 
Bar    2012-05-03 2012-05-02 2012-05-01 
Bar    2012-05-06    2012-05-06 
Bar    2012-05-07 2012-05-06 2012-05-06 
Bar    2012-05-08 2012-05-07 2012-05-06 
Bar    2012-05-09 2012-05-08 2012-05-06 
Bar    2012-05-10 2012-05-09 2012-05-06 
Bar    2012-05-15    2012-05-15 
Bar    2012-05-16 2012-05-15 2012-05-15 
Bar    2012-05-17 2012-05-16 2012-05-15 

http://sqlfiddle.com/#!3/35818/11

0

你可以嘗試這樣的事情(假設SQL Server的2005+):

WITH partitioned AS (
    SELECT 
    ProductCode, 
    Date, 
    GroupID = DATEDIFF(DAY, 0, Date) 
      - ROW_NUMBER() OVER (PARTITION BY ProductCode ORDER BY Date) 
    FROM atable 
), 
ranges AS (
    SELECT 
    ProductCode, 
    Date, 
    MinDate = MIN(Date) OVER (PARTITION BY ProductCode, GroupID), 
    MaxDate = MAX(Date) OVER (PARTITION BY ProductCode, GroupID) 
    FROM partitioned 
) 
SELECT 
    MinDate, 
    MaxDate 
FROM ranges 
WHERE ProductCode = @ProductCode 
    AND Date = @Date 
0

您還可以使用CROSS APPLY找到最近的日期:

with DetectLeaders as 
(
    select cr.ProductCode, CurRowDate = cr.Date, PrevRowDate = pr.Date 
    from tbl cr 
    left join tbl pr 
    on pr.ProductCode = cr.ProductCode AND cr.Date = DATEADD(DAY,1,pr.Date) 
), 
MembersLeaders as 
(
    select *  
    from DetectLeaders 
    cross apply(
     select top 1 MemberLeader = CurRowDate 
     from DetectLeaders nearest 
     where nearest.PrevRowDate is null 
      and nearest.ProductCode = DetectLeaders.ProductCode 
      and DetectLeaders.CurRowDate >= nearest.CurRowDate 
     order by nearest.CurRowDate desc 
    ) as xxx 
) 
select BeginDate = MIN(CurRowDate), EndDate = MAX(CurRowDate) 
from MembersLeaders 
where MemberLeader = 
    (select MemberLeader 
    from MembersLeaders 
    where ProductCode = 'Foo' and CurRowDate = '4/7/2012') 

現場試:http://sqlfiddle.com/#!3/3fd1f/2


基本上,這是它如何工作的:

PRODUCTCODE  CURROWDATE PREVROWDATE MEMBERLEADER 
Foo    2012-04-01    2012-04-01 
Foo    2012-04-02 2012-04-01 2012-04-01 
Foo    2012-04-03 2012-04-02 2012-04-01 
Foo    2012-04-06    2012-04-06 
Foo    2012-04-07 2012-04-06 2012-04-06 
Foo    2012-04-08 2012-04-07 2012-04-06 
Foo    2012-04-09 2012-04-08 2012-04-06 
Foo    2012-04-10 2012-04-09 2012-04-06 
Foo    2012-04-15    2012-04-15 
Foo    2012-04-16 2012-04-15 2012-04-15 
Foo    2012-04-17 2012-04-16 2012-04-15 
Bar    2012-05-01    2012-05-01 
Bar    2012-05-02 2012-05-01 2012-05-01 
Bar    2012-05-03 2012-05-02 2012-05-01 
Bar    2012-05-06    2012-05-06 
Bar    2012-05-07 2012-05-06 2012-05-06 
Bar    2012-05-08 2012-05-07 2012-05-06 
Bar    2012-05-09 2012-05-08 2012-05-06 
Bar    2012-05-10 2012-05-09 2012-05-06 
Bar    2012-05-15    2012-05-15 
Bar    2012-05-16 2012-05-15 2012-05-15 
Bar    2012-05-17 2012-05-16 2012-05-15 

http://www.sqlfiddle.com/#!3/3fd1f/3

CROSS APPLY/OUTER APPLY相比加盟,鱗片也很好:http://www.ienablemuch.com/2012/04/outer-apply-walkthrough.html