Mik*_*ke 5 sql postgresql aggregate-functions overlap window-functions
情况
我们有一个PostgreSQL 8.4数据库,其中包含用户会话,其中包含登录日期/时间和每行注销日期/时间.我们的Web应用程序会记录此次并处理用户未明确注销的情况(会话超时).因此,每种情况下都会给出登录日期/时间和注销日期/时间.
目标
我需要每天最大并发会话数的用户统计信息.所以,我可以说以下内容:"在2015年3月16日同时在线用户峰值登录为6."
类似的问题
这里回答了一个类似的问题:每天每小时SQL最大并发会话 但是,我无法使解决方案适应我的情况,我希望有一个结果表,显示最大值.每天并发用户会话数,而不是每小时.表格方案也略有不同,因为我的案例中的一行包含登录和注销日期/时间,而在示例中,每行代表登录或注销.此外,该问题基于MS SQL数据库环境而不是PostgreSQL.
注意事项
表格方案:
user_id | login_date | login_time | logout_date | logout_time
------------+--------------+--------------+---------------+-------------
USER32 | 2014-03-03 | 08:23:00 | 2014-03-03 | 14:44:00
USER82 | 2014-03-03 | 08:49:00 | 2014-03-03 | 17:18:00
USER83 | 2014-03-03 | 09:40:00 | 2014-03-03 | 17:31:00
USER36 | 2014-03-03 | 09:50:00 | 2014-03-03 | 16:10:00
USER37 | 2014-03-03 | 11:44:00 | 2014-03-03 | 15:21:00
USER72 | 2014-03-03 | 12:52:00 | 2014-03-03 | 12:55:00
Run Code Online (Sandbox Code Playgroud)
例
以下示例通过Google Charts API作为时间线说明应有助于了解问题:http://i.imgur.com/ZOjnLll.png
鉴于2015-03-03的这一天的例子,除了USER78(6个用户)之外的所有用户都在当天的12:52和12:55之间登录.这是同时登录用户的最大数量,我需要在给定时间范围内每天进行这样的统计.
Day | MaxNumberOfConcurrentSessions
------------+--------------------------------
2015-03-01 | 2
2015-03-02 | 3
2015-03-03 | 6
...
Run Code Online (Sandbox Code Playgroud)
上面的时间轴截图示例为Google Charts API.
google.setOnLoadCallback(drawChart);
function drawChart() {
var container = document.getElementById('example5.1');
var chart = new google.visualization.Timeline(container);
var dataTable = new google.visualization.DataTable();
dataTable.addColumn({ type: 'string', id: 'Room' });
dataTable.addColumn({ type: 'string', id: 'Name' });
dataTable.addColumn({ type: 'date', id: 'Start' });
dataTable.addColumn({ type: 'date', id: 'End' });
dataTable.addRows([
["USER78", '', new Date(2014,03,03,20,38), new Date(2014,03,03,21,14)],
["USER83", '', new Date(2014,03,03,09,40), new Date(2014,03,03,17,31)],
["USER72", '', new Date(2014,03,03,08,43), new Date(2014,03,03,08,43)],
["USER72", '', new Date(2014,03,03,09,40), new Date(2014,03,03,09,40)],
["USER72", '', new Date(2014,03,03,10,03), new Date(2014,03,03,10,06)],
["USER72", '', new Date(2014,03,03,12,52), new Date(2014,03,03,12,55)],
["USER72", '', new Date(2014,03,03,21,13), new Date(2014,03,03,21,13)],
["USER72", '', new Date(2014,03,03,21,37), new Date(2014,03,03,21,38)],
["USER72", '', new Date(2014,03,03,23,14), new Date(2014,03,03,23,15)],
["USER72", '', new Date(2014,03,03,23,27), new Date(2014,03,03,23,28)],
["USER36", '', new Date(2014,03,03,08,05), new Date(2014,03,03,09,17)],
["USER36", '', new Date(2014,03,03,09,50), new Date(2014,03,03,16,10)],
["USER36", '', new Date(2014,03,03,16,12), new Date(2014,03,03,20,29)],
["USER32", '', new Date(2014,03,03,08,23), new Date(2014,03,03,14,44)],
["USER82", '', new Date(2014,03,03,08,49), new Date(2014,03,03,17,18)],
["USER37", '', new Date(2014,03,03,08,04), new Date(2014,03,03,08,06)],
["USER37", '', new Date(2014,03,03,11,44), new Date(2014,03,03,15,21)],
["USER37", '', new Date(2014,03,03,15,34), new Date(2014,03,03,15,51)],
["USER37", '', new Date(2014,03,03,16,12), new Date(2014,03,03,16,14)],
["USER37", '', new Date(2014,03,03,16,52), new Date(2014,03,03,16,54)],
["USER37", '', new Date(2014,03,03,17,07), new Date(2014,03,03,17,08)],
["USER37", '', new Date(2014,03,03,20,20), new Date(2014,03,03,20,24)],
["USER37", '', new Date(2014,03,03,21,03), new Date(2014,03,03,21,20)],
["USER37", '', new Date(2014,03,03,22,42), new Date(2014,03,03,23,05)],
["USER37", '', new Date(2014,03,03,23,51), new Date(2014,03,03,23,56)],
["USER01", '', new Date(2014,03,03,16,11), new Date(2014,03,03,16,12)]
]);
var options = {
timeline: { colorByRowLabel: true }
};
chart.draw(dataTable, options);
}
Run Code Online (Sandbox Code Playgroud)
<script type="text/javascript" src="https://www.google.com/jsapi?autoload={'modules':[{'name':'visualization',
'version':'1','packages':['timeline']}]}"></script>
<div id="example5.1" style="width:5000px;height: 600px;"></div>
Run Code Online (Sandbox Code Playgroud)
我会序列化登录和注销UNION ALL
,“in”计为 1,“out”计为 -1。然后使用简单的窗口函数计算运行计数并获得每天的最大值。
由于尚未指定,假设:
WITH range AS (SELECT '2014-03-01'::date AS start_date -- time range
, '2014-03-31'::date AS end_date) -- inclusive bounds
, cte AS (
SELECT *
FROM tbl, range r
WHERE login_date <= r.end_date
AND logout_date >= r.start_date
)
, ct AS (
SELECT log_date, sum(ct) OVER (ORDER BY log_date, log_time, ct) AS session_ct
FROM (
SELECT logout_date AS log_date, logout_time AS log_time, -1 AS ct FROM cte
UNION ALL
SELECT login_date, login_time, 1 FROM cte
) sub
)
SELECT log_date, max(session_ct) AS max_sessions
FROM ct, range r
WHERE log_date BETWEEN r.start_date AND r.end_date -- crop actual time range
GROUP BY 1
ORDER BY 1;
Run Code Online (Sandbox Code Playgroud)
您可以在 中使用OVERLAPS
运算符cte
:
AND (login_date, logout_date) OVERLAPS (r.start_date, r.end_date)
Run Code Online (Sandbox Code Playgroud)
细节:
但这可能不是一个好主意,因为(根据文档):
每个时间段都被视为代表半开区间
start
<=
time
<
end
,除非start
和end
相等,在这种情况下它代表单个时间瞬间。这意味着例如只有一个共同端点的两个时间段不重叠。
大胆强调我的。您的范围的上限必须是您想要的时间范围之后的第二天。
第1 CTErange
只是提供时间范围的便利一次。
第二个 CTEcte
只选择相关的行:那些......
第三个 CTEct
序列化“in”和“out”点,值为 +/-1,并sum()
使用用作窗口函数的聚合函数计算运行计数。这些从 Postgres 8.4 开始可用。
在最后SELECT
修剪前导和尾随天数,并汇总每天的最大值。瞧。
用于 Postgres 9.6 的SQL 小提琴。
Postgres 8.4 太旧了,不再可用,但应该可以正常工作。我在测试用例中添加了一行 - 一个跨越多天。应该让它更有用。
我通常会使用timestamp
代替date
和time
。同样的尺寸,更容易处理。或者timestamptz
如果可以涉及多个时区。
索引(login_date, logout_date DESC)
作为最低限度的性能有帮助。