【问题标题】:Count number of rows that are not within 10 seconds of each other计算彼此不在 10 秒内的行数
【发布时间】:2011-04-01 11:27:16
【问题描述】:

我跟踪网络访问者。我存储了 IP 地址以及访问的时间戳。

ip_address    time_stamp
180.2.79.3  1301654105
180.2.79.3  1301654106
180.2.79.3  1301654354
180.2.79.3  1301654356
180.2.79.3  1301654358
180.2.79.3  1301654366
180.2.79.3  1301654368
180.2.79.3  1301654422

我有一个查询要获取总曲目:

SELECT COUNT(*) AS tracks FROM tracking

但是,我现在想忽略在每次访问后 10 秒内多次访问的用户的访问。由于我不认为这是另一次访问,因此它仍然是第一次访问的一部分。

当ip_address相同时,检查 时间戳,只计算那些行 距离每个 10 秒 其他。

我在将其放入 SQL 查询表单时遇到了困难,我将不胜感激!

【问题讨论】:

  • 如果我有 23,24,25,26,27,28,29,30,31,32,33,34,35 的条目,这算作两次访问还是一次?跨度>
  • 我似乎将其计为 2 次访问,由 cmets 回答。但这对我来说似乎很陌生。
  • 为了让我开始,我需要回答@MatthieuF 的问题。这是理解需求的关键答案。没有它,我什至无法投票给现有的答案。

标签: mysql sql


【解决方案1】:

让我从这张桌子开始。我将使用普通的时间戳,以便我们可以轻松查看发生了什么。

180.2.79.3   2011-01-01 08:00:00
180.2.79.3   2011-01-01 08:00:09
180.2.79.3   2011-01-01 08:00:20
180.2.79.3   2011-01-01 08:00:23
180.2.79.3   2011-01-01 08:00:25
180.2.79.3   2011-01-01 08:00:40
180.2.79.4   2011-01-01 08:00:00
180.2.79.4   2011-01-01 08:00:13
180.2.79.4   2011-01-01 08:00:23
180.2.79.4   2011-01-01 08:00:25
180.2.79.4   2011-01-01 08:00:27
180.2.79.4   2011-01-01 08:00:29
180.2.79.4   2011-01-01 08:00:50

如果我理解正确的话,你想这样计算这些。

180.2.79.3   3
180.2.79.4   3

您可以通过选择两者的最大时间戳来为每个 ip_address 执行此操作

  • 大于当前行的 时间戳,以及
  • 小于或等于比当前行的时间戳大 10 秒。

将这两个标准结合起来会引入一些空值,结果证明它们非常有用。

select ip_address, 
       t_s.time_stamp, 
       (select max(t.time_stamp) 
        from t_s t 
        where t.ip_address = t_s.ip_address 
          and t.time_stamp > t_s.time_stamp
          and t.time_stamp - t_s.time_stamp <= interval '10' second) next_page
from t_s 
group by ip_address, t_s.time_stamp
order by ip_address, t_s.time_stamp;

ip_address   time_stamp            next_page
180.2.79.3   2011-01-01 08:00:00   2011-01-01 08:00:09
180.2.79.3   2011-01-01 08:00:09   <null>
180.2.79.3   2011-01-01 08:00:20   2011-01-01 08:00:25
180.2.79.3   2011-01-01 08:00:23   2011-01-01 08:00:25
180.2.79.3   2011-01-01 08:00:25   <null>
180.2.79.3   2011-01-01 08:00:40   <null>
180.2.79.4   2011-01-01 08:00:00   <null>
180.2.79.4   2011-01-01 08:00:13   2011-01-01 08:00:23
180.2.79.4   2011-01-01 08:00:23   2011-01-01 08:00:29
180.2.79.4   2011-01-01 08:00:25   2011-01-01 08:00:29
180.2.79.4   2011-01-01 08:00:27   2011-01-01 08:00:29
180.2.79.4   2011-01-01 08:00:29   <null>
180.2.79.4   2011-01-01 08:00:50   <null>

标记访问结束的时间戳对于它自己的 next_page 有一个空值。那是因为没有时间戳小于或等于该行的 time_stamp + 10 秒。

要获得计数,我可能会创建一个视图并计算空值。

select ip_address, count(*)
from t_s_visits 
where next_page is null
group by ip_address

180.2.79.3   3
180.2.79.4   3

【讨论】:

  • 这里有一个问题:如果你有一个 23,24,25,26,27,28,29,30,31,32,33,34 的条目会发生什么?这是两次访问,但您算作一次。
  • 我认为 OP 希望将其计为一次访问;日志条目之间的 10 秒间隔表示第二次访问。在此网页上搜索“我没想到”。这是 OP 的评论澄清了这一点。
  • 我不确定他所说的是否算作澄清 :-) 如果他确实希望将其计为一次访问,那么我认为您的 SQL 是正确的。
  • 如果您考虑一下网络访问的含义,那是有道理的。访问者点击一页或多页,停止点击页面 'n' 秒,然后再次开始点击页面。如今,“访问”的整个概念很模糊。我已经在浏览器的标签页中打开了两三天。网络服务器无法判断我是否或何时正在阅读它们。
  • 感谢 Catcall - 这就是我想要的。 :)
【解决方案2】:

您可以通过添加WHEREclause 将跟踪表JOIN 过滤掉自己不需要的记录。

SELECT  t1.ip_address
        , COUNT(*) AS tracks
FROM    tracking t1
        LEFT OUTER JOIN tracking t2 ON t2.ip_address = t1.ip_address
                                       AND t2.time_stamp < t1.time_stamp + 10
WHERE   t2.ip_adress IS NULL
GROUP BY
        t1.ip_address

编辑

以下脚本在 SQL Server 中有效,但我无法在单个 SQL 语句中表达它,更不用说将其转换为 MySQL。它可能会给你一些关于需要什么的指示。

注意:我假设对于给定的输入,应该选择数字 1 和 11。

;WITH q (number) AS (
  SELECT 1
  UNION ALL SELECT 2
  UNION ALL SELECT 10
  UNION ALL SELECT 11  
  UNION ALL SELECT 12
)
SELECT  q1.Number as n1
        , q2.Number as n2
        , 0 as Done
INTO    #Temp
FROM    q q1
        LEFT OUTER JOIN q q2 ON q2.number < q1.number + 10
                                AND q2.number > q1.number

DECLARE @n1 INTEGER
DECLARE @n2 INTEGER

WHILE EXISTS (SELECT * FROM #Temp WHERE Done = 0)
BEGIN

  SELECT  TOP 1 @n1 = n1
          , @n2= n2
  FROM    #Temp
  WHERE   Done = 0

  DELETE  FROM #Temp
  WHERE   n1 = @n2

  UPDATE  #Temp 
  SET     Done = 1
  WHERE   n1 = @n1 
          AND n2 = @n2         
END        

SELECT  DISTINCT n1 
FROM    #Temp

DROP TABLE #Temp

【讨论】:

  • tracks 表目前大约有 70000 行,并且会增长得更多。就性能而言,加入本身会很昂贵吗?并不是说我能想出更好的查询!
  • @Lieven - 什么错误?您的意思是ip_address 列的错字?
  • @Abs,甚至没有看到。不,我认为它不会返回你需要的东西。您可以在数据子集上进行验证吗?
  • @Lieven - 我对其进行了测试,并在 on 子句 AND site_id = 25 中添加了一个条件。当我运行这个查询时,它花了一段时间,phpmyadmin 报告 mysql 已经消失了!然后我只尝试了上面的查询,它花了一段时间又消失了!我测试了其他查询,以防这是我的设置,但它们运行良好。
  • @Abs,你的桌子上有什么指数?如果还没有,(ip_address, time_stamp) 上的索引应该会使这个自连接更快。
【解决方案3】:

最简单的方法是将时间戳除以 10,然后计算这些值和 ip_address 值的不同组合。这样,每个 10 秒的周期都将单独计算。

如果您在示例数据上运行它,它将为您提供 4 个音轨,这就是您想要的。

试一试,看看它是否在您的完整数据集上为您提供了所需的结果:

SELECT COUNT(DISTINCT ip_address, FLOOR(time_stamp/10)) AS tracks 
FROM tracking

【讨论】:

  • 我认为上述方法行不通。假设有第 19 秒和第 21 秒的日志。如果你把它放在地板上,它将是 10 和 20 并且它们都将被包括在内,即使它们不应该被包括在内,因为它们相隔 2 秒。
  • 这一切都取决于你的要求,我做了一些假设,因为你没有为你的样本数据集指定你想要的输出。如果您有第 9、18、27、36、45 和 54 秒的日志怎么办。你会将这 6 个事件算作一个事件,因为每个事件都在 10 秒内?
  • 有趣,我没想到。我希望如果我得到 (12th), (25th, 26th, 27th), (40th)。那应该是3。它更复杂!我该怎么办?
  • 尽管如此,作为一种获得 initial 减少候选列表的方法,然后使用“适当的”左连接进一步减少,这样做仍然可能会带来性能优势.
  • +1 用于不需要自我加入的创意解决方案。如果你使用 ROUND(time_stamp / 10,0) 你已经很接近了。
【解决方案4】:

对相同ip和关闭时间的记录进行左连接,过滤掉匹配的记录:

select count(*) as visits
from (
  select t.ip_address
  from tracking t
  left join tracking t2
    on t2.ip_address = t.ip_address
    and t2.timestamp > t.timestamp and t2.timestamp <= t.timestamp + 10
  where t2.ip_address is null
) x

【讨论】:

  • 我试过上面的,对于上面的 ip_addresses 和时间戳,我得到了 8?不应该是4吗?顺便说一句,我更改了查询并为第二个选择语句添加了t.ip_address
  • @Abs:我更正了查询。您是正确的,所选字段需要一个表说明符。此外,查询没有过滤掉匹配的记录,正如描述所说,它做了相反的事情,所以它只计算重访次数,并且多次计算。现在查询应该与描述匹配。
  • @Guffa - 感谢您的更新。我不确定我的 mysql 设置或查询是否有问题。每次我运行它时,就像@liven 的查询一样,mysql 似乎在想了一会儿之后就消失了!顺便说一句,在哪里放置这个条件AND site_id = 20 的最佳位置,它应该在on 子句中减少正在调查的行数?
  • @Abs:这种行为对于任何查询都不正常,你的软件一定有问题,可能是你的 GUI。您可以在where 条件和on 条件中放置一个类似的条件,以便您在两次使用表时都对其进行过滤。
  • @Guffa - 很奇怪,我一直收到这个错误。但是当我将该条件放在whereon 中时,它执行得很好,但计数为 0。也许这个查询死了,因为它加入了一个大表?
【解决方案5】:

与 SQL 一样,您的问题有很多解决方案。 我会使用以下简单且应该“足够好”的查询:

SELECT COUNT(*) AS tracks 
FROM (
    SELECT ip_address 
    FROM tracking 
    GROUP BY ip_address, FLOOR(time_stamp / 10)
)

子查询以10s为间隔对单个用户的访问进行分组,计为一次访问。

当然,即使两次访问之间的间隔小于 10 秒,也可能会发现两次访问会出现在不同的 10 秒窗口中。消除这种情况需要更复杂的逻辑,而且这种增加的复杂性的分析价值是值得怀疑的(无论如何,10 秒的间隔听起来像是一个任意值)。

【讨论】:

    【解决方案6】:
    Select Z.IP, Count(*) As VisitCount
    From    (
            Select V.IP
            From visitors As V
                Left Join visitors As V2
                    On V2.IP = V.IP
                        And V2.time_stamp > V.time_stamp
            Group By V.IP, V.time_stamp
            Having (Min(V2.time_stamp) - V.time_stamp) >= 10
            ) As Z
    Group By Z.IP
    

    这会将下一个条目超过 10 秒的任何访问计为新访问。

    【讨论】:

      【解决方案7】:

      如果在前 10 秒内没有来自同一 IP 地址的先前记录,则以下逻辑仅将访问计为“唯一访问”。

      这意味着 {1,11,21,32,42,52,62,72} 将计为 2 次访问,分别有 3 和 5 个跟踪。

      它通过首先识别唯一访问来实现这一点。然后它会计算在该唯一访问和下一次唯一访问之间发生的所有访问。

      WITH
          unique_visits
      (
        SELECT
          ip_address, time_stamp
        FROM
          visitors
        WHERE
          NOT EXISTS (SELECT * FROM visitors AS [previous]
                      WHERE ip_address  = visitors.ip_address
                        AND time_stamp >= visitors.timestamp - 10
                        AND time_stamp <  visitors.timestamp)
      )
      SELECT
        unique_visitors.ip_address,
        unique_visitors.time_stamp,
        COUNT(*) AS [total_tracks]
      FROM
        unique_visitors
      INNER JOIN
        visitors
          ON  visitors.ip_address  = unique_visitors.ip_address
          AND visitors.time_stamp >= unique_visitors.time_stamp
          AND visitors.time_stamp <  ISNULL(
                                        (SELECT MIN(time_stamp) FROM unique_visitors [next]
                                         WHERE  ip_address = unique_visitors.ip_address
                                         AND    time_stamp > unique_visitors.ip_address)
                                        , visitors.time_stamp + 1
                                     )
      

      您还需要 (ip_address, time_stamp) 上的索引或主键

      【讨论】:

        【解决方案8】:

        为了咯咯笑,这里有一个 UPDATE hack 可以满足您的需要。不执行此操作的原因有很多,包括但不限于它可能有一天会停止工作。无论如何,假设您的表格最初是按 ip -> timestamp 排序的,这应该(通常)给您正确的答案。同样,这是为了完整性,如果你实现了这个,请看up the risks beforehand

        CREATE TABLE #TestIPs
        (
            ip_address varchar(max),
            time_stamp decimal(12,0),
            cnt int
            )
        
        INSERT INTO #TestIPs (ip_address, time_stamp)
        SELECT '180.2.79.3',  1301654105 UNION ALL
        SELECT '180.2.79.3',  1301654106 UNION ALL
        SELECT '180.2.79.3',  1301654354 UNION ALL
        SELECT '180.2.79.3',  1301654356 UNION ALL
        SELECT '180.2.79.3',  1301654358 UNION ALL
        SELECT '180.2.79.3',  1301654366 UNION ALL
        SELECT '180.2.79.3',  1301654368 UNION ALL
        SELECT '180.2.79.3',  1301654422 UNION ALL
        SELECT '180.2.79.4',  1301654105 UNION ALL
        SELECT '180.2.79.4',  1301654106 UNION ALL
        SELECT '180.2.79.4',  1301654354 UNION ALL
        SELECT '180.2.79.4',  1301654356 UNION ALL
        SELECT '180.2.79.4',  1301654358 UNION ALL
        SELECT '180.2.79.4',  1301654366 UNION ALL
        SELECT '180.2.79.4',  1301654368 UNION ALL
        SELECT '180.2.79.4',  1301654422
        
        DECLARE @count int; SET @count = 0
        DECLARE @ip varchar(max); SET @ip = 'z'
        DECLARE @timestamp decimal(12,0); SET @timestamp = 0;
        
        UPDATE #TestIPs
            SET @count = cnt = CASE WHEN time_stamp - @timestamp > 10 THEN @count + 1 ELSE CASE WHEN @ip <> ip_address THEN 1 ELSE @count END END,      
                @timestamp = time_stamp,
                @ip = ip_address
        
        
                SELECT ip_address, MAX(cnt) AS 'Visits' FROM #TestIPs GROUP BY ip_address
        

        结果:

        ip_address  Visits
        ------------ -----------
        180.2.79.3  3
        180.2.79.4  3
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多