我有一个像这样的表结构(垂直设计)。我可以为每个用户提供无限数量的属性(例如:城市,电话等)。
表: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解决方案,它非常慢,并且有时会挂起服务器。任何替代方法?
答案 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,输出你需要的任何字段名。