SQL Server - 2つの日付値の間に行を複製し、日付反復子列を追加する

型がvarchar、varchar、date、dateのテーブルがあります。

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

行を複製し、FROMとTHRUの日付の間に日付と日付の間に繰り返し使用するDate of Service(DOS)列を追加します。完成したテーブルは次のようになります。

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。*のように既存のテーブルに参加できるようになったら、yourtableからcalendartable.calendardate WHERE calendartable.calendardate yourtable.FROMとyourtable.THRU
追加された 著者 JNevill,
10/30datetime の有効な値ではありません。 datetimeデータ型は、1/300秒の精度の値を返しますが、それが何であるかはよくわかりません。月/日(だから、何年?)、月/年(だから何日?)?あなたが持っている本当の価値、または本当のデータ型は何ですか?あなたが本当に を MM/dd のようなフォーマットで保存しているのであれば、ある年から別の年に移動するときにそれを使うのは不可能になるでしょう。
追加された 著者 Larnu,
@ラルヌあなたは正しいです。より明確にするために年の値を追加し、datetimeをdateに変更しました。
追加された 著者 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')

次に、集計表が必要です。私は自分のシステム上のビューとして1つを保持しており、それは0回の読み込みで高速に膨れ上がっています。必要に応じて行数を自由に調整してください。

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
追加された
おそらくそれほど違いはありません。どちらの方法でも、行数をかなり増やすために7億5000万行を別のテーブルに結合しています。
追加された 著者 Sean Lange,
私のテーブルは7億5000万行以上あります。このような大きなテーブルでは、カレンダーテーブルや集計テーブルのほうが速いでしょうか。
追加された 著者 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 も長い時間がかかることです。これで始めるのに良い場所は(私の意見では)Aaron Bertrandの測定値です( 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は彼がテストした2番目に遅いオプションです。
追加された 著者 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りんごとオレンジ:)あなたは参加していますそして私はCROSS APPLYを持っています。しかし、私はあなたのものがもっと高性能であろうと確信しています。 +1
追加された 著者 John Cappelletti,
@SeanLange負担しなければならない負担。
追加された 著者 John Cappelletti,
私のものとよく似ています。 +1
追加された 著者 Sean Lange,
もしそうであれば、おそらくこの小さなデータセットでは検出できないでしょう。唯一のパフォーマンスの違いは、集計テーブルの作成です。私たちの間に4つ以上の「正しい」答えのうち2つがあります。 :)
追加された 著者 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,