我想查看两个日期之间特定名称的奖励总和
这是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
答案 0 :(得分:2)
主要困难在于您希望拥有表中没有数据的日期行。所以你必须找到一种方法来生成零值的这些行。
我认为最简单,最容易理解的解决方案是可选择的存储过程,即
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 * 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
列)。而不是MyTable
我使用了表名test
。
答案 1 :(得分:2)
我能想到的解决方案是:
此解决方案类似于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
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 ;#
此可选存储过程会生成从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
我在当前设计中看到的许多(潜在)问题
'Chris' as name
,这限制了灵活性(例如,您无法直接使用此解决方案获取Chris和John作为单个查询结果的列表)MyTable
中重复出现相同名称表明您需要维护一个单独的人员表(这也可以简化解决方案1)。MyTable
需要额外的信息为什么或什么是奖励。reward
列。或者,如果克里斯在5月7日没有获得奖励的事实很重要,那么这可能意味着你需要额外的专栏来解释原因。例如,替代设计可能类似于:
使用表格person
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之间填充日期(提示:使用上面创建的insert into .. select ..
程序使用daterange
。)
然后,您可以将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
表中的日期插入奖励。在这种情况下,relevantdate
表仅用作报告目的的支持对象。
作为选择,您现在可以使用以下内容:
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
表,您可以可以考虑完全取消id
列。