数据库设计 - 高效的文本搜索

时间:2012-03-11 16:31:15

标签: sql database-design relational-database

我有一个包含URL字符串的表,即

/A/B/C
/C/E
/C/B/A/R

每个字符串都分为标记,在我的情况下,分隔符为'/'。然后我为每个标记分配整数值,并将它们放入字典(不同的数据库表),即

A : 1
B : 2
C : 3
E : 4
D : 5
G : 6
R : 7

我的问题是在第一个表中找到包含给定令牌序列的那些行。另外一个问题是我的输入是整数序列,即我有

3, 2

我想找到以下行

/A/B/C
/C/B/A/R

如何以有效的方式做到这一点。我的意思是如何设计合适的数据库结构。

我使用PostgreSQL,解决方案应该适用于第一个表中的2百万行。

为了澄清我的例子 - 我需要'B'和'C'都在URL中。 “B”和“C”也可以在URL中以任何顺序出现。

我需要高效的SELECT。 INSERT不一定非常有效。如果这会改变任何内容,我不必在SQL中完成所有工作。

提前致谢

2 个答案:

答案 0 :(得分:1)

我不知道该怎么做,但我只是给你一些可能有用的想法。你已经有了初始表。您处理并创建令牌表:

+------------+---------+
| TokenValue | TokenId |
+------------+---------+
| A          |       1 |
| B          |       2 |
| C          |       3 |
| E          |       4 |
| D          |       5 |
| G          |       6 |
| R          |       7 |
+------------+---------+

这对我没问题。现在,我要做的是创建一个新表,在其中我将原始表与令牌表(OrderedTokens)的标记相匹配。类似的东西:

+-------+---------+---------+
| UrlID | TokenId | AnOrder |
+-------+---------+---------+
|     1 |       1 |       1 |
|     1 |       2 |       2 |
|     1 |       3 |       3 |
|     2 |       5 |       1 |
|     2 |       2 |       2 |
|     2 |       1 |       3 |
|     2 |       7 |       4 |
|     3 |       3 |       1 |
|     3 |       4 |       2 |
+-------+---------+---------+

这样,只要您使用订单字段,您甚至可以重新创建原始表格。例如:

select string_agg(t.tokenValue, '/' order by ot.anOrder) as OriginalUrl
from OrderedTokens as ot
join tokens t on t.tokenId = ot.tokenId
group by ot.urlId

上一个查询将导致:

+-------------+
| OriginalUrl |
+-------------+
| A/B/C       |
| D/B/A/R     |
| C/E         |
+-------------+

所以,你甚至不需要原来的桌子了。如果您想获得具有任何提供的令牌ID的Url(在本例中为BC),您可以使用此:

select string_agg(t.tokenValue, '/' order by ot.anOrder) as OriginalUrl
from OrderedTokens as ot
join Tokens t on t.tokenId = ot.tokenId
group by urlid
having count(case when ot.tokenId in (2, 3) then 1 end) > 0

这导致:

+-------------+
| OriginalUrl |
+-------------+
| A/B/C       | => It has both B and C
| D/B/A/R     | => It has only B
| C/E         | => It has only C
+-------------+

现在,如果你想获得所有有两个ID的Url,那么试试这个:

select string_agg(t.tokenValue, '/' order by ot.anOrder) as OriginalUrl
from OrderedTokens as ot
join Tokens t on t.tokenId = ot.tokenId
group by urlid
having count(distinct case when ot.tokenId in (2, 3) then ot.tokenId end) = 2

count中添加要过滤的所有ID,然后等于计算您添加的ID数量。上一个查询将导致:

+-------------+
| OriginalUrl |
+-------------+
| A/B/C       | => It has both B and C
+-------------+

有趣的是,我提供的解决方案都没有产生预期的结果。那么,我是否误解了您的要求,或者您提供的预期结果是错误的?

如果这是正确的,请告诉我。

答案 1 :(得分:0)

这实际上取决于你的效率是什么意思。这将是查询性能和存储之间的权衡。

如果您想有效地存储此信息,那么您当前的方法是合适的。您可以通过执行以下操作来查询数据:

SELECT DISTINCT
  u.url
FROM
  urls u
INNER JOIN
  dictionary d
ON
  d.id IN (3, 2)
  AND u.url ~ E'\\m' || d.url_component || E'\\m'

此查询将花费一些时间,因为需要执行全表扫描,并对每个URL执行正则表达式逻辑。但是,插入和存储数据非常容易。

但是,如果要优化查询性能,可以创建URL组件的引用表;它看起来像这样:

/A/B/C    A
/A/B/C    B
/A/B/C    C
/C/E      C
/C/E      E
/D/B/A/R  D
/D/B/A/R  B
/D/B/A/R  A
/D/B/A/R  R

然后,您可以在此表上的URL组件上创建聚簇索引。此查询可以非常快速地检索您的结果:

SELECT DISTINCT
  u.full_url
FROM
  url_components u
INNER JOIN
  dictionary d
ON
  d.id IN (3, 2)
  AND u.url_component = d.url_component

基本上,这种方法可以预先提高查询的复杂性。如果您正在进行少量插入,但对此数据进行了大量查询,那么这是合适的。

创建此URL组件表非常简单,具体取决于您可以使用的工具。一个简单的awk脚本可以在一两分钟内完成2M记录,随后的副本也可以快速复制到数据库中。如果您需要支持此表的实时更新,我建议使用非SQL解决方案:无论您的应用程序编码在何处,都可以使用正则表达式来解析URL并将组件插入到组件表中。如果您仅限于使用数据库,那么插入触发器可以实现相同的角色,但这将是一种更脆弱的方法。