如何正确测试日期在时间间隔内

How correctly test the date is within time intervals

我有用户操作的时间戳。以及用户获得执行操作授权的几个时间间隔。 我需要检查此操作的时间戳是否在至少一个时间间隔内。

Table 用户:

CREATE TABLE ausers (
    id serial PRIMARY KEY,
    user_name VARCHAR(255) default NULL,
    action_date TIMESTAMP
);
INSERT INTO ausers VALUES(1,'Jhon', '2018-02-21 15:05:06');
INSERT INTO ausers VALUES(2,'Bob', '2018-05-24 12:22:26');

#|id|user_name|action_date
----------------------------------
1|1 |Jhon     |21.02.2018 15:05:06
2|2 |Bob      |24.05.2018 12:22:26

Table 获得资助:

CREATE TABLE user_grants (
    id serial PRIMARY KEY,
    user_id INTEGER,
    start_date TIMESTAMP,
    end_date TIMESTAMP
);
INSERT INTO user_grants VALUES(1, 1, '2018-01-01 00:00:01', '2018-03-01 00:00:00');
INSERT INTO user_grants VALUES(2, 1, '2018-06-01 00:00:01', '2018-09-01 00:00:00');
INSERT INTO user_grants VALUES(3, 2, '2018-01-01 00:00:01', '2018-02-01 00:00:00');
INSERT INTO user_grants VALUES(4, 2, '2018-02-01 00:00:01', '2018-03-01 00:00:00');

#|id|user_id|start_date           |end_date
------------------------------------------------------
1|1 |1      |01.01.2018 00:00:01  |01.03.2018 00:00:00
2|2 |1      |01.06.2018 00:00:01  |01.09.2018 00:00:00
3|3 |2      |01.01.2018 00:00:01  |01.02.2018 00:00:00
4|4 |2      |01.02.2018 00:00:01  |01.03.2018 00:00:00

查询:

select u.user_name,
case 
    when array_agg(gr.range) @> array_agg(tstzrange(u.action_date, u.action_date, '[]')) then 'Yes'
    else 'No' 
end as "permition was granted"
from ausers u
left join (select tstzrange(ug.start_date, ug.end_date, '[]') as range, ug.user_id as uid 
from user_grants ug) as gr on gr.uid = u.id
group by u.user_name;

结果:

#|user_name|permition was granted
---------------------------------
1|Bob      |No
2|Jhon     |No

时间戳 '01.02.2018 15:05:06' 在“01.01.2018 00:00:01, 01.03.2018 00:00:00” 范围内,因此 "Bob" 有执行操作的授权第一行应该是 "Yes",而不是 "No".

预期的输出是这样的:

#|user_name|permition was granted
---------------------------------
1|Bob      |Yes
2|Jhon     |No

我试过这样测试:

select array_agg(tstzrange('2018-02-21 15:05:06', '2018-02-21 15:05:06', '[]')) <@ array_agg(tstzrange('2018-01-01 00:00:01', '2018-03-01 00:00:01', '[]'));

#|?column?
----------
 |false

结果是 "false"。 但是如果删除 array_agg 函数

select tstzrange('2018-02-21 15:05:06', '2018-02-21 15:05:06', '[]') <@ tstzrange('2018-01-01 00:00:01', '2018-03-01 00:00:01', '[]');

#|?column?
----------
 |true

它工作正常 - 结果是 "true"。为什么? array_agg 怎么了?

我必须使用 array_agg 因为我有几个时间间隔要比较。

我必须"fake"时间间隔

array_agg(tstzrange(u.action_date, u.action_date, '[]'))

来自一个时间戳,因为运算符 @> 不允许比较时间戳和时间戳间隔数组。 如何从时间间隔数组中比较一个日期是否在至少一个时间间隔内?

PostgreSQL 中有几个 @> 运算符:

  • tstzrange @> tstzrange 测试第一个区间是否包含第二个区间

  • anyarray @> anyarray 测试第一个数组是否包含第二个数组的所有元素。

    在您的查询中,将测试第二个数组中的每个间隔是否在第一个数组中有一个相等间隔。

有一种方法可以测试区间是否包含在区间数组的其中一个元素中:

someinterval <@ ANY (array_of_intervals)

但没有直接的方式用接线员表达您的情况。

不使用聚合,连接 @> 上的两个表并计算结果行数。

由于所有三个日期都是标量,不需要 Postgres 范围检查,因此简单的 BETWEEN 操作就足够了。

select au.user_name
     , case when ug.user_id is null then 'No' else 'Yes' end authorized 
  from ausers au
  left join user_grants ug   
         on (    au.id = ug.id
             and au.action_date between ug.start_date and ug.end_date
            );  

顺便说一句。我认为您发布的预期结果是倒退的。如描述中所示,两个用户名都没有时间戳“01.02.2018 15:05:06”。