2011-01-04 5 views
4

사용자가 메시지를주고받는 '메시지'테이블이 있습니다. 내가 뭘하고 싶은지 : receiver_id가 X 인 DISTINCT sender_ids를 검색하고, 수신자 X가 읽지 않은 메시지를 가진 사용자가 먼저 나타나고 수신자 X가 읽은 메시지의 사용자가 후에 모든 것이 정렬되어 정렬되도록 정렬해야합니다. created_at DESC.Mysql 쿼리, 정렬, 그룹화 및 성능에 대한 조언이 필요합니다.

어떻게하면이 아이디어를 얻을 수 있습니까? 참고 : 성능 또한 문제입니다.

이것은 내가 사용하고있는 쿼리이지만 정렬 작업이 실제로 올바르게 수행되지 않았거나 DISTINCT가 작동하지 않는 것 같습니다. 나는 결과 6, 5, 4, 2, 3 기대하고있다 -하지만 여기

SELECT DISTINCT sender_id 
FROM message m 
WHERE receiver_id = 1 
ORDER BY read_at, created_at DESC 

2 샘플 데이터로 테이블, 3, 4, 5, 6 얻고 다음

CREATE TABLE `message` (
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 
    `sender_id` bigint(20) NOT NULL, 
    `receiver_id` bigint(20) NOT NULL, 
    `message` text, 
    `read_at` datetime DEFAULT NULL, 
    `created_at` datetime DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    KEY `sender` (`sender_id`), 
    KEY `receiver` (`receiver_id`), 
    KEY `dates` (`receiver_id`,`read_at`,`created_at`) 
) ENGINE=MyISAM AUTO_INCREMENT=13 DEFAULT CHARSET=latin1; 


INSERT INTO `message` (id, sender_id, receiver_id, message, read_at, created_at) 
VALUES 
    (1,2,1,NULL,'2011-01-01 01:01:01','2011-01-01 01:01:01'), 
    (2,1,2,NULL,'2011-01-01 01:01:01','2011-01-01 01:01:02'), 
    (3,2,1,NULL,'2011-01-01 01:01:01','2011-01-01 01:01:03'), 
    (4,3,1,NULL,'2011-01-01 01:01:01','2011-01-01 01:01:04'), 
    (5,3,1,NULL,'2011-01-01 01:01:01','2011-01-01 01:01:05'), 
    (6,1,4,NULL,'2011-01-01 01:01:01','2011-01-01 01:01:06'), 
    (7,4,1,NULL,NULL,'2011-01-01 01:01:07'), 
    (8,5,1,NULL,NULL,'2011-01-01 01:01:08'), 
    (9,5,1,NULL,NULL,'2011-01-01 01:01:09'), 
    (10,1,6,NULL,NULL,'2011-01-01 01:01:10'), 
    (11,6,1,NULL,NULL,'2011-01-01 01:01:11'); 
+0

까다로운 상황은 동일한 사용자가 읽지 않은 메시지와 읽은 메시지가 모두 있고 두 번째가 아닌 첫 번째 그룹에 발신자가 배치되어 있는지 확인하는 것입니다. – outis

+0

보낸 사람이 보내는 메시지가 여러 개인 경우'created_at'를 정렬에 사용 하시겠습니까? 가장 최근 또는 가장 오래된 메시지? – outis

+0

@outis, 가장 최근의 created_at를 원합니다. 아래의 답안에서 귀하의 솔루션이 작동하지만, 성능 향상을 위해 할 수있는 일이 있는지 궁금합니다. 많은 다른 발신자/수신자간에 많은 메시지가있을 것을 두려워합니다. – BugBusterX

답변

0

다음 샘플 데이터에서 원하는 결과를 반환 : 당신이 created_at으로 정렬 할 때 가장 오래된 메시지를 사용하려면

SELECT sender_id 
    FROM message AS m 
    WHERE receiver_id=? 
    GROUP BY sender_id 
    ORDER BY COUNT(*)=COUNT(read_at), MAX(created_at) DESC; 

MINMAX을 변경합니다.

COUNT(read_at)은 null을 무시하고, COUNT(*)은 읽지 않으므로 두 개가 동일하지 않습니다. 수신자에게 너무 많은 메시지가 없으면 매우 빨리 수행해야합니다 (receiver_id의 색인이 도움이됩니다). 더 많은 최적화가 필요하다고 결정하기 전에 쿼리를 프로파일 링하십시오.

약간의 조정을 통해 The Scrum Meister의 집계식이 작동하도록 할 수 있습니다. COUNT(*)=COUNT(read_at) 대신 MIN(IF(read_at IS NULL, 0, 1))을 사용해보십시오. 나는 그것이 실행 시간을 향상시킬 것이라고 생각하지 않지만 적어도 (MySQL 내부에 의존하는 많은 최적화와 같은) 작은 기회가있다. 테스트 테이블에 EXPLAIN의

결과 :

+----+-------------+-------+------+----------------+----------+---------+-------+------+----------------------------------------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref | rows | Extra          | 
+----+-------------+-------+------+----------------+----------+---------+-------+------+----------------------------------------------+ 
| 1 | SIMPLE  | m  | ref | receiver,dates | receiver | 8  | const | 7 | Using where; Using temporary; Using filesort | 
+----+-------------+-------+------+----------------+----------+---------+-------+------+----------------------------------------------+ 

message 행에 적용되는 집계 함수를 제거하기 :

SELECT sender_id 
    FROM ((SELECT sender_id, 0 AS all_read, MAX(created_at) AS recent 
      FROM message AS m 
      WHERE receiver_id=:receiver AND read_at IS NULL 
      GROUP BY sender_id) 
     UNION 
     (SELECT sender_id, 1 AS all_read, MAX(created_at) AS recent 
      FROM message AS m 
      WHERE receiver_id=:receiver AND read_at IS NOT NULL 
      GROUP BY sender_id) 
     ) AS t 
    GROUP BY sender_id 
    ORDER BY MIN(all_read), recent DESC; 

모습 땅을 잃게. 이 쿼리는 집계식이 아닌 보낸 사람의 메시지가 읽지 않았는지 여부를 나타내는 열에 대해 상수 값 (별도의 쿼리에서 허용)을 사용하여 작동합니다. 다음은이 쿼리에 대한 output of EXPLAIN는 다음과 같습니다

+----+--------------+------------+-------+----------------+-------+---------+------+------+----------------------------------------------+ 
| id | select_type | table  | type | possible_keys | key | key_len | ref | rows | Extra          | 
+----+--------------+------------+-------+----------------+-------+---------+------+------+----------------------------------------------+ 
| 1 | PRIMARY  | <derived2> | ALL | NULL   | NULL | NULL | NULL | 5 | Using temporary; Using filesort    | 
| 2 | DERIVED  | m   | ref | receiver,dates | dates | 17  |  | 4 | Using where; Using temporary; Using filesort | 
| 3 | UNION  | m   | range | receiver,dates | dates | 17  | NULL | 3 | Using where; Using temporary; Using filesort | 
|NULL| UNION RESULT | <union2,3> | ALL | NULL   | NULL | NULL | NULL | NULL |            | 
+----+--------------+------------+-------+----------------+-------+---------+------+------+----------------------------------------------+
+0

이 쿼리는 내가 원하는 것을 정확하게 수행하고있는 것처럼 보입니다. 사용자간에 많은 메시지가 앞뒤로 반복 될 것이므로이 방법을 사용하면 더 효율적으로 동일한 작업을 수행 할 수 있는지 궁금합니다. 조사해야 할 대체 방법이 있습니까? 그런데 느린 성능의 요인은 무엇입니까? 많은 발신자 또는 발신자 당 많은 메시지? 아니면 둘다? – BugBusterX

+0

@BugBusterX : 둘 다 조금 있습니다. 전 (많은 발신자) 당신은별로 할 수 없습니다. 많은 보낸 사람이 정렬 자체에 가장 큰 영향을 미칩니다 (정렬 할 결과 테이블의 행이 늘어남에 따라). 이는 다른 쿼리에도 적용됩니다. 보낸 사람 당 많은 메시지가 정렬에 사용 된 집계 통계를 계산하는 데 영향을 미칩니다 (MySQL이 내부적으로 처리하는 방식에 따라 결과 열을이 열을 이동하는 것이 도움이되지 않을 수도 있음). – outis

+0

와우, 도와 줘서 고마워!나는 당신이 나를 도와 주는데 소비 한 모든 시간을 정말로 감사한다! 일단 더 빠른 테스트 데이터를로드하면 쿼리의 두 번째 버전을 테스트 할 것입니다. 다시 한번, 당신의 모든 노력에 대해 대단히 감사합니다, 당신은 최고입니다! – BugBusterX

1

GROUP BY 약 방법 :

SELECT sender_id 
FROM message m 
WHERE receiver_id = 1 
GROUP BY sender_id 
ORDER BY MAX(IFNULL(read_at,'9999-01-01')) DESC 
+0

ASC (필자가 추가했습니다.)에 의해 작성된 정렬 기준이 아니며 최상의 성능 기준인지 잘 모르겠습니다. – BugBusterX

+0

여전히 그룹화 및 집계 함수에 +1. – outis

0

이 방법으로 먼저 작은 테이블 optimalisation 내가 그것을 수행해야합니다

create table messages 
(
    message_id bigint unsigned not null auto_increment primary key, 
    sender_id begint unsigned not null, 
    receiver_id bigint unsigned not null, 
    read_at datetime default null, 
    created_at datetime 
) engine=innodb; 

create table message_body 
(
    message_id bigint unsigned not null, 
    message varchar(32000) not null 
) engine=innodb; 

을 나는 VARCHAR를 사용 텍스트 대신에 작은 메시지가있을 때 2 바이트가됩니다. 그리고 메시지에는 때로는 255 자 미만의 문자가 포함되므로 2 대신 1 바이트 만 저장됩니다. 시계 here.

메시지가 동일한 테이블에 없으면 행을로드 할 때 가중치가 크지 않습니다. 그리고 만약 당신이 데이터로부터 많은 것을 얻으려면 그것은 매우 유용 할 것입니다!

내 쿼리 U 같을 것이다 요청 :

select distinct(sender_id) 
from messages 
where receiver_id = x 
group by sender_id 
order by read_at desc 
0

정말 부분 "모든 것이 created_at 내림차순으로 정렬됩니다"이해가 안 돼요.

읽지 않은 메시지가 먼저 나타나야하는 경우 created_at에서 "모든 것을"정렬 할 수 없습니다.

하지만 (created_at으로 분류) 첫 번째 목록에 읽지 않은 모든 메시지를 의미하는 경우, 모든 읽기 메시지 (다시 created_at으로 분류) 한 후 다음이 작업을 수행 할 목록 :

 
SELECT * 
FROM message m 
WHERE receiver_id = 1 
ORDER BY 
    CASE 
     WHEN read_at IS NULL THEN 0 
     ELSE 1 
    END ASC, 
    created_at DESC; 

이 약간 다른 생성을 당신이 기대하는 것보다 오더 된 것이지만 샘플 데이터를 보면 나는 정확해야한다고 생각합니다.

+0

예 "읽지 않은 모든 메시지 (먼저 created_at로 정렬)를 말한 다음, 읽은 모든 메시지 (다시 created_at로 정렬)를 말합니다."@outis의 제안이 작동합니다. MAX (created_at)를 추가하고 sender_id 하지만 많은 사용자들 사이에서 많은 메시지가 나올 것이므로 성능에 대해 우려하고 있습니다. – BugBusterX

+0

아, 죄송합니다. 발신자별로 그룹화해야한다는 요구 사항을 간과했습니다. –

관련 문제