Chr*_*row 5 java google-app-engine database-design appointment
我正在构建一个管理应用程序,以帮助管理我的移动汽车细节公司(并希望其他人).我正在努力弄清楚如何建模一些数据.
这个问题与我发布的上一个问题有关,但我已经复制了以下相关信息: 数据库设计 - 谷歌应用引擎
在这个应用程序中,有"约会"和"行项目"的概念.
约会是指员工需要提供服务的地点和时间.
订单项是服务,费用或折扣及其相关信息.可能进入约会的订单项示例:
Name: Price: Commission: Time estimate Full Detail, Regular Size: 160 75 3.5 hours $10 Off Full Detail Coupon: -10 0 0 hours Premium Detail: 220 110 4.5 hours Derived totals(not a line item): $370 $185 8.0 hours
在我之前的此应用程序实现中,行项目包含在一个约会中.这在大多数时候都很好,但有时会引起问题.一个例子是如果一个约会因为下雨而中途中断,技术人员必须在第二天回来并完成.这种情况需要对同一个订单项进行两次约会.在这种情况下,我只是通过将第二个约会上的"行项目"设置为"完成"这样的内容来稍微捏造数据,然后成本为0美元.
在下一个版本中,我正在考虑启用行项目与多个约会匹配,表格结构如下所示:
Appointment
start_time
etc...
Line_Item
appointment_Key_List
name
price
etc...
Run Code Online (Sandbox Code Playgroud)
这种结构的一个普遍问题是它很复杂,我甚至不确定它是否适合将一个订单项与多个约会相匹配.如果行项目只能作为一个约会的一部分,那么我实际上可以在每个约会中放置一个行项目列表,当我得到约会时,我已经获得了行项目.
一个更具体的问题是我正在使用谷歌应用引擎,如果我想查询一组约会及其相关的订单项,我必须首先查询约会集,然后再对该行进行第二次查询使用IN运算符测试是否有任何Line_Item的约会密钥落入从上一个查询返回的约会密钥集中的项目.如果我有超过30个密钥要求我对查询进行分片,则第二个查询将失败.我可以对数据进行非规范化以避免这种复杂而广泛的读取查询,并且我可能不得不在某种程度上反规范化,但我宁愿在适当的地方避免复杂性.
我的问题是这种情况通常是如何建模的?是否适合将订单项与多个约会配对,或者将每个约会的订单项拆分为单独的约会是正常的,例如"2天工作的上半部分"和"2天工作的下半部分" ".类似的成功应用如何做到这一点?在这种情况下,有哪些经验法则?哪些实施变得不那么成问题?
谢谢!
您建议的方法效果很好;您可以将订单项的“appointment_Key_list”建模为列表属性,它将按您的预期工作。您不必使用 IN 运算符 - 这是为了将数据存储中的单个值与您拥有的键列表进行匹配(例如,“WHERE datastore_column IN ('a', 'b', 'c')),而您正在执行相反的操作 - 将单个值与数据存储中的列表进行匹配。
不过,我建议相反的做法可能更适合您的任务:让每个约会都有一个行项目键列表。其操作方式大致相同,但要检索约会的所有数据,您首先要获取约会,然后使用约会实体中的键对行项目进行批量获取。如果您知道约会的密钥,那么您就根本不需要进行任何查询。
我一直试图向 Pindatjuh 解释为什么查询列表属性的效率并不比单值属性低,但显然需要更详细的描述,所以言归正传,这里是......
尽管 Python 和 Java 为数据存储提供了各种高级接口,但数据存储本身讲的是较低级抽象,称为实体。一个实体由以下部分组成:
主键是您已经熟悉的数据存储区键。(名称、值)对的列表是 App Engine 对实体中数据的表示。到目前为止就这么简单。具有以下值的实体:
a_string = "Hello, world"
an_int = 123
Run Code Online (Sandbox Code Playgroud)
将被序列化为类似这样的内容:
[('a_string', 'Hello, world'), ('an_int', 123)]
Run Code Online (Sandbox Code Playgroud)
但这如何与列表交互呢?嗯,列表被视为“多值”属性。也就是说,包含 n 个项目的列表存储为 n 个单独的属性。一个例子可能会让这一点更清楚:
a_string = "Hello, world"
an_int = 123
a_list_of_ints = [42, 314, 9]
Run Code Online (Sandbox Code Playgroud)
将被序列化为:
[('a_string', 'Hello, world'), ('an_int', 123), ('a_list_of_ints', 42), ('a_list_of_ints', 314), ('a_list_of_ints', 9)]
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,该列表表示一系列值,所有值都具有相同的名称。当您从数据存储区加载数据时,SDK 会看到重复的值并将其转换为列表。
当它与索引交互时,这一点变得很重要。假设您在“a_string”和“an_int”上有一个索引。当您插入或修改一个值时,App Engine 会为其生成一组索引条目;对于上面的索引和上面的实体,它在索引中生成一行,如下所示:
('Hello, world', 123, a_key)
Run Code Online (Sandbox Code Playgroud)
(这里的“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)
Run Code Online (Sandbox Code Playgroud)
同样,查询的工作方式与以前相同 - App Engine 只需要在索引中查找具有正确前缀的行。列表中的条目数对查询速度没有影响,只影响生成和写入索引条目所需的时间。事实上,查询规划器不知道“a_list_of_ints”是一个多值属性 - 它只是将其视为任何其他索引条目。
简而言之: