2012-06-21 5 views
7

Название не совсем отражает то, что я имею в виду, и это может быть дубликат.Как сгенерировать диапазон дат в SQL Server

Вот длинная версия: учитывая имя гостя, дату регистрации и дату их выписки, как мне создать одну строку на каждый день, когда они были гостем?

Ex: Боб проверяет в 7/14 и оставляет 7/17. Я хочу

('Bob', 7/14), ('Bob', 7/15), ('Bob', 7/16), ('Bob', 7/17) 

как мой результат.

Спасибо!

+0

Посмотрите здесь http://stackoverflow.com/questions/1478951/tsql-generate-a-resultset-of-incrementing-dates [1]: HTTP: // StackOverflow ,com/questions/1478951/tsql-generate-a-resultset-of-incrementing-date – StoicFnord

+3

Как правило, вы этого не делаете. У вас есть справочная таблица и выберите их там. 'WHERE calendar.date> = user.start_date И calendar.date <= user.leave_date' Вы * МОЖЕТЕ * генерировать наборы с использованием циклов или рекурсивных запросов, но они никогда не бывают такими быстрыми, как при использовании справочной таблицы. – MatBailie

+0

Я задал очень похожий вопрос, но мои были часы, а не дни. Вы можете легко изменить свою потребность. http://stackoverflow.com/questions/10986344/get-every-hour-for-a-time-range – Limey

ответ

23

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

DECLARE @start DATE, @end DATE; 
SELECT @start = '20110714', @end = '20110717'; 

;WITH n AS 
(
    SELECT TOP (DATEDIFF(DAY, @start, @end) + 1) 
    n = ROW_NUMBER() OVER (ORDER BY [object_id]) 
    FROM sys.all_objects 
) 
SELECT 'Bob', DATEADD(DAY, n-1, @start) 
FROM n; 

Результаты:

Bob  2011-07-14 
Bob  2011-07-15 
Bob  2011-07-16 
Bob  2011-07-17 

Предположительно вы будете нуждаться в этом виде набора, а не для одного члена, так вот способ адаптировать эту технику:

DECLARE @t TABLE 
(
    Member NVARCHAR(32), 
    RegistrationDate DATE, 
    CheckoutDate DATE 
); 

INSERT @t SELECT N'Bob', '20110714', '20110717' 
UNION ALL SELECT N'Sam', '20110712', '20110715' 
UNION ALL SELECT N'Jim', '20110716', '20110719'; 

;WITH [range](d,s) AS 
(
    SELECT DATEDIFF(DAY, MIN(RegistrationDate), MAX(CheckoutDate))+1, 
    MIN(RegistrationDate) 
    FROM @t -- WHERE ? 
), 
n(d) AS 
(
    SELECT DATEADD(DAY, n-1, (SELECT MIN(s) FROM [range])) 
    FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id]) 
    FROM sys.all_objects) AS s(n) 
    WHERE n <= (SELECT MAX(d) FROM [range]) 
) 
SELECT t.Member, n.d 
FROM n CROSS JOIN @t AS t 
WHERE n.d BETWEEN t.RegistrationDate AND t.CheckoutDate; 
----------^^^^^^^ not many cases where I'd advocate between! 

Результаты:

Member d 
-------- ---------- 
Bob  2011-07-14 
Bob  2011-07-15 
Bob  2011-07-16 
Bob  2011-07-17 
Sam  2011-07-12 
Sam  2011-07-13 
Sam  2011-07-14 
Sam  2011-07-15 
Jim  2011-07-16 
Jim  2011-07-17 
Jim  2011-07-18 
Jim  2011-07-19 

Как @Dems отметил, это может быть упрощена:

;WITH natural AS 
(
    SELECT ROW_NUMBER() OVER (ORDER BY [object_id]) - 1 AS val 
    FROM sys.all_objects 
) 
SELECT t.Member, d = DATEADD(DAY, natural.val, t.RegistrationDate) 
    FROM @t AS t INNER JOIN natural 
    ON natural.val <= DATEDIFF(DAY, t.RegistrationDate, t.CheckoutDate); 
+0

Оптимизатор AFAIK SQL Server означает, что вам действительно не нужно 'WHERE n <= (SELECT MAX()), что означает, что это может быть еще более упрощено ...' WITH natural AS (SELECT ROW_NUMBER() OVER (ORDER) BY id) - 1 AS val FROM sys.objects) SELECT t.Member, DATEADD (DAY, natural.val, t.start) FROM @t AS t INNER JOIN natural ON natural.val <= DATEDIFF (DAY, t.start , t.end) '* [Но даже тогда прямая справочная таблица по-прежнему будет использовать меньше циклов процессора, по крайней мере.] * – MatBailie

+0

@ Когда я начал писать, моя цель состояла в том, чтобы использовать самый высокий диапазон в 'TOP' против' sys.all_objects'. Вы правы, что это можно упростить. –

+0

Спасибо, ваш запрос делает то, что именно я искал. Один вопрос - нужно ли использовать MAX и MIN в таблице «range»? В этом примере я вижу только одну строку, сгенерированную для «range», поэтому есть только один кандидат для max или min (в этом случае я бы просто поместил диапазон и дату начала в обычные переменные). Я очень впечатлен вашими SQL-отбивками и любопытно, есть ли какая-то тонкость там, где я отсутствую. –

0

Это может работать для тебя:

with mycte as 
(
    select cast('2000-01-01' as datetime) DateValue, 'Bob' as Name 
    union all 
    select DateValue + 1 ,'Bob' as Name 
    from mycte 
    where DateValue + 1 < '2000-12-31' 
) 
select * 
from mycte 
OPTION (MAXRECURSION 0) 
+2

Что содержит «Подсчет рекурсивных CTE». См. Следующую статью, почему они настолько плохи даже при подсчете небольших чисел. http://www.sqlservercentral.com/articles/T-SQL/74118/ –

-4

Я хотел бы создать триггер для создания дополнительных записей и запустить его на кассе. Кроме того, у вас может быть ежедневная полуночная работа, выполняющая то же самое (если вам нужна обновленная информация в вашей базе данных).

+1

На самом деле это не ответ - как триггер «создает дополнительные записи»? –

+0

@AaronBertrand это тривиальная задача программирования на любом языке. – Andy

+3

Если бы это было так тривиально, OP не спрашивал, верно? И не должно быть сложно создать резервную копию вашего ответа с помощью некоторого кода для * этого * языка? –

6

Я обычно делаю это с подвохом использованием row_number() на некоторой таблице. Таким образом:

select t.name, dateadd(d, seq.seqnum, t.start_date) 
from t left outer join 
    (select row_number() over (order by (select NULL)) as seqnum 
     from t 
    ) seq 
    on seqnum <= datediff(d, t.start_date, t.end_date) 

Расчет для seq идет довольно быстро, так как никаких расчетов или заказа не требуется. Тем не менее, вы должны быть уверены, что таблица достаточно велика для всех временных интервалов.

1

Если у вас есть таблица «Tally» или «Numbers», жизнь становится реальной для таких вещей.

SELECT Member, DatePresent = DATEADD(dd,t.N,RegistrationDate) 
    FROM @t 
    CROSS JOIN dbo.Tally t 
    WHERE t.N BETWEEN 0 AND DATEDIFF(dd,RegistrationDate,CheckoutDate) 
; 

Вот как построить таблицу «Талли».

--=================================================================== 
--  Create a Tally table from 0 to 11000 
--=================================================================== 
--===== Create and populate the Tally table on the fly. 
SELECT TOP 11001 
     IDENTITY(INT,0,1) AS N 
    INTO dbo.Tally 
    FROM Master.sys.ALL_Columns ac1 
    CROSS JOIN Master.sys.ALL_Columns ac2 
; 
--===== Add a CLUSTERED Primary Key to maximize performance 
    ALTER TABLE dbo.Tally 
    ADD CONSTRAINT PK_Tally_N 
     PRIMARY KEY CLUSTERED (N) WITH FILLFACTOR = 100 
; 
--===== Allow the general public to use it 
    GRANT SELECT ON dbo.Tally TO PUBLIC 
; 
GO 

Для получения дополнительной информации о том, что таблица «Tally» в SQL и как он может быть использован для замены Хотя петли и «Hidden RBAR» из reursive КТР, что кол-во, обратитесь к следующей статье.

http://www.sqlservercentral.com/articles/T-SQL/62867/