Postgres 专属标签搜索

Postgres exclusive tag search

我正在尝试 return 与一个用户相关联的所有行,该用户与所有查询 'tags' 相关联。我的 table 结构和所需的输出如下:

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' 行都是 returned,加入 system.users 也是为了 return 她的电子邮件。

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

使用相关子select统计用户不同标签的数量,使用非相关子select统计不同标签的数量:

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)

派生 table 中的查询为您提供具有所有指定标签的用户的用户 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')

如果您只想要只搜索过标签的用户和 none 其他(例如精确划分),您可以将派生 table 中的查询更改为:

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

鉴于您的示例数据,这不会 return 任何东西,因为用户 2 也有 葡萄

Sample SQL Fiddle

must-have-them-all 类关系除法问题的标准双重否定方法:(我将 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
        )
    )
    ;