【问题标题】:a question about sql group by关于sql group by的一个问题
【发布时间】:2010-09-29 18:35:04
【问题描述】:

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

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

顺便说一句,我使用 postgresql 8.3。 谢谢!

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

【问题讨论】:

    标签: sql database postgresql


    【解决方案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。

    【讨论】:

    • 是的,“一小时内”有点难以解释,但根据给出的示例,他们正在寻找“一小时内”。
    • 坦率地说,我认为从时间戳中提取会话是一个失败的提议,因此我的 sessionkey 列声明
    • "on the hour"不是我想要的,我只是修改了示例数据。
    【解决方案2】:

    这个问题似乎有点模糊。

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

    您似乎是在平滑总数之后 - 对于给定的访问,接下来的一小时内有多少次访问?

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

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

    【讨论】:

    • 是的,你明白我的意思,这正是我想要的。用sql好像很难做,我在考虑annakata提到的sessionkey方法...
    • unsliced 在描述方面比我更好:) - 是的,我认为如果可以的话,用你需要的数据为数据库播种比事后推导出数据要好得多
    【解决方案3】:

    没有简单的解决方案

    没有办法在单个 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
    

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

    【讨论】:

    • 因为第一种方法的sql查询复杂,难以维护和扩展,所以我更喜欢第二种方法。谢谢!
    【解决方案4】:

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

    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;
    

    【讨论】:

      【解决方案5】:

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

      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;
      

      【讨论】:

      • 我不会声称其中任何一个都非常有效,但我相信它们应该可以完成工作。
      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

        如果是 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 中写这个。

        【讨论】:

          猜你喜欢
          • 2023-03-08
          • 2021-12-24
          • 1970-01-01
          • 2010-12-18
          • 1970-01-01
          • 2011-06-11
          • 1970-01-01
          • 1970-01-01
          • 2018-05-13
          相关资源
          最近更新 更多