SQL 查询以获取两个日期之间每一天的总和
SQL query to get a sum for each day between two dates
我想查看两个日期之间特定名称的奖励总和
这是 MyTable
| NAME | REWARD | DATE |
+-----------+-----------+--------------+
| Chris | yes | 05.05.2018 |
| Chris | yes | 05.05.2018 |
| Chris | no | 07.05.2018 |
| John | yes | 10.05.2018 |
假设我要查找的姓名是 "Chris",日期介于 04.05.2018 - 08.05.2018 之间。查询还应计算每天的 REWARD="yes" 字段,并为未获得奖励的天数添加金额值“0”。
那么应该是这样的结果:
| NAME | AMOUNT | DATE |
+-----------+-----------+--------------+
| Chris | 0 | 04.05.2018 |
| Chris | 2 | 05.05.2018 |
| Chris | 0 | 06.05.2018 |
| Chris | 0 | 07.05.2018 |
| Chris | 0 | 08.05.2018 |
我正在使用 Firebird 2.5
我试过这个查询,但是当这样做时,没有生成数量为“0”的缺失日期
SELECT name, SUM(CASE WHEN reward='yes' THEN 1 ELSE 0 END) AS AMOUNT, DATE
from MyTable
WHERE DATE between '04.05.2018' and '08.05.2018'
AND NAME='Chris'
GROUP BY NAME, DATE
主要困难是您想要为 table 中没有数据的日期创建行。所以你必须找到一种方法来生成这些具有零值的行。
我认为最简单、最容易理解的解决方案是select可行的存储过程,即
CREATE PROCEDURE damounts(d1 date, d2 date, name varchar(20))
RETURNS (d date, amount integer)
AS
BEGIN
d = d1;
while(d <= d2)do begin
amount = (select sum(case when Reward = 'yes' then 1 else 0 end) from test where d = :d and name = :name);
if (amount is null) then amount = 0;
suspend;
d = d + 1;
end
END
要使用它,您只需从中 select:
select * from damounts('2018-05-04', '2018-05-10', 'Chris')
如果您不想使用 SP,那么 Firebird 2.5 支持递归 CTE,它可用于生成给定范围内的所有日期。使用另一个 CTE 计算有数据的日期的总和,然后按日期加入它们:
WITH RECURSIVE dates AS (
select cast('2018-05-04' as date) d from rdb$database
UNION ALL
select d+1 from dates where d < '2018-05-10'
)
,
sums (d, dsum) AS (
select
d,
sum(case when Reward = 'yes' then 1 else 0 end) AS amount
from test
where name = 'Chris' and d >= '2018-05-04' and d <= '2018-05-10'
group by d
)
select
'Chris' as name,
d.d as "date",
coalesce(s.dsum, 0) as amount
from dates d
left join sums s on(s.d = d.d);
请注意,在示例中我使用了列名 d
而不是 date
,因为在 Firebird 中不能有名为 date
的列,除非使用带引号的标识符(我从不这样做) ).我用 table 名字 test
.
而不是你的 MyTable
我能想到的解决办法是:
选择table存储过程来完成所有工作
已经显示在
使用递归通用 table 表达式生成日期
这个解决方案与ain提供的解决方案类似,但只有一个CTE,并且使用count
而不是sum
:
with recursive dates as (
select date'2018-05-04' as rewarddate
from rdb$database
union all
select rewarddate + 1
from dates
where rewarddate < date'2018-05-08'
)
select
'Chris' as name,
d.rewarddate,
count(case when g.reward = 'yes' then 1 end) as amount
from dates d
left join MyTable g
on d.rewarddate = g."DATE" and g.name = 'Chris'
group by d.rewarddate
为日期范围
选择table存储过程
set term #;
recreate procedure daterange(startdate date, enddate date)
returns (dateval date)
as
begin
dateval = startdate;
while (dateval <= enddate) do
begin
-- output row
suspend;
dateval = dateval + 1;
end
end#
set term ;#
此 selectable 存储过程生成从 startdate
到 enddate
(含)的日期范围。
然后我们可以像使用 CTE 的解决方案一样使用它:
select
'Chris' as name,
r.dateval,
count(case when g.reward = 'yes' then 1 end) as amount
from daterange(date'2018-05-04', date'2018-05-08') r
left join MyTable g
on r.dateval = g."DATE" and g.name = 'Chris'
group by r.dateval
重新考虑您的数据库设计
我在当前设计中看到的一些(潜在)问题
- 需要在 select 列表中将名称显式指定为
'Chris' as name
限制了灵活性(例如,您不能直接使用此解决方案来获取 Chris 和 John 的列表作为单个查询结果)
- 在
MyTable
中重复出现相同的名字表明您需要维护一个单独的 table 人(这也将简化求解 1)
- 没有 'rewards' 的日期很重要,这似乎表明您可能需要维护 table 个日期;这也将考虑到差距(例如,如果周末或假期应该被排除在外)。这样做有其缺点(例如,必须填充和维护日期,可能有自己的维护开销)
- Chris 在同一天获得多项奖励这一事实可能表明奖励本身也应该是 table(但前提是这是重要信息),或者
MyTable
需要更多信息为什么或什么被奖励。
- 您注册 Chris 在一次约会中没有得到奖励,但在其他日期却没有,这一事实表明,也许您应该只注册某些东西得到了奖励,而不是在没有奖励的时候。这消除了对
reward
列的需要。或者,如果 Chris 在 5 月 7 日没有得到奖励这一事实很重要,这可能意味着您需要额外的列来说明原因。
例如,替代设计可能类似于:
带一个tableperson
CREATE TABLE person (
id integer generated by default as identity constraint pk_person primary key,
name varchar(50) not null -- may need a unique constraint as well
);
填充为:
id name
1 Chris
2 John
和relevantdate
(由于缺乏上下文,我想不出更好的名字)
create table relevantdate (
dateval date constraint pk_relevantdate primary key
);
填充了 2018-05-04 和 2018-05-12 之间的日期(提示:使用上面创建的 daterange
过程的 insert into .. select ..
)。
然后您可以将 MyTable
(此处重命名为 reward
)的设计更改为:
create table reward (
id integer generated by default as identity constraint pk_reward primary key,
personid integer not null constraint fk_reward_person references person(id),
rewarddate date not null constraint fk_reward_relevantdate references relevantdate(dateval)
-- maybe add some more columns with information on why/what
)
填充为(留下不相关的 id):
personid rewarddate
1 2018-05-05
1 2018-05-05
2 2018-05-10
为了更大的灵活性,值得考虑不定义外键 fk_reward_relevantdate
。这将允许在 relevantdate
table 之外的日期插入奖励。在那种情况下,relevantdate
table 仅用作报告目的的支持对象。
作为 select,您现在可以使用类似的东西:
select
p.name,
rd.dateval,
count(r.rewarddate)
from person p
cross join relevantdate rd
left join reward r
on p.id = r.personid and rd.dateval = r.rewarddate
where rd.dateval between date'2018-05-04' and date'2018-05-08'
and p.name = 'Chris'
group by rd.dateval, p.name
取消 p.name = 'Chris'
条件,现在您将获得 Chris 和 John 的信息。
注意:我使用了 generated by default as identity
,这是 Firebird 3 的一项功能。对于这个例子来说并不是真的有必要。 Firebird 2.5 及更早版本中的等效项需要序列 + 触发器来生成 id,但在这些示例中,您可以简单地省略整个 generated by default as identity
,而在 reward
[=99 的情况下=],您可以考虑完全关闭 id
列。
我想查看两个日期之间特定名称的奖励总和
这是 MyTable
| NAME | REWARD | DATE |
+-----------+-----------+--------------+
| Chris | yes | 05.05.2018 |
| Chris | yes | 05.05.2018 |
| Chris | no | 07.05.2018 |
| John | yes | 10.05.2018 |
假设我要查找的姓名是 "Chris",日期介于 04.05.2018 - 08.05.2018 之间。查询还应计算每天的 REWARD="yes" 字段,并为未获得奖励的天数添加金额值“0”。
那么应该是这样的结果:
| NAME | AMOUNT | DATE |
+-----------+-----------+--------------+
| Chris | 0 | 04.05.2018 |
| Chris | 2 | 05.05.2018 |
| Chris | 0 | 06.05.2018 |
| Chris | 0 | 07.05.2018 |
| Chris | 0 | 08.05.2018 |
我正在使用 Firebird 2.5
我试过这个查询,但是当这样做时,没有生成数量为“0”的缺失日期
SELECT name, SUM(CASE WHEN reward='yes' THEN 1 ELSE 0 END) AS AMOUNT, DATE
from MyTable
WHERE DATE between '04.05.2018' and '08.05.2018'
AND NAME='Chris'
GROUP BY NAME, DATE
主要困难是您想要为 table 中没有数据的日期创建行。所以你必须找到一种方法来生成这些具有零值的行。
我认为最简单、最容易理解的解决方案是select可行的存储过程,即
CREATE PROCEDURE damounts(d1 date, d2 date, name varchar(20))
RETURNS (d date, amount integer)
AS
BEGIN
d = d1;
while(d <= d2)do begin
amount = (select sum(case when Reward = 'yes' then 1 else 0 end) from test where d = :d and name = :name);
if (amount is null) then amount = 0;
suspend;
d = d + 1;
end
END
要使用它,您只需从中 select:
select * from damounts('2018-05-04', '2018-05-10', 'Chris')
如果您不想使用 SP,那么 Firebird 2.5 支持递归 CTE,它可用于生成给定范围内的所有日期。使用另一个 CTE 计算有数据的日期的总和,然后按日期加入它们:
WITH RECURSIVE dates AS (
select cast('2018-05-04' as date) d from rdb$database
UNION ALL
select d+1 from dates where d < '2018-05-10'
)
,
sums (d, dsum) AS (
select
d,
sum(case when Reward = 'yes' then 1 else 0 end) AS amount
from test
where name = 'Chris' and d >= '2018-05-04' and d <= '2018-05-10'
group by d
)
select
'Chris' as name,
d.d as "date",
coalesce(s.dsum, 0) as amount
from dates d
left join sums s on(s.d = d.d);
请注意,在示例中我使用了列名 d
而不是 date
,因为在 Firebird 中不能有名为 date
的列,除非使用带引号的标识符(我从不这样做) ).我用 table 名字 test
.
MyTable
我能想到的解决办法是:
选择table存储过程来完成所有工作
已经显示在
使用递归通用 table 表达式生成日期
这个解决方案与ain提供的解决方案类似,但只有一个CTE,并且使用count
而不是sum
:
with recursive dates as (
select date'2018-05-04' as rewarddate
from rdb$database
union all
select rewarddate + 1
from dates
where rewarddate < date'2018-05-08'
)
select
'Chris' as name,
d.rewarddate,
count(case when g.reward = 'yes' then 1 end) as amount
from dates d
left join MyTable g
on d.rewarddate = g."DATE" and g.name = 'Chris'
group by d.rewarddate
为日期范围
选择table存储过程set term #;
recreate procedure daterange(startdate date, enddate date)
returns (dateval date)
as
begin
dateval = startdate;
while (dateval <= enddate) do
begin
-- output row
suspend;
dateval = dateval + 1;
end
end#
set term ;#
此 selectable 存储过程生成从 startdate
到 enddate
(含)的日期范围。
然后我们可以像使用 CTE 的解决方案一样使用它:
select
'Chris' as name,
r.dateval,
count(case when g.reward = 'yes' then 1 end) as amount
from daterange(date'2018-05-04', date'2018-05-08') r
left join MyTable g
on r.dateval = g."DATE" and g.name = 'Chris'
group by r.dateval
重新考虑您的数据库设计
我在当前设计中看到的一些(潜在)问题
- 需要在 select 列表中将名称显式指定为
'Chris' as name
限制了灵活性(例如,您不能直接使用此解决方案来获取 Chris 和 John 的列表作为单个查询结果) - 在
MyTable
中重复出现相同的名字表明您需要维护一个单独的 table 人(这也将简化求解 1) - 没有 'rewards' 的日期很重要,这似乎表明您可能需要维护 table 个日期;这也将考虑到差距(例如,如果周末或假期应该被排除在外)。这样做有其缺点(例如,必须填充和维护日期,可能有自己的维护开销)
- Chris 在同一天获得多项奖励这一事实可能表明奖励本身也应该是 table(但前提是这是重要信息),或者
MyTable
需要更多信息为什么或什么被奖励。 - 您注册 Chris 在一次约会中没有得到奖励,但在其他日期却没有,这一事实表明,也许您应该只注册某些东西得到了奖励,而不是在没有奖励的时候。这消除了对
reward
列的需要。或者,如果 Chris 在 5 月 7 日没有得到奖励这一事实很重要,这可能意味着您需要额外的列来说明原因。
例如,替代设计可能类似于:
带一个tableperson
CREATE TABLE person (
id integer generated by default as identity constraint pk_person primary key,
name varchar(50) not null -- may need a unique constraint as well
);
填充为:
id name
1 Chris
2 John
和relevantdate
(由于缺乏上下文,我想不出更好的名字)
create table relevantdate (
dateval date constraint pk_relevantdate primary key
);
填充了 2018-05-04 和 2018-05-12 之间的日期(提示:使用上面创建的 daterange
过程的 insert into .. select ..
)。
然后您可以将 MyTable
(此处重命名为 reward
)的设计更改为:
create table reward (
id integer generated by default as identity constraint pk_reward primary key,
personid integer not null constraint fk_reward_person references person(id),
rewarddate date not null constraint fk_reward_relevantdate references relevantdate(dateval)
-- maybe add some more columns with information on why/what
)
填充为(留下不相关的 id):
personid rewarddate
1 2018-05-05
1 2018-05-05
2 2018-05-10
为了更大的灵活性,值得考虑不定义外键 fk_reward_relevantdate
。这将允许在 relevantdate
table 之外的日期插入奖励。在那种情况下,relevantdate
table 仅用作报告目的的支持对象。
作为 select,您现在可以使用类似的东西:
select
p.name,
rd.dateval,
count(r.rewarddate)
from person p
cross join relevantdate rd
left join reward r
on p.id = r.personid and rd.dateval = r.rewarddate
where rd.dateval between date'2018-05-04' and date'2018-05-08'
and p.name = 'Chris'
group by rd.dateval, p.name
取消 p.name = 'Chris'
条件,现在您将获得 Chris 和 John 的信息。
注意:我使用了 generated by default as identity
,这是 Firebird 3 的一项功能。对于这个例子来说并不是真的有必要。 Firebird 2.5 及更早版本中的等效项需要序列 + 触发器来生成 id,但在这些示例中,您可以简单地省略整个 generated by default as identity
,而在 reward
[=99 的情况下=],您可以考虑完全关闭 id
列。