选择具有多个时间范围的行

时间:2014-01-09 22:24:58

标签: sql postgresql select time relational-division

我有这张桌子:

CREATE TABLE logins(
    id SERIAL NOT NULL PRIMARY KEY,
    login_time TSRANGE NOT NULL,
    user_id INTEGER NOT NULL REFERENCES users(id),
    CONSTRAINT overlapping_timeslots EXCLUDE USING GIST (
        user_id WITH =,
        timeslot WITH &&
    )
);

当用户登录使用tsrange(login_time,logout_time)保存的login_time时 现在我尝试搜索登录的用户:

-- ('2013-12-31 16:40:05','2013-12-31 17:40:05')
-- ('2014-01-04 14:27:45','2014-01-04 17:30:56')
-- ('2014-01-05 14:59:55','2014-01-05 16:03:39')
-- ('2014-01-01 17:20:54','2014-01-01 22:50:57')
-- Not logged in at ('2013-12-31 18:40:05','2014-01-01 01:20:05')

我有这个查询,但没有有用的结果

SELECT user_id FROM (


select * from logins 
    where user_id in(select user_id from timed_requests where timeslot && tsrange('2013-12-31 16:20:05','2013-12-31 17:40:05'))
    and user_id in(select user_id from timed_requests where timeslot && tsrange('2014-01-04 14:30:45','2014-01-04 17:20:56'))
    and user_id in(select user_id from timed_requests where timeslot && tsrange('2014-01-05 15:09:55','2014-01-05 16:00:39'))
    and user_id in(select user_id from timed_requests where timeslot && tsrange('2014-01-01 17:20:54','2014-01-01 22:50:57')
    and user_id not in(select user_id from timed_requests where timeslot && tsrange('2013-12-31 18:40:05','2014-01-01 01:20:05'))
    ) ss
GROUP BY user_id
order by user_id;

有没有人知道如何编写查询,搜索以3-4个给定时间点登录的用户。

2 个答案:

答案 0 :(得分:1)

这是relational division的典型案例。有多种方式来解决它。这应该是最快最简单的:

SELECT DISTINCT user_id
FROM   logins l1
JOIN   logins l2 USING (user_id)
JOIN   logins l3 USING (user_id)
JOIN   logins l4 USING (user_id)
LEFT   JOIN logins l5 ON t5.user_id = t1.user_id AND 
  NOT (l4.timeslot && tsrange('2013-12-31 18:40:05','2014-01-01 01:20:05'))
WHERE  l1.timeslot && tsrange('2013-12-31 16:20:05','2013-12-31 17:40:05')
AND    l2.timeslot && tsrange('2014-01-04 14:30:45','2014-01-04 17:20:56')
AND    l3.timeslot && tsrange('2014-01-05 15:09:55','2014-01-05 16:00:39')
AND    l4.timeslot && tsrange('2014-01-01 17:20:54','2014-01-01 22:50:57')
AND    l5.user_id IS NULL
ORDER  BY 1;

您有一个排除约束,但在单个测试范围内可以多次记录,因此我们需要GROUP BYDISTINCT

我们在这个相关答案中汇集了一整套技术: How to filter SQL results in a has-many-through relation

为了避免重复开始并同时从users表中检索整行(这不在您的问题中,但可能存在),此表单可能更快:

SELECT *
FROM   users u
WHERE  EXISTS (SELECT 1 FROM logins WHERE user_id = u.user_id
       AND timeslot && tsrange('2013-12-31 16:20:05','2013-12-31 17:40:05'))
AND    EXISTS (SELECT 1 FROM logins WHERE user_id = u.user_id
       AND timeslot && tsrange('2014-01-04 14:30:45','2014-01-04 17:20:56'))
AND    EXISTS (SELECT 1 FROM logins WHERE user_id = u.user_id
       AND timeslot && tsrange('2014-01-05 15:09:55','2014-01-05 16:00:39'))
AND    EXISTS (SELECT 1 FROM logins WHERE user_id = u.user_id
       AND timeslot && tsrange('2014-01-01 17:20:54','2014-01-01 22:50:57'))
AND    NOT EXISTS (SELECT 1 FROM logins WHERE user_id = u.user_id
       AND timeslot && tsrange('2013-12-31 18:40:05','2014-01-01 01:20:05'))
ORDER  BY u.user_id;

登录的排除约束有助于这些查询。它由多列GiST索引实现,使这些查找非常快。

答案 1 :(得分:0)

接近此类查询的一种方法是使用带有having子句的聚合进行过滤。有点不清楚您的查询实际上在做什么(例如timed_requestslogins的对比。)

以下内容包含逻辑:

select user_id
from timed_requests 
group by user_id
having sum(case when timeslot && tsrange('2013-12-31 16:20:05','2013-12-31 17:40:05') then 1 else 0 end) > 0 and
       sum(case when timeslot && tsrange('2014-01-04 14:30:45','2014-01-04 17:20:56') then 1 else 0 end) > 0 and
       sum(case when timeslot && tsrange('2014-01-05 15:09:55','2014-01-05 16:00:39') then 1 else 0 end) > 0 and
       sum(case when timeslot && tsrange('2014-01-01 17:20:54','2014-01-01 22:50:57') then 1 else 0 end) > 0 and
       sum(case when timeslot && tsrange('2013-12-31 18:40:05','2014-01-01 01:20:05') then 1 else 0 end) > 0;

having子句中的每个条件都计算满足特定条件的行数。