DB Schema:版本化的价格模型与发票相关的数据

时间:2017-05-18 07:42:30

标签: database database-design relational-database database-schema domain-model

我正在为租赁发票生成创建一些数据库模型。 发票包含N个预订时间范围。

每个预订都属于价格模型。价格模型是一组确定最终价格的规则(基价+季节价格+数量弃用+ ...)。

这意味着发票中N个预订的最终价格可能是一个复杂的计算,当然我想跟踪最终价格计算的各个方面,以便以后查看发票。

问题是,价格模型将来可能会发生变化。因此,在生成发票时,有两种可能性:

  • (a)永远不要更改价格模型。只需通过版本化使其变为不可变,并从发票中引用具体版本。

  • (b)将所有价格信息,折扣和额外费用放入发票中。这意味着很多数据,因为发票包含N个预订,可能部分在季节价格的范围内。 基本上,我会将每个预订细分为日期,每天我会有N行计算基本价格,折扣和额外费用。

可能的表格型号:

Invoice
   id: int

InvoiceBooking          # Each booking. One invoice has N bookings
   id: int
   invoiceId: int
   (other data, e.g. guest information)

InvoiceBookingDay       # Days of a booking. Each booking has N days
   id: int
   invoiceBookingId: id
   date: date

InvoiceBookingDayPriceItem   # Concrete discounts, etc. One days has many items
   id: int
   invoiceBookingDayId: int
   price: decimal
   title: string

我的问题是,我应该选择哪种方式以及为什么。

我的考虑因素:

  • 使用解决方案(a),每次查看数据时,都会使用价格模型信息重新计算发票。我不喜欢这样,因为算法可以改变。对于"只读"并不自然。发票的性质。 此外,价格模型的版本处理不是一项简单的任务,用户需要了解版本概念,这会增加应用程序的复杂性。

  • 使用解决方案(b),我会生成一堆嵌套数据,这会给架构增加很多复杂性。

您更喜欢哪种方式?我错过了什么吗?

谢谢

2 个答案:

答案 0 :(得分:2)

我推荐第三种选择。我称之为临时(时间)版本控制,表格的布局非常简单。您没有描述您的定价数据,所以我只是展示一个简单的例子。

Table: DailyPricing
ID  EffDate     Price ...
 A  01/01/2015  17.50 ...
 B  01/01/2015  20.00 ...
 C  01/01/2015  22.50 ...
 B  01/01/2016  19.50 ...
 C  07/01/2016  24.00 ...

这表明所有三个价格表(A,B和C仅代表您用来区分价格水平的任何方法)都在2015年1月1日给出了价格。2016年1月1日,B计划的价格是降低。 7月,计划C的价格上涨了。

要获取计划的当前价格,查询为:

select  dp.Price
from    DailyPricing dp
where   dp.ID = 'A'
    and dp.Effdate =(
        select  Max( dp2.EffDate )
        from    DailyPricing dp2
        where   dp2.ID = dp.ID
            and dp2.EffDate >= :DateOfInterest);

DateOfInterest变量将加载当前日期/时间。此查询返回当前有效的一个价格。在这种情况下,2015年1月1日的价格自生效以来从未改变。如果搜索是针对计划B,则2016年1月1日设定的价格将被退回,而计划C将于2016年7月1日设定价格。这些是为每个计划设定的最新价格;也就是说,当前的价格。

这样的查询更可能是与发票表的联接,因此您可以执行价格计算。

select  ...
from    Invoices i
join    DailyPricing dp
    on  dp.ID = i.ID
    and dp.Effdate =(
        select  Max( dp2.EffDate )
        from    DailyPricing dp2
        where   dp2.ID = dp.ID
            and dp2.EffDate >= i.InvoiceDate )
where   i.ID = 1234;

这比简单查询稍微复杂一点,但是您要求更复杂的数据(或者更复杂的数据视图)。但是,此计算可能只执行一次,最终价格存回发票数据或其他地方。

只有在客户进行了一些更改或您正在进行审核时,才会再次计算,重新检查计算的准确性。

然而,注意一些微妙但非常重要的东西。如果正在为刚刚创建的发票执行上述查询,则InvoiceDate将是当前日期,并且返回的价格将是当前价格。但是,如果查询是作为两年前发票上的验证运行的,那么InvoiceDate将在两年前,而且返回的价格将是两年前生效的价格。

换句话说,返回当前数据的查询和返回过去数据的查询是同一个查询。

这是因为当前数据和过去的数据保留在同一个表中,仅由数据生效的日期区分。我认为,这是关于你想做什么的最简单的解决方案。

答案 1 :(得分:1)

A B怎么样?

重新计算发票的任何组成部分并非最佳做法,尤其是在打印组件时。发票和发票详细信息应该是不可变的,您应该能够重新生成它而无需重新计算。

如果您在弄清楚如何达到一定数量时遇到问题,或者您的计划中存在错误,您会很高兴获得详细信息,特别是如果计算结果复杂的话。 / p>

此外,保留定价模型的历史记录是一个好主意,这样您就可以验证如何达到特定价格。您可以向用户简化这一过程。他们不必查看历史记录 - 但您应该在历史记录中记录他们的更改。