我有一个可以拨打电话的表,其中包含以下字段:
- ID
- 开始时间
- 时间结束
- 状态
- CALL_FROM
- 拨电至
在本地PostgreSQL数据库中加载了290万条记录。我在ID(唯一索引),starttime和endtime上添加了索引。
在stackoverflow上搜索,我找到了一些有用的SQL并将其修改为我认为逻辑应该工作的内容。问题是查询运行了很多个小时,从不返回:
SELECT T1.sid, count(*) as CountSimultaneous
FROM calls_nov T1, calls_nov T2
WHERE
T1.StartTime between T2.StartTime and T2.EndTime
and T1.StartTime between '2011-11-02' and '2011-11-03'
GROUP BY
T1.sid
ORDER BY CountSimultaneous DESC;
有人可以建议一种方法来修复查询/索引,以便它实际工作或建议另一种方式来计算并发调用?
编辑:
解释计划:
Sort (cost=11796758237.81..11796758679.47 rows=176663 width=35)
Sort Key: (count(*))
-> GroupAggregate (cost=0.00..11796738007.56 rows=176663 width=35)
-> Nested Loop (cost=0.00..11511290152.45 rows=57089217697 width=35)
表创建脚本:
CREATE TABLE calls_nov (
sid varchar,
starttime timestamp,
endtime timestamp,
call_to varchar,
call_from varchar,
status varchar);
索引创建:
CREATE UNIQUE INDEX sid_unique_index on calls_nov (sid);
CREATE INDEX starttime_index on calls_nov (starttime);
CREATE INDEX endtime_index on calls_nov (endtime);
1.)您的查询没有捕获所有重叠 - 这已由其他答案修复。
2.)列的数据类型 starttime
和 endtime
是 timestamp
。所以你的 WHERE
条款也有点错误:
BETWEEN '2011-11-02' AND '2011-11-03'
这将包括'2011-11-03 00:00'。上边界必须是 排除。
3.)删除了不带双引号的混合大小写语法。不带引号的标识符会自动转换为小写。简单来说:最好不要在PostgreSQL中使用混合大小写标识符。
4.)转换查询以使用显式JOIN,这总是更可取的。实际上,我把它设为LEFT [OUTER] JOIN,因为我想计算与其他呼叫重叠的呼叫。
5.)简化语法以获得此基本查询:
SELECT t1.sid, count(*) AS ct
FROM calls_nov t1
LEFT JOIN calls_nov t2 ON t1.starttime <= t2.endtime
AND t1.endtime >= t2.starttime
WHERE t1.starttime >= '2011-11-02 0:0'::timestamp
AND t1.starttime < '2011-11-03 0:0'::timestamp
GROUP BY 1
ORDER BY 2 DESC;
这个查询是 非常慢 对于一个大表,因为必须将从'2011-11-02'开始的每一行与整个表中的每一行进行比较,这导致(几乎)O(n²)成本。
更快
我们可以大幅降低成本 预选可能的候选人。只选择您需要的列和行。我用两个CTE做这个。
- 从当天开始选择通话。 - > CTE
x
- 计算这些呼叫的最新结束。 (CTE中的子查询
y
)
- 仅选择与CTE总范围重叠的呼叫
x
。 - > CTE y
- 最后的查询是 快多了 而不是查询庞大的基础表。
这是可能的重叠的样子,其中'A'是“参考”区间。请注意,下面的查询(远远低于)并未给出与已发布的任何答案相同的结果。
-- A |------|
-- B |-|
-- C |---|
-- D |---|
-- E |---|
-- F |---|
-- G |---|
-- H |---|
-- I |---|
“B”根本不与“A”重叠。 “C”紧靠它。 {“D”,“E”,“F”,“G”}重叠。 “H”紧靠它。 “我”根本不重叠。
create table calls_nov (
sid varchar(5) primary key,
starttime timestamp not null,
endtime timestamp not null
);
insert into calls_nov values
('A', '2012-01-04 08:00:00', '2012-01-04 08:00:10'),
('B', '2012-01-04 07:50:00', '2012-01-04 07:50:03'),
('C', '2012-01-04 07:59:57', '2012-01-04 08:00:00'),
('D', '2012-01-04 07:59:57', '2012-01-04 08:00:03'),
('E', '2012-01-04 08:00:01', '2012-01-04 08:00:04'),
('F', '2012-01-04 08:00:07', '2012-01-04 08:00:10'),
('G', '2012-01-04 08:00:07', '2012-01-04 08:00:13'),
('H', '2012-01-04 08:00:10', '2012-01-04 08:00:13'),
('I', '2012-01-04 08:00:15', '2012-01-04 08:00:18');
你可以看到这样的所有重叠间隔。 (我只是使用to_char()来查看所有数据。您可以在生产中省略它。)
select t1.sid, to_char(t1.starttime, 'HH12:MI:SS'),
to_char(t1.endtime, 'HH12:MI:SS'),
t2.sid, to_char(t2.starttime, 'HH12:MI:SS'),
to_char(t2.endtime, 'HH12:MI:SS')
from calls_nov t1
inner join calls_nov t2 on (t2.starttime, t2.endtime)
overlaps (t1.starttime, t1.endtime)
order by t1.sid, t2.sid;
A 08:00:00 08:00:10 A 08:00:00 08:00:10
A 08:00:00 08:00:10 D 07:59:57 08:00:03
A 08:00:00 08:00:10 E 08:00:01 08:00:04
A 08:00:00 08:00:10 F 08:00:07 08:00:10
A 08:00:00 08:00:10 G 08:00:07 08:00:13
B 07:50:00 07:50:03 B 07:50:00 07:50:03
C 07:59:57 08:00:00 C 07:59:57 08:00:00
C 07:59:57 08:00:00 D 07:59:57 08:00:03
D 07:59:57 08:00:03 A 08:00:00 08:00:10
D 07:59:57 08:00:03 C 07:59:57 08:00:00
D 07:59:57 08:00:03 D 07:59:57 08:00:03
D 07:59:57 08:00:03 E 08:00:01 08:00:04
E 08:00:01 08:00:04 A 08:00:00 08:00:10
E 08:00:01 08:00:04 D 07:59:57 08:00:03
E 08:00:01 08:00:04 E 08:00:01 08:00:04
F 08:00:07 08:00:10 A 08:00:00 08:00:10
F 08:00:07 08:00:10 F 08:00:07 08:00:10
F 08:00:07 08:00:10 G 08:00:07 08:00:13
G 08:00:07 08:00:13 A 08:00:00 08:00:10
G 08:00:07 08:00:13 F 08:00:07 08:00:10
G 08:00:07 08:00:13 G 08:00:07 08:00:13
G 08:00:07 08:00:13 H 08:00:10 08:00:13
H 08:00:10 08:00:13 G 08:00:07 08:00:13
H 08:00:10 08:00:13 H 08:00:10 08:00:13
I 08:00:15 08:00:18 I 08:00:15 08:00:18
你可以从这张表中看到“A”应该算5,包括它自己。 “B”应该算1;它重叠,但没有其他间隔重叠。这似乎是正确的做法。
计数很简单,但就像破裂的乌龟一样。那是因为评估重叠需要做很多工作。
select t1.sid, count(t2.sid) as num_concurrent
from calls_nov t1
inner join calls_nov t2 on (t2.starttime, t2.endtime)
overlaps (t1.starttime, t1.endtime)
group by t1.sid
order by num_concurrent desc;
A 5
D 4
G 4
E 3
F 3
H 2
C 2
I 1
B 1
为了获得更好的性能,您可以在公用表表达式中使用上面的“表”,并根据进行计数 那。
with interval_table as (
select t1.sid as sid_1, t1.starttime, t1.endtime,
t2.sid as sid_2, t2.starttime, t2.endtime
from calls_nov t1
inner join calls_nov t2 on (t2.starttime, t2.endtime)
overlaps (t1.starttime, t1.endtime)
order by t1.sid, t2.sid
)
select sid_1, count(sid_2) as num_concurrent
from interval_table
group by sid_1
order by num_concurrent desc;
试试这个代替你的中间和交叉连接:
select
t1.sid,
count(1) as CountSimultaneous
from
calls_nov t1
inner join nov t2 on
t1.starttime <= t2.endtime
and t1.endtime >= t2.starttime
where
t1.starttime between '2011-11-02' and '2011-11-03'
group by
t1.sid
order by CountSimultaneous desc
我假设您想知道任何给定时间的活动呼叫数量。其他答案可以显示当前呼叫处于活动状态时有多少其他呼叫处于活动状态。对于很长时间的通话,这可以给你很高的数字。有人告诉我,从你的一个评论到其他答案,你想要的活动电话数量(此外,我还在电信公司工作)。不幸的是,我没有足够的声誉来评论这个答案,因为我创建了自己的帐户来回答这个问题。要获得活动呼叫的数量,您可以使用一个变量,该变量在呼叫开始时增加1,在结束时减少1。我已经在拥有5000多万次调用的MySQL数据库上对此进行了测试。很抱歉MySQL和pgsql之间存在任何语法差异。
我为速度添加了临时表,但只有2m行和索引,可能不需要它们。 MySQL不能两次引用同一个临时表,所以我不得不创建两个。
CREATE TEMPORARY TABLE a
SELECT sid, StartTime, EndTime
FROM calls_nov
WHERE StartTime between '2011-11-02' and '2011-11-03';
CREATE TEMPORARY TABLE b
SELECT *
FROM a;
SET @i := 0;
SELECT *, @i := @i + c.delta AS concurrent
FROM (
SELECT StartTime AS time, 1 AS delta
FROM a
UNION ALL
SELECT EndTime AS time, -1 AS delta
FROM b
ORDER BY time
) AS c
ORDER BY concurrent DESC
;
内部SELECT返回两列。时间列包括原始表中的每个StartTime和每个EndTime(行数的两倍),delta列为+1或-1,具体取决于在“time”中放入的列。该集按时间排序,然后我们可以在外部SELECT中迭代。
不像你在查询中那样“ORDER BY concurrent DESC”,我会使用一个额外的外部SELECT,我可以获得MAX,MIN等值,我也可以GROUP BY日期,小时等。这部分查询(ORDER)通过并发DESC),我实际上没有测试。我使用自己的建议和一个额外的外部查询,因为当在同一个SELECT中设置的变量进行排序时,ORDER BY在MySQL中没有按预期执行。它通过变量的先前值来命令。如果你绝对需要通过并发调用来订购(并且pgsql有相同的问题),我相信你可以通过再次使用额外的外部SELECT并在那里订购来解决这个问题。
我跑的查询非常快!它扫描每个临时表一次,然后一次扫描两次(每行数据较少),对于我自己的版本,通过一个额外的外部查询再次扫描组合,然后对其进行分组。 每张桌子只扫描一次!这一切都将在RAM中完成 如果您的配置和硬件允许它。如果没有,其他答案(或问题)将对您有所帮助。