【发布时间】:2009-11-06 19:16:48
【问题描述】:
有没有处理数据库中标签的通用方法?
我正在考虑使用带有管道的 tinytext。 我认为添加另一个表并使用 ID 可能会使其变得更加复杂而收效甚微。
你最喜欢的方法是什么?
在表中进行查询以查找匹配多个或单个标签的结果的正确方法是什么?
谢谢
【问题讨论】:
-
您也可以考虑对您的标签进行 JSON 编码。
有没有处理数据库中标签的通用方法?
我正在考虑使用带有管道的 tinytext。 我认为添加另一个表并使用 ID 可能会使其变得更加复杂而收效甚微。
你最喜欢的方法是什么?
在表中进行查询以查找匹配多个或单个标签的结果的正确方法是什么?
谢谢
【问题讨论】:
实现一个简单的 N:N 关系。
项目:
-id
-名称
-标题
-foo
-酒吧
ItemTagRelations:
-Fkey itemId
-Fkey tagId
标签:
-id
-名称
-等
【讨论】:
我会在这里散布一些异端。
出于可扩展性的原因,包括本网站在内的大男孩正在使用非规范化模式作为标签,在每行的文本类型字段中存储逗号、竖线或空格分隔的标签,并为带有计数的标签存储单独的表格。在插入或更新项目时,只需检查添加或删除了哪些标签并相应地更新计数(分解为新旧标签字符串数组并执行 array_diff() )。
现在您可以通过简单的SELECT * FROM tags 以廉价的方式显示带有计数的标签云,无需花哨的查询。要查找标有给定名称的项目,只需执行LIKE '%TAG%',这对于小流量网站(比如每天少于 100k 的页面浏览量)和小型数据集(比如少于 100k 的记录)非常有效。除此之外,您可以使用全文搜索来加快速度,并最终使用 Lucene 或 Sphinx 等合适的搜索引擎。
在 SO 上查找相关标签也很容易(Kohana 特定代码,基于 LIKE,MySQL 特定):
$tags = array('foo', 'bar');
private function get_related_tags( $tags )
{
## Get db entries with specific tags and build array with counts
## is it cached already? ------------------------------------------------
$this->cache = Cache::instance();
$tags = array_filter( array_flip(array_flip($tags)) );
sort($tags);
$cache_name = implode('', $tags);
$cache = $this->cache->get( $cache_name );
if( $cache )
return $cache;
## not cached, fire up ---------------------------------------------------
$db = Database::instance();
## count tagged items ----------------------------------------------------
// build like string
$like = array();
foreach( $tags as $tag )
$like[] = "tags LIKE '%$tag%'";
$like = implode(' AND ', $like);
// get counts
$count = $db->query("SELECT count(id) AS count FROM `articles` WHERE $like")->current()->count;
## check what tags are related ------------------------------------------
$offset = 0;
$step = 300;
$related_tags = array();
while( $offset < $count )
{
$assets = $db->query("SELECT tags FROM `articles` WHERE $like ORDER BY id ASC LIMIT $step OFFSET $offset");
foreach($assets as $asset)
{
// tags
$input = explode( ' ', trim($asset->tags) );
foreach( $input as $k => $v )
{
if( $v == ''){
//do nothing, shouldnt be here anyway
}
elseif( array_key_exists($v, $related_tags) ){
$related_tags[$v]++;
}
else{
$related_tags[$v] = 1;
}
}
}
$offset += $step;
}
// remove already displayed from list
foreach( $tags as $tag )
unset( $related_tags[$tag] );
ksort($related_tags);
// set cache
$this->cache->set( $cache_name, array($related_tags, $count), 'related_tags_counts', 0);
return array($related_tags, $count);
}
这并不便宜,所以我一直缓存给定标签集的计数,直到我对文章表中的标签进行更改。
这种设置无论如何都不是完美的,但肯定有一些优势。模式很简单,获取标签云很简单,通过一个简单的查询(即没有子查询)获取文章和标签。作为主要缺点,我会看到无法在不修改出现的每一行的情况下在系统范围内重命名或删除标签,但是,嘿,你多久这样做一次?
目前我在我的几个项目中使用这个设置,它就像一个梦想一样工作,但我必须承认这些不是高流量网站(因此我逃脱了 LIKE),明年我将能够测试它与繁忙的网站,但我很确定它会做。规范化纳粹也许会投票反对我,但我只是喜欢它的简单性,我很乐意为此牺牲 cpu 周期。
实际上,我不久前打算在 SO 上发布这个标签系统,并询问专家他们对此有何看法,所以请随时离开 cmets。
传统上,对不起我的英语,我相信这很有趣 =)
编辑
由于您已在 cmets 中提供了您的要求,我认为此设置非常适合您。我已经在 pastehere 中发布了完整的标签模型,其中包含处理计数的方法,Kohana 是特定的,但如果你知道 Codeigniter,你会感到宾至如归。就这样使用吧:
table TAGS: id, tag_name, tag_count
// insert new item/article
$tag_model->update_tags( $tags_str, null );
// update existing item
$tag_model->update_tags( $new_tags_str, $old_tags_str ); // $old_tags as stored in db
// delete item, you'll have to get item from db before deletion
$tag_model->update_tags( null, $old_tags_str );
我已经修改了代码,因为 markdown 已经破坏了它,查询也是 mySQL 风格,而不是 SQLite。
【讨论】:
是的,不要害怕标准化并将每个标签都放在自己的记录中。这最终将是最灵活的,并且正确的索引最快。
【讨论】: