为对象的易变层次结构建模数据库结构

时间:2015-10-28 09:38:08

标签: sql database database-design data-modeling database-performance

[注意:开始]

希望有些人,专业人士已经不得不应对这种情况。 关于邮局的具体案例是虚构的,只是为了提出问题。 这个问题不是通过添加索引来提高性能。

[注:结束]

最近,我一直想知道如何有效(!)创建数据库结构来处理易变的层次结构级别。 让我们进入这个例子,以便更好地理解这个问题。

假设我们的邮局以不同的方式存储物理邮件,具体取决于任何因素(这里并不重要)。 我们将这种情况映射到数据库模型。

好的,我们收到这些邮件。邮件可以物理存储在箱子,盒子,抽屉,安全存款等等(因为我们不想严格限制我们的存储类型和结构,而是允许它灵活地满足我们未来的变化)。这意味着现在我们有这样的类型,例如,一个邮件可以存放在存放在保险箱中的盒子里,但实际上可以有任何组合。

为简单起见,我们假设单个邮件是我们可以获得的最低粒度。但是,我们必须记住,我们也可能有空盒子(它下面和上面没有任何物体),我们也想将它存储在数据库中。

此模型必须:

  • 高效 - 它可以(或应该吗?)全部存储在一个表中,因为我们每天处理许多邮件
  • 可调整 - 我们可能需要更改存储一些已存在对象的方式,因此我们需要为某些对象更改这些已分组的层次结构
  • 要灵活 - 我们肯定将来需要添加新对象,因此层次结构可能会针对即将到来的现有特定对象进行更改

[我的想法] 到目前为止,我已经提出了这个想法:

让我们将所有对象存储在一个自引用"层次结构中"表格并将每个对象标记为某种类型,以便递归地我们可以看到电子邮件所在的路径,或者显示顶部依赖于安全存款的层次结构的每个对象。 这种方法需要:

a)要在此表中详细说明的每条记录,这使得它包含许多空值,因为邮件不会被描述为与抽屉相同的属性,

b)或者每个对象类型可能都有自己的表描述它,并在"层次表&#34中使用外键;

[警告] 此表可能会变得如此之大,以至于查找可能会导致严重的性能问题。这还需要我们为存储单元的#34;层次结构中的每个新对象添加新结构(物理表),我认为这很好。

[问题] 如果我的想法(考虑B是我选择的要求)是我能得到的最好的,请你告诉我吗?我能改进什么?

[样本数据]

电子邮件表:

id
---
1
2

案例表:

id
---
1
2

方框表:

id
---
1

层次关系表:

seq | id_obj | obj_type | id_parent_obj | parent_obj_type |
#1  |  1     | Email    |  1            | Case            | -- email 1 in case 1
#2  |  1     | Case     |  1            | Box             | -- case 1 in box 1
#3  |  1     | Box      |  [null]       | [null]          | -- box 1 no parent
#4  |  2     | Email    |  [null]       | [null]          | -- email 2 no parent
#5  |  3     | Email    |  1            | Box             | -- email 3 in box 1
#6  |  1     | Box      |  [null]       | [null]          | -- box 1 no parent

通过查看此示例数据,我看到我们有一些冗余信息,例如关于seq #3 and #6中层次关系表中的框。我认为对此有一些不同的方法,我认为也应该保留seq的参考。 我们可以看到,case 2是空的。

2 个答案:

答案 0 :(得分:1)

您应该在一个" normal"中定义您的存储类型。表

将所有信息添加到此表,该表绑定到存储本身。但没有关于存储在那里的东西。

如果存储可以"嵌套" (例如......中较大框内的框)你可以添加一个"自我加入" (您没有声明您的RDBMS,SQL Server为此提供HIERARCHYID)指定位置作为parent storage的引用,或者您可以定义一个Locator表,用于存储存储的ID和其容器的ID(1:n - >一个存储可以恰好位于一个容器中)。

比您需要一张包含您要存储的物品的表格。如果您只有一种商品("邮件"),您需要一张表来定义"邮件"具有所有属性。

您可以将位置(它存储的位置)作为外键存储到" mail" table(以便每封邮件知道其位置),或者 - 再次 - 您可以使用邮件的ID和存储ID定义映射。

当您想要向流程添加更多信息时,需要第二种方法"我存储邮件" (例如,谁,何时,价格......)你可以对此进行历史考验。如果你改变一个位置,你只需设置一个" ValidUntilDatetime"并使用新位置添加新行。因此,您可以按照放置内容的过程...如果这只是您邮件表中的FK列,则无法实现。

如果要存储的项目更多,您可以考虑一个Master-table和多个不同的项目表,因为您要存储不同的东西。他们共享一个ID。一般信息(也是位置)是主表的一部分,是子表的特定数据部分。

嗯,我希望这能让你走上正确的道路......

编辑:从您的评论中回答您的问题: ad"为什么需要定位表":

只是想象一张桌子

CREATE TABLE storage(id INT, Name VARCHAR(100), ...)

样本数据如

id    Name, ...
1     Box1  ...
2     Box2  ...
3     Case1 ...
[...]

和这样的表

CREATE TABLE Mail( id INT, Creation DATETIME, From VARCHAR(100), To VARCHAR(100),LocationID INT FOREING KEY REFERENCES Storage(id),...)

样本数据如:

id    Creation          From          To            LocationID    ...
1     2015-10-28 12:00  adr@mail.com  xyz@mail.com      2         ...
2     2015-10-28 12:05  adr@mail.com  xyz@mail.com      2         ...
3     2015-10-28 12:10  adr@mail.com  xyz@mail.com      3         ...

这将表明,邮件1和2实际存储在Box2中,邮件3存储在Case1中。但你没有任何信息:谁把它放在那里?什么时候放在那里?它会待多久?谁来接它?...

如果你有像

这样的定位表
CREATE TABLE Location(id INT, mailID INT FOREIGN KEY REFERENCES Mail(id),storageID INT FOREIGN KEY REFERENCES Storage(id), When, ...)

您可以存储" put-process"。如果某个项目从Box2转移到Box1,您将在第一个示例中更改locationID。但你不会保留此项目在Box2之前的信息,谁转移它,何时发生这种情况等等......

ad"存储的不同方式":如果存储了邮件" no where",您可能允许storageID保持为NULL,或者您定义名为&#34的存储项;无处"并使用此ID。

存储直接容器的ID就足够了。容器本身必须知道它自己的位置。为此,请阅读我的答案的第一部分。

答案 1 :(得分:1)

这是一个完整的模型(航空代码,未经测试......)

CREATE TABLE StorageType(ID INT IDENTITY PRIMARY KEY
                        ,StorageTypeName VARCHAR(100) NOT NULL);
INSERT INTO StorageType VALUES('Box'),('Case'),('OtherStorage');

CREATE TABLE Storage(ID INT IDENTITY PRIMARY KEY
                    ,StorageTypeID INT NOT NULL CONSTRAINT FK_Storage_StorageTypeID FOREIGN KEY REFERENCES StorageType(ID)
                    ,StorageName VARCHAR(100) NOT NULL
                    /*more columns to describe a storage*/
                    );
INSERT INTO Storage VALUES(1,'Box1'),(1,'Box2'),(3,'SomeStorage1');                  


CREATE TABLE ObjectType(ID INT IDENTITY PRIMARY KEY
                       ,ObjectTypeName VARCHAR(100) NOT NULL);
INSERT INTO ObjectType VALUES('Mail'),('OtherItem');

CREATE TABLE MyObject(ID INT IDENTITY PRIMARY KEY
                     ,MyObjectTypeID INT NOT NULL CONSTRAINT FK_MyObject_MyObjectTypeID FOREIGN KEY REFERENCES ObjectType(ID)
                     ,MyObjectName VARCHAR(100) NOT NULL
                     /*more columns to describe an object*/
                     );
INSERT INTO MyObject VALUES(1,'Mail1'),(1,'Mail2'),(2,'SomeObject1');

CREATE TABLE StorageLocation(ID INT IDENTITY PRIMARY KEY
                            ,CreatedOn DATETIME NOT NULL
                            ,OutDate DATETIME NULL
                            ,StorageID INT NOT NULL CONSTRAINT FK_StorageLocation_StorageID FOREIGN KEY REFERENCES Storage(ID) 
                            ,ContainerID INT NULL CONSTRAINT FK_StorageLocation_ContainerID FOREIGN KEY REFERENCES Storage(ID) 
                            /*more columns to describe the "put storage" process: who, when, how long, ... */
                            );

INSERT INTO StorageLocation VALUES(GETDATE(),NULL,2,1); --puts the Box2 into Box1, current, because OutDate IS NULL
                            /*put all storages in their places*/

CREATE TABLE ObjectLocation (ID INT IDENTITY PRIMARY KEY
                            ,CreatedOn DATETIME NOT NULL
                            ,OutDate DATETIME NULL
                            ,MyObjectID INT NOT NULL CONSTRAINT FK_ObjectLocation_MyObjectID FOREIGN KEY REFERENCES MyObject(ID) 
                            ,ContainerID INT NULL CONSTRAINT FK_ObjectLocation_ContainerID FOREIGN KEY REFERENCES Storage(ID) 
                            /*more columns to describe the "put object" process: who, when, how long, ... */
                            );

INSERT INTO ObjectLocation VALUES(GETDATE(),NULL,1,2); --puts the Mail1 into Box2 (which is in Box1), current, because OutDate IS NULL
                            /*put all objects in their places*/