我正在设计一个新数据库,以跟踪帐户实现的访问次数与每月目标次数。最终报告应在开始/结束日期和一个帐户之前申请,以显示月度目标和访问量之间的月份。 当我知道账户数量超过10万并且目标应该每月更改一次或者不会针对唯一更改的账户目标(即每个目标将具有开始和结束日期)时开始复杂化。如果没有结束日期则目标始终是有效)。此时我输了,我需要帮助
为简单起见,我假设我有一个日期周期表和最简单的情况如下
accounts
+----+---------+
|id | name |
+----+---------+
| 1 | account1|
| 2 | account2|
+----+---------+
targets
+---+------------+------------+-----------+----------------+
|id | account_id | start_date | end_date | monthly_target |
+---+------------+------------+-----------+----------------+
|1 | 1 | 1-1-2016 | 31-1-2016 | 5 |
|2 | 1 | 1-2-2016 | 31-5-2016 | 4 |
|3 | 1 | 1-7-2016 | null | 7 |
|4 | 2 | 1-1-2016 | null | 10 |
+---+------------+------------+-----------+----------------+
visits
+---+-----------+------------+
|id | date | account_id |
+----------------------------+
|1 | 15-1-2016 | 1 |
|2 | 20-1-2016 | 1 |
|3 | 10-5-2016 | 1 |
|3 | 20-5-2016 | 1 |
|4 | 20-5-2016 | 2 |
+---+-----------+------------+
calendar (Optional)
----------+----------+
|start | end |
----------+----------+
|1-1-2016 | 31-1-2016|
|1-2-2016 | 29-2-2016|
|1-3-2016 | 31-3-2016|
|1-4-2016 | 30-4-2016|
|1-5-2016 | 31-5-2016|
|1-6-2016 | 30-6-2016|
|1-7-2016 | 31-7-2016|
|1-8-2016 | 31-7-2016|
+---------+----------+
2016年1月1日至2016年7月31日期间账户1覆盖的预期报告
+---------+-----------+--------+----+
|start | end | target | sum|
+---------+-----------+--------+----+
|1-4-2016 | 30-4-2016 | 4 | 0 |
|1-5-2016 | 31-5-2016 | 4 | 2 |
|1-6-2016 | 30-6-2016 | 0 | 0 |
|1-7-2016 | 31-7-2016 | 7 | 0 |
+---------+-----------+--------+----+
我可以接受更改我的初始设计,如果它导致问题,但假设目标表的设计是系统管理员最实用的设计。
我需要SQL生成最终报告所需的帮助。
答案 0 :(得分:2)
我将targets
中的日期范围修改为具有明确的结束日期,即使这意味着年终。这样,避免null,sql范围可以正常。它还使用ISO 8601标准作为日期。它在存储过程中实现,它包含3个参数:account_id,开始和结束日期。
派生表的别名v
可防止双重计数与针对访问表的展平LEFT JOIN
。例如,如果没有该策略,那2将是错误的7。所以它使用了LAST_DAY()
函数。
架构:
create table accounts
( id int not null,
name varchar(100) not null
);
insert accounts values
(1,'account1'),
(2,'account2');
-- drop table targets;
create table targets
( id int not null,
account_id int not null,
start_date date not null,
end_date date not null,
monthly_target int not null
);
-- truncate targets;
insert targets values
(1,1,'2016-01-01','2016-01-31',5),
(2,1,'2016-02-01','2016-05-31',4),
(3,1,'2016-07-01','2016-12-31',7),
(4,2,'2016-01-01','2016-12-31',10);
create table visits
( id int not null,
date date not null,
account_id int not null
);
-- truncate visits;
insert visits values
(1,'2016-01-15',1),
(2,'2016-01-20',1),
(3,'2016-05-10',1),
(4,'2016-05-20',1),
(5,'2016-05-20',2);
create table calendar
( start date not null,
end date not null
);
insert calendar values
('2016-01-01','2016-01-31'),
('2016-02-01','2016-02-29'),
('2016-03-01','2016-03-31'),
('2016-04-01','2016-04-30'),
('2016-05-01','2016-05-31'),
('2016-06-01','2016-06-30'),
('2016-07-01','2016-07-31'),
('2016-08-01','2016-08-31'),
('2016-09-01','2016-09-30'),
('2016-10-01','2016-10-31'),
('2016-11-01','2016-11-30'),
('2016-12-01','2016-12-31');
存储过程:
DROP PROCEDURE IF EXISTS uspGetRangeReport007;
DELIMITER $$
CREATE PROCEDURE uspGetRangeReport007
( p_account_id INT,
p_start DATE,
p_end DATE
)
BEGIN
SELECT c.start,c.end,
IFNULL(t.monthly_target,0) as target,
-- IFNULL(sum(v.id),0) as visits
IFNULL(v.theCount,0) as visits
FROM calendar c
LEFT JOIN targets t
ON account_id=p_account_id
AND c.start BETWEEN t.start_date AND t.end_date
AND c.end BETWEEN t.start_date AND t.end_date
LEFT JOIN
( SELECT LAST_DAY(date) as lastDayOfMonth,
count(id) as theCount
FROM VISITS
WHERE account_id=p_account_id
GROUP BY LAST_DAY(date)
) v
ON v.lastDayOfMonth BETWEEN c.start AND c.end
WHERE c.start BETWEEN p_start AND p_end
AND c.end BETWEEN p_start AND p_end
GROUP BY c.start,c.end,t.monthly_target
ORDER BY c.start;
END;$$
DELIMITER ;
测试:
call uspGetRangeReport007(1,'2016-04-01','2016-07-31');
+------------+------------+--------+--------+
| start | end | target | visits |
+------------+------------+--------+--------+
| 2016-04-01 | 2016-04-30 | 4 | 0 |
| 2016-05-01 | 2016-05-31 | 4 | 2 |
| 2016-06-01 | 2016-06-30 | 0 | 0 |
| 2016-07-01 | 2016-07-31 | 7 | 0 |
+------------+------------+--------+--------+
答案 1 :(得分:1)
SELECT c.start,
c.end,
t.monthly_target AS target,
(
SELECT COUNT(*)
FROM visits
WHERE `date` BETWEEN c.start AND c.end
AND account_id = ? -- Specify '1'
) AS `sum` -- Correlated subquery for counting visits
FROM Calendar AS c
JOIN targets AS t ON c.start_date >= t.start_date
AND ( t.end_date IS NULL
OR c.start_date < t.end_date )
WHERE c.start >= ? -- Specify date range
AND c.end <= ?