SQL Server - дублировать строки и добавлять столбец итератора даты между двумя значениями даты

У меня есть таблица с типами varchar, varchar, date и date:

NAME | ID   | FROM       | THRU
Bob  | A123 | 10/30/2010 | 11/2/2010
Bob  | B567 | 10/30/2010 | 11/2/2010

Я хочу добавить столбец «Дата обслуживания» (DOS), который дублирует строки и выполняет итерации для каждого дня между датами FROM и THRU, включая даты. Готовая таблица должна выглядеть так:

NAME | ID   | FROM       | THRU       | DOS
Bob  | A123 | 10/30/2010 | 11/02/2010 | 10/30/2010
Bob  | A123 | 10/30/2010 | 11/02/2010 | 10/31/2010
Bob  | A123 | 10/30/2010 | 11/02/2010 | 11/01/2010
Bob  | A123 | 10/30/2010 | 11/02/2010 | 11/02/2010
Bob  | B567 | 10/30/2010 | 11/02/2010 | 10/30/2010
Bob  | B567 | 10/30/2010 | 11/02/2010 | 10/31/2010
Bob  | B567 | 10/30/2010 | 11/02/2010 | 11/01/2010
Bob  | B567 | 10/30/2010 | 11/02/2010 | 11/02/2010

Я видел другой ответ, который использовал cte, но не сохранил исходные значения даты и добавил столбец DOS. Как я мог сделать это в SQL Server?

1
Это отличный вариант использования для календарной таблицы (календарная таблица будет содержать строку для каждой даты и будет содержать все даты). Есть много ресурсов в Интернете о том, как быстро его создать. Как только вы это сделаете, вы можете просто присоединиться к существующей таблице, например SELECT yourtable. *, Calendartable.calendardate FROM yourtable WHERE calendartable.calendardate МЕЖДУ yourtable.FROM и yourtable.THRU
добавлено автор JNevill, источник
10/30 не является допустимым значением для datetime . Тип данных datetime возвращает значение с точностью до 1/300 секунды, но не уверен, что это такое; месяц/день (итак, на какой год?), месяц/год (ну и на какой день?)? Какие у вас реальные ценности или реальные типы данных? Если вы действительно храните даты в таком формате, как MM/dd , то с этим будет невозможно работать при переходе от одного года к другому.
добавлено автор Larnu, источник
@ Ларну, ты прав. Я добавил значение года для большей ясности и изменил дату и время на дату.
добавлено автор user3347996, источник

6 ответы

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

Сначала давайте настроим ваши данные.

declare @Something table
(
    NAME varchar(10)
    , ID varchar(10)
    , DateFrom date
    , THRU date
)

insert @Something values
('Bob', 'A123', '20101030', '20101102')
, ('Bob', 'B567', '20101030', '20101102')

Далее нам нужен наш счетный стол. Я сохраняю один вид моей системы, и он быстро растет, когда не читается. Не стесняйтесь регулировать количество строк в соответствии с вашими потребностями.

create View [dbo].[cteTally] as

WITH
    E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
    E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
    E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
    cteTally(N) AS 
    (
        SELECT  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
    )
select N from cteTally

Теперь запрос для вашей ситуации довольно прост.

select s.Name
    , s.ID
    , s.DateFrom
    , s.THRU
    , DOS = DATEADD(day, t.N - 1, DateFrom)
from @Something s
join cteTally t on t.N <= datediff(day, DateFrom, THRU) + 1
order by s.Name
    , s.ID
    , t.N
3
добавлено
Вероятно, не имеет большого значения. В любом случае вы объединяете 750 миллионов строк в другую таблицу, чтобы немного увеличить количество строк.
добавлено автор Sean Lange, источник
В моей таблице более 750 миллионов строк. Будет ли таблица календаря или таблица подсчета быстрее для такой большой таблицы?
добавлено автор user3347996, источник

Я часто использую рекурсивные CTE для такого рода вещей:

with cte as (
      select t.ame, t.id, t.from, t.thru, t.from as dos
      from t
      union all
      select cte.ame, cte.id, cte.from, cte.thur, dateadd(day, 1, dos)
      from cte
      where dos < t.thru
     )
select cte.*
from cte
option (maxrecursion 0);
2
добавлено
@SeanLange. , , Проблема в том, что когда вы попадаете в большие диапазоны, join может также занять много времени. Хорошее место для начала (на мой взгляд) - измерения Аарона Бертранда ( sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1 ). Я предпочитаю, потому что это стандартный SQL, не требует дополнительных таблиц и работает для любого количества значений. Я бы не стал утверждать, что это самый быстрый метод выполнения.
добавлено автор Gordon Linoff, источник
Ничего страшного, если диапазон мал, но для больших диапазонов (где-то около 1000) это действительно может увязнуть. Рекурсивные ctes для генерации последовательных значений - это действительно скрытый RBAR. sqlservercentral.com/articles/T-SQL/74118
добавлено автор Sean Lange, источник
В его статье рекурсивный cte - второй самый медленный вариант, который он тестировал.
добавлено автор Sean Lange, источник
Я никогда не придавал большого значения стандартному ANSI-совместимому sql. За 20 лет работы я переключил базы данных на систему ровно ноль раз. И даже если бы это случилось, было бы много других вещей, которые должны были бы произойти. Я использую рекурсивную технику cte с видом, чтобы он нигде не сохранялся и был молниеносным.
добавлено автор Sean Lange, источник

Если у вас нет таблицы календаря (настоятельно рекомендуется), другой вариант - специальная таблица

<�Сильный> Пример </сильный>

Select A.* 
      ,DOS = B.D
 From  YourTable A
 Cross Apply (
                Select Top (DateDiff(DAY,[FROM],[THRU])+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),[FROM]) 
                  From  master..spt_values n1,master..spt_values n2
             ) B

<�Сильный> Возвращает

NAME    ID      FROM        THRU        DOS
Bob     A123    2010-10-30  2010-11-02  2010-10-30
Bob     A123    2010-10-30  2010-11-02  2010-10-31
Bob     A123    2010-10-30  2010-11-02  2010-11-01
Bob     A123    2010-10-30  2010-11-02  2010-11-02
Bob     B567    2010-10-30  2010-11-02  2010-10-30
Bob     B567    2010-10-30  2010-11-02  2010-10-31
Bob     B567    2010-10-30  2010-11-02  2010-11-01
Bob     B567    2010-10-30  2010-11-02  2010-11-02
2
добавлено
@SeanLange Яблоки и апельсины :) У вас есть объединение, и у меня есть ПРИМЕНЕНИЕ КРЕСТА. Тем не менее, я готов поспорить, что ваши будут более эффективными. +1
добавлено автор John Cappelletti, источник
@SeanLange Бремя, которое мы должны нести.
добавлено автор John Cappelletti, источник
Выглядит очень похоже на мой. +1
добавлено автор Sean Lange, источник
Если это так, то, вероятно, его нельзя обнаружить в этом небольшом наборе данных. Единственная разница в производительности заключается в создании таблицы подсчета. Между нами у нас есть 2 из 4 или более «правильных» ответов. :)
добавлено автор Sean Lange, источник

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

SELECT YT.Name,
       YT.ID,
       YT.[From],
       YT.Thru,
       CT.CalendarDate AS DOS
FROM dbo.YourTable YT
     JOIN dbo.CalendarTable CT ON CONVERT(date,YT.[From]) <= CT.CalendarDate
                              AND CONVERT(date,YT.Thru) >= CT.CalendarDate;

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

2
добавлено

Похоже, CROSS APPLY выполнят эту работу

CREATE TABLE T(
  [NAME] varchar(3), 
  [ID] varchar(4), 
  [FROM] datetime, 
  [THRU] datetime
);

INSERT INTO T
    ([NAME], [ID], [FROM], [THRU])
VALUES
    ('Bob', 'A123', '2001-10-30 00:00:00', '2001-11-02 00:00:00'),
    ('Bob', 'B567', '2001-10-30 00:00:00', '2001-11-02 00:00:00');

SELECT T.*,
       DATEADD(Day, TT.N, [FROM]) DOS
FROM T CROSS APPLY (VALUES (0), (1), (2), (3)) TT(N)

Возврат:

+------+------+---------------------+---------------------+---------------------+
| NAME |  ID  |        FROM         |        THRU         |         DOS         |
+------+------+---------------------+---------------------+---------------------+
| Bob  | A123 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 30/10/2001 00:00:00 |
| Bob  | A123 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 31/10/2001 00:00:00 |
| Bob  | A123 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 01/11/2001 00:00:00 |
| Bob  | A123 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 02/11/2001 00:00:00 |
| Bob  | B567 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 30/10/2001 00:00:00 |
| Bob  | B567 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 31/10/2001 00:00:00 |
| Bob  | B567 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 01/11/2001 00:00:00 |
| Bob  | B567 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 02/11/2001 00:00:0  |
+------+------+---------------------+---------------------+---------------------+
0
добавлено

Вы можете создать дату службы в виде вычисляемого столбца. Чтобы увеличить дату, вы можете попробовать это:

SELECT DATEADD(day, 1, '2017/08/25') AS DateAdd;
0
добавлено
Как это отвечает на их вопрос? Они хотят каждую дату между FROM и THRU.
добавлено автор Sean Lange, источник
Как это генерирует дополнительные строки? Такое выражение обеспечивает дополнительный столбец, а не дополнительные строки.
добавлено автор Larnu, источник
SqlCom.ru - Стиль жизни SQL
SqlCom.ru - Стиль жизни SQL
908 участник(ов)

Правила чата - https://t.me/sqlcom/88269 @sqlcom - основной канал (только MS SQL) @sql_ninja - второй канал (SQL вопросы начального уровня и свободное общение) @Gopnegbot - Викторина по SQL Server (наберите в привате /quiz). Предложения в @sql_ninja

SQL_Ninja
SQL_Ninja
340 участник(ов)

Правила чата - https://t.me/sqlcom/88269 @sqlcom - основной канал (только SQL) @sql_ninja - второй канал (SQL вопросы начального уровня и свободное общение) @Gopnegbot - Викторина по SQL Server (наберите в привате /quiz)