Postgres独家标签搜索

时间:2015-08-26 13:45:49

标签: sql postgresql relational-division

我正在尝试返回与所有查询的“标记”相关联的用户关联的所有行。我的表结构和所需的输出如下:

admin.tags:
user_id |   tag   |   detail   |    date
   2    |  apple  | blah...    | 2015/07/14
   3    |  apple  | blah.      | 2015/07/17
   1    |  grape  | blah..     | 2015/07/23
   2    |  pear   | blahblah   | 2015/07/23
   2    |  apple  | blah, blah | 2015/07/25
   2    |  grape  | blahhhhh   | 2015/07/28 

system.users:
id  |    email
 1  | joe@test.com
 2  | jane@test.com
 3  | bob@test.com

queried tags:
'apple', 'pear'

desired output:
user_id |   tag   |   detail   |    date    |  email
   2    |  apple  | blah...    | 2015/07/14 | jane@test.com
   2    |  pear   | blahblah   | 2015/07/23 | jane@test.com
   2    |  apple  | blah, blah | 2015/07/25 | jane@test.com

由于user_id 2与'apple'和'pear'相关联,因此返回每个'apple'和'pear'行,加入system.users以便还返回她的电子邮件。

我对如何正确设置这个postgresql查询感到困惑。我用左反连接做了几次尝试,但似乎无法获得理想的结果。

3 个答案:

答案 0 :(得分:2)

派生表中的查询为您提供具有所有指定标记的用户的用户ID,外部查询将为您提供详细信息。

select * 
from "system.users" s
join "admin.tags" a on s.id = a.user_id
join (
    select user_id 
    from "admin.tags" 
    where tag in ('apple', 'pear')
    group by user_id 
    having count(distinct tag) = 2
) t on s.id = t.user_id;

请注意,此查询将包含同时具有您搜索的两个标记的用户,但只要他们至少具有指定的两个标记,就可以包含其他标记。

使用您的样本数据,输出将是:

| id |         email | user_id |   tag |     detail |                   date | user_id |
|----|---------------|---------|-------|------------|------------------------|---------|
|  2 | jane@test.com |       2 | grape |   blahhhhh | July, 28 2015 00:00:00 |       2 |
|  2 | jane@test.com |       2 | apple | blah, blah | July, 25 2015 00:00:00 |       2 |
|  2 | jane@test.com |       2 |  pear |   blahblah | July, 23 2015 00:00:00 |       2 |
|  2 | jane@test.com |       2 | apple |    blah... | July, 14 2015 00:00:00 |       2 |

如果您想要使用grape排除行,只需在外部查询中添加where tag in ('apple', 'pear')

如果您只想要只拥有搜索过的标签且没有其他用户(例如精确分割)的用户,您可以将派生表中的查询更改为:

select user_id 
from "admin.tags" 
group by user_id
having sum(case when tag = 'apple' then 1 else 0 end) >= 1
   and sum(case when tag = 'pear' then 1 else 0 end) >= 1 
   and sum(case when tag not in ('apple','pear') then 1 else 0 end) = 0

由于用户2也有 grape

,因此不会返回任何给定样本数据的内容

Sample SQL Fiddle

答案 1 :(得分:2)

必须拥有所有类型的关系除法问题的标准双重否定方法:(我将date重命名为zdate以避免使用关键字作为标识符)

    -- For convenience: put search arguments into a temp table or CTE
    -- I cheat by extracting this from the admin_tags table
    -- (in fact, there should be a table with all possible tags somwhere) 
-- WITH needed_tags AS (
    -- SELECT DISTINCT tag
    -- FROM admin_tags
    -- WHERE tag IN ('apple' , 'pear' )
    -- )
    -- Even better: directly use a VALUES() as a constructor
    -- (thanks to @jpw )
WITH needed_tags(tag) AS (
    VALUES ('apple' ) , ( 'pear' )
    )
SELECT at.user_id , at.tag , at.detail , at.zdate
    , su.email
FROM admin_tags at
JOIN system_users su ON su.id = at.user_id
WHERE NOT EXISTS (
    SELECT * FROM needed_tags nt
    WHERE NOT EXISTS (
        SELECT * FROM admin_tags nx
        WHERE nx.user_id = at.user_id
        AND nx.tag = nt.tag
        )
    )
    ;

答案 2 :(得分:0)

使用相关子选择来计算用户的不同标签数量,使用不相关的子选择来计算不同标签的数量:

select at.user_id, at.tag, at.detail, at.date, su.email
from admin.tags at
  join system.users su on at.user_id = su.id
where (select count(distinct tag) from admin.tags at2
       where at2.user_id = at.user_id)
    = (select count(distinct tag) from admin.tag)