从日期范围生成天数
我想运行像这样的查询
select ... as days where `date` is between '2010-01-20' and '2010-01-24'
并返回像这样的数据:
days ---------- 2010-01-20 2010-01-21 2010-01-22 2010-01-23 2010-01-24
I would like to run a query like
select ... as days where `date` is between '2010-01-20' and '2010-01-24'
And return data like:
days ---------- 2010-01-20 2010-01-21 2010-01-22 2010-01-23 2010-01-24
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(30)
此解决方案不使用循环、过程或临时表。子查询生成最近 10,000 天的日期,并且可以扩展到您希望的向后或向前。
输出:
性能说明
在此处进行测试,性能出奇的好:上面的查询需要 0.0009 秒。
如果我们扩展子查询来生成大约 0.0009 秒。 100,000 个数字(因此大约有 274 年的日期),它的运行时间为 0.0458 秒。
顺便说一句,这是一种非常可移植的技术,只需稍作调整即可适用于大多数数据库。
返回 1,000 天的 SQL Fiddle 示例
This solution uses no loops, procedures, or temp tables. The subquery generates dates for the last 10,000 days, and could be extended to go as far back or forward as you wish.
Output:
Notes on Performance
Testing it out here, the performance is surprisingly good: the above query takes 0.0009 sec.
If we extend the subquery to generate approx. 100,000 numbers (and thus about 274 years worth of dates), it runs in 0.0458 sec.
Incidentally, this is a very portable technique that works with most databases with minor adjustments.
SQL Fiddle example returning 1,000 days
这是使用视图的另一种变体:
然后您可以简单地执行以下操作(看看它有多优雅?):
更新
值得注意的是,您只能生成从当前日期。如果您想生成任何类型的日期范围(过去、未来和中间),则必须使用此视图:
Here is another variation using views:
And then you can simply do (see how elegant it is?):
Update
It is worth noting that you will only be able to generate past dates starting from the current date. If you want to generate any kind of dates range (past, future, and in between), you will have to use this view instead:
接受的答案不适用于 PostgreSQL(“a”处或附近的语法错误)。
在 PostgreSQL 中执行此操作的方法是使用
generate_series
函数,即:Accepted answer didn't work for PostgreSQL (syntax error at or near "a").
The way you do this in PostgreSQL is by using
generate_series
function, i.e.:使用递归通用表表达式 (CTE),您可以生成日期列表,然后从中进行选择。显然,您通常不想创建三百万个日期,因此这只是说明了可能性。您可以简单地限制 CTE 内的日期范围,并使用 CTE 从 select 语句中省略 where 子句。
在 Microsoft SQL Server 2005 上,生成所有可能日期的 CTE 列表需要 1 分 08 秒。生成一百年只需要不到一秒钟的时间。
Using a recursive Common Table Expression (CTE), you can generate a list of dates, then select from it. Obviously you normally wouldn't want to create three million dates, so this just illustrates the possibilities. You could simply limit the date range inside the CTE and omit the where clause from the select statement using the CTE.
On Microsoft SQL Server 2005, generating the CTE list of all possible dates took 1:08. Generating one hundred years took less than a second.
MSSQL 查询
输出
MSSQL Query
Output
在没有循环/游标的情况下执行此操作的老式解决方案是创建一个
NUMBERS
表,该表有一个 Integer 列,其值从 1 开始。您需要使用足够的记录填充表来覆盖您的数据。需求:
一旦有了
NUMBERS
表,您就可以使用:绝对低技术的解决方案是:
您将用它做什么?
生成日期或数字列表以便左连接。您这样做是为了查看数据中存在间隙的位置,因为您左连接到序列数据列表 - 空值将使存在间隙的位置变得明显。
The old school solution for doing this without a loop/cursor is to create a
NUMBERS
table, which has a single Integer column with values starting at 1.You need to populate the table with enough records to cover your needs:
Once you have the
NUMBERS
table, you can use:The absolute low-tech solution would be:
What would you use it for?
To generate lists of dates or numbers in order to LEFT JOIN on to. You would to this in order to see where there are gaps in the data, because you are LEFT JOINing onto a list of sequencial data - null values will make it obvious where gaps exist.
对于 Access 2010 - 需要多个步骤;我遵循与上面发布的相同的模式,但我认为我可以帮助 Access 中的某人。对我来说效果很好,我不必保留日期的种子表。
创建一个名为 DUAL 的表(类似于 Oracle DUAL 表的工作方式)
创建一个名为“ZeroThru9Q”的查询;手动输入以下语法:
创建名为“TodayMinus1KQ”的查询(适用于今天之前的日期);手动输入以下语法:
创建名为“TodayPlus1KQ”的查询(适用于今天之后的日期);手动输入以下语法:
创建名为“TodayPlusMinus1KQ”的联合查询(适用于日期 +/- 1000 天):
现在您可以使用该查询:
For Access 2010 - multiple steps required; I followed the same pattern as posted above, but thought I could help someone in Access. Worked great for me, I didn't have to keep a seeded table of dates.
Create a table called DUAL (similar to how the Oracle DUAL table works)
Create a query named "ZeroThru9Q"; manually enter the following syntax:
Create a query named "TodayMinus1KQ" (for dates before today); manually enter the following syntax:
Create a query named "TodayPlus1KQ" (for dates after today); manually enter the following syntax:
Create a union query named "TodayPlusMinus1KQ" (for dates +/- 1000 days):
Now you can use the query:
在 MariaDB >= 10.3 和 MySQL >= 8.0 中使用新的递归(通用表表达式)功能的优雅解决方案。
上面返回“2019-01-01”和“2019-04-30”之间的日期表。它也相当快。在我的机器上返回 1000 年的日期(约 365,000 天)大约需要 400 毫秒。
Elegant solution using new recursive (Common Table Expressions) functionality in MariaDB >= 10.3 and MySQL >= 8.0.
The above returns a table of dates between '2019-01-01' and '2019-04-30'. It is also decently fast. Returning 1000 years worth of dates (~365,000 days) takes about 400ms on my machine.
程序+临时表:
Procedure + temporary table:
谢谢 Pentium10 - 你让我加入了 stackoverflow :) -
这是我对 msaccess 的移植 - 认为它适用于任何版本:
引用 MSysObjects 只是因为访问需要在 from 子句中至少计数 1 条记录的表 - 任何至少有 1 条记录的表都可以。
thx Pentium10 - you made me join stackoverflow :) -
this is my porting to msaccess - think it'll work on any version:
referenced MSysObjects just 'cause access need a table countin' at least 1 record, in a from clause - any table with at least 1 record would do.
正如许多已经给出的精彩答案所述(或至少暗示),一旦您拥有一组可供使用的数字,这个问题就很容易解决。
注意:以下是 T-SQL,但它只是我对此处和整个互联网上已经提到的一般概念的特定实现。将代码转换为您选择的方言应该相对简单。
怎么做? 考虑这个查询:
上面生成的日期范围是 1/22/0001 - 1/27/0001,而且非常简单。上述查询中有 2 条关键信息:
0001-01-22
的开始日期和5 的偏移
。如果我们将这两条信息结合起来,那么我们显然就得到了结束日期。因此,给定两个日期,生成一个范围可以像这样分解:查找两个给定日期之间的差异(偏移量),很简单:
<代码>-- 返回 125
选择 ABS(DATEDIFF(d, '2014-08-22', '2014-12-25'))
此处使用
ABS()
可确保日期顺序无关。生成一组有限的数字,也很简单:
-- 返回数字 0-2
选择 N = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1
FROM(SELECT 'A' AS UNION ALL SELECT 'A' UNION ALL SELECT 'A')
请注意,我们实际上并不关心我们在这里选择的
FROM
。我们只需要一个集合来计算其中的行数。我个人使用 TVF,有些使用 CTE,其他人使用数字表,你明白了。我主张使用您也了解的性能最佳的解决方案。结合这两种方法将解决我们的问题:
上面的示例是可怕的代码,但演示了所有内容是如何组合在一起的。
更有趣
我需要经常做这种事情,所以我将逻辑封装到两个 TVF 中。第一个生成一系列数字,第二个使用此功能生成一系列日期。数学是为了确保输入顺序无关紧要,因为我想使用
GenerateRangeSmallInt
中可用的全部数字。以下函数需要大约 16 毫秒的 CPU 时间来返回 65536 个日期的最大范围。
As stated (or at least alluded to) in many of the wonderful answers already given, this problem is easily solved once you have a set of numbers to work with.
Note: The following is T-SQL but it's simply my particular implementation of general concepts already mentioned here and on the internet at large. It should be relatively simple to convert the code to your dialect of choice.
How? Consider this query:
The above produces the date range 1/22/0001 - 1/27/0001 and is extremely trivial. There are 2 key pieces of information in the above query: the start date of
0001-01-22
and the offset of5
. If we combine these two pieces of information then we obviously have our end date. Thus, given two dates, generating a range can be broken down like so:Find the difference between two given dates (the offset), easy:
-- Returns 125
SELECT ABS(DATEDIFF(d, '2014-08-22', '2014-12-25'))
Using
ABS()
here ensures that the date order is irrelevant.Generate a limited set of numbers, also easy:
-- Returns the numbers 0-2
SELECT N = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1
FROM(SELECT 'A' AS S UNION ALL SELECT 'A' UNION ALL SELECT 'A')
Notice we don't actually care what we're selecting
FROM
here. We just need a set to work with so that we count the number of rows in it. I personally use a TVF, some use a CTE, others use a numbers table instead, you get the idea. I advocate for using the most performant solution that you also understand.Combining these two methods will solve our problem:
The above example is horrible code but demonstrates how everything comes together.
More Fun
I need to do this kind of thing a lot so I encapsulated the logic into two TVFs. The first generates a range of numbers and the second uses this functionality to generate a range of dates. The math is to ensure that input order doesn't matter and because I wanted to use the full range of numbers available in
GenerateRangeSmallInt
.The following function takes ~16ms of CPU time to return the maximum range of 65536 dates.
试试这个。
try this.
对于任何想要将此作为已保存视图的人(MySQL 不支持视图中的嵌套选择语句):
然后您可以
执行以下操作:
For anyone who wants this as a saved view (MySQL doesn't support nested select statements in views):
You can then do
to get
您想要获取日期范围。
在您的示例中,您希望获取“2010-01-20”和“2010-01-24”之间的日期
可能的解决方案:
解释
MySQL有一个date_add 函数所以
会给你
datediff函数会让您经常知道您必须重复此操作,
这会返回
获取日期范围内的日期列表归结为创建整数序列,请参阅在 MySQL 中生成整数序列
这里投票最多的答案采用了与 https://stackoverflow.com/a/2652051/1497139 作为基础:
这将导致
现在可以使用行来创建从给定开始日期开始的日期列表。为了包含开始日期,我们从 -1 行开始;
You'd like to get the a date range.
In your example you'd like to get the dates between '2010-01-20' and '2010-01-24'
possible solution:
Explanation
MySQL has a date_add function so
will give you
The datediff function would let you know often you'd have to repeat this
which returns
Getting a list of dates in a date range boils down to creating a sequence of integer numbers see generate an integer sequence in MySQL
The most upvoted answer here has taken a similar approach as https://stackoverflow.com/a/2652051/1497139 as a basis:
which will result in
The rows can now be used to create a list of dates from the given start date. To include the start date we start with row -1;
一个适用于 AWS MySQL 的更通用的答案。
A more generic answer that works in AWS MySQL.
如果您需要超过几天的时间,您需要一张桌子。
在 mysql 中创建日期范围
然后,
if you will ever need more then a couple days, you need a table.
Create a date range in mysql
then,
在两个日期字段之间生成日期
如果您了解 SQL CTE 查询,那么此解决方案将帮助您解决您的问题
这里是示例
我们在一个表中有
日期: “testdate”
要求结果:
解决方案:
说明:CTE递归查询说明
查询第一部分:
SELECT DISTINCT Convert(varchar(10), StartTime, 101) AS StartTime, datediff(dd, StartTime, endTime) AS diff FROM dbo.testdate
说明:第一列是“开始日期”,第二列是开始和结束的差值
以天为单位的日期,它将被视为“差异”列
查询的第二部分:
UNION ALL SELECT StartTime, diff-1 AS diff FROM CTE WHERE diff<>0
说明:Union all 将继承上述查询的结果,直到结果变为 null,
因此“StartTime”结果继承自生成的 CTE 查询,并从 diff 继承,减少 - 1,因此它看起来像 3、2 和 1 直到 0
例如
结果规范
查询的第三部分
从 CTE 中选择 DISTINCT DateAdd(dd,diff, StartTime) AS StartTime
它将在“startdate”中添加日期“diff”,因此结果应如下所示
所示
Generate dates between two date fields
If you are aware with SQL CTE query, then this solution will helps you to solve your question
Here is example
We have dates in one table
Table Name: “testdate”
Require Result:
Solution:
Explanation: CTE Recursive query explanation
First part of query:
SELECT DISTINCT convert(varchar(10), StartTime, 101) AS StartTime, datediff(dd, StartTime, endTime) AS diff FROM dbo.testdate
Explanation: firstcolumn is “startdate”, second column is difference of start and end
date in days and it will be consider as “diff” column
Second part of query:
UNION ALL SELECT StartTime, diff-1 AS diff FROM CTE WHERE diff<>0
Explanation: Union all will inherit result of above query until result goes null,
So “StartTime” result is inherit from generated CTE query, and from diff, decrease - 1, so its looks like 3, 2, and 1 until 0
For example
Result Specification
3rd Part of Query
SELECT DISTINCT DateAdd(dd,diff, StartTime) AS StartTime FROM CTE
It will add day “diff” in “startdate” so result should be as below
Result
比接受的答案更短,同样的想法:
Shorter than accepted answer, same idea:
mysql 8.0.1 和 mariadb 10.2.2 使用递归公用表表达式的另一种解决方案:
One more solution for mysql 8.0.1 and mariadb 10.2.2 using recursive common table expressions:
即时生成这些日期是个好主意。但是,我觉得在相当大的范围内执行此操作不太舒服,因此我最终得到了以下解决方案:
就是这样。
WHERE i << 0
或WHERE i > 0
(PK)It's a good idea with generating these dates on the fly. However, I do not feel myself comfortable to do this with quite large range so I've ended up with the following solution:
That's it.
WHERE i < 0
orWHERE i > 0
(PK)好吧..试试这个:
http://www.devshed.com/c /a/MySQL/Delving-Deeper-into-MySQL-50/
http://dev.mysql.com/doc/refman/ 5.0/en/loop-statement.html
http://www.roseindia.net/sql/mysql-example/ mysql-loop.shtml
使用它来生成一个临时表,然后在临时表上执行 select * 。或者一次输出一个结果。
你所说的你想要做的事情不能用 SELECT 语句来完成,但它可能可以通过 MySQL 特定的事情来完成。
再说一遍,也许您需要游标:http://dev.mysql。 com/doc/refman/5.0/en/cursors.html
Alright.. Try this:
http://www.devshed.com/c/a/MySQL/Delving-Deeper-into-MySQL-50/
http://dev.mysql.com/doc/refman/5.0/en/loop-statement.html
http://www.roseindia.net/sql/mysql-example/mysql-loop.shtml
Use that to, say, generate a temp table, and then do a select * on the temp table. Or output the results one at a time.
What you say you want to do can't be done with a SELECT statement, but it might be doable with things specific to MySQL.
Then again, maybe you need cursors: http://dev.mysql.com/doc/refman/5.0/en/cursors.html
对于Oracle,我的解决方案是:
可以将Sysdate更改为特定日期,并且可以更改级别编号以提供更多日期。
For Oracle, my solution is:
Sysdate can be changed to specific date and level number can be changed to give more dates.
如果您想要两个日期之间的日期列表:
*在这里小提琴:http://sqlfiddle.com/ #!6/9eecb/3469
if you want the list of dates between two dates:
*fiddle here: http://sqlfiddle.com/#!6/9eecb/3469
RedFilters 顶级解决方案的 SQLite 版本
SQLite version of RedFilters top solution
改进了工作日加入自定义假期表
microsoft MSSQL 2012 for powerpivot 日期表
https://gist.github.com/josy1024/cb1487d66d9e0ccbd420bc4a23b6e90e
improved with weekday an joining a custom holiday table
microsoft MSSQL 2012 for powerpivot date table
https://gist.github.com/josy1024/cb1487d66d9e0ccbd420bc4a23b6e90e
还可以创建一个过程来创建日历表,其中时间戳与日期不同。
如果您想要每个季度都有一个表格,
例如
您可以使用
然后通过
该表格
进行操作,您也可以从这里开始添加其他信息,例如
或使用创建表格语句创建一个真实的表格
Can create a procedure also to create calendar table with timestmap different from day.
If you want a table for each quarter
e.g.
you can use
and then manipulate through
that give you also ts
from here you can start to add other information such as
or create a real table with create table statement
类似于 D'Arcy Rittich 的答案,但适用于 SQL SERVER
或没有 CTE
Similar to D'Arcy Rittich's Answer, but for SQL SERVER
or without CTE