如果我有这样的表结构:
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
和{{1} }。
示例:
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
不会返回任何内容
等
答案 0 :(得分: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'
答案 1 :(得分:1)
如果SQL Server 2005支持LAG,则可以使用LAG。不幸的是,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
答案 2 :(得分: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
答案 3 :(得分: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)非递归解决方案:
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
答案 4 :(得分: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
答案 5 :(得分: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 APPLY
与JOIN相比,也可以很好地扩展:http://www.ienablemuch.com/2012/04/outer-apply-walkthrough.html