在准备采访时,我遇到了一个SQL问题,我希望能够对如何更好地回答它有所了解。
给定时间戳,userid,如何确定一周内每天活跃的用户数量?
它很少,但这就是我面前的问题。
答案 0 :(得分:7)
我将基于对我最有意义的内容以及如果问题与此处相同的话我会回复的方式展示这样的想法:
首先,让我们假设一个数据集,我们将表格命名为logins
:
+---------+---------------------+
| user_id | login_timestamp |
+---------+---------------------+
| 1 | 2015-09-29 14:05:05 |
| 2 | 2015-09-29 14:05:08 |
| 1 | 2015-09-29 14:05:12 |
| 4 | 2015-09-22 14:05:18 |
| ... | ... |
+---------+---------------------+
可能还有其他专栏,但我们不介意。
首先,我们应该确定那周的边界,因为我们可以使用ADDDATE()
。结合今天的日期 - 今天的工作日(MySQL的DAYOFWEEK()
)的想法,是星期日的日期。
例如:如果今天是10日星期三,Wed - 3 = Sun
,那么10 - 3 = 7
,我们可以预期星期日是第7个。
我们可以通过这种方式获得WeekStart
和WeekEnd
个时间戳:
SELECT
DATE_FORMAT(ADDDATE(CURDATE(), INTERVAL 1-DAYOFWEEK(CURDATE()) DAY), "%Y-%m-%d 00:00:00") WeekStart,
DATE_FORMAT(ADDDATE(CURDATE(), INTERVAL 7-DAYOFWEEK(CURDATE()) DAY), "%Y-%m-%d 23:59:59") WeekEnd;
注意:在PostgreSQL中有一个DATE_TRUNC()
函数,它返回指定时间单位的开头,给定日期,例如星期开始,月,小时等。但这在MySQL中不可用。
接下来,让我们利用WeekStart和weekEnd来收集我们的数据集,在这个例子中,我将展示如何使用硬编码日期进行过滤:
SELECT *
FROM `logins`
WHERE login_timestamp BETWEEN '2015-09-29 14:05:07' AND '2015-09-29 14:05:13'
这应该返回我们的数据集切片,只有相关的结果:
+---------+---------------------+
| user_id | login_timestamp |
+---------+---------------------+
| 2 | 2015-09-29 14:05:08 |
| 1 | 2015-09-29 14:05:12 |
+---------+---------------------+
然后,我们可以将结果集简化为user_id
,并过滤掉重复项。然后以这种方式计算:
SELECT COUNT(DISTINCT user_id)
FROM `logins`
WHERE login_timestamp BETWEEN '2015-09-29 14:05:07' AND '2015-09-29 14:05:13'
DISTINCT
将过滤掉重复项,而count将只返回金额。
组合,这变为:
SELECT COUNT(DISTINCT user_id)
FROM `logins`
WHERE login_timestamp
BETWEEN DATE_FORMAT(ADDDATE(CURDATE(), INTERVAL 1- DAYOFWEEK(CURDATE()) DAY), "%Y-%m-%d 00:00:00")
AND DATE_FORMAT(ADDDATE(CURDATE(), INTERVAL 7- DAYOFWEEK(CURDATE()) DAY), "%Y-%m-%d 23:59:59")
将CURDATE()
替换为任意时间戳,以获取该周的用户登录次数。
但我需要把它打破几天,我听到你哭了。当然!这就是:
首先,让我们将过度信息化的时间戳转换为日期数据。我们添加DISTINCT
,因为我们不介意同一天的同一用户登录两次。我们统计用户,而不是登录,对吧? (注意我们回到这里):
SELECT DISTINCT user_id, DATE_FORMAT(login_timestamp, "%Y-%m-%d")
FROM `logins`
这会产生:
+---------+-----------------+
| user_id | login_timestamp |
+---------+-----------------+
| 1 | 2015-09-29 |
| 2 | 2015-09-29 |
| 4 | 2015-09-22 |
| ... | ... |
+---------+-----------------+
这个查询,我们将用一秒来换行,以便计算每个日期的出现次数:
SELECT `login_timestamp`, count(*) AS 'count'
FROM (SELECT DISTINCT user_id, DATE_FORMAT(login_timestamp, "%Y-%m-%d") AS `login_timestamp` FROM `logins`) `loginsMod`
GROUP BY `login_timestamp`
我们使用计数和分组来按日期获取列表,返回:
+-----------------+-------+
| login_timestamp | count |
+-----------------+-------+
| 2015-09-29 | 1 +
| 2015-09-22 | 2 +
+-----------------+-------+
经过艰苦的努力,两者结合在一起:
SELECT `login_timestamp`, COUNT(*)
FROM (
SELECT DISTINCT user_id, DATE_FORMAT(login_timestamp, "%Y-%m-%d") AS `login_timestamp`
FROM `logins`
WHERE login_timestamp BETWEEN DATE_FORMAT(ADDDATE(CURDATE(), INTERVAL 1- DAYOFWEEK(CURDATE()) DAY), "%Y-%m-%d 00:00:00") AND DATE_FORMAT(ADDDATE(CURDATE(), INTERVAL 7- DAYOFWEEK(CURDATE()) DAY), "%Y-%m-%d 23:59:59")) `loginsMod`
GROUP BY `login_timestamp`;
本周将每天为您提供每日登录信息。再次,替换CURDATE()
以获得不同的一周。
对于登录的用户自己,让我们以不同的顺序组合相同的东西:
SELECT `user_id`
FROM (
SELECT `user_id`, COUNT(*) AS `login_count`
FROM (
SELECT DISTINCT `user_id`, DATE_FORMAT(`login_timestamp`, "%Y-%m-%d")
FROM `logins`) `logins`
GROUP BY `user_id`) `logincounts`
WHERE `login_count` > 6
我有两个内部查询,第一个是logins
:
SELECT DISTINCT `user_id`, DATE_FORMAT(`login_timestamp`, "%Y-%m-%d")
FROM `logins`
将提供用户列表以及他们登录的日期,没有重复。
然后我们有logincounts
:
SELECT `user_id`, COUNT(*) AS `login_count`
FROM `logins` -- See previous subquery.
GROUP BY `user_id`) `logincounts`
将返回相同的列表,并计算每个用户登录的次数。
最后:
选择user_id
FROM logincounts
- 参见前面的子查询。
在哪里login_count
> 6
过滤我们未登录7次的人,并删除日期列。
这种方式很长,但我认为它充满了想法,我认为它肯定有助于在工作面试中以有趣的方式回答。 :)
答案 1 :(得分:3)
create table fbuser(id integer, date date);
insert into fbuser(id,date)values(1,'2012-01-01');
insert into fbuser(id,date)values(1,'2012-01-02');
insert into fbuser(id,date)values(1,'2012-01-01');
insert into fbuser(id,date)values(1,'2012-01-01');
insert into fbuser(id,date)values(1,'2012-01-01');
insert into fbuser(id,date)values(1,'2012-01-01');
insert into fbuser(id,date)values(1,'2012-01-02');
insert into fbuser(id,date)values(1,'2012-01-03');
insert into fbuser(id,date)values(1,'2012-01-04');
insert into fbuser(id,date)values(1,'2012-01-05');
insert into fbuser(id,date)values(1,'2012-01-06');
insert into fbuser(id,date)values(1,'2012-01-07');
insert into fbuser(id,date)values(4,'2012-01-08');
insert into fbuser(id,date)values(4,'2012-01-08');
insert into fbuser(id,date)values(1,'2012-01-08');
insert into fbuser(id,date)values(1,'2012-01-09');
select * from fbuser;
id | date
----+------------
1 | 2012-01-01
1 | 2012-01-02
1 | 2012-01-01
1 | 2012-01-01
1 | 2012-01-01
1 | 2012-01-01
1 | 2012-01-02
1 | 2012-01-03
1 | 2012-01-04
1 | 2012-01-05
1 | 2012-01-06
1 | 2012-01-07
2 | 2012-01-07
3 | 2012-01-07
4 | 2012-01-07
4 | 2012-01-08
4 | 2012-01-08
1 | 2012-01-08
1 | 2012-01-09
select id,count(DISTINCT date) from fbuser
where date BETWEEN '2012-01-01' and '2012-01-07'
group by id having count(DISTINCT date)=7
id | count
----+-------
1 | 7
(1 row)
查询计算用户在给定时间段内登录的唯一日期,并返回7次出现的id。如果您的日期也有时间,可以使用date_format。
答案 2 :(得分:0)
使用以下数据:userid
和timestamp
;如何计算"活跃用户的数量" 一周中的每一天?
问题当然是根本没有登录,或者一周中某些日子都没有登录,因此这种要求的基本解决方案是必须有一系列日期比较登录。
有多种方法可以生成一周的日期,所选择的方法取决于两个主要因素:
如果我需要定期这样做(我认为这是真的)那么我会创建一个"日历表"每天一行,合理的一段时间(比如10年),只有大约3652行,其主键为日期列。在此表中,我们还可以存储" weeknumber"使用week()
函数,这使得逐周报告变得更简单(我们也可以在此表中添加其他列)。
因此,假设我已经构建了包含每个日期和周数的日历表,那么我们可以从今天的日期算起一周的数字,减去1,并收集所需的登录数据,如下所示:
select
c.caldate, count(distinct l.userid) as user_logins
from calendar_table as c
left join login_table l on l.timestamp >= c.caldate and l.timestamp < date_add(c.caldate,INTERVAL 1 DAY)
where c.week_number = WEEK(curdate())-1
group by c.caldate
我是如何创建日历表的?
如前所述,有很多方法,对于MySQL,这里有一些选项:How to populate a table with a range of dates?
答案 3 :(得分:-1)
这个怎么样?我尝试了它,但它确实有效。
select yearweek(ts) as yearwk, user_id,
count(user_id) as counts
from log
group by 1,2
having count(user_id) =7;