在WHERE子句中多次使用相同的列

时间:2017-11-17 13:28:55

标签: sql postgresql relational-division

我有一个下表结构。

USERS

USERS data

PROPERTY_VALUE

PROPERTY_VALUE data

PROPERTY_NAME

PROPERTY_NAME data

USER_PROPERTY_MAP

USER_PROPERTY_MAP data

我正在尝试从users表中检索property_value表中具有匹配属性的用户。

单个用户可以拥有多个属性。这里的示例数据有2个用户'1'的属性,但可以有2个以上。我想在WHERE子句中使用所有这些用户属性。

如果用户具有单个属性但是对于多个属性失败,则此查询有效:

SELECT * FROM users u
INNER JOIN user_property_map upm ON u.id = upm.user_id
INNER JOIN property_value pv ON upm.property_value_id = pv.id
INNER JOIN property_name pn ON pv.property_name_id = pn.id
WHERE (pn.id = 1 AND pv.id IN (SELECT id FROM property_value WHERE value like '101')
AND pn.id = 2 AND pv.id IN (SELECT id FROM property_value WHERE value like '102')) and u.user_name = 'user1' and u.city = 'city1'

我理解,因为查询有pn.id = 1 AND pn.id = 2,所以它总是会失败,因为pn.id可以是1或2,但不能同时是两者。那么如何重新编写它以使其适用于n个属性?

在上面的示例数据中,只有一个id = 1用户具有WHERE子句中使用的匹配属性。查询应返回包含USERS表的所有列的单个记录。

澄清我的要求

我正在开发一个应用程序,该应用程序在UI上有一个用户列表页面,列出了系统中的所有用户。此列表包含用户ID,用户名,城市等信息 - USERS表中的所有列。用户可以拥有上述数据库模型中详述的属性。

用户列表页面还提供了基于这些属性搜索用户的功能。搜索具有2个属性'property1'和'property2'的用户时,该页面应该只获取并显示匹配的行。根据上面的测试数据,只有用户'1'符合账单。

具有4个属性的用户包括'property1'和'property2'符合条件。但是由于缺少'property2',将只排除只有一个属性'property1'的用户。

5 个答案:

答案 0 :(得分:6)

SELECT *
  FROM users u
 WHERE u.id IN(
         select m.user_id
           from property_value v
           join USER_PROPERTY_MAP m
             on v.id=m.property_value_id 
          where (v.property_name_id, v.value) in( (1, '101'), (2, '102') )
          group by m.user_id
         having count(*)=2
      )

OR

SELECT u.id
  FROM users u
 INNER JOIN user_property_map upm ON u.id = upm.user_id
 INNER JOIN property_value pv ON upm.property_value_id = pv.id
 WHERE (pv.property_name_id=1 and pv.value='101')
    OR (pv.property_name_id=2 and pv.value='102')
 GROUP BY u.id
HAVING count(*)=2

如果propery_name_id已知,则查询中不需要property_name个表。

答案 1 :(得分:5)

如果您只想过滤:

SELECT users.*
FROM users
where (
    select count(*)
    from user_property_map
    left join property_value on user_property_map.property_value_id = property_value.id
    left join property_name on property_value.property_name_id = property_name.id
    where user_property_map.user_id = users.id -- join with users table
    and (property_name.name, property_value.value) in (
        values ('property1', '101'), ('property2', '102') -- filter properties by name and value
    )
) = 2 -- number of properties you filter by

或者,如果您需要根据匹配数量下降的用户,您可以这样做:

select * from (
    SELECT users.*, (
        select count(*) as property_matches
        from user_property_map
        left join property_value on user_property_map.property_value_id = property_value.id
        left join property_name on property_value.property_name_id = property_name.id
        where user_property_map.user_id = users.id -- join with users table
        and (property_name.name, property_value.value) in (
            values ('property1', '101'), ('property2', '102') -- filter properties by name and value
        )
    )
    FROM users
) t
order by property_matches desc

答案 2 :(得分:2)

您在两个ANDpn.id=1之间使用pn.id=2运算符。然后你如何得到答案:

(SELECT id FROM property_value WHERE value like '101') and
(SELECT id FROM property_value WHERE value like '102') 

与上述评论一样,请使用or运算符。

更新1:

SELECT * FROM users u
INNER JOIN user_property_map upm ON u.id = upm.user_id
INNER JOIN property_value pv ON upm.property_value_id = pv.id
INNER JOIN property_name pn ON pv.property_name_id = pn.id
WHERE pn.id in (1,2) AND pv.id IN (SELECT id FROM property_value WHERE value like '101' or value like '102');

答案 3 :(得分:2)

SELECT * FROM users u
INNER JOIN user_property_map upm ON u.id = upm.user_id
INNER JOIN property_value pv ON upm.property_value_id = pv.id
INNER JOIN property_name pn ON pv.property_name_id = pn.id
WHERE (pn.id = 1 AND pv.id IN (SELECT id FROM property_value WHERE value 
like '101') )
OR ( pn.id = 2 AND pv.id IN (SELECT id FROM property_value WHERE value like 
'102'))

OR (...)
OR (...)

你不能做AND因为没有这样的情况,其中id为1和2为SAME ROW,你指定每行的where条件!

如果你运行一个简单的测试,比如

SELECT * FROM users where id=1 and id=2 
你会得到0分的结果。实现这一目标

 id in (1,2) 

 id=1 or id=2

该查询可以进行更多优化,但我希望这是一个良好的开端。

答案 4 :(得分:2)

如果你只想要U中的不同列,那就是:

SELECT DISTINCT u.* 
  FROM Users u INNER JOIN USER_PROPERTY_MAP upm ON u.id = upm.[user_id]
                INNER JOIN PROPERTY_VALUE pv ON upm.property_value_id = pv.id
                INNER JOIN PROPERTY_NAME pn ON pv.property_name_id = pn.id

  WHERE (pn.id = 1 AND pv.[value] = '101')
     OR (pn.id = 2 AND pv.[value] = '102')

注意我使用pv.[value] =而不是子查询重新获取id ...这是简化。