2012-04-21 19 views
5

の問い合わせ:SQL Serverの - 私はこのようなテーブル構造がある場合は最も近い日付の範囲

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は、再帰CTEと何もできない
など

+0

バージョンは何? –

+0

私はSQL Server 2005を考えています。[sql-server-2005]タグで2つの質問があります。 –

答えて

0

を返さないだろう。

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年にそのような変数を宣言することはできないと思っています。 – deutschZuid

+0

@JamesJiao 2008年に導入されたシンタックス・シュガーです。この例とは無関係です。あなたは 'source_table' CTEを削除し、その名前を実際のテーブル名で置き換えることになっています。変数宣言は、2つの行(宣言と代入)に分割できます。 – GSerg

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'節では許されません。 – GSerg

+0

@GSerg:おそらく古いSQL Serverのバージョンを使用しています。 SQL Fiddleの例が動作します。 – Andomar

+0

@GSerg集約のための 'OVER'句は、SQL Server 2012で導入されました(合計の実行には役に立ちます)。 –

0

注:私はあまり論理的には(より良い性能)を読み取り有する第2溶液(非再帰的)を追加しました。

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を使用している可能性があります。残念ながら、唯一のSQL Server 2012の上LAG window function作品、およびSQL Server 2005の上PostgreSQL 8.4 and above ;-)

作品私が想定し、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

あなたは(2005+と仮定SQL Serverの)このような何かを試みることができる:

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を使用することができます。

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、あまりにもうまくスケール:SQL Serverののhttp://www.ienablemuch.com/2012/04/outer-apply-walkthrough.html

関連する問題