2012-04-21 2 views
5

Если у меня есть структура таблицы, как это:SQL Server - Запросы на Closest хребет Даты

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 и Date (предполагая, что диапазоны должны быть последовательными)? Другими словами, для этой таблицы Foo существует в 3 диапазонах дат: 4/1-4/3; 4/6-4/10; и 4/15-4/17, и я ищу диапазон дат с датой.

Пожалуйста, обратите внимание, что Foo не Дейта 4/4, 4/5, 4/11, 4/12, 4/13 и 4/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 не будет возвращать ничего
и т.д.

+0

Какую версию SQL Server? –

+0

Я думаю, что SQL Server 2005. У него есть два вопроса с тегом [sql-server-2005]. –

ответ

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 году, и «значения» не останутся без «вставки», по крайней мере, afaik! – deutschZuid

+0

@JamesJiao Это синтаксический сахар, введенный в 2008 году. Это не относится к примеру, вы должны удалить CTE 'source_table' и заменить его имя в запросе фактическим именем таблицы. Объявление переменной можно разделить на две строки (объявление затем присвоение). – GSerg

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' 

Example at SQL Fiddle.

+0

Не компилируется. 'Некорректный синтаксис около 'order'.' 'Order' не разрешен в предложении' over' для агрегатных оконных функций, таких как 'sum'. – GSerg

+0

@GSerg: Вероятно, вы используете старую версию SQL Server. Пример SQL Fiddle работает. – Andomar

+0

@GSerg Предложение 'OVER' для агрегатов было введено в SQL Server 2012 (полезно для текущих итогов) –

0

Примечание: я добавил второе решение (нерекурсивную), который имеет менее логического чтения (лучше производительность).

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) Non-рекурсивной решение:

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

Может использоваться LAG, если SQL Server 2005 поддерживает его.К сожалению LAG window function работает только на SQL Server 2012, и PostgreSQL 8.4 and above ;-)

Работы по 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

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 
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 по сравнению с JOIN, весы красиво тоже: http://www.ienablemuch.com/2012/04/outer-apply-walkthrough.html

Смежные вопросы