简体   繁体   中英

MySQL SELECT latest record from a subquery with UNION

I have the following table

+--------+-----------+---------+----------------------------+---------------------+--------------------+
| msg_id | user_from | user_to |          msg_text          |      msg_time       |      msg_read      |
+--------+-----------+---------+----------------------------+---------------------+--------------------+
|      1 |         1 |      72 | Hello Mark from Andy       | 2014-09-18 12:44:09 | 2014-09-20 12:44:09|
|      2 |        72 |       1 | Hello Andy from Mark       | 2014-09-22 12:45:26 | 2014-09-28 12:45:26| 
|      3 |         1 |      72 | Back to you Mark from Andy | 2014-10-18 12:46:01 |                    |
|      4 |     12388 |       1 | Hello Andy from Graham     | 2014-09-20 12:45:37 | 2014-09-20 12:46:37|
|      5 |         1 |   12388 | Hello Graham from Andy     | 2014-09-20 12:51:08 |                    |
|      6 |       106 |       1 | Hello Andy from Carol      | 2015-04-18 12:47:04 |                    |
+--------+-----------+---------+----------------------------+---------------------+--------------------+

As SQLFiddle is down at the moment, here is the query.

-- ----------------------------
-- Table structure for `messages`
-- ----------------------------
DROP TABLE IF EXISTS `messages`;
CREATE TABLE `messages` (
  `msg_id` int(11) NOT NULL AUTO_INCREMENT,
  `user_from` int(11) NOT NULL,
  `user_to` int(11) NOT NULL,
  `msg_text` text,
  `msg_time` datetime DEFAULT NULL,
  `msg_read` datetime DEFAULT NULL,
  PRIMARY KEY (`msg_id`),
  KEY `IX_MESSAGES` (`user_from`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;

-- ----------------------------
-- Records of messages
-- ----------------------------
INSERT INTO `messages` VALUES ('1', '1', '72', 'Hello Mark from Andy', '2014-09-18 12:44:09', '2014-09-20 12:44:09');
INSERT INTO `messages` VALUES ('2', '72', '1', 'Hello Andy from Mark', '2014-09-22 12:45:26', '2014-09-28 12:45:26');
INSERT INTO `messages` VALUES ('3', '1', '72', 'Back to you Mark from Andy', '2014-10-18 12:46:01', null);
INSERT INTO `messages` VALUES ('4', '12388', '1', 'Hello Andy from Graham', '2014-09-20 12:45:37', '2014-09-20 12:46:37');
INSERT INTO `messages` VALUES ('5', '1', '12388', 'Hello Graham from Andy', '2014-09-20 12:51:08', null);
INSERT INTO `messages` VALUES ('6', '106', '1', 'Hello Andy from Carol', '2015-04-18 12:47:04', null);

As you may have guessed, this is a for a messaging system. In order to show in a Facebook style inbox, I want to extract a result set that shows the distinct users that a specific user has had communication with but I also want to include the latest message along with the message time and whether it was read, bearing in mind the latest message could either be from the sender or the recipient.

Getting the distinct users was easy enough. I simply use a UNION as follows:

SELECT
    t.user_id,
    t.msg_read,
    t.msg_time,
    t.msg_text
FROM
    (
        (
            SELECT
                m.user_from AS user_id,
                m.msg_time,
                m.msg_text,
                m.msg_read
            FROM
                messages m
            WHERE
                m.user_to = 1
        )
        UNION
            (
                SELECT
                    m.user_to AS user_id,
                    m.msg_time AS msg_time,
                    m.msg_text,
                    m.msg_read
                FROM
                    messages m
                WHERE
                    m.user_from = 1
            )
    ) t
GROUP BY user_id

This produces:

+---------+--------------------+---------------------+------------------------+
| user_id |      msg_read      |      msg_time       |        msg_text        |
+---------+--------------------+---------------------+------------------------+
|      72 | 2014-09-28 12:45:26| 2014-09-22 12:45:26 | Hello Andy from Mark   |
|     106 |                    | 2015-04-18 12:47:04 | Hello Andy from Carol  |
|   12388 | 2014-09-20 12:46:37| 2014-09-20 12:45:37 | Hello Andy from Graham |
+---------+--------------------+---------------------+------------------------+

Getting the latest message though is proving tricky. In the past I have simply used a JOIN to another subquery, but when trying to do the same with this, it (of course) doesn't recognise the t table.

SELECT
    t.user_id,
    t.msg_read,
    t.msg_time AS msg_time,
    t.msg_text
FROM
    (
        (
            SELECT
                m.user_from AS user_id,
                m.msg_time AS msg_time,
                m.msg_text,
                m.msg_read
            FROM
                messages m
            WHERE
                m.user_to = 1
        )
        UNION
            (
                SELECT
                    m.user_to AS user_id,
                    m.msg_time AS msg_time,
                    m.msg_text,
                    m.msg_read
                FROM
                    messages m
                WHERE
                    m.user_from = 1
            )
    ) t
INNER JOIN (SELECT MAX(msg_time) AS msg_time, user_id FROM t GROUP BY user_id) t2 ON (t.user_id=t2.user_id AND t.msg_time=t2.msg_time)
GROUP BY user_id

Table 't' doesn't exist

I realise that I could simply JOIN to another query containing the UNION but this seems a rather inefficient way of working.

I also hoped that I could create a temporary table, however it seems this is forbidden by the hosting provider.

Does anyone have any suggestions? I am happy to consider alternatives to the UNION concept.

For reference, the expected outcome should be:

+---------+--------------------+---------------------+----------------------------+
| user_id |      msg_read      |      msg_time       |          msg_text          |
+---------+--------------------+---------------------+----------------------------+
|     106 |                    | 2015-04-18 12:47:04 | Hello Andy from Carol      |
|      72 |                    | 2014-10-18 12:46:01 | Back to you Mark from Andy |
|   12388 | 2014-09-20 12:46:37| 2014-09-20 12:51:08 | Hello Graham from Andy     |
+---------+--------------------+---------------------+----------------------------+

First, you don't need the union . This following query gets all messages:

SELECT (case when m.user_to = 1 then m.user_from else m.user_to end) AS user_id,
       m.msg_time, m.msg_text, m.msg_read
FROM messages m
WHERE 1 in (m.user_to, m.user_from);

If you want the most recent one for each user, just use aggregation to get the most recent message and use a join for filtering:

SELECT m.*
FROM (SELECT (case when m.user_to = 1 then m.user_from else m.user_to end) AS user_id,
             m.msg_time, m.msg_text, m.msg_read
      FROM messages m
      WHERE 1 in (m.user_to, m.user_from)
     ) m JOIN
     (SELECT (case when m.user_to = 1 then m.user_from else m.user_to end) AS user_id,
             MAX(m.msg_time) as maxt
      FROM messages m
      WHERE 1 in (m.user_to, m.user_from)
      GROUP BY (case when m.user_to = 1 then m.user_from else m.user_to end) 
     ) mm
     ON m.user_id = mm.user_id and
        m.msg_time = mm.maxt;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM