Получить даты между периодами - PullRequest
2 голосов
/ 05 февраля 2012

У меня есть таблица, содержащая следующие данные

periodID periostart periodend
1        01-01-2012 10-01-2012
2        11-01-2012 01-04-2012

Я бы хотел, чтобы запрос возвратил что-то подобное.periodID date

1        01-01-2012
1        02-01-2012
1        03-01-2012
etc.
1        09-01-2012
2        11-01-2012
2        12-01-2012
etc.
2        31-03-2012

Поскольку у меня около 100 000 строк, содержащих точки, я хотел бы поработать над решением, которое не повлияет на производительность (курсоры, циклы).Возможно ли получить желаемый результат без использования курсоров или циклов?

Спасибо за участие.

Я уже получил это решение

create table #p (id int, periodstart smalldatetime, periodend smalldatetime );
insert into #p values 
(1, '2012-01-01', '2015-01-10')
insert into #p values
(2, '2012-04-10', '2015-11-20');


SELECT TOP 366 --aprox one year
        IDENTITY(INT,0,1) AS N
   INTO #Tally
   FROM Master.dbo.SysColumns sc1,
        Master.dbo.SysColumns sc2



SELECT DATEADD(day, T.N, periodstart) AS [Date]
FROM #p
cross join #tally T
WHERE (T.N >= 0 AND T.N < DATEDIFF(day, periodstart, periodend))
ORDER BY [Date]

Ответы [ 3 ]

4 голосов
/ 05 февраля 2012

Вот оно:

create table #p (id int, periostart date, periodend date );
insert into #p values 
(1, '20120101', '20120110'),
(2, '20120110', '20120120');


with cte as (
 select
   id, periostart as aDay
 from
   #p
 union all
 select
   cte.id, dateadd( day, 1, cte.aDay) as aDay
 from
   #p
 inner join
   cte on #p.id = cte.id 
 where
   cte.aDay < #p.periodend
)
select * from cte

Результаты:

id aDay          
-- ------------- 
1  2012-01-01 00:00:00
2  2012-01-10 00:00:00
...
2  2012-01-17 00:00:00
2  2012-01-18 00:00:00
2  2012-01-19 00:00:00
2  2012-01-20 00:00:00
1  2012-01-02 00:00:00
1  2012-01-03 00:00:00
...
1  2012-01-08 00:00:00
1  2012-01-09 00:00:00
1  2012-01-10 00:00:00

Объяснение : я использую CTE рекурсию для добавления новой даты1 день до текущей даты, ограничение до конца периода.Сложно получить последнюю сгенерированную дату в рекурсивном соединении, я решаю это с помощью предложения OVER .Я думаю, что это хороший запрос.

РЕДАКТИРОВАНИЕ из-за комментария OP

Я опубликовал этот подход, потому что OP запрашивает решение без циклов и курсоров.Я не знаю другого способа написания SQL-предложения без циклов или курсоров, кроме этого.

Кроме того, я думаю, что правильный подход к генерации дат - это курсор на таблицу периодов с внутренним цикломили с кросс-соединением в стиле Ицик .

1 голос
/ 05 февраля 2012

Мое первое кросс-соединение в стиле Ицик :

declare @longestPeriod int
set @longestPeriod = 1000  --you should calculate it with a single query

create table #p (id int, periostart date, periodend date );
insert into #p values 
(1, '20120101', '20120110'),
(2, '20120110', '20120120');

WITH 
  E1(N) AS (
            SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
            SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
            SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
           ),                          -- 1*10^1 or 10 rows
  E2(N) AS (SELECT 1 FROM E1 a, E1 b), -- 1*10^2 or 100 rows
  E4(N) AS (SELECT 1 FROM E2 a, E2 b), -- 1*10^4 or 10,000 rows
  E8(N) AS (SELECT 1 FROM E4 a, E4 b),  -- 1*10^8 or 100,000,000 rows
  NN as (SELECT top(@longestPeriod) ROW_NUMBER() 
                OVER (ORDER BY (SELECT NULL)) as N  FROM E8 )
 select 
   id,dateadd( dd, NN.N , periostart) as aDay
 from
   #p
 cross join
   NN
 where NN.N between 0 and datediff( dd, periostart, periodend )

Каждый день я чему-то учусь.

Кросс-соединение в стиле Ицик :

Что удивительного в этом плохом парне, так это то, что он производит НОЛЬ ЧИТАЕТ. Абсолютно нет, нада, ноль.

Пожалуйста, Джон, опубликуйте тест производительности !!!

1 голос
/ 05 февраля 2012

Не совсем уверен, что понимаю, но я думаю, что вы хотите использовать оператор МЕЖДУ .Что-то вроде

SELECT PeriodID, @Date FROM Periods WHERE @Date BETWEEN periodstart AND periodend

или, если вы получаете дату через присоединение к таблице, содержащей даты, используйте BETWEEN в соединении

SELECT periodTable.PeriodID, dateTable.Date 
FROM periodTable 
    INNER JOIN dateTable ON dateTable.Date BETWEEN periodTable.periodstart AND periodTable.periodEnd

В этом случае у вас будет таблицазаполнить каждую дату от самой ранней до самой последней и присоединиться к таблицам, как показано выше.Такую таблицу можно создать так:

CREATE TABLE dateTable(ID int identity(1,1), Date datetime NOT NULL)
Declare @d datetime

set @d=CONVERT(datetime, '1/1/1990')--start date

While @d<=CONVERT(datetime, '1/1/2020')--enddate
Begin
Insert into dateTable values (@d)
set @d=DATEADD(dd, 1, @d)
End
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...