如何在垂直设计的表格上实现搜索?

时间:2009-12-14 14:45:56

标签: sql mysql database

我有一个像这样的表结构(垂直设计)。我可以为每个用户提供无限数量的属性(例如:城市,电话等)。

表:tbl_UserAttributes

┌────────┬───────────┬────────────┐
| UserID │ FieldName │ Value      |
├────────┼───────────┼────────────┤
│ 341    │ city      │ MyCity1    │
│ 772    │ phone     │ 1234567890 │
│ 033    │ city      │ MyCity2    │
│ 044    │ sex       │ M          │
│ 772    │ firstname │ MyName     │
│ ---    │ ---       │ ---        │
└────────┴───────────┴────────────┘

我必须实现一个搜索功能,该功能应输出我们对水平设计的表应用查询的行:

SELECT 
    FieldName 
FROM 
    tbl_UserAttributes 
WHERE 
    city='%Mumbai%' AND 
    sex='M' AND ...

请不要让我改变数据库设计。

更新:目前,我有一个JOIN解决方案,它非常慢,并且有时会挂起服务器。任何替代方法?

9 个答案:

答案 0 :(得分:7)

只要您不需要一次搜索多个值,

EAV表就是一件好事,在这种情况下它会变得很糟糕。

您不能一次索引多个值,因为它们位于不同的记录中。

SQL Server表中,您可以为多个值创建索引视图,并将其用于搜索。

Oracle中,您可以按UserID对表进行聚类,这会使所有记录在一个数据页中保持相同的UserID,这将使用最具选择性的值的索引并快速扫描其他值。

PostgreSQL中,您可以将所有值存储在单个数组中,并使用GIN索引对其进行索引。

MySQL中,您无法做到这两点。

这是一个返回值的查询:

SELECT  *
FROM    tbl_UserAttributes tcity
JOIN    tbl_UserAttributes tsex
ON      tsex.userid = tcity.userid
WHERE   tcity.fieldname = 'city'
        AND tcity.value LIKE '%Mumbai%'
        AND tsex.fieldname = 'sex'
        AND tsex.value = 'M'

但不要指望它非常快。

<强>更新

如果您需要完全匹配,可以在(fieldname, value, userid)上创建综合索引,将最具选择性的fieldname放入第一个表格并使用STRAIGHT_JOIN强制执行订单:

SELECT  *
FROM    tbl_UserAttributes tcity
STRAIGHT_JOIN
        tbl_UserAttributes tsex
ON      tsex.userid = tcity.userid
WHERE   tcity.fieldname = 'city'
        AND tcity.value = 'Mumbai'
        AND tsex.fieldname = 'sex'
        AND tsex.value = 'M'

但是,这对您当前的查询没有帮助,因为您正在寻找通配符匹配,在这种情况下索引不是很有帮助。除非您查询产科医院数据库,否则您的第二个表格不会从索引中获益太多。

由于可以使用索引扫描而不是表扫描,所以它会节省一些时间。

答案 1 :(得分:3)

之前看过这个。而不是搜索匹配城市和性别等的东西,而不是搜索与您的搜索查询匹配的属性。如果此计数等于搜索查询中的属性数,则为结果之一。

答案 2 :(得分:2)

是否有一组固定的FieldNames?

如果可以,我可以建议设置一个视图以使其水平并使其易于查询。在SQL Server 2005中,它将类似于:

SELECT *
FROM
(SELECT [UserID], [FieldName], [Value]
FROM [tbl_UserAttributes] ) ps
PIVOT
(
MAX([Value])
FOR [FieldName] IN
( [City], [Phone], [sex], [firstname])
) AS pvt

这应该使其成为水平,尽管所有必需的[FieldName]值都需要在IN()部分中为每个值拉出一个字段。同样使用Max意味着如果同一FieldName有多个值,它将拉出Max。

答案 3 :(得分:1)

对于那些提供帮助的人来说,这是EAV(实体属性值)的经典案例。在设计应用程序时,强烈建议不要这样做。

答案 4 :(得分:1)

你必须在userid = userid上加入同一个表,显然联接的一边是“where fieldname ='city'和value ='houston'”而另一边是“where fieldname ='性'和价值='M'“。希望您不希望有太多不同的字段同时搜索!

Quassnoi击败了我30秒。

答案 5 :(得分:1)

也许不推荐,但我明白它的来源。为了获得灵活性,您可以在颈部查询中获得一些痛苦。

要查询两个属性,您需要加入。

select a1.userid from tbl_UserAttributes a1, tbl_UserAttributes a2 where
a1.userid=a2.userid 
and a1.FieldName='city' and a1.Value='Mumbai'
and a2.FieldName='sex' and a2.Value='M'

很快就会变得笨拙。

UPD:

正如布莱恩所说,你最好算一下比赛的数量。

select userid, count(*) from tbl_UserAttributes 
where (FieldName='city' and a1.Value='Mumbai')
or (FieldName='sex' and a2.Value='M')
group by userid
having count(*)=2

这应该更快

答案 6 :(得分:1)

select ua.userID
from tblUserAttributes ua
INNER JOIN tblUserAttributes ua2
ON ua.userID = ua2.userID
and ua2.firstname = 'john'
INNER JOIN tblUserAttributes ua3
ON ua.userID = ua3.userID
and ua3.lastname = 'smith'
where ua.sex = 'M'

答案 7 :(得分:1)

如果之前提到的内部联接方法不可接受,我可以建议您每隔X分钟将表格聚合成横向格式,然后使用新表作为搜索的基础。

答案 8 :(得分:1)

我认为而不是尝试将其作为一个慢速查询,我会将其作为几个快速查询,每个条件一个。也许是这样的。

CREATE TEMPORARY table search_results (User_id,score)
  SELECT User_id, 1 FROM tbl_UserAttributes
    WHERE FieldName ='blah' and FieldValue='x'; //should put an index on search_results.User_id

UPDATE search_results s JOIN tbl_UserAttributes u USING (User_id)
SET s.score=s.score+1 WHERE u.FieldName ='foo' and FieldValue='y';

为许多条件重复更新。

SELECT User_id FROM search_results WHERE score= 'number of conditions'.

上面的SELECT可以连接到tbl_UserAttributes,输出你需要的任何字段名。