5

如果我有这样的表结构:

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/34/6-4/10; 并且4/15-4/17我正在寻找给定日期的日期范围。

请注意,没有Foo日期的4/44/54/114/12和。4/134/14

示例:
ProductCode=Foo, Date=4/2将返回4/1-4/3,因为条目是连续的。
ProductCode=Foo, Date=4/4将不返回任何内容
ProductCode=Foo, Date=4/74/6-4/10因为条目是连续的。
ProductCode=Foo, Date=4/12不会返回任何东西
等。

4

6 回答 6

1

当前一天没有行时,新范围开始。如果您运行的是 SQL Server 2012,则可以使用lag窗口函数来检查一行是否引入了新范围。一旦您知道哪些行引入了一个新范围,您就可以计算头行的数量,以便为每个范围分配一个唯一编号。

拥有一个范围编号,您可以使用 和 查找开始和结束min日期max。之后,这只是选择行的问题:

; 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'

SQL Fiddle 的示例。

于 2012-04-21T13:33:45.087 回答
1

如果 SQL Server 2005 支持它,可以使用 LAG。不幸的是, LAG 窗口功能仅适用于 SQL Server 2012,以及PostgreSQL 8.4 及更高版本;-)

我想可以在 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

于 2012-04-22T03:06:40.820 回答
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
于 2012-04-21T13:30:56.790 回答
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与 JOIN 相比,也可以很好地扩展:http ://www.ienablemuch.com/2012/04/outer-apply-walkthrough.html

于 2012-04-23T12:07:30.317 回答
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
于 2012-04-23T01:00:10.090 回答
0

注意:我添加了第二个解决方案(非递归),它的逻辑读取更少(性能更好)。

  1. 您可以使用递归 CTE (此处为演示):

    DECLARE @Test TABLE ( ID INT IDENTITY NOT NULL UNIQUE, --ID 用于插入订单 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';

    声明@MyProductCode VARCHAR(10)、@MyDate SMALLDATETIME;

    选择 @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 --添加接下来的几天 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 --添加前几天 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] /*--或 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
  1. 非递归解决方案:

    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';

    声明@MyProductCode VARCHAR(10)、@MyDate SMALLDATETIME;

    选择 @MyProductCode = 'Foo',@MyDate = '20120402';

    声明@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);

    选择@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
于 2012-04-21T13:41:38.020 回答