考虑以下表格
=# \d users
Column | Type
--------+-----------------------
id | integer
name | character varying(32)
=# \d profiles
Column | Type
---------+---------
id | integer
user_id | integer
=# \d views
Column | Type
------------+-----------------------------
id | integer
profile_id | integer
time | timestamp without time zone
我需要在给定日期范围的每个月找到具有关联视图的所有用户。目前我正在做以下事情:
with months as (
select to_char(month, 'MM/YYYY') from generate_series('2014-07-01', '2014-09-01', INTERVAL '1 month') as month
)
select * from users
join profiles on user_id = users.id
join views on profile_id = profiles.id
and to_char(views.time, 'MM/YYYY') in (select * from months)
我设置了一个小提琴here。
目前,搜索结果包括8月和9月没有观看次数的用户 Kyle 。正确的结果应该只包括在给定范围内的所有3个月内都有视图的用户 Stan 。我们如何修改此查询以返回所需的结果?
答案 0 :(得分:2)
也许这就足够了(我不知道Postgresql)
select u.id, u.name from users u
join profiles on user_id = users.id
join views on profile_id = profiles.id
and views.time between ? and ?
group by u.id, u.name
having count(distinct to_char(views.time, 'MM/YYYY')) = 3;
答案 1 :(得分:2)
您似乎有一个扩展的关系部门,即您正在寻找仅在给定范围内拥有观看次数的用户,尽管他们的观点也可能超出了感兴趣的范围。
与GROUP BY
一起,您可以通过EXCEPT
构造进行检查。基本上,如果您使用给定范围内的所有视图减去范围内的所有月份,则不应接收任何行:
WITH months(month) AS (
SELECT DATE '2014-07-01' + m*INTERVAL'1mon'
FROM generate_series(0,2) m
)
SELECT *
FROM users u
JOIN profiles p ON p.user_id=u.id
JOIN views v ON v.profile_id=p.id
WHERE 0 = (SELECT count(*) FROM (
SELECT month FROM months
EXCEPT ALL
SELECT date_trunc('mon',time) FROM views
WHERE date_trunc('mon',time) IN (SELECT * FROM months)
AND profile_id=p.id) minus);
您可以通过= ALL
构造稍微简化此构造,因为在子查询没有返回任何行时它将返回true
:
WITH months(month) AS (
SELECT DATE '2014-07-01' + m*INTERVAL'1mon'
FROM generate_series(0,2) m
)
SELECT *
FROM users u
JOIN profiles p ON p.user_id=u.id
JOIN views v ON v.profile_id=p.id
WHERE date_trunc('mon',time) = ALL (
SELECT month FROM months
EXCEPT ALL
SELECT date_trunc('mon',time) FROM views
WHERE date_trunc('mon',time) IN (SELECT * FROM months)
AND profile_id=p.id);
ALL
手册中的引用:
ALL的结果是" true"如果所有行都产生真 (包括子查询不返回任何行的情况)。
我的查询实际上都是一样的。第一个计算内侧的行数并将它们与零进行比较(我同意,这更明显)。第二个将当前views.time
与子查询的所有结果进行比较。仅当子查询返回的所有条目等于views.time
(当然,截断到月边界)时,此构造才会生成true。并且,如果引用,如果子查询不返回任何行,则此构造也会生成true。
根据意图,子查询不应该产生任何行,这表示所有视图都在期望的时间范围内发生。
答案 2 :(得分:1)
with months (month, month_count) as (
select to_char(month, 'MM/YYYY'),
count(*) over ()
from generate_series('2014-07-01', '2014-09-01', INTERVAL '1' month) as month
), counted as (
select *,
count(*) over (partition by user_id) as cnt
from users
join profiles on user_id = users.id
join views on profile_id = profiles.id
join months on months.month = to_char(views.time, 'MM/YYYY')
)
select *
from counted
where cnt = month_count
第一个CTE现在包括所有月份的计数,第二个CTE然后计算每个用户的月份并且是必要的,因为窗口函数不能直接放入where子句。最后的选择然后消除了没有所有月份的用户。
答案 3 :(得分:0)
确保它不仅是匹配的计数,而且还有在转换月份系列后可以进行数组比较的内容,以及视图到数组。
示例:
with months as (
select array_agg(to_char(month, 'MM/YYYY')) m
from generate_series('2014-07-01', '2014-09-01', INTERVAL '1 month') as month
), user_months as (
select p.user_id, array_agg(distinct to_char(v.time, 'MM/YYYY')) m
from views v
inner join profiles p on p.id = v.profile_id
group by p.user_id
)
select um.*
from user_months um
inner join months m on m.m = um.m;