用于跟踪访问次数与动态目标的数据库

时间:2016-09-11 23:36:34

标签: mysql sql database database-design

我正在设计一个新数据库,以跟踪帐户实现的访问次数与每月目标次数。最终报告应在开始/结束日期和一个帐户之前申请,以显示月度目标和访问量之间的月份。 当我知道账户数量超过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生成最终报告所需的帮助。

2 个答案:

答案 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   <= ?