GROUP BY用户显示结果ORDERed BY时间

时间:2017-07-26 21:00:03

标签: mysql sql group-by sql-order-by

我正试图为用户制作收件箱。我需要显示按通讯员分组的所有线程,并按照特定通信的最后发布消息的时间排序。 我坚持使用这个sql并且不知道我该怎么做:

CREATE TABLE `user_mail` (
  `id` int(10) NOT NULL,
  `author` int(10) NOT NULL,
  `recipient` int(10) NOT NULL,
  `title` varchar(100) NOT NULL,
  `message` text NOT NULL,
  `date` int(100) NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

SELECT * FROM user_mail t1 
        INNER JOIN 
        (SELECT author, recepient, MAX(date) AS Ordered FROM user_mail
        WHERE recepient = '$thisUser' OR author = '$thisUser' GROUP BY author) t2
        ON t1.author = t2.author
        WHERE t1.recepient = '$thisUser' OR t1.author = '$thisUser' 
        ORDER BY t2.Ordered DESC

这是我需要展示的方案:

Correspondence with User 1        

 Newest reply  - author: User 1    | time: 11:00
 Next reply    - author: This user | time: ...
 Reply         - author: User 1    | time: ...
 ...
 Original post - author: This user | time: 09:30
________________________________________________
Correspondence with User 2

 Newest reply  - author: This user | time: 10:30
 ...
 Original post - author: User 2    | time: 10:00

你可以看到与用户1的通信如何处于最佳位置,因为它有最新的回复(虽然它的原始帖子比另一个更旧)。

此外,应显示所有对应关系,无论是用户启动了还是另一个用户启动了它们。

3 个答案:

答案 0 :(得分:1)

使用以下SQL语句,结果将与您的显示示例相同。

SELECT id
      ,CASE WHEN rn_min = 1
            THEN 'Original Post - '
            WHEN rn_max = 1
            THEN 'Newest reply  - '
            WHEN rn_min = 2 AND rn_max != 2
            THEN 'Reply         - '
            ELSE 'Next reply    - '
        END +
       CASE WHEN author = @thisuser
            THEN 'author: This ' + CONVERT(VARCHAR, author) 
            ELSE 'author: User ' + CONVERT(VARCHAR, author) 
        END +
       CASE WHEN rn_min = 1 OR rn_max = 1
            THEN ' | time: '+ CONVERT(VARCHAR(8),posteddate,108)
            ELSE ''
        END value
  FROM (SELECT id
              ,author
              ,recipient
              ,message
              ,posteddate
              ,row_number() OVER (PARTITION BY id ORDER BY posteddate) rn_min
              ,row_number() OVER (PARTITION BY id ORDER BY posteddate desc) rn_max
          FROM user_mail
         WHERE author = @thisuser OR recipient = @thisuser
       ) t1

答案 1 :(得分:1)

由于您的用户可以同时位于两个列中,因此您必须使用搜索中的两列值和您的组。

试试这个:

select * 
from user_mail t1
join 
(  
  select max(date) as ConvMaxDate, 
    case when author = '$thisUser' then recipient 
         else author 
    end as OtherUser
  from user_mail
  where author = '$thisUser' or recipient = '$thisUser'
  group by case when author = '$thisUser' then recipient 
                else author 
           end
) ConversationMaxDate
on Author = '$thisUser' and OtherUser = recipient 
   or Recipient = '$thisUser' and OtherUser = Author
order by ConvMaxDate desc, Date desc;

ConversationMaxDate的内部查询首先确定会话伙伴,然后通过此“OtherUser”进行分组,计算每个主题的最新日期。这是有效的,因为您可以提供“ThisUser”(因为只有这样您才能在特定的电子邮件中知道哪一个是对话中的那个。)

您需要(author, recipient, date)(recipient, author, date)上的索引,因为MySQL可以使用索引合并。否则,它将需要完整的表/索引扫描。

答案 2 :(得分:1)

由于$thisUserauthorrecipient,您不知道每条消息,您可以使用LEAST(author, recipient)GREATEST(author, recipient)来识别"线程"并在子查询的GROUP BY子句和JOIN条件中使用它们。

SELECT m.* 
FROM user_mail m
JOIN (
    SELECT
        LEAST(author, recipient)    as user1,
        GREATEST(author, recipient) as user2,
        MAX(date) as date
    FROM user_mail
    WHERE $thisUser IN (author, recipient)
    GROUP BY user1, user2
) s ON  s.user1 = LEAST(m.author, m.recipient)
    AND s.user2 = GREATEST(m.author, m.recipient)
WHERE $thisUser IN (m.author, m.recipient)
ORDER BY
    s.date DESC,
    LEAST(m.author, m.recipient),
    GREATEST(m.author, m.recipient),
    m.date DESC

但是对于大数据集来说这会很慢,因为没有索引可以用于GROUP BY子句和JOIN条件。 我会制作id AUTO_INCREMENT PRIMARY KEY并使用它代替date。 这样,您至少可以为JOIN使用索引(PK)。查询也会缩短。

SELECT m.* 
FROM user_mail m
JOIN (
    SELECT MAX(id) as id
    FROM user_mail
    WHERE $thisUser IN (author, recipient)
    GROUP BY
        LEAST(author, recipient),
        GREATEST(author, recipient)
) s ON s.id = m.id
ORDER BY s.id DESC, m.id DESC

使用针对子查询的UNION ALL优化,您可以获得更好的性能。

SELECT m.* 
FROM user_mail m
JOIN (
    SELECT MAX(id) as id
    FROM (
        SELECT recipient as user, MAX(id) as id
        FROM user_mail
        WHERE author = $thisUser
        GROUP BY recipient
        UNION ALL
        SELECT author as user, MAX(id) as id
        FROM user_mail
        WHERE recipient = $thisUser
        GROUP BY author
    ) sub1
    GROUP BY user
) s ON s.id = m.id
ORDER BY s.id DESC, m.id DESC

对于此查询,您应该在(author, recipient)(recipient, author)上定义复合索引。

更新

您的评论是正确的:最后两个查询仅返回每个对话的最新消息。但第一个应该返回所有消息。

但是 - 这是UNION ALL优化查询的正确版本:

SELECT m.*, s.max_id
FROM user_mail m
JOIN (
    SELECT other_user, MAX(id) as max_id
    FROM (
        SELECT recipient as other_user, MAX(id) as id
        FROM user_mail
        WHERE author = $thisUser
        GROUP BY recipient
        UNION ALL
        SELECT author as other_user, MAX(id) as id
        FROM user_mail
        WHERE recipient = $thisUser
        GROUP BY author
    ) sub1
    GROUP BY other_user
) s ON s.other_user = m.recipient
WHERE m.author = $thisUser

UNION ALL

SELECT m.*, s.max_id
FROM user_mail m
JOIN (
    SELECT other_user, MAX(id) as max_id
    FROM (
        SELECT recipient as other_user, MAX(id) as id
        FROM user_mail
        WHERE author = $thisUser
        GROUP BY recipient
        UNION ALL
        SELECT author as other_user, MAX(id) as id
        FROM user_mail
        WHERE recipient = $thisUser
        GROUP BY author
    ) sub1
    GROUP BY other_user
) s ON s.other_user = m.author
WHERE m.recipient = $thisUser

ORDER BY max_id DESC, id DESC

虽然看起来很大,但这个查询在我的百万行测试数据集上的运行时间不到20毫秒(而其他解决方案需要300 - 500毫秒)。 请注意,子查询在两个部分中都是相同的。 MySQL应该能够缓存并重用结果。 为避免代码重复,您可以将子查询存储在字符串变量中并重用它。如果您使用MariaDB 10.2,您可能还想尝试CTE。

也不要忘记在(author, recipient)(recipient, author)上定义索引