如何使用SQL将事件归因于流量日志中的访问源?

How to use SQL to attribute events to visit source in traffic logs?

我正在尝试将事件归因于流量日志中的来源。对于任何半精明的技术营销人员或网站管理员来说,这似乎是相当基本的领域,但我在谷歌上搜索了一下,令人惊讶的是,似乎没有人涵盖这一点。

背景: 我正在使用 T-SQL 我们 运行 在 SQL Server 2016 上。我的事件日志 ( HAProxy) 看起来像这样,其中 RefererHost 对于内部推荐是 '%mysite%',但对于外部推荐(入口)可以是其他任何东西。

User Agent ||     IP     ||  RefererHost ||     Event     ||   CreationDate
------------------------------------------------------------------------------
qwertyuiop || 99.99.99.9 ||   google.com ||   Home/View   || 2015-05-29 00:00:25
------------------------------------------------------------------------------
qwertyuiop || 99.99.99.9 ||   mysite/x   ||   Home/View   || 2015-05-29 00:00:27
------------------------------------------------------------------------------
abcdefghij || 11.11.11.1 ||   yahoo.com  ||   Home/View   || 2015-05-29 00:00:49
------------------------------------------------------------------------------
qwertyuiop || 99.99.99.9 ||   mysite/y   ||     Submit    || 2015-05-29 00:01:28
------------------------------------------------------------------------------
abcdefghij || 11.11.11.1 ||   mysite/p   ||   Photo/View  || 2015-05-29 00:02:04
------------------------------------------------------------------------------
abcdefghij || 11.11.11.1 ||   mysite/n   ||     Submit    || 2015-05-29 00:02:09

目标: 我试图将所有 Submit 事件与其入口相关联。汇总后,结果将如下所示:

RefererHost || SubmitCount  ||
------------------------------
google.com  ||      1       || 
yahoo.com   ||      1       || 

复杂因素:但这是一个非常简单的例子。事实上,单个用户每个周期可以访问多次,并且每个访问(会话)他们可以 Submit 多次。此外,用户在进入后可能会长时间闲置:Submit 可能会在进入后数小时内发生。

所以我 认为 我想做的是 select 对于所有 CreationDates 事件 = Submit 和用户 ( IP + UA 校验和),然后找到最近的先前事件,其中 = RefererHost 不是 '%mysite%',并将其存储...与该 Submit 事件关联的某个位置。然后我可以计算 Submit 个事件,按 RefererHost 分组以获得我要查找的内容。

这种方法对我来说有些意义,但我不知道如何编写 "looks back" 的查询来查找最近的先前引用者。另外,我不确定 SQL 是否可以在操作超时的情况下单独处理这个问题。而且我不确定我是否遗漏了一个边缘案例。以前有人做过这样的事吗?

如果您正在使用具有 window 函数的数据库,您可以使用相当短的查询来完成此操作。如果您想在实时数据上修改此查询,您还可以看到此查询的工作示例(带有一些虚拟数据):https://modeanalytics.com/benn/reports/9f72b24dce58/query

其中的每一步都分解为一个通用的 table 表达式。虽然这使描述更容易,但如果您更喜欢这种风格,则可以将查询编写为一系列子查询。

第一步:我做了你的table。

WITH event_table AS (
    SELECT user_id AS dummy_ip,
           occurred_at,
           location AS dummy_referer,
           event_name
      FROM tutorial.playbook_events 
)

我的示例数据没有完全映射到您的示例,但这创建了一个大致相同的 table。我将 user_id 映射到 ip_address,因为这两个字段在概念上是相同的。 locationreferer 彼此完全没有关系,但它们都是与每个事件关联的事件属性。我的数据中有一个 location 字段,所以我使用了它。我想可以把它想象成物理推荐人之类的。

第 2 步:确定自上次事件以来的时间。

with_last_event AS (
    SELECT *,
           LAG(occurred_at,1) OVER (PARTITION BY dummy_ip ORDER BY occurred_at) AS last_event
      FROM event_table
)

此处的 LAG 函数查找该 IP 上最后一个事件的时间。如果没有最后一个事件,则为空。

第 3 步:找出哪些事件标志着新会话的开始。

with_new_session_flag AS (
    SELECT *,
           CASE WHEN EXTRACT('EPOCH' FROM occurred_at) - EXTRACT('EPOCH' FROM last_event) >= (60 * 10) OR last_event IS NULL 
                THEN 1 ELSE 0 END AS is_new_session,
           CASE WHEN EXTRACT('EPOCH' FROM occurred_at) - EXTRACT('EPOCH' FROM last_event) >= (60 * 10) OR last_event IS NULL 
                THEN dummy_referer ELSE NULL END AS first_referer
      FROM with_last_event
)

大多数平台将新会话定义为一段时间不活动后的操作。第一个 case 语句通过查找自上一个事件以来已经过了多长时间来做到这一点。如果它比您选择的时间长(在本例中为 60 秒 * 10,即 10 分钟),则该事件将被标记为新会话中的第一个事件。它标有 1;非第一事件用 0 标记。

第二个 case 语句找到相同的事件,但它没有用 1 标记该事件以将其标记为新会话,而是 return 引用者。如果不是新会话,则 returns null.

第 4 步:创建会话 ID。

with_session_ids AS (
    SELECT *,
           SUM(is_new_session) OVER (ORDER BY dummy_ip, occurred_at) AS global_session_id,
           SUM(is_new_session) OVER (PARTITION BY dummy_ip ORDER BY occurred_at) AS user_session_id
      FROM with_new_session_flag
)

这些 window 函数产生 运行 总会话标志(新会话时为 1 的列,不是新会话时为 0 的列)。结果是一个列在会话未更改时保持不变,并在每次新会话开始时递增 1。根据您如何划分和排序此 window 函数,您可以创建对该用户唯一且全局唯一的会话 ID。

第 5 步:查找原始会话 referer。

with_session_referer AS (
    SELECT *,
           MAX(first_referer) OVER (PARTITION BY global_session_id) AS session_referer
      FROM with_session_ids
)

最后的 window 函数查找 first_refererMAXglobal_session_id。由于该列对于除该会话的第一个事件以外的每个值都设为 null,因此对于该会话中的每个事件,这将 return 该会话的 first_referer

第 6 步:数数。

SELECT session_referer,
       COUNT(1) AS total_events,
       COUNT(DISTINCT global_session_id) AS distinct_sessions,
       COUNT(DISTINCT dummy_ip) AS distinct_ips
  FROM with_session_referer
 WHERE event_name = 'send_message'
 GROUP BY 1

最后一步很简单 - 将您的事件过滤为您关心的事件(Submit,在您的示例中)。然后通过 session_referer 计算事件数,这是发生该事件的会话的第一个 referer。通过计算 global_session_iddummy_ip,您还可以找到会话如何产生该事件,以及有多少不同的 IP 记录了该事件。