在EAV中选择不同的值对

时间:2012-01-24 17:06:57

标签: sql entity-attribute-value

我正在开发一个用户数据库,其中配置文件数据已从简单表更改为实体 - 属性 - 值表。

结构沿着这些方向前的地方:

userid (int)
address 1 (varchar)   
city (varchar)
country (varchar)

现在就是这样:

userid (int)
key (varchar)  
value (varchar) 

例如

userid key      value
150    city     London
150    country  UK
151    city     New York
151    country  USA
152    country  Mexico   

我需要获得一个不同的城市/国家/地区对列表以及每个国家/地区的所有用户数量:

city      country  count
London    UK       18
New York  USA      25

无法保证每个用户都存在每个密钥值对,即可能存在城市,国家或两者,或者既不存在也不存在任何数量的其他键值对。

这对于旧结构来说很简单,但我甚至无法想到如何开始这一点,并且会对一些指针感激不尽

2 个答案:

答案 0 :(得分:1)

你最好的解决方案是回到传统的桌面,因为EAV使得大多数查询都比应该更加困难 - 在这里见证你的问题。在你厌倦了它们之前,你将会进行自我连接,重新制造允许你进行合理查询的表结构。

每个用户ID的城市和国家/地区:

SELECT a.userID, a.value AS city, b.value AS country
  FROM EAV AS a
  JOIN EAV AS b ON a.UserID = b.UserID
 WHERE a.key = 'city'
   AND b.key = 'country';

所以,你最终得到:

SELECT city, country, count(*)
  FROM (SELECT a.userID, a.value AS city, b.value AS country
          FROM EAV AS a
          JOIN EAV AS b ON a.UserID = b.UserID
         WHERE a.key = 'city'
           AND b.key = 'country'
       ) AS c
 GROUP BY city, country;

如果有人可能有两个城市或两个国家/地区的记录,这将为您提供一个笛卡尔积,其中包含该用户的行数,作为该用户的城市和国家/地区记录数量的乘积。

这非常刻意且有意识地忽略了拥有城市而没有国家或国家而没有城市的用户(更不用说那些没有城市的用户)。扩展解决方案以处理这些问题只是非常痛苦 - 我认为你最终得到了一个3路UNION,尽管你可能能够设计出具有多个左外连接的东西。但是,数据可以输入EAV系统而没有必要的限制来确保用户有一个城市和一个国家,这只是拒绝EAV的众多原因之一。

我很抱歉你把这个强加给了你。我建议将http://careers.stackoverflow.com/看作是摆脱痛苦的一种方法,因为这只是它的开始。


处理没有城市或国家/地区或两者的用户。我认为这或多或少会这样做:

SELECT a.userID, b.value AS city, c.value AS country
  FROM (SELECT DISTINCT UserID FROM EAV) AS a
  LEFT JOIN EAV AS b ON a.UserID = b.UserID
  LEFT JOIN EAV AS c ON a.UserID = c.UserID
 WHERE b.key = 'city'
   AND c.key = 'country';

只要该用户没有多个城市或国家/地区记录,这应该为每个用户提供一条记录。 a扫描为您提供EAV表中存在的唯一用户ID列表;两个外部联接为每个此类用户ID提供相应的一个或多个城市和相应的国家或国家/地区,如果给定的用户ID没有城市记录或国家/地区记录(或两者),则会生成空值。

答案 1 :(得分:0)

re:我需要获得一份明确的城市/国家/地区对

SELECT DISTINCT country,city
FROM
(SELECT DISTINCT userid, VALUE AS country FROM TABLE WHERE KEY = 'country') country INNER JOIN
(SELECT DISTINCT userid, VALUE AS city FROM TABLE WHERE KEY = 'city') city ON
country.userid = city.userid

--count of all users for each country
SELECT VALUE AS country, 
COUNT(DISTINCT userid) AS user_count 
FROM TABLE 
WHERE KEY = 'country'
GROUP BY 
VALUE