如何在类似EAV的设计中查询多个记录

时间:2017-11-22 16:28:48

标签: sql postgresql

我有两张桌子。一个像user / person这样的对象,另一个有额外的属性。

我创建了一个演示集来测试它。不要被那里的数据搞糊涂。这样可以很容易地将其插入查询控制台。

SELECT * FROM
(
    SELECT * FROM (VALUES 
    ('object_1'), 
    ('object_2'), 
    ('object_3'),
    ('object_4')) AS objects (id)
) AS o
LEFT JOIN (
    SELECT * FROM (VALUES 
        ('object_1', 'name', 'john'), 
        ('object_1', 'lastname', 'joans'),
        ('object_2', 'name', 'john'),
        ('object_2', 'lastname', 'johnson'),
        ('object_3', 'name', 'joan'),
        ('object_3', 'lastname', 'johnson')
    ) AS properties (o_id, property, value)
) AS p ON o.id = p.s_id;

我想找到名为'john'和姓'johnson'的对象。 正确答案当然是'object_2' 我该怎么做?

以下我没有得到任何结果(显然):

WHERE 
    (p.property = 'name' AND p.value = 'john') 
AND
    (p.property = 'lastname' AND p.value = 'johnson')
;

以下我得到3个对象,

WHERE 
    (p.property = 'name' AND p.value = 'john') 
OR
    (p.property = 'lastname' AND p.value = 'johnson')
;

我正在考虑两次加入物业的方向。但是你必须为每一个新的财产重复这个技巧。

SELECT * FROM
(
    SELECT * FROM (VALUES 
    ('object_1'), 
    ('object_2'), 
    ('object_3'),
    ('object_4')) AS objects (id)
) AS o
LEFT JOIN (
SELECT * FROM (VALUES 
    ('object_1', 'name', 'john'), 
    ('object_1', 'lastname', 'joans'),
    ('object_2', 'name', 'john'),
    ('object_2', 'lastname', 'johnson'),
    ('object_3', 'name', 'joan'),
    ('object_3', 'lastname', 'johnson')
) AS properties (s_id, property, value)
) AS p1 ON o.id = p1.s_id
LEFT JOIN (
SELECT * FROM (VALUES 
    ('object_1', 'name', 'john'), 
    ('object_1', 'lastname', 'joans'),
    ('object_2', 'name', 'john'),
    ('object_2', 'lastname', 'johnson'),
    ('object_3', 'name', 'joan'),
    ('object_3', 'lastname', 'johnson')
) AS properties (s_id, property, value)
) AS p2 ON o.id = p2.s_id
WHERE 
    (p1.property = 'name' AND p1.value = 'john') 
AND
    (p2.property = 'lastname' AND p2.value = 'johnson')
;

(是的丑陋重复的桌子,但方便吗?)

我知道数据库设计有点奇怪,但这是因为某些属性应该是可扩展的。我知道这是一种模式。(https://en.wikipedia.org/wiki/Entity%E2%80%93attribute%E2%80%93value_model

我还想过GROUP BY,HAVING,但不知道怎么做。

3 个答案:

答案 0 :(得分:2)

您可以使用exists

SELECT * 
  FROM objects AS o
 WHERE EXISTS
         ( SELECT 1 
             FROM properties AS p
            WHERE p.property = 'lastname' 
              AND p.value = 'johnson'
              AND o.object = p.object
         )
    AND EXISTS
         ( SELECT 1 
             FROM properties AS p
            WHERE p.property = 'name' 
              AND p.value = 'john'
              AND o.object = p.object
         )

答案 1 :(得分:0)

EAV模型不是一种模式 - 它是一种反模式。几乎是教科书,“不要像这样设计你的数据库。”如果你谷歌“Bill Karwin反模式你应该能够找到一个完整的描述为什么这个设计是一个非常糟糕的主意。

您遇到的原因之一就是其中一个原因。几十年来,人们已经获得了足够的经验来揭示这些原因。 非常几种情况下,正确的数据库设计与正确的数据库设计相反是正确的。大多数人认为他们发现异常是错误的。 ;)

所有这些说明,如果您仍想沿着这条路走下去,您可以通过加入属性一次然后对匹配数量做COUNT(*)并确保它等于数字来实现目标你正在寻找。例如:

SELECT O.object_id
FROM
    My_Objects O
INNER JOIN My_Properties P ON P.object_id = O.object_id AND
    (O.property = 'name' AND O.value = 'John') OR
    (O.property = 'lastname' AND O.value = 'Johnson')
GROUP BY
    O.object_id
HAVING
    COUNT(*) = 2

答案 2 :(得分:0)

我建议在这种情况下使用intersect

我更喜欢with子句来设置测试数据。 (如果您已经有properties表,那么这里只需要最后三行)

with
properties as (
  select 'object_1' s_id, 'lastname' property, 'joans'   val union
  select 'object_2'     , 'name'             , 'john'        union
  select 'object_2'     , 'lastname'         , 'johnson'     union
  select 'object_3'     , 'name'             , 'joan'        union
  select 'object_3'     , 'lastname'         , 'johnson'
)
select s_id from properties where property='name'     and val='john'
INTERSECT
select s_id from properties where property='lastname' and val='johnson';

结果:

object_2