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

ProductCode特定のandの日付範囲を照会する方法はありますDateか (範囲が連続している必要があると仮定して)? つまり、このテーブルでは、Foo は 3 つの日付範囲に存在します4/1-4/34/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/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フィドルでの例。

于 2012-04-21T13:33:45.087 に答える
1

SQL Server 2005 でサポートされていれば、LAG を使用できたはずです。残念ながら、LAG ウィンドウ関数は SQL Server 2012 でのみ動作し、PostgreSQL 8.4以降で動作します;-)

SQL Server 2005 で動作すると思いましたが、SQLFiddle には SQL 2005 のサポートがなく、2012 ではなく SQLFiddle の SQL Server 2008 のみを試しました。

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

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 APPLYJOINと比較すると、スケーリングも適切です:http ://www.ienablemuch.com/2012/04/outer-apply-walkthrough.html

于 2012-04-23T12:07:30.317 に答える
0

注:論理読み取りが少ない(パフォーマンスが向上する)2番目のソリューション(非再帰的)を追加しました。

  1. 再帰的な CTEを使用できます(デモはこちら):

    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 --次の日を追加 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';

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

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

    DECLARE @StartDate SMALLDATETIME, @EndDate SMALLDATETIME;

    SELECT @EndDate = MAX(b.[日付]) FROM
    ( SELECT a.[日付], ROW_NUMBER() OVER(ORDER BY a.Date ASC)-1 AS RowNum FROM @Test a WHERE a.ProductCode = @MyProductCode AND a .[日付] >= @MyDate ) b WHERE b.[日付] = 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 .[日付] <= @MyDate ) b WHERE b.[日付] = 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
于 2012-04-21T13:41:38.020 に答える
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

次のようなことを試すことができます (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 に答える