我是一名软件开发人员。我喜欢编码,但我讨厌数据库...目前,我正在创建一个网站,允许用户将实体标记为喜欢(如FB),标记和评论。
我被困在数据库表设计上来处理这个功能。解决方案是微不足道的,如果我们只能为一种类型的东西(例如照片)做到这一点。但我需要为5种不同的东西启用它(现在,但我也假设随着整个服务的增长,这个数字会增长)。
我在这里发现了一些类似的问题,但没有一个问题得到满意的答案,所以我再次提出这个问题。
问题是,如何正确,高效和弹性地设计数据库,以便它可以存储不同表的注释,喜欢不同的表和标签。一些设计模式作为答案将是最好的;)
详细说明:
我有一个带有一些用户数据的表User,还有3个表:带有照片的Photo,带有文章的Articles,带有地点的Places。我想启用任何已登录的用户:
第一种方法:
a)对于标签,我将创建一个表Tag [TagId, tagName, tagCounter],然后我将为:Photo_has_tags,Place_has_tag,Article_has_tag创建多对多关系表。
b)同样重要的评论。
c)我将创建一个表LikedPhotos [idUser, idPhoto],LikedArticles[idUser, idArticle],LikedPlace [idUser, idPlace]。喜欢的数量将通过查询计算(我认为这是坏的)。和...
我真的不喜欢这个设计的最后一部分,它对我来说很难闻;)
第二种方法:
我将创建一个表ElementType [idType, TypeName == some table name],它将由管理员(我)填充,其中包含可以被喜欢,评论或标记的表的名称。然后我将创建表:
a)LikedElement [idLike, idUser, idElementType, idLikedElement]和注释和标签相同,每个都有适当的列。现在,当我想拍照时,我会插入:
和地方:
......等等......我认为第二种方法更好,但我觉得这个设计中也缺少一些东西......
最后,我还想知道哪个最好的地方存放计数器元素被喜欢多少次。我只能想到两种方式:
在元素(Photo/Article/Place)表中
通过select count()。
我希望我对这个问题的解释现在更彻底。
-
你考虑过XML吗?
-
我很少发现这样的问题100%是我的想法,你的问题非常完整! 谢谢@Kokos。
最具扩展性的解决方案是只有一个"基础"表(连接到"喜欢",标签和注释),并"继承"其中的所有其他表。添加一种新的实体只需添加一个新的"继承"表 - 然后它会自动插入整个like / tag / comment机器。
实体关系术语是"类别"(参见ERwin方法指南,部分:"子类型关系")。类别符号是:
假设用户可以喜欢多个实体,同一个标签可以用于多个实体,但注释是特定于实体的,您的模型可能如下所示:
顺便说一下,实施"ER类别"大致有3种方式:
-
所有类型都在一个表中。
-
所有具体类型在单独的表中。
-
所有具体和抽象类型在单独的表中。
除非您有非常严格的性能要求,否则第三种方法可能是最好的(意味着物理表与上图中的实体1:1匹配)。
-
很好的答案,谢谢。我希望,我会设法实现它......我想知道Django ORM将如何处理它(或者我将如何自己做...但是,这是另一个问题;)但是,你能解释一下吗?我,因为我认为我不理解它 - 你为我画的(谢谢!)是你提到的第三种方法?
-
@Kokos本质上,方法(3)表示ENTITY是表,PHOTO是表,ARTICLE是表,PLACE是表。方法(2)意味着没有ENTITY表,方法(1)意味着只有一个表。所有这些方法的存在(都有它们的优点和缺点)是典型的RDBMS本身不支持表继承这一事实的不幸结果。
-
@Branko这很棒..一个问题..我很困惑......你有ENTITY表链接到所有照片,文章(表格)等......我需要在ENTITY中使用GUID数据类型作为PK实体ID以及所有类别,例如,您可以拥有ID为1的照片和ID为1的地方吗?或者我错过了这一点...... tnx ......
-
@rjovic嗯,ENTITY_ID使用什么类型并不重要,只要它在所有子类型的级别上都是唯一的,而不仅仅是一个子类型。 GUID本质上符合该标准,但对于整数,这意味着您不能拥有PHOTO 1和第1条,但您可以拥有PHOTO 1和第2条。如果由于某种原因重叠的特定于子类型的键值很重要,你总是可以创建一个特定于子语言的备用键(即UNIQUE约束)。或者,您可以在PK中添加类型鉴别器(例如{ENTITY_TYPE,ENTITY_ID}),如果您不介意某些冗余。
-
+1感谢您对"类别"的精彩解释和参考。我打算发一个接近这个的问题,但你在这里回答了。
-
如果其中一张照片需要成为用户的个人资料图片怎么样?假设用户是照片实体的所有者。什么是最好的方法?
-
@tylerdurden可能最简单的方法是在USER中引用一个FOREIGN KEY引用PHOTO。这可以独立于用户是否已通过LIKED_ENTITY连接到照片。如果你想确保用户的照片也必须通过LIKED_ENTITY链接 - 这是一个不同的"蠕虫"并且不容易通过声明方式强制执行。
-
@BrankoDimitrijevic假设你在实体引用用户中有外键owner_id。照片也是一个实体,然后在用户中将entity_id作为外键引用照片是正确的吗?
-
@tylerdurden我不知道,取决于你想要完成什么。我没有看到一个明显的原因,为什么你不能同时拥有这两个,如果这就是你所要求的。
-
谢谢你这个很棒的解释。 Howerver,是否有一种有效的方式来获取所有用户喜欢或评论的对象,即我可以很容易地获得所有权利ID但我应该运行一个查询照片,一个文章,地方等搜索同一组实体ID里面每张桌子?在我看来,这不是一个有效的方法。有什么建议?
-
@ fabio.cionini您不需要使用ENTITY加入 - 您可以直接使用PHOTO(或ARTICLE或PLACE)加入,因此该模型的查询效率不比具有独立表的"经典"模型低。
-
@BrankoDimitrijevic说的没错!太好了谢谢。
-
@BrankoDimitrijevic如何确保将ENTITY_ID中的PK作为身份规范插入到实体表中?这是在应用程序端而不是数据库端完成的吗?
-
@volumeone这是程序逻辑的一部分。首先插入ENTITY并获取自动生成的ENTITY_ID,然后为引用它的FK重用相同的值。"过程逻辑"可以是一系列客户端调用,也可以是存储过程(以及介于两者之间的任何内容)。
-
@BrankoDimitrijevic为什么不能将实体表Photo,Article,Place都有自己的PK,例如PhotoID,ArticleID等,还有另一列Entity_ID作为FK?这不必要吗?
-
@volumeone他们可以,但是没必要。
-
@BrankoDimitrijevic如果我有很多继承的表会怎么样? BIGINT对于ENTITY_ID可能不够大,不是吗?
-
@Orion BIGINT的最大值是9223372036854775807.假设您每秒插入一行,您将在大约3000亿年内用完可用值。当然,到那时你将能够移植到128位整数!
-
@BrankoDimitrijevic我喜欢你的统计数据,还有你的128 bit GIANTINT。顺便说一句,因为ENTITY表仅用于ID,为什么不使用GUID呢?
-
@Orion GUID更大(损害存储和缓存),通常在较小程度上填充B树(再次浪费空间并损害缓存)并且不能很好地进行群集(表群集而不是数据库群集)。他们有自己的位置,但可能不在这里。
-
假设COMMENT,ARTICLE和PLACE有照片,您是否仍然将照片存储在一张表中或将它们分成三个相应的照片?
-
@Tresdin这真的是一个与继承正交的不同问题。告诉我更多关于你打算如何使用这些照片的信息,我或许可以告诉你如何存储它们的更多信息......
-
@BrankoDimitrijevic这些照片最初是可评论和可爱的。但我也想让它们在未来可分享和收藏。
-
@Tresdin然后看起来你需要保持上面的层次结构,并适当地从其他表链接到照片...当您在实体级别添加对更多功能的支持时,它们将"传播"到照片。
-
@BrankoDimitrijevic - 只是为了弄清楚事情。如果我要插入一篇新文章,我会为这篇文章创建一个新实体吗?这意味着该文章将与那个entityId绑定
-
@ James111是的。您首先插入一个新的ENTITY行(它以某种方式生成一个新的ENTITY_ID,通常通过自动递增的字段,或序列或DBMS中可用的任何机制)。然后,对新的ARTICLE行使用相同的ENTITY_ID。
-
@BrankoDimitrijevic 1-Will上面的结构是否支持Facebook评论(Hierarchical Data Structure)? 2-如果是,如何在MySQL中实现它,因为它不支持分层数据结构?
-
@BrankoDimitrijevic在ENTITY_COMMENT表中COMMENT_NO是什么意思?它是评论实体的主键吗?
-
@Matt它是复合主键的一部分(与ENTITY_ID一起)。按照惯例,实体框中水平线上方的属性构成主键。
-
伟大的桌子设计。我遇到的唯一(语义)问题,它将它实现到Symfony项目结构中。这不是一个问题,我只是在努力找到一个合适的替代单词ENTITY,因为ENTITY被用作覆盖项目中所有实体的通用术语,我不希望有任何混淆使用名称Entity的实体导致的。名称空间App Entity Entity也感觉不对。有什么建议?
-
另外,我对用户和实体之间的N:N感到有些困惑。我理解User可以有Many Entities,但为什么ENTITY会有很多Users?
-
@Doug"喜欢"关系是N:N,因为一个实体可以被多个用户喜欢,一个用户可以喜欢多个实体。至于命名,我可以建议诸如"对象","项目","元素"或甚至"帖子"或"贡献"之类的术语......在一天结束时,你是根据什么决定的人最适合您的特定项目。
-
@Branko Dimitrijevic感谢您对n:n表的澄清。至于命名,在我提出问题之后我将实体放入同义词库中,经过多次点击后,最终得到了元素,这非常适合。
既然你"讨厌"数据库,你为什么要尝试实现一个?相反,向喜欢和呼吸这些东西的人寻求帮助。
否则,学会爱你的数据库。精心设计的数据库简化了编程,设计网站并平滑其持续运营。即使是经验丰富的d / b设计师也不会有完整和完美的远见:随着使用模式的出现或需求的变化,将需要一些架构变化。
如果这是一个单人项目,则使用存储过程将数据库接口编程为简单操作:add_user,update_user,add_comment,add_like,upload_photo,list_comments等。不要将模式嵌入到一行代码中。通过这种方式,可以在不影响任何代码的情况下更改数据库模式:只有存储过程应该知道模式。
您可能需要多次重构架构。这个是正常的。不要担心第一次完美。只需使其功能足以原型化初始设计。如果您有足够的时间,请使用它,然后删除架构并再次执行。第二次总是好一些。
-
因为我需要自己实施。至少现在......而且,我认为这可能是一个开始喜欢数据库的好时机;)感谢您对存储过程的建议。有人知道,如果它们是由Django ORM自动映射的吗?
-
不能这样做:学会爱你的数据库
-
我爱你的最后一句 - 第二次总是好一点。
-
第二次总是好一些。对
这是一个普遍的想法
请不要太注意字段名称样式,但更多的是关系和结构
这个伪代码将获得ID为5的所有照片评论
SELECT * FROM actions
WHERE actions.id_Stuff = 5
AND actions.typeStuff ="photo"
AND actions.typeAction ="comment"
这个伪代码将获得喜欢ID为5的照片的所有喜欢或用户
(你可以使用count()来获得喜欢的数量)
1 2 3 4
| SELECT * FROM actions
WHERE actions.id_Stuff = 5
AND actions.typeStuff ="photo"
AND actions.typeAction ="like" |
-
我想您甚至可能喜欢评论,因为点击评论中的"赞"链接。此查询将获得ID为133的注释(操作):SELECT * FROM actions WHERE actions.id=133 AND actions.typeStuff ="comment" AND actions.typeAction ="like"
-
对于系统的进一步发布,我一定会记住这个解决方案:)
-
我有2个东西表stuff1和stuff2 ...我按照这个图但是在使用这个时有sql错误... stuff1,stuff2是两个独立的表及其独立的主键,而action表有一个列id_stuff,它引用了这两个tabel stuff1,stuff2。现在举例来说stuff1有5行,stuff2有10行,当我尝试用id_stuff在action表中添加任何小于5的行时,让它说'3'它执行查询,因为在stuff1和stuff1中都存在一行id_stuff'3' stuff2,但如果我尝试添加id_stuff大于5的行...(继续下一条评论)
-
并且少于10让我们说"7"它显示错误,因为动作表中的id_stuff引用了stuff1,stuff2中的id_stuff,即使stuff2有一行有stuff_id'7',stuff1也没有它,并且因为它引用了stuff1也会产生错误,我该如何解决这个问题呢?
-
如果要以这种方式实现,则会更难以通知用户新的喜欢。这需要另一张桌子。
-
id_stuff列如何在三个表中的每一个中包含唯一值?
-
@volumeone如果我理解你在说什么,我认为在这种情况下,id_stuff和typeStuff的组合将组成表键。
绝对采用第二种方法,你有一个表并存储每行的元素类型,它会给你更多的灵活性。基本上,当逻辑上可以用更少的表完成某些事情时,使用更少的表几乎总是更好。我现在想到的关于你的特定情况的一个优点,考虑你想要删除某个用户的所有喜欢的元素,你需要为你的第一种方法为每种元素类型发出一个查询,但是使用第二种方法可以完成只有一个查询或考虑何时想要添加新的元素类型,第一种方法涉及为每种新类型创建一个新表,但使用第二种方法则不应该做任何事情......
查看您将需要的访问模式。他们中的任何一个似乎特别困难或低效我的一个设计选择或另一个?
如果不喜欢需要较少表格的那个
在这种情况下:
添加注释:您要么选择一个特定的多/多个表,要么插入一个具有已知特定标识符的公共表中,我认为客户端代码在第二种情况下会稍微简单一些。
查找项目的注释:这里似乎使用公共表稍微容易一些 - 我们只有一个按实体类型参数化的查询
查找一个人关于某种事情的评论:两种情况下的简单查询
查找一个人关于所有事情的所有评论:无论哪种方式,这似乎都不太可能。
我认为你的"歧视"方法,选项2,在某些情况下会产生更简单的查询,而在其他情况下看起来并不差,所以我会选择它。
据我所理解。需要几张桌子。他们之间有很多关系。
-
使用标识字段存储名称,姓氏,出生日期等用户数据的表。
-
存储数据类型的表。这些类型可能是照片,分享,链接。每种类型都必须有一个唯一的表。因此,它们各自的表与该表之间存在关系。
-
每种不同的数据类型都有其表。例如,状态更新,照片,链接。
-
最后一个表用于存储id,用户id,数据类型和数据id的多对多关系。
-
或多或少...也许我会在下一篇文章中更好地解释...
-
如果您发布数据库图表。我可以画出这种关系。
考虑使用每个实体的表来进行注释等。更多表 - 更好的分片和缩放。对于我所知道的所有框架,控制许多类似的表并不是一个问题。
有一天,您需要优化来自此类结构的读取。您可以轻松地在基础表上创建agragating表,并在写入时丢失一些。
一天有字典的大表可能会变得无法控制。
-
更多表意味着它的可维护性较差。大多数d / b可以对各个表进行分片。