是否有针对此问题的基于集合的解决方案?

时间:2010-03-11 18:57:56

标签: sql sql-server-2005

我们有一个表格设置如下:

|ID|EmployeeID|Date     |Category       |Hours|
|1 |1         |1/1/2010 |Vacation Earned|2.0  |
|2 |2         |2/12/2010|Vacation Earned|3.0  |
|3 |1         |2/4/2010 |Vacation Used  |1.0  |
|4 |2         |5/18/2010|Vacation Earned|2.0  |
|5 |2         |7/23/2010|Vacation Used  |4.0  |

业务规则是:

  • 假期余额按度假减去假期计算。
  • 使用假期始终首先应用于最早的度假收入金额。

我们需要返回Vacation Earned的行,这些行没有被使用的假期抵消。如果使用的假期仅抵消了度假获得记录的一部分,我们需要返回显示差异的记录。例如,使用上表,结果集如下所示:

|ID|EmployeeID|Date     |Category       |Hours|
|1 |1         |1/1/2010 |Vacation Earned|1.0  |
|4 |2         |5/18/2010|Vacation Earned|1.0  |

请注意,记录2已被删除,因为它已被使用时间完全抵消,但记录1和记录4仅部分使用,因此它们被计算并按原样返回。

我们想到的唯一方法是将所有度假获得的记录放在临时表中。然后,获取使用的总休假并循环通过临时表,删除最旧的记录并从使用的总休假中减去该值,直到使用的总假期为零。我们可以清理它,因为剩下的假期只是最古老的度假记录的一部分。这将使我们留下优秀的度假收入记录。

这有效,但效率很低,表现不佳。此外,随着越来越多的记录被添加,性能会随着时间的推移而降低。

对于更好的解决方案有什么建议吗?如果没有,我们只需要这样做。

编辑:这是供应商数据库。我们无法以任何方式修改表结构。

5 个答案:

答案 0 :(得分:2)

在思考这个问题时,我突然意识到,当度假时,你需要关心的唯一原因是它是否过期。如果是这种情况,最简单的解决方案是在表中添加“休假过期”记录,这样员工剩余的休假金额始终只是sum(vacation earned) - (sum(vacation expired) + sum(vacatation used))。您甚至可以使用上次休假过期记录作为查询的起点来显示您想要的确切记录。

但我猜这不是一个选择。要解决所提出的问题,请记住,每当您发现自己使用临时表时,请尝试将该数据放入CTE(公用表表达式)中。不幸的是我现在开会,所以我没有时间写这个查询(可能以后,听起来很有趣),但这应该让你开始。

答案 1 :(得分:2)

以下应该这样做..

(但正如其他人所说,最好的解决方案是在花费时调整剩余假期。)

select 
    id, employeeid, date, category, 
    case 
    when  earned_so_far + hours - total_spent > hours then 
        hours 
    else 
        earned_so_far + hours - total_spent
    end as hours
from 
    (
                select 
                    id, employeeid, date, category, hours,
                    (
                        select 
                            isnull(sum(hours),0)
                        from 
                            vacations 
                        WHERE 
                            category = 'Vacation Earned' 
                            and 
                            date < v.date
                            and
                            employeeid = v.employeeid
                    ) as earned_so_far,
                    (
                        select
                            isnull(sum(hours),0)
                        from
                            vacations
                        where 
                            category = 'Vacation Used'
                            and 
                            employeeid = v.employeeid
                    ) as total_spent
                from 
                    vacations V
                where category = 'Vacation Earned'
    ) earned
where
    earned_so_far + hours > total_spent

逻辑是

  1. 计算每个earned行,获得的小时数到目前为止
  2. 计算此用户使用的总小时数
  3. 选择记录,如果此记录的total_hours_so_far +小时数 - total_spent_hours&gt; 0

答案 2 :(得分:1)

我发现你的整个结果集令人困惑和不准确,我可以看到员工说,“不,我在1月25日2小时没有赢得1小时。”他们在那个日期赚了1个小时并没有被部分抵消,如果你选择以这种方式显示,你就不会有任何问题。我会以不同的方式来展示这些信息。通常,您要么显示所有休假操作(已获奖,已过期和已使用)的列表,并且总计在底部,或者您提供可供使用和使用的摘要。

在劳动力领域超过30年,并且在许多不同的计时系统下(以及在我担任管理分析师时研究得更多),我从未见过有人想以这种方式显示计时信息。我在想是有原因的。如果这是一个要求,我建议推迟它并解释如何阅读这些数据会让人感到困惑,以及难以获得性能良好的解决方案。如果不试图说服客户说这是一个糟糕的主意,我不会接受这个要求。

答案 3 :(得分:0)

随着时间的推移和记录的增加,除非您对此做些什么,否则性能会越来越差,例如:

  • 一旦他们被“取消”就清除旧行(例如,假期获得的假期已使用相应的假期使用行并计入;假期使用已设置为“过期”假期获得“消费”)
  • 添加一个标记行是否已“取消”的列,并将此列合并到索引中

以这种方式跟踪数据的变化似乎是修改表结构的一个参数(有几个,而不仅仅是一个),但这超出了当前问题的范围。

至于查询本身,我会构建两个聚合,做一些减法,使它成为一个子查询,然后加入一些巧妙使用其中一个排名函数。在某处也闻起来像是一个相关的子查询。我可能会稍后尝试将其解决(我的时间很短),但我打赌有人会打败我。

答案 4 :(得分:0)

我建议修改表格,以便在自己的专栏中跟踪余额。这样,您只需要获取最新记录即可知道员工的位置。

这样,你可以满足简单的情况(“我有多少休假时间”),同时仍然能够做你正在寻找的尴尬汇总“哪些假期时间不行与其他位“报告,我希望这是你经常不需要的东西。