【问题标题】:Finding consecutive attendance for a series of events寻找一系列活动的连续出席人数
【发布时间】:2018-07-25 17:49:18
【问题描述】:

我试图找到一个SQL 唯一的解决方案来解决与计算连续活动出席率相关的问题。这些事件发生在不同的日子,所以我不能使用任何顺序日期方法来确定连续出席。要计算一个人的连续出席率,我会从最近的事件开始,然后按时回溯。我会计算该人参加的每个活动,当我遇到该人未参加的活动时,我会停下来。这使我可以计算最近连续参加的活动。目前,所有数据都托管在SQL 表中,以下是包含数据的示例模式:

USERS
ID  UserName    MinutesWatched
--- --------    --------------
1   jdoe        30
2   ssmith      400
3   bbaker      350
4   tduke       285

EVENTS
ID  Name        StartDate
--  ----------- ---------
1   1st Event   07/15/2018
2   2nd Event   07/16/2018
3   3rd Event   07/18/2018
4   4th Event   07/20/2018

ATTENDANCE
ID  User_ID Event_ID
--  ------- --------
1   1   1
2   1   2
3   1   3
4   1   4
5   2   4
6   2   3
7   3   4
8   3   2
9   3   1
10  4   4
11  4   3
12  4   2

对于我想要得到的输出:

OUTPUT
User_ID Consecutive   WatchedMinutes
------- -----------   --------------
1            4            30
2            2            400
3            1            350
4            3            285

我已经构建了 C# 代码以迭代方式执行此操作,但是当我处理 300,000 多个用户和数百个事件时它会很慢。我很想看到这个的SQL 版本。

以下是根据 Dan 的要求计算热门活动观众的方法。输出实际上只是一个列出 Top X 事件查看器的字符串。

public string GetUsersTopWatchedConsecutiveStreams(int topUserCount)
{

    string results = "Top " + topUserCount + " consecutive viewers - ";
    Dictionary<ChatUser, int> userinfo = new Dictionary<ChatUser, int>();

    using (StorageModelContext db = new StorageModelContext())
    {
        IQueryable<ChatUser> allUsers = null;
        if (mainViewModel.CurrentStream != null)
            allUsers = db.ViewerHistory.Include("Stream").Include("User").Where(x => x.Stream.Id == mainViewModel.CurrentStream.Id).Select(x => x.User);
        else
            allUsers = db.ViewerHistory.Include("Stream").Include("User").Where(x => x.Stream.Id == (db.StreamHistory.OrderByDescending(s => s.StreamEnd).FirstOrDefault().Id)).Select(x => x.User);


        foreach (var u in allUsers)
        {
            int totalStreams = 0;
            var user = db.Users.Include("History").Where(x => x.UserName == u.UserName).FirstOrDefault();
            if (user != null)
            {
                var streams = user.History;
                if (streams != null)
                {
                    var allStreams = db.StreamHistory.OrderByDescending(x => x.StreamStart);
                    foreach (var s in allStreams)
                    {
                        var vs = streams.Where(x => x.Stream == s);
                        if (vs.Count() > 0)
                            totalStreams++;
                        else
                            break;
                    }
                }
            }
            userinfo.Add(u, totalStreams);
            totalStreams = 0;
        }

        var top = userinfo.OrderByDescending(x => x.Value).ThenByDescending(x => x.Key.MinutesWatched).Take(topUserCount);
        int cnt = 1;
        foreach (var t in top)
        {
            results += "#" + cnt + ": " + t.Key + "(" + t.Value.ToString() + "), ";
            cnt++;
        }
        if (cnt > 1)
            results = results.Substring(0, results.Length - 2);

    }
    return results;
}

当没有活动正在运行时,mainViewModel.CurrentStream 为 null。当直播事件发生时,它将包含一个对象,其中包含与直播事件相关的信息。

【问题讨论】:

  • 用您正在使用的数据库标记您的问题。
  • "我已经构建了 C# 代码来执行此操作" 它必须是透明墨水,因为我看不到它。
  • 戈登 - 完成。谢谢
  • Dan - 我最初没有包含代码,因为我认为它不会增加太多价值。我已经用信息更新了帖子。

标签: c# sql sql-server


【解决方案1】:

也许你想试试这个:

事件按降序获得行号(按StartDate),用户出勤按降序获得编号StartDate。现在,连续出席的事件编号和出席人数的差异将是相同的。我使用这些差异进行分组,计算组中的出席人数并返回差异最小的组(按用户):

WITH
  evt (ID, StartDate, evt_no) AS (
    SELECT ID, StartDate,
      ROW_NUMBER() OVER (ORDER BY StartDate DESC)
    FROM EVENTS
  ),
  att ([User_ID], grp_no) AS (
    SELECT [User_ID], evt_no - 
      ROW_NUMBER() OVER (PARTITION BY [User_ID] ORDER BY StartDate DESC)
    FROM ATTENDANCE a
      INNER JOIN evt ON a.Event_ID = evt.ID
  ),
  con ([User_ID], Consecutive, rn) AS (
    SELECT [User_ID], COUNT(*),
      ROW_NUMBER() OVER (PARTITION BY User_ID ORDER BY grp_no)
    FROM att
    GROUP BY [User_ID], grp_no
  )
SELECT u.ID AS [User_ID], u.UserName, u.MinutesWatched, con.Consecutive
FROM con
  INNER JOIN USERS u ON con.[User_ID] = u.ID
WHERE con.rn = 1;

对这个查询在您的系统上运行多长时间感兴趣。

【讨论】:

  • 初步测试看起来很有希望。我需要仔细查看结果并仔细检查所有内容。从速度的角度来看,它快得吓人……大约 1 秒(或更短)。
  • 看起来就是这样!我仍在努力思考这一切是如何运作的。 SQL 是有时让我头晕目眩的事情之一。哈哈。非常感谢您!
  • 谢谢,很高兴听到这个消息! :-) 诀窍在于,“连续”出勤时,出勤和活动的编号都会一一递增。当错过一项或多项活动时,活动人数增加超过出席人数。
【解决方案2】:

您似乎想要一个人没有参加的最大活动 id,它小于该人确实参加过的最大 id。然后你要计算参加人数。

以下方法将其处理为:

  • 将用户与所有事件合并到最大事件
  • 获取匹配的最大事件
  • 带回计数为 0 的行并计数

所以,这给出了带有计数的事件:

select u.user_id,
       sum(case when a.event_id is null then e.id end) over (partition by user_id) as max_nonmatch_event_id
from (select user_id, max(event_id) as max_event_id
      from attendance 
      group by user_id
     ) u join
     events e
     on e.id <= u.max_event_id left join
     attendance a
     on a.user_id = u.id and a.event_id = e.id
order by num_nulls_gt;

另外一个子查询应该完成剩下的工作:

select u.user_id, count(*) as num_consecutive
from (select u.user_id,
       sum(case when a.event_id is null then e.id end) over (partition by user_id) as max_nonmatch_event_id
      from (select user_id, max(event_id) as max_event_id
            from attendance 
            group by user_id
           ) u join
           events e
           on e.id <= u.max_event_id left join
           attendance a
           on a.user_id = u.id and a.event_id = e.id
     ) ue
where event_id > max_nonmatch_event_id
group by user_id;

【讨论】:

  • 在深入研究结果后,我发现了一些问题,即用户显示出非常高的连续查看次数,即使他们只看到了最近的事件。我不确定是什么导致了这个问题,深入研究数据集。似乎第一个选择应该是选择 ue.user_id 而不是 u.user_id,对吗?
  • 不幸的是,这似乎不符合要求。我花了一些时间来剖析你做了什么,我不明白这会给我连续观看次数的数量。如果用户参加了过去的所有 4 场活动,我希望得到 4 分。如果用户参加了第一个活动,错过了第二个活动,然后参加了第三个和第四个活动,我希望从该用户的查询中得到 2。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-08
  • 2017-06-02
  • 2021-05-01
  • 1970-01-01
  • 2017-01-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多