Выберите строку с несколькими временными диапазонами
-
21-12-2019 - |
Вопрос
У меня есть эта таблица:
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)
.
Теперь я пытаюсь найти пользователя, который входит в систему по адресу:
-- ('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 заданных момента времени.
Решение
Это типичный случай реляционное деление.Есть много способов чтобы решить эту проблему.Это должно быть одним из самых быстрых и простых:
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 BY
или DISTINCT
.
В этом ответе мы собрали целый арсенал техник:
Как фильтровать результаты SQL в отношении «многие сквозные»
Чтобы избежать дублирования и одновременно получить целую строку из users
table (которой нет в вашем вопросе, но, вероятно, существует), эта форма может быть быстрее:
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, что делает поиск очень быстрым.
Другие советы
Один из способов выполнить этот тип запроса — использовать агрегацию с помощью having
пункт для фильтрации.Немного неясно, что на самом деле делает ваш запрос (что такое timed_requests
против logins
например).
Следующее инкапсулирует логику:
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
Предложение подсчитывает количество строк, соответствующих определенному условию.