在数据库中存储标签的最有效方法是什么?

时间:2008-12-02 14:52:00

标签: database database-design tags tagging

我在我的网站上实现了一个类似于stackoverflow使用的标记系统,我的问题是 - 存储标记的最有效方法是什么,以便搜索和过滤它们?

我的想法是:

Table: Items
Columns: Item_ID, Title, Content

Table: Tags
Columns: Title, Item_ID

这太慢了吗?还有更好的方法吗?

9 个答案:

答案 0 :(得分:178)

一个项目将有许多标签。一个标签将属于许多项目。这对我来说意味着你很可能需要一个中间表来克服多对多的障碍。

类似的东西:

  

表:项目
  列:Item_ID,Item_Title,Content

     

表:标签
  列:Tag_ID,Tag_Title

     

表:Items_Tags
  列:Item_ID,Tag_ID

可能是你的网络应用程序非常受欢迎,并且需要在路上进行非规范性处理,但是过早地混淆海水是毫无意义的。

答案 1 :(得分:104)

您应该阅读Philipp Keller关于标记数据库模式的博客文章。他尝试了一些并在terms of ease of constructing common queriesin terms of performance中报告了他的结果。标签数量,标记项目数量和每个项目的标签数量都是因素。这些职位来自2005年;从那时起我就不知道有任何更新。

答案 2 :(得分:7)

实际上,我认为对标签表进行去标准化可能是更好的前进方式,具体取决于规模。

这样,tags表只有tagid,itemid,tagname。

您将获得重复的标记名,但它会使特定项目的添加/删除/编辑标记更加简单。您不必创建新标记,删除旧标记的分配并重新分配新标记,只需编辑标记名即可。

要显示标签列表,只需使用DISTINCT或GROUP BY,当然也可以计算标签的易用次数。

答案 3 :(得分:3)

如果您不介意使用一些非标准内容,Postgres版本9.4及更高版本可以选择存储JSON文本数组类型的记录。

您的架构将是:

Table: Items
Columns: Item_ID:int, Title:text, Content:text

Table: Tags
Columns: Item_ID:int, Tag_Title:text[]

欲了解更多信息,请参阅Josh Berkus撰写的精彩文章: http://www.databasesoup.com/2015/01/tag-all-things.html

对于性能进行彻底比较有更多种选择,上面提到的选项总体上是最好的。

答案 4 :(得分:2)

我建议使用中间第三表来存储标签< =>项关联,因为我们在标签和项之间有多对多关系,即一个项可以与多个标签关联,一个标签可以关联有多个项目。 HTH, 阀。

答案 5 :(得分:1)

根据您在问题中提供的数据,您无法真正谈论缓慢。而且我认为你甚至不应该在这个发展阶段过分担心表现。它被称为premature optimization

但是,我建议你在Tags表中包含Tag_ID列。通常一个好的做法是每个表都有一个ID列。

答案 6 :(得分:1)

如果空间成为问题,请使用第3个表格标记(Tag_Id,标题)来存储标记的文本,然后将标记表更改为(Tag_Id,Item_Id)。这两个值也应该提供唯一的复合主键。

答案 7 :(得分:0)

项目应具有“ID”字段,而标签应具有“ID”字段(主键,群集)。

然后制作一个ItemID / TagID的中间表,并在其上放置“Perfect Index”。

答案 8 :(得分:0)

标签架构:标签表和属性:

表格:

tags (each row only keeps information about a particular tag)
taggings (each row keeps information about trigger and who will receive the trigger )
activitietis_tags (each row keeps information about tag with particular activity)
tag_status (each row keeps track of a tag status)

表:标签标签表的属性:

id(PK)
userId(FK users)(not null)(A tag only belongs to one user, but  a user can create multiple tags. So it is one to many relationships.)
genreId(FK activity_geners)(not null)
name (string) (not null)
description (string)
status (int) (0=inactive, 1=pending, 2=active, there could be more flag)
rank(int)  (rank is the popularity of a particular tag), this field can be use for sorting among similar tags.)
type (int) (0=tag, 1=challenge, 2=mission)
photo(string)
visibility (int) (0=public, 2=protected, 3 = private)(private means the tag only  visible to assigned users of a activity, protected means a tag only visible to all friends and followers of the creator of the tag, public means search by public, such as all admin created tag)
createdAt(timestamp for the tag was created at)
updatedAt (timestamp for the tag last time updated)
deletedAt (default value null) (timestamp when tag was deleted, we need this field because we will delete tag permanently from audit table). 

注意:保留 10 号字段稍后会派上用场。

表格:标签:

此表将用于触发,例如广播其他用户的提要或向他们发送通知。在此表中插入一行后,将有一个服务读取一行执行相关操作以删除该行。

标签表的属性:

Id(PK)
tagId(a tagging row only belongs to a tag, but a tag can have multiple row).
taggableId (id of a user who will receive notification)
taggableType(int) (0=notification, 1=feed message)
taggerId(the person who triggered the broadcast)
taggerType(ad, activity, news)
createdAt(timestamp for the tag was created at)

表格:活动标签

从用户的角度来看,用户可以在实例化活动后创建标签,因此下表将保留有关哪些活动具有哪些标签的信息。

标签属性表的属性:

Id (PK)
activityId(FK)
tagId(FK)

表:tag_status

当用户创建标签时,将在此表中创建一行带有 tagId 和默认状态为 inactive/pending 的行,管理员将从标签表中提取所有标签,其中 status=pending/inactive,如果管理员批准了标签,则在审查标签后tag 那么 tag 表中 status 的值将被批准并且 tag_status 的行将被删除。如果 admin 被拒绝,那么 tag_status 表的 status 字段的值将被拒绝,并且将广播一个触发器,接收者将向该标签的关联用户发送通知,其中包含他的标签被拒绝的消息。

id(PK)
senderId(Id of the user)
receiverId(Id of admin user)
createdAt(timestamp of created at)
updatedAt(timestamp of updated at)
deletedAt(timestamp of deletedAt) default value null
expiredAt (if a tag never gets approved it will expire after a certain time for removing its information from the database. If a rejected tag gets updated by user then expiredAt will reset to new future time)
status 
Message (string varchar(256)) (message for user)