Рекомендации, необходимые для Mysql Query, сортировки, группировки и производительности - PullRequest
4 голосов
/ 04 января 2011

У меня есть таблица «сообщений», где пользователи отправляют и получают сообщения, довольно просто. То, что я хотел бы сделать: получить DISTINCT sender_ids, ГДЕ receive_id это X, и отсортировать его таким образом, чтобы сначала появлялись пользователи, у которых получатель X имел непрочитанные сообщения, а затем появлялись пользователи, у которых получатель X прочитал сообщения, и все сортировалось made_at DESC.

Есть идеи, как мне это сделать? Примечание. Производительность также является проблемой.

Это запрос, который я использовал, но похоже, что сортировка на самом деле не выполнена правильно, возможно, DISTINCT все испортил? Я ожидаю результата 6, 5, 4, 2, 3 - но получаю 6, 5, 4, 3, 2

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

Вот таблица с образцами данных:

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');

Ответы [ 4 ]

1 голос
/ 04 января 2011

Как насчет 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 голосов
/ 04 января 2011

Я не совсем понимаю, что "все отсортировано по созданному дескриптору".

Если непрочитанные сообщения должны появиться первыми, то вы не можете отсортировать «все» по create_at.

Но если вы хотите сначала перечислить все непрочитанные сообщения (отсортированные по create_at), а затем перечислить все прочитанные сообщения (снова отсортированные по creation_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 голосов
/ 04 января 2011

Сначала небольшая оптимизация таблицы так, как я должен это сделать:

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 символов, поэтому вы будете хранить только 1 байт вместо 2. смотреть здесь .

Таким образом, нагрузка на строку не так велика, если ваши сообщения не находятся в одной таблице. и если вы собираетесь получить много данных, это будет очень полезно!

Мой запрос, который вы просите, будет выглядеть так:

select distinct(sender_id) 
from messages
where receiver_id = x
group by sender_id
order by read_at desc
0 голосов
/ 04 января 2011

Следующее возвращает желаемый результат для данных примера:

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

Если вы хотите использовать самое старое сообщение при сортировке по created_at, измените MAX на MIN.

COUNT(read_at) игнорирует нули, в то время как COUNT(*) - нет, поэтому они будут неравными, если будут какие-либо непрочитанные сообщения.Если получателю не слишком много сообщений, он должен работать довольно быстро (поможет индекс на receiver_id).Профилируйте запрос, прежде чем решить, что необходима дополнительная оптимизация.

С некоторыми изменениями можно заставить работать агрегатное выражение Scrum Meister.Попробуйте MIN(IF(read_at IS NULL, 0, 1)) вместо COUNT(*)=COUNT(read_at).Я не думаю, что это улучшит время выполнения, но есть хотя бы небольшой шанс (как большая часть оптимизации зависит от внутренних компонентов 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;

выглядят потерянными.Этот запрос работает с использованием константных значений (отдельные запросы позволяют это) для столбца, указывающего, являются ли какие-либо из сообщений отправителя непрочитанными, а не агрегированных выражений.Вот вывод 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 |                                              |
+----+--------------+------------+-------+----------------+-------+---------+------+------+----------------------------------------------+
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...