[注意:开始]
希望有些人,专业人士已经不得不应对这种情况。 关于邮局的具体案例是虚构的,只是为了提出问题。 这个问题不是通过添加索引来提高性能。
[注:结束]
最近,我一直想知道如何有效(!)创建数据库结构来处理易变的层次结构级别。 让我们进入这个例子,以便更好地理解这个问题。
假设我们的邮局以不同的方式存储物理邮件,具体取决于任何因素(这里并不重要)。 我们将这种情况映射到数据库模型。
好的,我们收到这些邮件。邮件可以物理存储在箱子,盒子,抽屉,安全存款等等(因为我们不想严格限制我们的存储类型和结构,而是允许它灵活地满足我们未来的变化)。这意味着现在我们有这样的类型,例如,一个邮件可以存放在存放在保险箱中的盒子里,但实际上可以有任何组合。
为简单起见,我们假设单个邮件是我们可以获得的最低粒度。但是,我们必须记住,我们也可能有空盒子(它下面和上面没有任何物体),我们也想将它存储在数据库中。
此模型必须:
[我的想法] 到目前为止,我已经提出了这个想法:
让我们将所有对象存储在一个自引用"层次结构中"表格并将每个对象标记为某种类型,以便递归地我们可以看到电子邮件所在的路径,或者显示顶部依赖于安全存款的层次结构的每个对象。 这种方法需要:
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
是空的。
答案 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*/