私の質問は this MySQLの質問に似ていますが、SQL Serverを対象としています。
2つの日付間の日のリストを返す関数またはクエリはありますか?たとえば、ExplodeDatesという関数があるとします。
SELECT ExplodeDates('2010-01-01', '2010-01-13');
これにより、値を持つ単一の列テーブルが返されます。
2010-01-01
2010-01-02
2010-01-03
2010-01-04
2010-01-05
2010-01-06
2010-01-07
2010-01-08
2010-01-09
2010-01-10
2010-01-11
2010-01-12
2010-01-13
カレンダー/数字の表がここで私を助けることができるかもしれないと思っています。
更新
提供された3つのコードの回答を確認することにしました。実行の結果(バッチ全体の%)は次のとおりです。
低いほど良い
(KMとStingyJackの両方の回答で使用されている)数値テーブルソリューションは私のお気に入りの1つですが、Rob Farleyの回答は最速であったため、受け入れました。ロブ・ファーリーは3分の2速くなりました。
更新2
Aliviaの answer は、はるかに簡潔です。受け入れられた答えを変更しました。
この数行は、SQLサーバーでのこの質問に対する簡単な答えです。
WITH mycte AS
(
SELECT CAST('2011-01-01' AS DATETIME) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue + 1 < '2021-12-31'
)
SELECT DateValue
FROM mycte
OPTION (MAXRECURSION 0)
次のようなものを試してください:
CREATE FUNCTION dbo.ExplodeDates(@startdate datetime, @enddate datetime)
returns table as
return (
with
N0 as (SELECT 1 as n UNION ALL SELECT 1)
,N1 as (SELECT 1 as n FROM N0 t1, N0 t2)
,N2 as (SELECT 1 as n FROM N1 t1, N1 t2)
,N3 as (SELECT 1 as n FROM N2 t1, N2 t2)
,N4 as (SELECT 1 as n FROM N3 t1, N3 t2)
,N5 as (SELECT 1 as n FROM N4 t1, N4 t2)
,N6 as (SELECT 1 as n FROM N5 t1, N5 t2)
,nums as (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as num FROM N6)
SELECT DATEADD(day,num-1,@startdate) as thedate
FROM nums
WHERE num <= DATEDIFF(day,@startdate,@enddate) + 1
);
次に使用します:
SELECT *
FROM dbo.ExplodeDates('20090401','20090531') as d;
編集(承認後):
十分に大きなnumsテーブルが既にある場合は、以下を使用する必要があることに注意してください。
CREATE FUNCTION dbo.ExplodeDates(@startdate datetime, @enddate datetime)
returns table as
return (
SELECT DATEADD(day,num-1,@startdate) as thedate
FROM nums
WHERE num <= DATEDIFF(day,@startdate,@enddate) + 1
);
そして、次を使用してそのようなテーブルを作成できます。
CREATE TABLE dbo.nums (num int PRIMARY KEY);
INSERT dbo.nums values (1);
GO
INSERT dbo.nums SELECT num + (SELECT COUNT(*) FROM nums) FROM nums
GO 20
これらの行は、1M行を含む数字のテーブルを作成します...そして、それらを1つずつ挿入するよりもはるかに高速です。
Query Optimizerが照会をまったく簡素化できなくなるため、BEGINとENDを含む関数を使用してExplodeDates関数を作成しないでください。
これは、Willの以前の投稿から修正された、まさにあなたが望むことをします。ヘルパーテーブルまたはループは不要です。
WITH date_range (calc_date) AS (
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, '2010-01-13') - DATEDIFF(DAY, '2010-01-01', '2010-01-13'), 0)
UNION ALL SELECT DATEADD(DAY, 1, calc_date)
FROM date_range
WHERE DATEADD(DAY, 1, calc_date) <= '2010-01-13')
SELECT calc_date
FROM date_range;
私はオラクルの男ですが、MS SQL Serverはconnect by句をサポートしていると思います。
select sysdate + level
from dual
connect by level <= 10 ;
出力は次のとおりです。
SYSDATE+LEVEL
05-SEP-09
06-SEP-09
07-SEP-09
08-SEP-09
09-SEP-09
10-SEP-09
11-SEP-09
12-SEP-09
13-SEP-09
14-SEP-09
Dualは、Oracleに付属する単なる「ダミー」テーブルです(1行と、単一列の値として「ダミー」という単語が含まれています)。
DECLARE @MinDate DATETIME = '2012-09-23 00:02:00.000',
@MaxDate DATETIME = '2012-09-25 00:00:00.000';
SELECT TOP (DATEDIFF(DAY, @MinDate, @MaxDate) + 1) Dates = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, @MinDate)
FROM sys.all_objects a CROSS JOIN sys.all_objects b;
あなたがしなければならないのは、以下に提供されているコードのハードコードされた値を変更するだけです
DECLARE @firstDate datetime
DECLARE @secondDate datetime
DECLARE @totalDays INT
SELECT @firstDate = getDate() - 30
SELECT @secondDate = getDate()
DECLARE @index INT
SELECT @index = 0
SELECT @totalDays = datediff(day, @firstDate, @secondDate)
CREATE TABLE #temp
(
ID INT NOT NULL IDENTITY(1,1)
,CommonDate DATETIME NULL
)
WHILE @index < @totalDays
BEGIN
INSERT INTO #temp (CommonDate) VALUES (DATEADD(Day, @index, @firstDate))
SELECT @index = @index + 1
END
SELECT CONVERT(VARCHAR(10), CommonDate, 102) as [Date Between] FROM #temp
DROP TABLE #temp
いくつかのアイデア:
それらをループするためにリストの日付が必要な場合は、開始日と日数のパラメーターを設定し、日付を作成して使用しながらwhileループを実行できますか?
C#CLRストアドプロシージャを使用して、C#でコードを記述します。
コードでデータベース外でこれを行う
これらの日付はすべてデータベースにすでに入っていますか、それとも2つの日付の間の日だけを知りたいですか?最初の場合は、[〜#〜] between [〜#〜]または<=> =を使用して、
例:
SELECT column_name(s)
FROM table_name
WHERE column_name
BETWEEN value1 AND value2
OR
SELECT column_name(s)
FROM table_name
WHERE column_name
value1 >= column_name
AND column_name =< value2
本当にパフォーマンスが必要な場合は、Mark RedmanのCLR proc/Assemblyのアイデアを使用できますが、間違いなく数字のテーブルです。
日付の表を作成する方法(および数値表を作成する超高速の方法)
/*Gets a list of integers into a temp table (Jeff Moden's idea from SqlServerCentral.com)*/
SELECT TOP 10950 /*30 years of days*/
IDENTITY(INT,1,1) as N
INTO #Numbers
FROM Master.dbo.SysColumns sc1,
Master.dbo.SysColumns sc2
/*Create the dates table*/
CREATE TABLE [TableOfDates](
[fld_date] [datetime] NOT NULL,
CONSTRAINT [PK_TableOfDates] PRIMARY KEY CLUSTERED
(
[fld_date] ASC
)WITH FILLFACTOR = 99 ON [PRIMARY]
) ON [PRIMARY]
/*fill the table with dates*/
DECLARE @daysFromFirstDateInTheTable int
DECLARE @firstDateInTheTable DATETIME
SET @firstDateInTheTable = '01/01/1998'
SET @daysFromFirstDateInTheTable = (SELECT (DATEDIFF(dd, @firstDateInTheTable ,GETDATE()) + 1))
INSERT INTO
TableOfDates
SELECT
DATEADD(dd,nums.n - @daysFromFirstDateInTheTable, CAST(FLOOR(CAST(GETDATE() as FLOAT)) as DateTime)) as FLD_Date
FROM #Numbers nums
日付のテーブルができたので、KMのような関数(PROCではない)を使用して日付のテーブルを取得できます。
CREATE FUNCTION dbo.ListDates
(
@StartDate DATETIME
,@EndDate DATETIME
)
RETURNS
@DateList table
(
Date datetime
)
AS
BEGIN
/*add some validation logic of your own to make sure that the inputs are sound.Adjust the rest as needed*/
INSERT INTO
@DateList
SELECT FLD_Date FROM TableOfDates (NOLOCK) WHERE FLD_Date >= @StartDate AND FLD_Date <= @EndDate
RETURN
END
おそらくもっと簡単な方法で行きたいのであれば、これでうまくいくでしょう。
WITH date_range (calc_date) AS (
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP) - 6, 0)
UNION ALL SELECT DATEADD(DAY, 1, calc_date)
FROM date_range
WHERE DATEADD(DAY, 1, calc_date) < CURRENT_TIMESTAMP)
SELECT calc_date
FROM date_range;
しかし、一時テーブルも非常に優れたアプローチです。おそらく、データが入力されたカレンダーテーブルも考慮する必要があります。
手順と機能が禁止されているで、SQLユーザーに挿入の許可がない場合、私のような状況にある場合、挿入は許可されません、「set/@cのような一時変数を宣言することは許可されていません」が、あなたは特定の期間の日付のリストを生成するにしたい
select * from
(select adddate('1970-01-01',t4*10000 + t3*1000 + t2*100 + t1*10 + t0) gen_date from
(select 0 t0 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 t1 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 t2 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 t3 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(select 0 t4 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v
where gen_date between '2017-01-01' and '2017-12-31'
パーティーに少し遅れましたが、このソリューションはかなり気に入っています。
CREATE FUNCTION ExplodeDates(@startDate DateTime, @endDate DateTime)
RETURNS table as
return (
SELECT TOP (DATEDIFF(DAY, @startDate, @endDate) + 1)
DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, @startDate) AS DATE
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
)
Declare @date1 date = '2016-01-01'
,@date2 date = '2016-03-31'
,@date_index date
Declare @calender table (D date)
SET @date_index = @date1
WHILE @date_index<=@date2
BEGIN
INSERT INTO @calender
SELECT @date_index
SET @date_index = dateadd(day,1,@date_index)
IF @date_index>@date2
Break
ELSE
Continue
END
私の関数を使用する前に、「ヘルパー」テーブルをセットアップする必要があります。データベースごとにこれを行う必要があるのは1回だけです。
CREATE TABLE Numbers
(Number int NOT NULL,
CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
DECLARE @x int
SET @x=0
WHILE @x<8000
BEGIN
SET @x=@x+1
INSERT INTO Numbers VALUES (@x)
END
ここに関数があります:
CREATE FUNCTION dbo.ListDates
(
@StartDate char(10)
,@EndDate char(10)
)
RETURNS
@DateList table
(
Date datetime
)
AS
BEGIN
IF ISDATE(@StartDate)!=1 OR ISDATE(@EndDate)!=1
BEGIN
RETURN
END
INSERT INTO @DateList
(Date)
SELECT
CONVERT(datetime,@StartDate)+n.Number-1
FROM Numbers n
WHERE Number<=DATEDIFF(day,@StartDate,CONVERT(datetime,@EndDate)+1)
RETURN
END --Function
これを使って:
select * from dbo.ListDates('2010-01-01', '2010-01-13')
出力:
Date
-----------------------
2010-01-01 00:00:00.000
2010-01-02 00:00:00.000
2010-01-03 00:00:00.000
2010-01-04 00:00:00.000
2010-01-05 00:00:00.000
2010-01-06 00:00:00.000
2010-01-07 00:00:00.000
2010-01-08 00:00:00.000
2010-01-09 00:00:00.000
2010-01-10 00:00:00.000
2010-01-11 00:00:00.000
2010-01-12 00:00:00.000
2010-01-13 00:00:00.000
(13 row(s) affected)
-###半ダースのうち6個。 MsSqlを想定した別の方法
Declare @MonthStart datetime = convert(DateTime,'07/01/2016')
Declare @MonthEnd datetime = convert(DateTime,'07/31/2016')
Declare @DayCount_int Int = 0
Declare @WhileCount_int Int = 0
set @DayCount_int = DATEDIFF(DAY, @MonthStart, @MonthEnd)
select @WhileCount_int
WHILE @WhileCount_int < @DayCount_int + 1
BEGIN
print convert(Varchar(24),DateAdd(day,@WhileCount_int,@MonthStart),101)
SET @WhileCount_int = @WhileCount_int + 1;
END;
このクエリはMicrosoft SQL Serverで機能します。
select distinct format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) as aDate
from (
SELECT ones.n + 10 * tens.n + 100 * hundreds.n + 1000 * thousands.n as v
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n)
) a
where format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) < cast('2010-01-13' as datetime)
order by aDate asc;
それでは、その仕組みを見てみましょう。
内部クエリは、0〜9999の整数のリストを返すだけです。日付を計算するために10,000個の値の範囲を提供します。 ten_thousandsやmillion_thousandsなどの行を追加することで、より多くの日付を取得できます。
SELECT ones.n + 10 * tens.n + 100 * hundreds.n + 1000 * thousands.n as v
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n)
) a;
この部分は、文字列を日付に変換し、内部クエリから数値を追加します。
cast('2010-01-01' as datetime) + ( a.v / 10 )
次に、結果を必要な形式に変換します。これは列名でもあります!
format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' )
次に、個別の値のみを抽出し、列名にaDateのエイリアスを指定します。
distinct format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) as aDate
Where句を使用して、必要な範囲内の日付のみでフィルタリングします。 SQL Serverはwhere句内の列エイリアスaDateを受け入れないため、ここで列名を使用していることに注意してください。
where format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) < cast('2010-01-13' as datetime)
最後に、結果を並べ替えます。
order by aDate asc;
特定の年から現在の日付までの年を印刷する場合。受け入れられた答えを変更しました。
WITH mycte AS
(
SELECT YEAR(CONVERT(DATE, '2006-01-01',102)) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue + 1 < = YEAR(GETDATE())
)
SELECT DateValue
FROM mycte
OPTION (MAXRECURSION 0)