问题
If I have a table that looks like this
begin date end date data
2013-01-01 2013-01-04 7
2013-01-05 2013-01-06 9
How can I make it be returned like this...
date data
2013-01-01 7
2013-01-02 7
2013-01-03 7
2013-01-04 7
2013-01-05 9
2013-01-06 9
One thing I was thinking of doing is to have another table that just has all the dates and then join the table with just dates to the above table using date>=begin date and date<=end date but that seems a little clunky to have to maintain that extra table with nothing but repetitive dates.
In some instances I don't have a data range but just an as of date which basically looks like my first example but with no end date. The end date is implied by the next row's 'as of' date (ie end date should be the next row's as of -1). I had a "solution" for this that uses the row_number() function to get the next value but I suspect that methodology, which the way I'm doing it has a bunch of nested self joins, contributes to very long query times.
回答1:
Using some sample data...
create table data (begindate datetime, enddate datetime, data int);
insert data select
'20130101', '20130104', 7 union all select
'20130105', '20130106', 9;
The Query: (Note: if you already have a numbers/tally table - use it)
select dateadd(d,v.number,d.begindate) adate, data
from data d
join master..spt_values v on v.type='P'
and v.number between 0 and datediff(d, begindate, enddate)
order by adate;
Results:
| COLUMN_0 | DATA |
-----------------------------------------
| January, 01 2013 00:00:00+0000 | 7 |
| January, 02 2013 00:00:00+0000 | 7 |
| January, 03 2013 00:00:00+0000 | 7 |
| January, 04 2013 00:00:00+0000 | 7 |
| January, 05 2013 00:00:00+0000 | 9 |
| January, 06 2013 00:00:00+0000 | 9 |
Alternatively you can generate a number table on the fly (0-99) or as many numbers as you need
;WITH Numbers(number) AS (
select top(100) row_number() over (order by (select 0))-1
from sys.columns a
cross join sys.columns b
cross join sys.columns c
cross join sys.columns d
)
select dateadd(d,v.number,d.begindate) adate, data
from data d
join Numbers v on v.number between 0 and datediff(d, begindate, enddate)
order by adate;
SQL Fiddle Demo
回答2:
You can use recursive CTE to get all the dates between two dates. Another CTE is to get ROW_NUMBERs to help you with those missing EndDates.
DECLARE @startDate DATE
DECLARE @endDate DATE
SELECT @startDate = MIN(begindate) FROM Table1
SELECT @endDate = MAX(enddate) FROM Table1
;WITH CTE_Dates AS
(
SELECT @startDate AS DT
UNION ALL
SELECT DATEADD(DD, 1, DT)
FROM CTE_Dates
WHERE DATEADD(DD, 1, DT) <= @endDate
)
,CTE_Data AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY BeginDate) AS RN FROM Table1
)
SELECT DT, t1.data FROM CTE_Dates d
LEFT JOIN CTE_Data t1 on d.DT
BETWEEN t1.[BeginDate] AND COALESCE(t1.EndDate,
(SELECT DATEADD(DD,-1,t2.BeginDate) FROM CTE_Data t2 WHERE t1.RN + 1 = t2.RN))
SQLFiddle DEMO
来源:https://stackoverflow.com/questions/16358959/sql-how-to-convert-row-with-date-range-to-many-rows-with-each-date