约会和订单项

我正在构建一个管理应用程序来帮助管理我的移动汽车细节公司(并希望其他人)。 我正在努力弄清楚如何建模一些数据。

这个问题与我发布的上一个问题有关,但我已经复制了以下相关信息: 数据库设计 – 谷歌应用程序引擎

在这个应用程序中,有“约会”和“行项目”的概念。

预约是指员工需要提供服务的地点和时间。

订单项是服务,费用或折扣及其相关信息。 可能进入约会的订单项示例:

名称:价格:佣金:时间估计详细资料,常规尺寸:160 75 3.5小时$ 10全部详情优惠券:-10 0 0小时Premium详情:220 110 4.5小时派生总数(不是专项):$ 370 $ 185 8.0小时 

在我之前的此应用程序实现中,行项目由一个约会包含。 这在大多数时候都很好,但有时会引起问题。 一个例子是如果一个约会因为下雨而中途中断,技术人员必须在第二天回来并完成。 这种情况需要对同一个订单项进行两次约会。 在这种情况下,我只是通过将第二个约会上的“行项目”设置为“完成”这样的内容来稍微捏造数据,然后成本为0美元。

在下一个版本中,我正在考虑启用行项目与多个约会匹配,表格结构如下所示:

Appointment start_time etc... Line_Item appointment_Key_List name price etc... 

这种结构的一个普遍问题是它很复杂,我甚至不确定它是否适合将一个订单项与多个约会相匹配。 如果行项目只能作为一个约会的一部分,那么我实际上只需在每个约会中放置一个行项目列表,当我得到约会时,我已经获得了行项目。

一个更具体的问题是我正在使用谷歌应用引擎,如果我想查询一组约会及其相关的订单项,我必须首先查询约会集,然后再对该行进行第二次查询使用IN运算符测试任何Line_Item的约会密钥是否属于从上一个查询返回的约会密钥集的项目。 如果我有超过30个密钥要求我对查询进行分片,则第二个查询将失败。 我可以对数据进行非规范化以避免这种复杂而广泛的读取查询,并且我可能不得不在某种程度上反规范化,但我宁愿在适当的地方避免复杂性。

我的问题是这种情况通常是如何建模的? 是否适合将订单项与多个约会配对,或者将每个约会的订单项拆分为单独的约会是正常的,例如“2天工作的上半部分”和“2天工作的下半部分” “。 类似的成功应用如何做到这一点? 在这种情况下有哪些经验法则? 哪些实施变得不那么成问题?

谢谢!

你建议的方法会很好; 您可以将订单项的“appointment_Key_list”建模为列表属性,它将按预期工作。 您不必使用IN运算符 – 用于将数据存储区中的单个值与您拥有的键列表进行匹配(例如,“WHERE datastore_column IN(’a’,’b’,’c’)),同时您正在执行相反操作 – 将单个值与数据存储区中的列表进行匹配。

不过,我建议反向可能更适合您的任务:让每个约会都有一个行项目列表列表。 这的操作方式大致相同,但是要检索约会上的所有数据,您首先获取约会,然后使用约会实体中的键批量获取订单项。 如果您知道约会的关键,那么您就完全无需进行任何查询。

我一直试图向Pindatjuh解释为什么查询列表属性的效率并不低于单值属性,但显然需要更详细的描述,所以没有任何进一步的麻烦,这里是……

关于App Engine数据存储索引的简要介绍

虽然Python和Java为数据存储区提供了各种高级接口,但数据存储区本身就是一种较低级别的抽象,称为实体。 实体包括以下内容:

  1. 唯一的主键
  2. (名称,值)对的列表

主键是您已熟悉的数据存储区键。 (名称,值)对列表是App Engine对您实体中数据的表示。 到目前为止如此直截了当。 具有以下值的实体:

 a_string = "Hello, world" an_int = 123 

将被序列化为类似于此的东西:

 [('a_string', 'Hello, world'), ('an_int', 123)] 

但这如何与列表互动? 好吧,列表被视为“多值”属性。 也就是说,具有n个项目的列表被存储为n个单独的属性。 一个例子可能会使这更清楚:

 a_string = "Hello, world" an_int = 123 a_list_of_ints = [42, 314, 9] 

将被序列化为:

 [('a_string', 'Hello, world'), ('an_int', 123), ('a_list_of_ints', 42), ('a_list_of_ints', 314), ('a_list_of_ints', 9)] 

如您所见,列表代表一系列值,所有值都具有相同的名称。 从数据存储区加载数据时,SDK会看到重复的值并将其转换为列表。

重要的是它与索引进行交互的时候。 假设你有’a_string’和’an_int’的索引。 插入或修改值时,App Engine会为其生成一组索引条目; 对于上面的索引和上面的实体,它在索引中生成一行,如下所示:

 ('Hello, world', 123, a_key) 

(’a_key’这里是原始实体的键的占位符。)当您执行使用此索引的查询时,它只需要对索引执行查找以查找具有适当前缀的行(例如,’SELECT * FROM Kind WHERE a_string =“Hello,world”ORDER BY an_int’)。

但是,在索引列表时,App Engine会插入多个索引行。 ‘an_int’和’a_list_of_ints’的索引将为上述实体生成以下行:

 (123, 42, a_key) (123, 314, a_key) (123, 9, a_key) 

同样,查询的工作方式与之前相同 – App Engine只需在索引中查找具有正确前缀的行。 列表中的条目数对查询的速度没有影响 – 仅限于生成和写入索引条目所花费的时间。 事实上,查询规划器并不知道’a_list_of_ints’是一个多值属性 – 它只是像任何其他索引条目一样对待它。

简而言之:

  1. 在索引和查询术语中,具有一个元素的列表与单个属性之间没有实际区别
  2. 索引列表的大小会影响索引所需的时间和空间,但不会影响查询。
  3. 您可以使用简单的相等filter执行与列表中给定值匹配的任何实体的查询。

这种问题的通常解决方案是将模型归一化,即归一化。

您的模型以规范化forms将具有第三个表,其中包含对AppointmentLine_Item行的引用:

 Appointment start_time ... Line_Item name price ... Appointment_Line_Item appointment_key line_item_key 

但是有一个问题! 由于您使用的是Google App Engine,并且他们的数据存储非常有限(“GQL无法执行类似SQL的JOIN”)并且大多数都需要非规范化。

您建议使用类似列表的字段。 使用它是可能的,但很难对其进行索引。 在数据库中每行的列表中搜索一个键( appointment_key )并没有真正执行。 我提出了两个可能性:

  1. 重复Line_Item

     Line_Item appointment_key name price finished ... 

    当项目由员工完成或不完成时, Line_Item应该具有finished状态。 如果员工尚未完成所有订单项,请将其标记为未完成,创建新约会并复制所有未完成的项目。 您可以在所有Line_Items上的appointment_key字段上建立Line_Items ,这是一件好事。 但是, 重复数据可能是个问题。

  2. Line_Item动态字段:

     Line_Item duplicate_key appointment_key name price finished ... 

    Line_Item创建一个新字段duplicate_key ,该字段指向另一个Line_Item或null(保留此密钥!)。 Null表示Line_Item是原始的,任何其他值表示此Line_Item是该字段指向的Line_Item的副本。 标记为重复的Line_Item所有字段都inheritance原始Line_Item的字段,除了appointment_key :因此它将占用更少的存储空间。 此解决方案也应该将appointment_key编入索引,以加快查找时间。 这需要每个重复的Line_Item一个额外的查询 ,这可能是一个问题。

现在,这是一个明确的选择:更快的速度或更好的存储。 我会选择第一个,因为它降低了模型的复杂性,而存储从来不是现代系统的问题。 较低的复杂性通常意味着更少的错误和更少的开发/测试成本,这certificate了存储要求的成本。