关于sql group的问题

时间:2009-01-06 08:57:32

标签: sql database postgresql

我有一个名为 visit 的表,如下所示:

id | visitor_id | visit_time 
-------------------------------------
 1 |          1 | 2009-01-06 08:45:02 
 2 |          1 | 2009-01-06 08:58:11
 3 |          1 | 2009-01-06 09:08:23 
 4 |          1 | 2009-01-06 21:55:23
 5 |          1 | 2009-01-06 22:03:35

我想制定一个sql,它可以获取用户在一个会话中访问的次数(连续访问的间隔小于1小时)。

因此,对于示例数据,我想获得以下结果:

visitor_id | count
-------------------
         1 |     3
         1 |     2
BTW,我使用postgresql 8.3。 谢谢!

更新:更新了示例数据表中的时间戳。抱歉混淆。
UPDATE :如果解决方案是单个sql查询,使用存储过程,子查询等,我不在乎。我只关心如何完成它:)

7 个答案:

答案 0 :(得分:1)

这个问题有点含糊不清,因为你正在做出假设,或者要求小时数从一个设定点开始,即自然查询也会表明所有的结果都有(1,2)的结果记录。在08:58和09:58之间的访问。您必须“告诉”您的查询,开始时间是针对某些可确定的原因访问1和4,或者您将获得自然结果集:

visitor_id | count 
--------------------
         1 | 3
         1 | 2 <- extra result starting at visit 2
         1 | 1 <- extra result starting at visit 3
         1 | 2
         1 | 1 <- extra result starting at visit 5

这个额外的逻辑对于我今天早上脆弱的头脑而言将是昂贵且复杂的,在postgres上比我更好的人可以解决这个问题。

我通常希望通过在表格中有一个sessionkey列来解决这个问题,因为出于性能原因我可以便宜地分组,但我认为这也是一个逻辑问题。从时间中获取会话信息对我来说似乎很危险,因为我不相信用户在一小时活动之后肯定会被注销。大多数会话系统通过在不活动一段时间后使会话到期,即很可能在9:45之后的访问将在同一会话中,因为您的每小时时段将重置为9:08。

答案 1 :(得分:1)

问题似乎有点模糊。

它变得更复杂,因为id 3在id 1和2的一小时内,但如果用户在9:50访问那么那将是在1小时内但不是1小时。

你似乎完成了一个平滑的总数 - 对于一次特定的访问,在接下来的一小时内有多少次访问?

也许您应该询问访问不到一小时的访问次数是多少?如果访问距离前一个小时不到一个小时,那么它应该“计数”吗?

所以你可能想要的是你有多少链,其中链接少于任意数量(因此假设的9:50访问将包含在以id 1开头的链中)。

答案 2 :(得分:1)

没有简单的解决方案

在单个SQL语句中无法执行此操作 以下是2个想法:一个使用循环来计算访问次数,另一个使用循环来填充visiting表。

循环解决方案

但是,循环可以毫不费力地完成 (我试图让postgresql语法正确,但我不是专家)

/* find entries where there is no previous entry for */ 
/* the same visitor within the previous hour:        */ 

select v1.* , 0 visits 
into temp_table
from visiting v1
where not exists ( select 1 
                   from   visiting v2
                   where  v2.visitor_id = v1.visitor_id 
                   and    v2.visit_time < v1.visit_time 
                   and    v1.visit_time - interval '1 hour' <     v2.visit_time 
                 )  
select @rows = @@rowcount 

while @rows > 0 
begin
    update temp_table
    set    visits = visits + 1 , 
           last_time = v.visit_time 
    from   temp_table t , 
           visiting   v 
    where  t.visitor_id = v.visitor_id 
    and    v.visit_time - interval '1 hour' < t.last_time
    and    not exists ( select 1 
                        from   visiting v2 
                        where  v2.visitor_id = t.visitor_id 
                        and    v2.visit_time between t.last_time and v.visit_time 
                      ) 

    select @rows = @@rowcount 
end

/* get the result: */ 

select visitor_id, 
       visits 
from temp_table 

这里的想法是这样做:

  • 在一小时内没有事先访问的所有访问。
    • 这标识了会话
  • 循环,为每次“首次访问”进行下一次访问
    • 直到没有“下一次访问”
  • 现在您可以读取每个会话中的访问次数。

最佳解决方案?

我建议:

  • visiting表格中添加一列:session_id int not null
  • 更改进行输入的过程,以便检查当前访问者之前的访问是否不到一小时。如果是,则将 session_id 设置为与之前访问的 session id 相同。如果没有,则会生成新的 session_id
  • 您可以将此逻辑置于触发器中。

然后您的原始查询可以通过以下方式解决:

SELECT session_id, visitor_id, count(*)
FROM   visiting 
GROUP BY session_id, visitor_id

希望这会有所帮助。如果我犯了错误(我确信我有错误),请发表评论我会纠正错误。

答案 3 :(得分:1)

PostgreSQL 8.4将有一个窗口函数,到那时我们可以消除创建临时表只是为了模拟rownumbers(序列目的)

create table visit
(
visitor_id int not null,
visit_time timestamp not null
);




insert into visit(visitor_id, visit_time) 
values
(1, '2009-01-06 08:45:02'),
(2, '2009-02-06 08:58:11'),
(1, '2009-01-06 08:58:11'),
(1, '2009-01-06 09:08:23'),
(1, '2009-01-06 21:55:23'),
(2, '2009-02-06 08:59:11'),
(2, '2009-02-07 00:01:00'),
(1, '2009-01-06 22:03:35');




create temp table temp_visit(visitor_id int not null, sequence serial not null, visit_time timestamp not null);
insert into temp_visit(visitor_id, visit_time) select visitor_id, visit_time from visit order by visitor_id, visit_time;


select 
    reference.visitor_id, count(nullif(reference.visit_time - prev.visit_time < interval '1 hour',false))
from temp_visit reference
left join temp_visit prev 
on prev.visitor_id = reference.visitor_id and prev.sequence = reference.sequence - 1
group by reference.visitor_id;

答案 4 :(得分:0)

其中一个或两个可能有用吗?但是,两者最终会在结果中为您提供比您要求的更多列。

SELECT visitor_id,
       date_part('year', visit_time),
       date_part('month', visit_time),
       date_part('day', visit_time),
       date_part('hour', visit_time),
       COUNT(*)
  FROM visiting
 GROUP BY 1, 2, 3, 4, 5;


SELECT visitor_id,
       EXTRACT(EPOCH FROM visit_time)-(EXTRACT(EPOCH FROM visit_time) % 3600),
       COUNT(*)
  FROM visiting
 GROUP BY 1, 2;

答案 5 :(得分:0)

这不能在单个SQL中完成。 更好的选择是在存储过程中处理它

答案 6 :(得分:0)

如果是T-SQL,我会写一些东西:

SELECT  visitor_id, COUNT(id), 
        DATEPART(yy, visit_time), DATEPART(m, visit_time), 
        DATEPART(d, visit_time), DATEPART(hh, visit_time)
FROM visiting
GROUP BY
    visitor_id, 
    DATEPART(yy, visit_time), DATEPART(m, visit_time), 
    DATEPART(d, visit_time), DATEPART(hh, visit_time)

给了我:

1   3   2009    1   6   8
1   2   2009    1   6   21

我不知道如何或者你是否可以在postgre中写这个。