两个月来我一直在尝试解决这个问题,与其他开发人员进行了无数次头脑风暴会议,但仍然无法提出一个好的解决方案。
我们 正在为会议、公共活动等建立一个搜索引擎。
数据
我有一个包含数万个事件(未来和历史)的数据集,其结构如下:
{
id: 10,
name: "CES",
intervals: [
{
interval_start: "2013-01-01 08:00",
interval_end: "2013-01-15 10:00",
tags_by_type: {
people: [{name: "Eric Schmidt", weight: 20}, ...]
companies: [{name: "Google", weight: 100}, {name: "Microsoft", weight: 100}, ...],
topics: [{name: "Social Networking", weight: 80}, {name: "Internet marketing", weight: 95}, ...],
places: [{name: "Cannes Palace Hotel", weight: 100}, {name: "Cannes", weight: 100}, {name: "France", weight: 100}]
},
tags: ["Eric Schmidt", "Google", "Microsoft", "Social Networking", "Internet Marketing", "Cannes Palace Hotel", "Cannes", "France"]
},
{
interval_start: "2011-01-01 10:00",
interval_end: "2011-01-15 12:00",
tags_by_type: {
people: [{name: "Marissa Meyer", weight: 20}, ...]
companies: [{name: "Yahoo", weight: 100}, {name: "Facebook", weight: 100}, ...],
topics: [{name: "Recruiting", weight: 80}, {name: "Internet marketing", weight: 15}, ...],
places: [{name: "New york", weight: 100}, {name: "USA", weight: 100}]
},
tags: ["Marissa Mayer", "Yahoo", "Facebook", "Recruiting", "Internet marketing", "New york", "USA"]
},
...
],
}
我们使用规范化的 MySQL 数据库来添加/更新/删除事件和标签,然后针对各种搜索场景编译各种格式的数据(如上面的文档)。
- 标签之间有层次结构(营销是互联网营销的父级,所以只要互联网营销是一个标签,营销也将是一个标签)
- 权重数字表示相应标签在相应时间范围内的重要性/相关性
问题 我们希望为用户提供一个菜单,他们可以用来点击和过滤事件,例如:
地点: [推荐地点] 美国、法国、... [点击浏览所有地点]
人物: [推荐人] Eric Schmidt、Marissa Meyer、... [点击浏览所有人]
主题: [推荐话题] 网络营销, Startups, ... [点击浏览所有主题]
- 单击菜单中的任何标签**必须**导致至少一个结果(菜单中没有死角标签)。
- 每当用户单击菜单中的任何标签时,都会执行搜索,菜单应重新填充来自搜索结果的事件子集的标签,以便用户可以继续单击
- 只有前 5 个标签(基于其权重)将显示在 [单击浏览所有...] 链接之前。
- 单击[单击以浏览所有...]链接会弹出一个分层菜单。对于位置,它将是一个大陆列表。单击一个大陆会调出国家列表。单击一个国家会调出城市列表。这里没有加权,只是分层浏览
目前的方法
鉴于我们提出的上述文档结构,如果非常简单,请使用 MongoDb 搜索事件:
{"intervals.tags": { $in: [selectedtag1, selectedtag2, selectedtag3]}}
然而,在标签菜单中找出哪些标签来进一步向用户显示被证明是一件痛苦的事情:) 假设我们忽略了权重,只是试图找出最常见的标签,我们尝试了这个:
db.events.aggregate( { $unwind: "$intervals" }, {$unwind: "$intervals.tags"}, {$group: {"_id": "$intervals.tags", "evCount": {$sum:1}}}, {$match: {"evCount": {$lt: TOTAL_COUNT_OF_EVENTS_MATCHING_OUR_SEARCH}}} );
- 该查询的第一个问题是最后一个条件应该忽略与匹配的所有事件相关的标签(因为没有必要显示点击时不过滤结果的标签)。上面的查询当前过滤掉了与所有 INTERVALS(而不是 EVENTS)相关的标签。
- 该查询的第二个问题是,对于大型数据集,它可能会耗尽内存
我们还尝试 了仅针对菜单问题,我们尝试从标签而不是事件开始:
"Eric Schmidt" relates to "Google", "Microsoft", "Social Networking", "Internet Marketing", "Cannes Palace Hotel" ... in the interval "2013-01-01 08:00" and "2013-01-01 10:00"
"Google" relates to "Eric Schmidt", "Microsoft" ... in the interval "2013-01-01 08:00" and "2013-01-01 10:00"
...
然后我们将这些关系映射到 MySQL 表中:
| tag | related tag | event | start time | end time |
----------------------------------------------------------------------------
| Eric Schmidt | Google | CES | 2013-01-01 08:00 | 2013-01-01 10:00 |
| Eric Schmidt | Microsoft | CES | 2013-01-01 08:00 | 2013-01-01 10:00 |
...
并且,假设用户从菜单中选择了 SELECTED_TAG_1 和 SELECTED_TAG_2,尝试使用 SELF JOIN 查询它,确保间隔匹配:
SELECT a.related_tag FROM tag_relations a JOIN tag_relations b
ON a.related_tag = b.related_tag
AND a.tag = SELECTED_TAG_1 AND b.tag = SELECTED_TAG_2
AND ( (a.start_time < b.start_time AND a.end_time > b.start_time) OR (a.start_time > b.start_time AND a.start_time < b.end_time) )
但是有两个问题:
- 对于添加到选择中的每个额外标签,区间匹配会增加复杂性(对于三个标签,我们将匹配区间 a 与 b、b 与 c 以及 a 与 c)
- 它不返回每个标签的事件数,以便我们可以排除匹配所有结果事件的那些
你们对如何改进这两种方法中的任何一种或建议一种新方法有什么想法吗?
我知道这不是一个快速的回复,我感谢你一百万次花时间阅读和理解这个问题。