2012-04-21 12 views
5

için sorgulama:SQL Server - Böyle bir tablo yapısı varsa en yakın Tarih Aralığı

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 

Verilen ProductCode ve Date (aralıkları sıralı OLMALIDIR varsayarak için tarih aralığında sorgulamak için bir yol var mı)? Başka bir deyişle, bu tablo için, Foo 3 tarih aralığında mevcuttur: 4/1-4/3; 4/6-4/10; ve 4/15-4/17 ve bir tarih verilen tarih aralığını arıyorum.

Foo olmadığını unutmayın tarih en 4/4, 4/5, 4/11, 4/12, 4/13 ve 4/14.

Örnekler: girişler, sıralı çünkü
ProductCode=Foo, Date=4/24/1-4/3 dönecekti.
hiçbiri
ProductCode=Foo, Date=4/74/6-4/10 döndürmez, çünkü girişler sıralıdır.
ProductCode=Foo, Date=4/12 bir özyinelemeli CTE ile yapabileceği hiçbir şey
vb

+0

hangi sürümüTemelde bu nasıl işliyor? –

+0

SQL Server 2005'i düşünüyorum. [Sql-server-2005] etiketi ile iki sorusu var. –

cevap

0

dönecekti.

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'te hiç çalışmıyor. 2005'te böyle bir değişkeni bildirebileceğinizi düşünmüyorum ve 'değerler' en az afaik olan 'insert' olmadan kendi başına durmuyor! – deutschZuid

+0

@JamesJiao 2008'de başlatılan sözdizimi şekeridir. Bu örnekle alakasız, 'source_table' CTE'sini kaldırmanız ve adını gerçek tablo adıyla değiştirmeniz gerekiyor. Değişken beyanı iki satıra ayrılabilir (bildirim sonra ödev). – GSerg

1

Bir önceki gün için hiç satır olmadığında yeni bir aralık başlar. SQL Server 2012 çalıştırıyorsanız, bir satırın yeni bir aralık tanıttıp olmadığını kontrol etmek için lag pencere işlevini kullanabilirsiniz. Hangi satırların yeni bir aralık tanıttığını öğrendikten sonra, her aralığa benzersiz bir numara atamak için başlık satırlarının sayısını sayabilirsiniz.

Aralık numarası olması, başlangıç ​​ve bitiş tarihlerini min ve max ile bulmanızı sağlar. Bundan sonra, bu satırı seçerek sadece bir soru:

; 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

Derleme yapmıyor. ''Sipariş''e yakın hatalı sözdizimi,' toplam 'gibi' toplam 'gibi işlevler için' toplam 'işlevine izin verilmez. – GSerg

+0

@GSerg: Muhtemelen eski bir SQL Server sürümünü kullanıyorsunuzdur. SQL Fiddle örneği çalışır. – Andomar

+0

@GSerg Agregatlar için "OVER" deyimi SQL Server 2012'de tanıtıldı (toplamlar için iyi) –

0

Not: Daha az mantıksal (daha iyi performans) okur sahiptir (özyinesiz) ikinci çözüm ekledik.

1) Bir recursive CTE (demo here) kullanabilirsiniz:

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 
*/ 

Sonuçlar:

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) Sigara özyinelemeli çözüm:

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

Sonuçlar:

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

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

SQL Server 2005 desteklediği takdirde LAG kullanmış olabilir.Maalesef SQL Server 2012 sadece üzerinde LAG window function çalışır ve ben sözde SQL Server 2005 tarihinde PostgreSQL 8.4 and above ;-)

İşleri, SQLFiddle hiçbir SQL 2005 desteği vardır, SQLFiddle en SQL Server denedik 2008 değil, yalnızca 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') 

Canlı testi: http://sqlfiddle.com/#!3/3fd1f/1


Temelde bu nasıl işliyor:

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

Sen (2005+ varsayarak SQL Server) böyle bir şey deneyebilirsiniz:

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

Ayrıca yakın tarihi bulmak için GEÇERLİ ÇAPRAZ kullanabilirsiniz:

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

Canlı testi : 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 ile karşılaştırıldığında, güzel de ölçekler: SQL Server'ın http://www.ienablemuch.com/2012/04/outer-apply-walkthrough.html

İlgili konular