2011-08-17 5 views
8

도시에있는 각 사용자의 수표를 보유하고있는 데이터베이스 테이블이 있습니다. 사용자가 도시에 몇 일 동안 있었는지, 그리고 사용자가 도시를 몇 번 방문했는지 (방문은 도시에서 연속적인 일로 구성됨)를 알아야합니다.MySQL : 연속 일 및 그룹 수별 그룹

그래서, 내가 (단지 DATETIME의 포함, 단순화 - 동일한 사용자와 도시를) 다음 표를 고려해

 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).

문제는이 쿼리를 어떻게 작성해야할지 모르겠다는 것입니다.

도움이 되었으면 좋겠습니다. 제 1 서브 작업에 대한

답변

10

.

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' 
+0

의 Devart 데이터 세트 당 최종 정확한 결과를 계산 ... 나는 그것이 가능 ... 완전히 제안을 이해할 수없는 것 좀 더 자세한 내용을 알려주시겠습니까? 고맙습니다! 두 번째 질문에 관해서는 제 질문에서 언급 한 것처럼 사용자와 도시를 세지 않는다면 제 질문은 옳습니다. – linkyndy

+0

죄송합니다. "사용자가 한 도시에 며칠 동안"에 대한 결과가 (user_id, count_of_days)와 같아야한다고 가정했습니다. – Simon

+0

감사합니다. 내 실제 데이터베이스 테이블에 맞게 여러 가지 조정을하면 쿼리가 매력처럼 작동합니다. 다시 감사합니다! – linkyndy

0

: 당신은 어떤 전날 체크인 없었다 체크 인을 찾아 각 방문의 첫 날을 찾을 수 있습니다

select count(*) 
from (
select TO_DAYS(p.d) 
from p 
group by TO_DAYS(p.d) 
) t 
0

나는 데이터베이스 구조를 바꾸는 것을 고려해야한다고 생각합니다. checkins 테이블에 테이블 방문과 visit_id를 추가 할 수 있습니다. 새로운 체크인을 등록하고 싶을 때마다 하루 전 체크 인이 있는지 확인하십시오. 그렇다면 어제 체크인에서 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를 추가해야 함).

+0

답장을 보내 주셔서 감사합니다.하지만 적어도 현재 현재의 데이터베이스 구조를 고수하고 싶습니다. 또한 하루에 여러 개의 체크 인이있을 수 있으므로 삽입 할 때 추가 작업이 필요합니다. 따라서 하루에 체크 인이 있는지 확인하는 것이 매우 간단합니다. 이러한 종류의 데이터 조작은 제공된 데이터베이스 구조로 PHP에서도 수행 할 수 있지만 더 깨끗하고 편리하므로이 작업을 수행 할 쿼리를 찾고있었습니다. – linkyndy

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; 

이 쿼리는 모든 행을 반환하고 방문수에 대한 순위를 지정합니다. 이것은 변수를 기반으로하는 알려진 순위 방법입니다. 행은 사용자 및 날짜 필드별로 정렬됩니다. 'COUNT (DISTINCT (DATE (:

+---------+---------------------+------+------------+----+ 
| 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 | 
+---------+---------------------+------+------------+----+ 

그럼 우리 그룹이 데이터를 사용자가 설정 집계 함수를 사용 - 데이터가 days 열이 방문 횟수에 대한 순위를 제공하는 경우 설정 옆에이 쿼리는 사용자가 방문, 출력을 계산 dt))) - 일수를 계산합니다. 'MAX (일)'- 방문수로, 하위 쿼리의 days 필드의 최대 값입니다. 모든 인

; Devart 의해 제공된 데이터 샘플로)

+0

꽤 복잡해 보입니다 ... 코드에 대한 자세한 내용을 알려주시겠습니까? 감사하겠습니다! – linkyndy

+0

몇 가지 세부 정보를 추가했습니다. – Devart

+0

감사합니다. 내가 2 가지 대답에 현상금을 줄 수는 없다는 것은 아주 슬픈 일입니다. 그러나 쿼리가 조금 더 간단하므로 다른 대답을 선택했습니다. 정말 죄송합니다. 다시 답변 해 주셨습니다. – linkyndy

1

은 내측 "PreQuery"는 SQL 작동 변수. @LUser를 -1로 설정하면 (존재하지 않는 사용자 ID 일 가능성이 있음) IF() 테스트는 마지막 사용자와 현재 사이의 차이점을 확인합니다. 신규 사용자가 되 자마자 1의 값을 얻습니다. 또한 마지막 날짜가 새 체크인 일로부터 1 일 이상이면 값은 1이됩니다. 그런 다음 후속 열은 @LUser와 @LDate는 다음주기에 대해 테스트 된 들어오는 레코드의 값을가집니다. 그런 다음, 외부 쿼리는 단지 그들을 요약 및 제 1 양태에 관한

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 
+0

답변 해 주셔서 감사합니다. – linkyndy

+0

도움이 되니 기쁩니다 ... 필요한 정확한 솔루션을 얻었습니까 (따라서 사용자 ID 정보도 포함되어 도움이됩니다). – DRapp

+0

한 가지 대답 만 받아 들여 보상받을 수있는 것은 너무 나빴습니다 ... – linkyndy