日付の結果セットを作成する必要があることを考慮してください。開始日と終了日があり、その間の日付のリストを生成したいと思います。
DECLARE @Start datetime
,@End datetime
DECLARE @AllDates table
(@Date datetime)
SELECT @Start = 'Mar 1 2009', @End = 'Aug 1 2009'
--need to fill @AllDates. Trying to avoid looping.
-- Surely if a better solution exists.
WHILE
ループを使用した現在の実装を検討してください。
DECLARE @dCounter datetime
SELECT @dCounter = @Start
WHILE @dCounter <= @End
BEGIN
INSERT INTO @AllDates VALUES (@dCounter)
SELECT @dCounter=@dCounter+1
END
質問: T-SQLを使用して、ユーザー定義の範囲内にある一連の日付をどのように作成しますか? SQL 2005+を想定しています。回答がSQL 2008の機能を使用している場合は、そのようにマークしてください。
日付の間隔が2047日以内の場合:
declare @dt datetime, @dtEnd datetime
set @dt = getdate()
set @dtEnd = dateadd(day, 100, @dt)
select dateadd(day, number, @dt)
from
(select number from master.dbo.spt_values
where [type] = 'P'
) n
where dateadd(day, number, @dt) < @dtEnd
そうするようにいくつかの要求の後、答えを更新しました。なぜですか?
元の回答にはサブクエリが含まれていました
select distinct number from master.dbo.spt_values
where name is null
sQL Server 2008、2012、および2016でテストした結果、同じ結果が得られます。
ただし、spt_values
からクエリを実行するときにMSSQLが内部的にコードを分析しようとすると、SELECT
ステートメントには常にWHERE [type]='[magic code]'
句が含まれることがわかりました。
したがって、クエリは正しい結果を返しますが、間違った理由で正しい結果を提供することにしました。
SQL Serverの将来のバージョンでは、[type]
の値としてNULL
を含む異なる[name]
値を定義する可能性があり、0-2047の範囲外、または非連続ですらあります、その場合、結果は単に間違っています。
以下では、再帰CTE(SQL Server 2005+)を使用しています。
WITH dates AS (
SELECT CAST('2009-01-01' AS DATETIME) 'date'
UNION ALL
SELECT DATEADD(dd, 1, t.date)
FROM dates t
WHERE DATEADD(dd, 1, t.date) <= '2009-02-01')
SELECT ...
FROM TABLE t
JOIN dates d ON d.date = t.date --etc.
この方法が機能するには、このワンタイムテーブルのセットアップを行う必要があります。
SELECT TOP 10000 IDENTITY(int,1,1) AS Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)
Numbersテーブルを設定したら、次のクエリを使用します。
SELECT
@Start+Number-1
FROM Numbers
WHERE Number<=DATEDIFF(day,@Start,@End)+1
それらをキャプチャするには:
DECLARE @Start datetime
,@End datetime
DECLARE @AllDates table
(Date datetime)
SELECT @Start = 'Mar 1 2009', @End = 'Aug 1 2009'
INSERT INTO @AllDates
(Date)
SELECT
@Start+Number-1
FROM Numbers
WHERE Number<=DATEDIFF(day,@Start,@End)+1
SELECT * FROM @AllDates
出力:
Date
-----------------------
2009-03-01 00:00:00.000
2009-03-02 00:00:00.000
2009-03-03 00:00:00.000
2009-03-04 00:00:00.000
2009-03-05 00:00:00.000
2009-03-06 00:00:00.000
2009-03-07 00:00:00.000
2009-03-08 00:00:00.000
2009-03-09 00:00:00.000
2009-03-10 00:00:00.000
....
2009-07-25 00:00:00.000
2009-07-26 00:00:00.000
2009-07-27 00:00:00.000
2009-07-28 00:00:00.000
2009-07-29 00:00:00.000
2009-07-30 00:00:00.000
2009-07-31 00:00:00.000
2009-08-01 00:00:00.000
(154 row(s) affected)
@KMの答えは、最初に数値テーブルを作成し、それを使用して日付の範囲を選択します。一時番号テーブルなしで同じことを行うには:
DECLARE @Start datetime
,@End datetime
DECLARE @AllDates table
(Date datetime)
SELECT @Start = 'Mar 1 2009', @End = 'Aug 1 2009';
WITH Nbrs_3( n ) AS ( SELECT 1 UNION SELECT 0 ),
Nbrs_2( n ) AS ( SELECT 1 FROM Nbrs_3 n1 CROSS JOIN Nbrs_3 n2 ),
Nbrs_1( n ) AS ( SELECT 1 FROM Nbrs_2 n1 CROSS JOIN Nbrs_2 n2 ),
Nbrs_0( n ) AS ( SELECT 1 FROM Nbrs_1 n1 CROSS JOIN Nbrs_1 n2 ),
Nbrs ( n ) AS ( SELECT 1 FROM Nbrs_0 n1 CROSS JOIN Nbrs_0 n2 )
SELECT @Start+n-1 as Date
FROM ( SELECT ROW_NUMBER() OVER (ORDER BY n)
FROM Nbrs ) D ( n )
WHERE n <= DATEDIFF(day,@Start,@End)+1 ;
もちろん、これを頻繁に行う場合は、永続テーブルの方がパフォーマンスが向上する可能性があります。
上記のクエリは、 この記事 からの修正版です。シーケンスの生成について説明し、多くの可能な方法を提供します。これは、一時テーブルを作成せず、sys.objects
テーブル内の要素の数に制限されないため、これが気に入りました。
これを試して。ルーピング、CTE制限などはありません。生成されたレコードの。必要に応じて、相互結合と上部を管理します。
select top 100000 dateadd(d,incr,'2010-04-01') as dt from
(select incr = row_number() over (order by object_id, column_id), * from
(
select a.object_id, a.column_id from sys.all_columns a cross join sys.all_columns b
) as a
) as b
ネストは、制御やビューへの変換などを簡単にするためのものです。
読みやすく、メンテナンスが簡単なので、CTEが好きです
Declare @mod_date_from date =getdate();
Declare @mod_date_to date =dateadd(year,1,@mod_date_from);
with cte_Dates as (
SELECT @mod_date_from as reqDate
UNION ALL
SELECT DATEADD(DAY,1,reqDate)
FROM cte_Dates
WHERE DATEADD(DAY,1,reqDate) < @mod_date_to
)
SELECT * FROM cte_Dates
OPTION(MAXRECURSION 0);
MAXRECURSIONの設定を忘れないでください
このソリューションは、MySQLに対する同じ質問に対するすばらしい回答に基づいています。また、MSSQLでも非常に優れたパフォーマンスを発揮します。 https://stackoverflow.com/a/2157776/466677
select DateGenerator.DateValue from (
select DATEADD(day, - (a.a + (10 * b.a) + (100 * c.a) + (1000 * d.a)), CONVERT(DATE, GETDATE()) ) as DateValue
from (select a.a from (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) as a(a)) as a
cross join (select b.a from (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) as b(a)) as b
cross join (select c.a from (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) as c(a)) as c
cross join (select d.a from (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) as d(a)) as d
) DateGenerator
WHERE DateGenerator.DateValue BETWEEN 'Mar 1 2009' AND 'Aug 1 2009'
ORDER BY DateGenerator.DateValue ASC
過去の日付、将来の日付、DATEADD関数のマイナス記号でのみ機能します。クエリはSQL Server 2008+でのみ機能しますが、「値から選択」構文をユニオンに置き換えることにより、2005年でも書き換えることができます。
別のオプションは、対応する関数を.NETで作成することです。これは次のようになります。
[Microsoft.SqlServer.Server.SqlFunction(
DataAccess = DataAccessKind.None,
FillRowMethodName = "fnUtlGetDateRangeInTable_FillRow",
IsDeterministic = true,
IsPrecise = true,
SystemDataAccess = SystemDataAccessKind.None,
TableDefinition = "d datetime")]
public static IEnumerable fnUtlGetDateRangeInTable(SqlDateTime startDate, SqlDateTime endDate)
{
// Check if arguments are valid
int numdays = Math.Min(endDate.Value.Subtract(startDate.Value).Days,366);
List<DateTime> res = new List<DateTime>();
for (int i = 0; i <= numdays; i++)
res.Add(dtStart.Value.AddDays(i));
return res;
}
public static void fnUtlGetDateRangeInTable_FillRow(Object row, out SqlDateTime d)
{
d = (DateTime)row;
}
これは基本的にプロトタイプであり、はるかにスマートにすることができますが、アイデアを示しています。私の経験から、小規模から中程度の期間(数年など)で、この関数はT-SQLで実装された関数よりも優れたパフォーマンスを発揮します。 CLRバージョンのもう1つの素晴らしい機能は、一時テーブルを作成しないことです。
概要
これが私のバージョンです(2005互換)。このアプローチの利点は次のとおりです。
SQLフィドル: http://sqlfiddle.com/#!6/c3896/1
コード
指定されたパラメーターに基づいて数値の範囲を生成するための再利用可能な関数:
create function dbo.generate_series
(
@start bigint
, @stop bigint
, @step bigint = 1
, @maxResults bigint = 0 --0=unlimitted
)
returns @results table(n bigint)
as
begin
--avoid infinite loop (i.e. where we're stepping away from stop instead of towards it)
if @step = 0 return
if @start > @stop and @step > 0 return
if @start < @stop and @step < 0 return
--ensure we don't overshoot
set @stop = @stop - @step
--treat negatives as unlimited
set @maxResults = case when @maxResults < 0 then 0 else @maxResults end
--generate output
;with myCTE (n,i) as
(
--start at the beginning
select @start
, 1
union all
--increment in steps
select n + @step
, i + 1
from myCTE
--ensure we've not overshot (accounting for direction of step)
where (@maxResults=0 or i<@maxResults)
and
(
(@step > 0 and n <= @stop)
or (@step < 0 and n >= @stop)
)
)
insert @results
select n
from myCTE
option (maxrecursion 0) --sadly we can't use a variable for this; however checks above should mean that we have a finite number of recursions / @maxResults gives users the ability to manually limit this
--all good
return
end
これをシナリオに使用する:
declare @start datetime = '2013-12-05 09:00'
,@end datetime = '2014-03-02 13:00'
--get dates (midnight)
--, rounding <12:00 down to 00:00 same day, >=12:00 to 00:00 next day
--, incrementing by 1 day
select CAST(n as datetime)
from dbo.generate_series(cast(@start as bigint), cast(@end as bigint), default, default)
--get dates (start time)
--, incrementing by 1 day
select CAST(n/24.0 as datetime)
from dbo.generate_series(cast(@start as float)*24, cast(@end as float)*24, 24, default)
--get dates (start time)
--, incrementing by 1 hour
select CAST(n/24.0 as datetime)
from dbo.generate_series(cast(@start as float)*24, cast(@end as float)*24, default, default)
2005互換
私は次を使用します:
SELECT * FROM dbo.RangeDate(GETDATE(), DATEADD(d, 365, GETDATE()));
-- Generate a range of up to 65,536 contiguous DATES
CREATE FUNCTION dbo.RangeDate (
@date1 DATE = NULL
, @date2 DATE = NULL
)
RETURNS TABLE
AS
RETURN (
SELECT D = DATEADD(d, A.N, CASE WHEN @date1 <= @date2 THEN @date1 ELSE @date2 END)
FROM dbo.RangeSmallInt(0, ABS(DATEDIFF(d, @date1, @date2))) A
);
-- Generate a range of up to 65,536 contiguous BIGINTS
CREATE FUNCTION dbo.RangeSmallInt (
@num1 BIGINT = NULL
, @num2 BIGINT = NULL
)
RETURNS TABLE
AS
RETURN (
WITH Numbers(N) AS (
SELECT N FROM(VALUES
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 16
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 32
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 48
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 64
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 80
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 96
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 112
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 128
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 144
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 160
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 176
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 192
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 208
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 224
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 240
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 256
) V (N)
)
SELECT TOP (
CASE
WHEN @num1 IS NOT NULL AND @num2 IS NOT NULL THEN ABS(@num1 - @num2) + 1
ELSE 0
END
)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) + CASE WHEN @num1 <= @num2 THEN @num1 ELSE @num2 END - 1
FROM Numbers A
, Numbers B
WHERE ABS(@num1 - @num2) + 1 < 65537
);
すでに提案されている多くのソリューションとそれほど違いはありませんが、私が気に入っている点がいくつかあります。
0から2つの日付の差までの整数で一時テーブルを作成します。
SELECT DATE_ADD(@Start, INTERVAL tmp_int DAY) AS the_date FROM int_table;
これは動作するはずです。
sysobjectsから上位1000個のDATEADD(d、ROW_NUMBER()OVER(ORDER BY Id)、getdate())を選択します
推奨事項:数値の補助テーブルを作成し、それを使用して日付のリストを生成します。再帰的なCTEを使用することもできますが、補助的な数値テーブルへの結合と同様に機能しない場合があります。両方のオプションの詳細については、 SQL、数値の補助表 を参照してください。
上記のKMのソリューションが本当に気に入っています(+1)が、「ループなし」という前提に疑問を呈する必要があります。アプリが動作するもっともらしい日付範囲を考えると、ループはそれほど高価ではないはずです。主なトリックは、ステージング/キャッシュテーブルでループの結果を取得することです。これにより、非常に大きなクエリセットが同じ正確な日付を再計算してシステムの速度を低下させることはありません。例えば。各クエリは、まだキャッシュに存在しない必要な日付範囲のみを計算/キャッシュします(アプリケーションのビジネスニーズによって決定される範囲で、2年前などの現実的な日付範囲をテーブルに事前設定します)。
SQL Server 2000で実行する必要がある(つまりCTEを使用できない)まさにこのようなものが必要だったので、Devioのソリューションが本当に好きですが、特定の曜日に合わせた日付のみを生成するように修正する方法はありますか?たとえば、月曜日、水曜日、金曜日、または次の番号スキームに基づいて選択した特定のシーケンスに一致する日付のみが必要です。
Sunday = 1
Monday = 2
Tuesday = 3
Wednesday = 4
Thursday = 5
Friday = 6
Saturday = 7
例:
StartDate = '2015-04-22' EndDate = '2017-04-22' --2 years worth
Filter on: 2,4,6 --Monday, Wednesday, Friday dates only
私がコーディングしようとしているのは、2つの追加フィールドを追加することです:day、day_code次に、生成されたリストを条件でフィルタリングします...
私は次のことを思いつきました:
declare @dt datetime, @dtEnd datetime
set @dt = getdate()
set @dtEnd = dateadd(day, 1095, @dt)
select dateadd(day, number, @dt) as Date, DATENAME(DW, dateadd(day, number, @dt)) as Day_Name into #generated_dates
from
(select distinct number from master.dbo.spt_values
where name is null
) n
where dateadd(day, number, @dt) < @dtEnd
select * from #generated_dates where Day_Name in ('Saturday', 'Friday')
drop table #generated_dates
最善の答えはおそらくCTEを使用することですが、それを使用できるという保証はありません。私の場合、クエリジェネレーターによって動的に作成された既存のクエリ内にこのリストを挿入する必要がありました... CTEもストアドプロシージャも使用できませんでした。
そのため、Devioからの回答は本当に役に立ちましたが、自分の環境で動作するように修正する必要がありました。
マスターデータベースにアクセスできない場合は、データベース内の別のテーブルを使用できます。前の例と同様に、最大日付範囲は、選択されたテーブル内の行数によって指定されます。
難しい例では、row_numberを使用して、実際のint列なしでテーブルを使用できます。
declare @bd datetime --begin date
declare @ed datetime --end date
set @bd = GETDATE()-50
set @ed = GETDATE()+5
select
DATEADD(dd, 0, DATEDIFF(dd, 0, Data)) --date format without time
from
(
select
(GETDATE()- DATEDIFF(dd,@bd,GETDATE())) --Filter on the begin date
-1 + ROW_NUMBER() over (ORDER BY [here_a_field]) AS Data
from [Table_With_Lot_Of_Rows]
) a
where Data < (@ed + 1) --filter on the end date