2012-04-21 14 views
5

Se ho una struttura della tabella come questa:SQL Server - Interrogazione per il più vicino intervallo di date

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 

Esiste un modo per interrogare per l'intervallo di date per un dato ProductCode e Date (assumendo che intervalli devono essere sequenziale)? In altre parole, per questa tabella, Foo esiste su 3 intervalli di date: 4/1-4/3; 4/6-4/10; e 4/15-4/17 e sto cercando l'intervallo di date data una data.

prega di notare che Foo non dispone di 4/4, 4/5, 4/11, 4/12, 4/13 e 4/14 data.

Esempi:
ProductCode=Foo, Date=4/2 restituire 4/1-4/3 perché le voci sono sequenziali.
ProductCode=Foo, Date=4/4 non restituisce nulla
ProductCode=Foo, Date=4/7 restituire 4/6-4/10 perché le voci sono sequenziali.
ProductCode=Foo, Date=4/12 sarebbero tornati nulla
ecc

+0

Quale versione di SQL Server? –

+0

Penso che SQL Server 2005. Ha due domande con il tag [sql-server-2005]. –

risposta

0

Potrebbe fare con una CTE ricorsiva.

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

Non funziona affatto su SQL 2005. Non penso che si possa dichiarare una variabile del genere nel 2005 e "valori" non reggono da soli senza "inserire", almeno afaik! – deutschZuid

+0

@JamesJiao È lo zucchero della sintassi introdotto nel 2008. È irrilevante per l'esempio, è necessario rimuovere il CTE 'source_table' e sostituire il nome nella query con il nome effettivo della tabella. La dichiarazione delle variabili può essere divisa in due righe (dichiarazione, quindi assegnazione). – GSerg

1

Un nuovo intervallo inizia quando non è presente alcuna riga per il giorno precedente. Se si esegue SQL Server 2012, è possibile utilizzare la funzione finestra lag per verificare se una riga introduce un nuovo intervallo. Una volta che sai quali righe introducono un nuovo intervallo, puoi contare il numero di file principali per assegnare un numero univoco a ciascun intervallo.

Avere un numero di intervallo consente di trovare la data di inizio e di fine con min e max. Dopo di che, è solo una questione di selezionando la riga:

; 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

Non si compila. 'Sintassi errata vicino a 'order''.' 'Ordine' non è ammesso nella clausola' over' per funzioni di finestra aggregate come 'sum'. – GSerg

+0

@ Gerg: Probabilmente stai usando una vecchia versione di SQL Server. L'esempio di SQL Fiddle funziona. – Andomar

+0

@GSerg La clausola 'OVER' per gli aggregati è stata introdotta in SQL Server 2012 (valida per totali parziali) –

0

Nota: Ho aggiunto una seconda soluzione (non ricorsiva), che ha meno letture logiche (prestazioni migliori).

1) È possibile utilizzare un recursive CTE (demo 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 
*/ 

Risultati:

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) soluzione non ricorsiva:

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

risultati:

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

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

Potrebbe essere stato utilizzato il LAG, se SQL Server 2005 l'ha supportato.Purtroppo LAG window function opere su SQL Server 2012 solo, e PostgreSQL 8.4 and above ;-)

Opere su SQL Server 2005 avrei dovuto, SQLFiddle non ha il supporto SQL 2005, ha cercato di SQLFiddle SQL Server 2008 non solo, 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') 

prova dal vivo: http://sqlfiddle.com/#!3/3fd1f/1


Fondamentalmente questo è come funziona:

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

Si potrebbe provare qualcosa di simile (supponendo che 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

È inoltre possibile utilizzare CROSS APPLY per trovare la data più vicina:

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

Live Test : http://sqlfiddle.com/#!3/3fd1f/2


Fondamentalmente questo è come funziona:

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 rispetto al JOIN, scale ben troppo: http://www.ienablemuch.com/2012/04/outer-apply-walkthrough.html

Problemi correlati