我目前正在处理休假申请(这是我的电子调度程序项目的一个子集),我的数据库设计如下:
event (event_id, dtstart, dtend... *follows icalendar standard*)
event_leave (event_id*, leave_type_id*, total_days)
_leave_type (leave_type_id, name, max_carry_forward)
_leave_allocation (leave_allocation_id, leave_type_id*, name, user_group_id, total_days, year)
_leave_carry_forward(leave_carry_forward_id, leave_type_id*, user_id, year)
stackoverflow中的任何人都在使用电子假应用吗?介意分享您的数据库设计,因为我正在寻找比我更好的设计。我当前设计的问题仅发生在系统计算可以结转的天数的年初。
总的来说,我必须运行1 + {$ number_of users} * 2个查询(第一个查找分配规则的数量和最大结转配额。然后对于每个用户,我需要找出平衡,然后将余额插入数据库)
答案 0 :(得分:2)
我没有很好地遵循模式(看起来每个leave_type都有一个结转?事件*表上没有用户?)但你应该能够在任何时间点动态地得出余额 - 包括跨年。
AAMOF,规范化规则要求您能够派生余额。如果您选择 denormalize 以获得性能取决于您,但设计应该支持计算的查询。鉴于此,然后计算年末结转是基于单一集合的查询。
编辑:我不得不稍微更改架构以适应这种情况,我选择规范化以使逻辑更容易 - 但是如果需要,可以在整个过程中插入非规范化:
首先对这个场景很重要的表...希望我的伪语法有意义:
User { User_Id (PK) }
// Year may be a tricky business logic issue here...Do you charge the Start or End year
// if the event crosses a year boundary? Or do you just do 2 different events?
// You want year in this table, though, so you can do a FK reference to Leave_Allocation
// Some RDBMS will let you do a FK from a View, though, so you could do that
Event { Event_Id (PK), User_Id, Leave_Type_Id, Year, DtStart, DtEnd, ...
// Ensure that events are charged to leave the user has
FK (User_Id, Leave_Type_Id, Year)->Leave_Allocation(User_Id, Leave_Type_Id, Year)
}
Leave_Type { Leave_Type_Id, Year, Max_Carry_Forward
// Max_Carry_Forward would probably change per year
PK (Leave_Type_Id, Year)
}
// Starting balance for each leave_type and user, per year
// Not sure the name makes the most sense - I think of Allocated as used leave,
// so I'd probably call this Leave_Starting_Balance or something
Leave_Allocation { Leave_Type_Id (FK->Leave_Type.Leave_Type_Id), User_Id (FK->User.User_Id), Year, Total_Days
PK (Leave_Type_Id, User_Id, Year)
// Ensure that leave_type is defined for this year
FK (Leave_Type_Id, Year)->Leave_Type(Leave_Type_Id, Year)
}
然后,视图(你可能想要应用一些非规范化的地方):
/* Just sum up the Total_Days for an event to make some other calcs easier */
CREATE VIEW Event_Leave AS
SELECT
Event_Id,
User_Id,
Leave_Type_Id,
DATEDIFF(d, DtEnd, DtStart) as Total_Days,
Year
FROM Event
/* Subtract sum of allocated leave (Event_Leave.Total_Days) from starting balance (Leave_Allocation) */
/* to get the current unused balance of leave */
CREATE VIEW Leave_Current_Balance AS
SELECT
Leave_Allocation.User_Id,
Leave_Allocation.Leave_Type_Id,
Leave_Allocation.Year,
Leave_Allocation.Total_Days - SUM(Event_Leave.Total_Days) as Leave_Balance
FROM Leave_Allocation
LEFT OUTER JOIN Event_Leave ON
Leave_Allocation.User_Id = Event_Leave.User_Id
AND Leave_Allocation.Leave_Type_Id = Event_Leave.Leave_Type_Id
AND Leave_Allocation.Year = Event_Leave.Year
GROUP BY
Leave_Allocation.User_Id,
Leave_Allocation.Leave_Type_Id,
Leave_Allocation.Year,
Leave_Allocation.Total_Days
现在,我们的离开CarryForward查询只是截至1/1午夜的当前余额或最大结转的最小值。
SELECT
User_Id,
Leave_Type_Id,
Year,
/* This is T-SQL syntax...your RDBMS may be different, but should be able to do the same thing */
/* If not, you'd do a UNION ALL to Max_Carry_Forward and select MIN(BalanceOrMax) */
CASE
WHEN Leave_Balance < Max_Carry_Forward
THEN Leave_Balance
ELSE
Max_Carry_Forward
END as Leave_Carry_Forward
FROM Leave_Current_Balance
JOIN Leave_Type ON
Leave_Current_Balance.Leave_Type_Id = Leave_Type.Leave_Type_Id
/* This assumes max_carry_forward is how much you can carry_forward into the next year */
/* eg,, a max_carry_forward of 300 hours for year 2008, means I can carry_forward up to 300 */
/* hours into 2009. Otherwise, you'd join on Leave_Current_Balance.Year + 1 if it's how much */
/* I can carry forward into *this* year. */
AND Leave_Current_Balance.Year = Leave_Type.Year
因此,在今年年底,您将把CarryForward余额重新插入到新年的LeaveAllocation中。
答案 1 :(得分:0)
总有更好的设计!!
您当前的设计是否有效?您期望多少用户(即,您必须运行x千个查询才重要)。
如果当前设计的问题只出现在年初,那么也许你可以忍受它!
干杯
NZS
答案 2 :(得分:0)
关于我的数据库设计和一些用例的进一步说明。
这是存储事件的主表(基本上基于iCalendar架构)。该活动可能是典型的活动,也可能是会议,公众假期等。
event (event_id (PK), dtstart, dtend, ... --other icalendar fields--)
如果特定类型的事件具有我必须跟踪的额外信息,我用另一个表来装饰它。例如,用于存储电子假特定信息的表。 (total_days不是作为要求的一部分的计算字段)
event_leave (event_id (PK/FK->event), total_days, leave_type_id (FK->leave_type))
保留类型表存储每种休假类型的一些信息。例如,应用程序是否需要批准/推荐等。除此之外,它还存储允许的最大结转。我认为最大结转不会经常改变。
leave_type (leave_type_id (PK), name, require_support, require_recommend, max_carry_forward)
用户被分成几组,每组将有一些天可用于离开某些的leave_type。存储在该表中的数据将每年填充一次(每年的新版本)。它仅存储每个组给出的假期数,而不是每个用户。
leave_allocation (leave_allocation_id, year(PK), leave_type_id (PK/FK->leave_type), total_days, group_id)
接下来是存储结转信息的表。此表格每年将为每个用户 填充一次。此表将每年填充一次,因为动态计算并不容易。为用户计算leave_carry_forward的公式为:
leave_carry_forward(2009) = min(leave_allocation(2008) + leave_carry_forward(2007) - leave_taken(2008), maximum_carry_forward());
leave_carry_forward (leave_carry_forward_id, user_id, year, total_days)
为了计算余额,我对声明如下的视图进行查询
DROP VIEW IF EXISTS leave_remaining_days;
CREATE OR REPLACE VIEW leave_remaining_days AS
SELECT year, user_id, leave_type_id, SUM(total_days) as total_days
FROM (
SELECT allocated.year, usr.uid AS "user_id", allocated.leave_type_id,
allocated.total_days
FROM users usr
JOIN app_event._leave_allocation allocated
ON allocated.group_id = usr.group_id
UNION
SELECT EXTRACT(year FROM event.dtstart) AS "year", event.user_id,
leave.leave_type_id, leave.total_days * -1 AS total_days
FROM app_event.event event
LEFT JOIN app_event.event_leave leave
ON event.event_id = leave.event_id
UNION
SELECT year, user_id, leave_type_id, total_days
FROM app_event._leave_carry_forward
) KKR
GROUP BY year, user_id, leave_type_id;
public function populate_allocation($year) {
return $this->db->query(sprintf(
'INSERT INTO %s (%s)' .
"SELECT '%s' AS year, %s " .
'FROM %s ' .
'WHERE "year" = %s',
'event_allocation',
'year, leave_type_id, total_days ...', //(all the fields in the table)
empty($year) ? date('Y') : $year,
'leave_type_id, total_days, ..', //(all fields except year)
$this->__table,
empty($year) ? date('Y') - 1 : $year - 1
))
->count() > 0; // using the database query builder in Kohana PHP framework
}
我可能需要重命名这个视图(我在命名内容时很糟糕......)。它实际上是一个用户的leave_allocation表。
DROP VIEW IF EXISTS user_leave_type;
CREATE OR REPLACE VIEW user_leave_type AS
SELECT la.year, usr.uid AS user_id, lt.leave_type_id, lt.max_carry_forward
FROM users usr
JOIN app_event._leave_allocation la
JOIN app_event._leave_type lt
ON la.leave_type_id = lt.leave_type_id
ON usr.group_id = la.group_id
INSERT INTO leave_carry_forward (year, user_id, leave_type_id, total_days)
SELECT '{$this_year}' AS year, user_id, leave_type_id, MIN(carry_forward) AS total_days
FROM (
SELECT year, user_id, leave_type_id, total_days AS carry_forward
FROM leave_remaining_days
UNION
SELECT year, user_id, leave_type_id, max_carry_forward AS carry_forward
FROM user_leave_type
) KKR
WHERE year = {$last_year}
GROUP BY year, user_id, leave_type_id;