我有一个数据库表,用于保存每个用户在城市中的签到。我需要知道一个用户在一个城市的天数,然后是一个用户对一个城市的访问次数(一次访问包括在一个城市中连续几天的访问)。
所以,请考虑我有下表(简化,仅包含DATETIME
s - 相同的用户和城市):
datetime
-------------------
2011-06-30 12:11:46
2011-07-01 13:16:34
2011-07-01 15:22:45
2011-07-01 22:35:00
2011-07-02 13:45:12
2011-08-01 00:11:45
2011-08-05 17:14:34
2011-08-05 18:11:46
2011-08-06 20:22:12
此用户访问此城市的天数 6 ( 30.06 , 01.07 , 02.07 , 01.08 , 05.08 , 06.08 )。
我想过使用SELECT COUNT(id) FROM table GROUP BY DATE(datetime)
然后,对于此用户对此城市的访问次数,查询应返回 3 ( 30.06-02.07 , 01.08 , 05.08-06.08 )。
问题是我不知道如何构建此查询。
任何帮助都将受到高度赞赏!
答案 0 :(得分:11)
您可以通过查找前一天没有签到的签到来找到每次访问的第一天。
select count(distinct date(start_of_visit.datetime))
from checkin start_of_visit
left join checkin previous_day
on start_of_visit.user = previous_day.user
and start_of_visit.city = previous_day.city
and date(start_of_visit.datetime) - interval 1 day = date(previous_day.datetime)
where previous_day.id is null
此查询有几个重要部分。
首先,每个签到都加入前一天的任何签到。但由于它是外部联接,如果前一天没有签入,则联接的右侧将有NULL
个结果。 WHERE
过滤在连接之后发生,因此它仅保留左侧没有右侧的那些签到。 LEFT OUTER JOIN/WHERE IS NULL
非常便于查找不的内容。
然后它计算不同签入日期,以确保如果用户在访问的第一天多次签入,则不会重复计算。 (当我发现可能的错误时,我实际上在编辑时添加了那部分。)
编辑:我刚刚重新阅读了您提出的第一个问题的查询。您的查询将获得给定日期的签到次数,而不是日期计数。我想你想要这样的东西:
select count(distinct date(datetime))
from checkin
where user='some user' and city='some city'
答案 1 :(得分:3)
尝试将此代码应用于您的任务 -
CREATE TABLE visits(
user_id INT(11) NOT NULL,
dt DATETIME DEFAULT NULL
);
INSERT INTO visits VALUES
(1, '2011-06-30 12:11:46'),
(1, '2011-07-01 13:16:34'),
(1, '2011-07-01 15:22:45'),
(1, '2011-07-01 22:35:00'),
(1, '2011-07-02 13:45:12'),
(1, '2011-08-01 00:11:45'),
(1, '2011-08-05 17:14:34'),
(1, '2011-08-05 18:11:46'),
(1, '2011-08-06 20:22:12'),
(2, '2011-08-30 16:13:34'),
(2, '2011-08-31 16:13:41');
SET @i = 0;
SET @last_dt = NULL;
SET @last_user = NULL;
SELECT v.user_id,
COUNT(DISTINCT(DATE(dt))) number_of_days,
MAX(days) number_of_visits
FROM
(SELECT user_id, dt
@i := IF(@last_user IS NULL OR @last_user <> user_id, 1, IF(@last_dt IS NULL OR (DATE(dt) - INTERVAL 1 DAY) > DATE(@last_dt), @i + 1, @i)) AS days,
@last_dt := DATE(dt),
@last_user := user_id
FROM
visits
ORDER BY
user_id, dt
) v
GROUP BY
v.user_id;
----------------
Output:
+---------+----------------+------------------+
| user_id | number_of_days | number_of_visits |
+---------+----------------+------------------+
| 1 | 6 | 3 |
| 2 | 2 | 1 |
+---------+----------------+------------------+
<强>解释强>
要了解它是如何工作的,让我们检查子查询,就在这里。
SET @i = 0;
SET @last_dt = NULL;
SET @last_user = NULL;
SELECT user_id, dt,
@i := IF(@last_user IS NULL OR @last_user <> user_id, 1, IF(@last_dt IS NULL OR (DATE(dt) - INTERVAL 1 DAY) > DATE(@last_dt), @i + 1, @i)) AS
days,
@last_dt := DATE(dt) lt,
@last_user := user_id lu
FROM
visits
ORDER BY
user_id, dt;
如您所见,查询返回所有行并执行访问次数排名。这是基于变量的已知排名方法,请注意行按用户和日期字段排序。此查询计算用户访问次数,并输出下一个数据集,其中days
列提供了访问次数排名 -
+---------+---------------------+------+------------+----+
| user_id | dt | days | lt | lu |
+---------+---------------------+------+------------+----+
| 1 | 2011-06-30 12:11:46 | 1 | 2011-06-30 | 1 |
| 1 | 2011-07-01 13:16:34 | 1 | 2011-07-01 | 1 |
| 1 | 2011-07-01 15:22:45 | 1 | 2011-07-01 | 1 |
| 1 | 2011-07-01 22:35:00 | 1 | 2011-07-01 | 1 |
| 1 | 2011-07-02 13:45:12 | 1 | 2011-07-02 | 1 |
| 1 | 2011-08-01 00:11:45 | 2 | 2011-08-01 | 1 |
| 1 | 2011-08-05 17:14:34 | 3 | 2011-08-05 | 1 |
| 1 | 2011-08-05 18:11:46 | 3 | 2011-08-05 | 1 |
| 1 | 2011-08-06 20:22:12 | 3 | 2011-08-06 | 1 |
| 2 | 2011-08-30 16:13:34 | 1 | 2011-08-30 | 2 |
| 2 | 2011-08-31 16:13:41 | 1 | 2011-08-31 | 2 |
+---------+---------------------+------+------------+----+
然后我们按用户对此数据集进行分组并使用聚合函数:
'COUNT(DISTINCT(DATE(dt)))' - 计算天数
'MAX(天)' - 访问次数,它是我们子查询中days
字段的最大值。
就是这样;)
答案 2 :(得分:1)
作为Devart提供的数据样本,内部“PreQuery”与sql变量一起使用。通过将@LUser默认为-1(可能不存在的用户ID),IF()测试检查最后一个用户和当前之间的任何差异。一旦新用户,它获得值1 ...此外,如果最后一个日期距离新登记日期超过1天,则它获得值1.然后,后续列重置@LUser和@LDate为刚刚测试的传入记录的值,用于下一个周期。然后,外部查询将它们相加并根据
的Devart数据集计算它们的最终正确结果User ID Distinct Visits Total Days
1 3 9
2 1 2
select PreQuery.User_ID,
sum( PreQuery.NextVisit ) as DistinctVisits,
count(*) as TotalDays
from
( select v.user_id,
if( @LUser <> v.User_ID OR @LDate < ( date( v.dt ) - Interval 1 day ), 1, 0 ) as NextVisit,
@LUser := v.user_id,
@LDate := date( v.dt )
from
Visits v,
( select @LUser := -1, @LDate := date(now()) ) AtVars
order by
v.user_id,
v.dt ) PreQuery
group by
PreQuery.User_ID
答案 3 :(得分:0)
第一个子任务:
select count(*)
from (
select TO_DAYS(p.d)
from p
group by TO_DAYS(p.d)
) t
答案 4 :(得分:0)
我认为您应该考虑更改数据库结构。您可以将表访问和visit_id添加到您的签入表中。每次您想要注册新的签到时,您都会检查一天是否有任何签到。如果是,那么您可以使用昨天的checkin中的visit_id添加新的签到。如果没有,那么您可以使用新的visit_id添加新访问次数和新签入次数。
然后你可以用一个类似的东西在一个查询中获取数据:
SELECT COUNT(id) AS number_of_days, COUNT(DISTINCT visit_id) number_of_visits FROM checkin GROUP BY user, city
它不是非常优化,但仍然比使用当前结构做任何事情更好,它会起作用。此外,如果结果可以是单独的查询,它将非常快速地工作。
但当然缺点是您需要更改数据库结构,执行更多脚本并将当前数据转换为新结构(即您需要将visit_id添加到当前数据)。