我有一个很大的数据集,我想对记录重叠时间的计数求和。例如,给定数据
[
{"id": 1, "name": 'A', "start": '2018-12-10 00:00:00', "end": '2018-12-20 00:00:00', count: 34},
{"id": 2, "name": 'B', "start": '2018-12-16 00:00:00', "end": '2018-12-27 00:00:00', count: 19},
{"id": 3, "name": 'C', "start": '2018-12-16 00:00:00', "end": '2018-12-20 00:00:00', count: 56},
{"id": 4, "name": 'D', "start": '2018-12-25 00:00:00', "end": '2018-12-30 00:00:00', count: 43}
]
您可以看到活动有两个重叠的时期。我想根据重叠中涉及的活动返回这些“重叠”的总数。因此,上面的输出将类似于:
[
{start:'2018-12-16', end: '2018-12-20', overlap_ids:[1,2,3], total_count: 109},
{start:'2018-12-25', end: '2018-12-27', overlap_ids:[2,4], total_count: 62},
]
问题是,如何通过postgres查询生成此信息?正在查看generate_series,然后计算出每个时间间隔内有哪些活动,但这并不完全正确,因为数据是连续的-我真的需要确定确切的重叠时间,然后对重叠的活动进行求和。
EDIT添加了另一个示例。正如@SRack所指出的,由于A,B,C重叠,这意味着B,CA,A,B和A,C也重叠。没关系,因为我要查找的输出是日期范围的数组,它们包含重叠活动,而不是重叠的所有唯一组合。另请注意,日期是时间戳记,因此将具有毫秒级的精度,并且不一定都在00:00:00。 如果有帮助,则总数上可能会存在WHERE条件。例如,只想查看总数> 100的结果
答案 0 :(得分:1)
demo:db<>fiddle(使用带有重叠的A-B部分的旧数据集)
免责声明::此功能适用于日期间隔,而不适用于时间戳记。 ts的要求稍后出现。
SELECT
s.acts,
s.sum,
MIN(a.start) as start,
MAX(a.end) as end
FROM (
SELECT DISTINCT ON (acts)
array_agg(name) as acts,
SUM(count)
FROM
activities, generate_series(start, "end", interval '1 day') gs
GROUP BY gs
HAVING cardinality(array_agg(name)) > 1
) s
JOIN activities a
ON a.name = ANY(s.acts)
GROUP BY s.acts, s.sum
generate_series
生成开始和结束之间的所有日期。因此,活动存在的每个日期都会与特定的count
HAVING
过滤掉只有一项活动的日期DISTINCT ON
过滤所有重复项这是时间戳的版本:
WITH timeslots AS (
SELECT * FROM (
SELECT
tsrange(timepoint, lead(timepoint) OVER (ORDER BY timepoint)),
lead(timepoint) OVER (ORDER BY timepoint) -- 2
FROM (
SELECT
unnest(ARRAY[start, "end"]) as timepoint -- 1
FROM
activities
ORDER BY timepoint
) s
)s WHERE lead IS NOT NULL -- 3
)
SELECT
GREATEST(MAX(start), lower(tsrange)), -- 6
LEAST(MIN("end"), upper(tsrange)),
array_agg(name), -- 5
sum(count)
FROM
timeslots t
JOIN activities a
ON t.tsrange && tsrange(a.start, a.end) -- 4
GROUP BY tsrange
HAVING cardinality(array_agg(name)) > 1
主要思想是确定可能的时隙。因此,我将每个已知的时间(开始和结束)都放入一个已排序的列表中。因此,我可以进行第一个已知的拖曳时间(从起点A开始的17:00和从起点B开始的18:00),并检查其中的间隔。然后我检查第二和第三,然后第三和第四,依此类推。
在第一个时隙中,仅A适用。在18-19的第二个中,B也适合。在下一个19-20插槽中,也C,从20到20:30 A不再适合,仅B和C。下一个是20:30-22,其中仅B适合,最后将22-23 D添加到B,最后但并非唯一的D都适合23-23:30。
因此,我选择了这个时间清单,并将其加入到间隔相交的活动表中。之后,仅按时间分组并汇总计数即可。
unnest
扩展为每个元素的一行。所以我把所有时间都放在一列中,可以简单地排序tsrange
NULL
值,该值由tsrange
解释为无穷大。因此,这将产生令人难以置信的错误时隙。因此,我们需要将此行过滤掉。&&
运算符检查两个范围类型是否重叠。HAVING
子句lower
获取)。例如。以20-20:30时隙为例:它开始20h,但B和C都没有起点。结束时间类似。答案 1 :(得分:0)
因为它被标记为Ruby on Rails,所以我也为此提供了一个Rails解决方案。我更新了数据,使它们不会全部重叠,并使用以下方法:
data = [
{"id": 1, "name": 'A', "start": '2017-12-10 00:00:00', "end": '2017-12-20 00:00:00', count: 34},
{"id": 2, "name": 'B', "start": '2018-12-16 00:00:00', "end": '2018-12-21 00:00:00', count: 19},
{"id": 3, "name": 'C', "start": '2018-12-20 00:00:00', "end": '2018-12-29 00:00:00', count: 56},
{"id": 4, "name": 'D', "start": '2018-12-21 00:00:00', "end": '2018-12-30 00:00:00', count: 43}
]
(2..data.length).each_with_object({}) do |n, hash|
data.combination(n).each do |items|
combination = items.dup
first_item = combination.shift
first_item_range = (Date.parse(first_item[:start])..Date.parse(first_item[:end]))
if combination.all? { |i| (Date.parse(i[:start])..Date.parse(i[:end])).overlaps?(first_item_range) }
hash[items.map { |i| i[:name] }.sort] = items.sum { |i| i[:count] }
end
end
end
我已经更新了数据,所以它们不会全部重叠,从而产生以下结果:
# => {["B", "C"]=>75, ["B", "D"]=>62, ["C", "D"]=>99, ["B", "C", "D"]=>118}
...因此,您可以看到项B
,C
和D
重叠,总数为118
。 (自然,这也意味着B, C
,B, D
和C, D
重叠。)
这是逐步执行的操作:
通过这种方式,我们获得了唯一的数据名称条目,并在它们旁边存储了一个计数。
希望这是有用的-乐意就可以改进此问题的任何方面收集反馈。让我知道你过得怎么样!