我以Django作为后端创建基于Web的销售点(思考收银机)解决方案。我一直都是经典的'建模发票及其订单项的方法。
InvoiceTable
id
date
customer
salesperson
discount
shipping
subtotal
tax
grand_total
[...]
InvoiceLineItems
invoice_id // foreign key
product_id
unit_price
qty
item_discount
extended_price
[...]
在尝试研究最佳实践后,我发现没有多少 - 至少没有广泛使用的权威来源。
Kimball Group建议:"我们建议您将标题的所有维度降低到订单项。"""而不是保留交易标题“对象”的操作概念。"
请参阅http://www.kimballgroup.com/2007/10/02/design-tip-95-patterns-to-avoid-when-modeling-headerline-item-transactions/和http://www.kimballgroup.com/2001/07/01/design-tip-25-designing-dimensional-models-for-parent-child-applications/。
我刚接触开发(之前只使用过桌面数据库软件) - 但据我所知,这是有道理的,因为我们可以按照我们想要的方式深入查看数据(尽管我想我们可以做到与加入表格的第一种方法相同。
我的问题
我们如何处理发票'备注' - 我们有打印和/或内部注释,我们是否会将数据放入订单项中,并为每个订单项重复一次?这似乎违反了数据规范化。把它放在相关的表格中?
任何使用此方法的开源项目我都可以查看一下?不知道如何搜索它们。
答案 0 :(得分:3)
听起来你混淆了关系设计和维度设计。
关系设计用于促进事务处理,并最小化数据异常和重复。它是您的运营数据库。尺寸设计用于促进分析。
关系设计将有一个发票表和一个line_items表,而维度设计将有一个company_invoices_customer事实表,其中包含一粒发票行项目。
由于这是针对POS的,我假设您首先想要一个关系设计。
关于你的问题:
首先,这种情况有大量良好的数据建模模式。见https://dba.stackexchange.com/questions/12991/ready-to-use-database-models-example/23831#23831
每行需要重复发票ID(所以我们可以 生成发票总计等数据。这是故意的吗? 这种数据建模方式的特点是什么?
是
我们经常有发票级数据,如票据,折扣,运费 收费等 - 我们如何使用这种方法来表示这些?
可能最容易/最简单的有"笔记"发票表上的字段。
对于费用和折扣,您应该使用抽象(请参阅表继承),并将它们添加为订单调整。请参阅上面链接中的Silverston一书。
某些折扣是特定于产品的 - 因此它们属于订单项 无论如何,其他人都是发票宽(想想你买两个的交易 分开产品并获得两者的折扣) - 我们可以 以某种方式在订单项中分配它?
项目的价格应根据其默认价格以及当前"场景"中适用的任何折扣或费用在运行时计算,附近的政府折扣示例销售日。您可以拥有相互引用的分层行项目,以保持秩序井然有序。再次,请参阅Silverston的书。
我们如何处理发票'备注' - 我们有印刷和/或内部 注意,我们会将数据放在订单项中,然后重复一下 每个订单项?
如果您需要订单项备注,请在订单项表格中添加备注列。
这似乎与数据规范化无关。把它放在一个相关的 表
如果注释可以为空,并且您希望对规范化严格,那么是,添加invoice_notes表。
答案 1 :(得分:0)
Invoice / InvoiceLineItem
是父(Invoice)/ Child(Item)关系模型的示例,在标准化关系数据库中很常见。看起来你正在为IMO建立正确的关系。
每行需要重复发票ID(因此我们可以生成发票总计等数据)。这是这种数据建模方式的有意特征吗?
是的,这很好 - 您需要不断加入InvoiceID
并按其过滤(在LineItem
上,InvoiceId应该是Invoice的外键,而在Invoice上它将会可能是主键,或至少是一个独特的约束)
我们经常有发票级数据,如票据,折扣,运费等。 - 我们如何使用此方法表示这些数据?一些折扣是特定的产品(我假定行) - 因此它们无论如何都属于订单项,其他折扣是发票范围(想想您购买两个单独产品并获得折扣的交易)在这两个) - 我们可以以某种方式分配线项目?与运费相同,通过在订单项之间进行分配来进行分配?
经常有'似乎这些数据是可选的,每个发票或行项目可以有多个备注/折扣等。这些关系通常被建模为单独的表,外键返回到关联的发票或行项目。这样,您可以有许多笔记,许多折扣等。您可能希望在这些表上引入唯一约束,以防止多个发票具有2个相同折扣。这里的一个设计选项是行折扣和发票折扣之间是否有足够的通用性,以保证单个表对父/子进行建模,并使用可选的外键。一般来说,我会错误地分离专门的InvoiceDiscount和LineDiscount表。
我们如何处理发票'备注' - 我们有打印和/或内部注释,我们是否会将数据放入订单项中,并为每个订单项重复一次?这似乎违反了数据规范化。把它放在相关的表格中?
听起来这些音符可能是“静态数据”,例如一套50条标准评论,可能编纂成文。笔记'模板'应该建模为(例如NoteTemplate)。然后,您可以在LineItem
到NoteTemplate
之间添加N:N联结表。如果这里允许有可选的自由流量注释,这可以作为联结表上的附加列(虽然有些ORMs
不赞成这样做。)
关注
FWIW,我已经交付了几个零售系统,所有系统(现成和定制)都使用这种方法来模拟发票(以及采购订单,交货单等)。
您可能习惯使用NoSql
或单个文档/聚合根存储方法,并且对标准化可能导致的大量表有所保留?虽然毫无疑问你可以为#34; Invoice"聚合根,问题将来自查询数据(例如find me the sum of all sales of XYZ ToothPaste for sales on DD/MM/YYYY
,尤其是在行项目或笔记等子节点上)。
实施
出于性能原因,通常会汇总父发票级别上所有子订单项的净额,税金和总额的总和。虽然这可能是多余的,但它确实具有优于不断重新获得该数据的性能优势,例如,来自View或Computed列。另一个性能考虑因素是,每次提取发票时,您都会经常检索所有订单项。因此,在Parent Child数据建模中通常使用父InvoiceId
作为子表上的Clustering Key(NB这与子项上的Primary Key不同,但您可以选择这里加倍,例如,如果一个行项目每个发票只能有一个产品,那么InvoiceId, ProductId
可能是群集密钥和主密钥的好选择。
HTH
修改强>
SqlServer
和MySql
的聚簇索引与数据的物理存储相关 - 聚类键(和填充因子)决定了记录如何存储在数据页面上。 Oracle有一个名为Indexed Organized Tables (IOT)
的类似概念。
如果是亲子关系(例如Invoice
到InvoiceLineItem
),那么检索给定父级Invoice
的所有子级是很常见的,确保尽可能多的孩子尽可能存储在同一物理页面上(如果需要多个页面,则在相邻页面上)。这将最大限度地减少读取IO。
我已经提出了一个SqlServer
SqlFiddle here,它希望通过父发票说明两种聚类方法 - 一种是具有代理非群集PK的详细信息,另一种是使用复合PK到InvoiceID, ProductID
。