【问题标题】:electronic leave application database design电子请假申请数据库设计
【发布时间】:2010-09-15 23:14:00
【问题描述】:

我目前正在处理请假申请(这是我的电子排程项目的一个子集),我的数据库设计如下:

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 个查询(第一个查询分配规则的数量和最大结转配额。然后对于每个用户,我需要找出余额,然后将余额插入数据库)

【问题讨论】:

    标签: php postgresql


    【解决方案1】:

    我没有很好地遵循架构(看起来每个 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
    

    现在,我们的 Leave 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
    

    因此,在年终时,您将在新的一年将结转余额重新插入 LeaveAllocation。

    【讨论】:

    • * er... 我错过了事件表中对用户的引用 * 是的,为了简化内容,每个休假类型都有结转 * 设计中唯一的非规范化字段 (afaik) 是event_leave 表中的 total_days 字段,有时管理用户希望能够更改
    • 你错过了什么……在你的 leave_current_balance 视图中,你没有添加从去年结转的天数……还是我错过了什么?
    • 我存储的天数被转发回 Leave_Allocation。不过,我认为我完全错过了 Leave_Allocation 的要点。所以假装它叫 Leave_Allotment 什么的。
    • 我真的希望我可以通过使用 sql 来计算递归...我被困在将这个公式转换为 sql => leave_brought_forward(next_year) = min(leave_allocation(current_year) + leave_brought_forward(last_year) - leave_taken (current_year), maximum_carry_forward());
    【解决方案2】:

    总会有更好的设计!!

    您当前的设计可行吗?您期望有多少用户(即,您必须运行 x 千次查询是否重要)。

    如果当前设计的问题只是在年初,那么也许你可以忍受它!

    干杯

    新西兰

    【讨论】:

    • 预计有大约 600 个用户。我担心运行 1200 多个查询(应该远不止于此)最终会导致超时......现在我担心我是否在创建讨论而不是 QnA
    【解决方案3】:

    关于我的数据库设计和一些用例的进一步说明。

    餐桌设计

    这是存储事件的主表(基本上基于 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)
    

    一些示例用例和解决方案

    计算余额 (WIP)

    为了计算余额,我对声明如下的视图进行查询

    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;
    

    在年初填充 leave_allocation 表

    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_carry_forward 表

    找出分配给用户的休假类型

    我可能需要重命名这个视图(我不擅长命名东西......)。它实际上是一个用户的 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;
    

    【讨论】:

    • 修订版。 1 通知我没有考虑到可能会出现紧急休假(这是一种休假)会从年假配额中扣除的情况
    • 修订版。 2 仍在努力将 leave_carry_forward 表更改为视图
    • 修订版。 2 放弃...今年余额(结转)=当年分配-休假天数+去年余额,其中去年余额=去年分配-休假天数+去年余额....这是递归...
    • 当其他一切似乎都是时,为什么不是 Leave_Type 的 Leave_Carry_Forward? Leave_Allocation 只是一年中休假的总和吗?我认为今年的余额是今年的起始余额。我想我只是无法确定起始 bal 的存储位置。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-16
    • 2019-10-23
    • 1970-01-01
    相关资源
    最近更新 更多