skip to Main Content

I have a ‘users’ table for general information of every user (primary key is ‘user_id’) and a table ‘chat’ to store messages (primary key is ‘msg_id’) that have ‘incoming_msg_id’ (which eventually corresponds with ‘users.user_id’) and ‘outgoing_msg_id’ (which eventually corresponds with ‘users.user_id’).
‘chat.read_at’ is ‘null’ as long as the message is not read but is a date/time field.

I wish to list all users and their corresponding last message, if there is any.

With the following statement I have errors as I get also messages that are not from or to the corresponding user.

I am pretty new to SQL and trying to dig my way through it and did several attempts to do it with (INNER) JOIN but never got so far as with this statement:

    SELECT user_id, username, image, status, active,
    CASE WHEN status IN ("Active Now") THEN true ELSE false END AS isonline,
    (SELECT msg 
    FROM chat
    WHERE
    (incoming_msg_id = users.user_id) OR
    (outgoing_msg_id = users.user_id)
    ORDER BY msg_id DESC LIMIT 1
    ) AS last_msg,

    (SELECT msg_id
    FROM chat
    WHERE
    (incoming_msg_id = users.user_id) OR
    (outgoing_msg_id = users.user_id)
    ORDER BY msg_id DESC LIMIT 1 ) AS last_msg_id,

    (SELECT outgoing_msg_id
    FROM chat
    WHERE msg_id = last_msg_id
    ) AS outgoing_msg_id,

    (SELECT incoming_msg_id
    FROM chat
    WHERE msg_id = last_msg_id
    ) AS incoming_msg_id,

    (SELECT created_at
    FROM chat
    WHERE msg_id = last_msg_id
    ) AS last_interaction, 

    (SELECT IF(read_at = null, false, true)
    FROM chat
    WHERE msg_id = last_msg_id
    ) AS has_unread_msg,

    (SELECT read_at
    FROM chat
    WHERE msg_id = last_msg_id
    ) AS read_at,

    (SELECT IF(user_id = incoming_msg_id, true, false) AS isme
    FROM chat
    WHERE msg_id = last_msg_id
    ) AS self 

    FROM users
    WHERE
    (NOT user_id = ?) AND 
    (users.username LIKE ?)
    ORDER BY active DESC, status ASC, has_unread_msg DESC, last_interaction DESC

My code after the first comment with the exact same result (showing messages that do not belong to me and some of the other users listed:

   WITH 
   cte1 AS (
      SELECT *, incoming_msg_id as user_id FROM chat
      UNION ALL
      SELECT *, outgoing_msg_id FROM chat
      ),
   cte2 AS (
      SELECT *, IF(ISNULL(read_at), true, false) AS has_unread_msg, msg AS last_msg, IF(user_id = incoming_msg_id, true, false) AS self, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) rn
      FROM cte1
      )
   SELECT *, IF(status = "Active now", true, false) AS isonline
   FROM users
   JOIN cte2 USING (user_id)
   WHERE rn = 1

Create and inserts of the tables:

CREATE TABLE `chat` (
  `msg_id` int(11) NOT NULL,
  `incoming_msg_id` int(255) NOT NULL,
  `outgoing_msg_id` int(255) NOT NULL,
  `msg` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  `read_at` timestamp NULL DEFAULT NULL,
  `created_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


INSERT INTO `chat` (`msg_id`, `incoming_msg_id`, `outgoing_msg_id`, `msg`, `read_at`, `created_at`) VALUES
(3, 1, 5, 'no way', '2022-10-23 17:53:13', '2022-10-23 17:53:13'),
(4, 1, 5, 'I am sitting here and nothing happens!', '2022-10-23 17:53:13', '2022-10-23 17:53:13'),
(5, 1, 5, 'Still sitting here...', '2022-10-23 17:53:13', '2022-10-23 17:53:13'),
(9, 5, 1, 'Hi there', '2022-10-23 16:13:25', '2022-10-23 16:13:25'),
...
(45, 5, 25, 'Hey there. How is ya doing??', '2022-10-22 08:16:18', '2022-10-22 08:16:18'),
(46, 3, 25, 'hello there! Are you available?', '2022-10-23 18:16:08', '2022-10-23 18:16:08'),
(47, 3, 25, 'Something?', '2022-10-23 18:16:08', '2022-10-23 18:16:08'),
(48, 1, 25, 'hi!', '2022-10-24 11:45:13', '2022-10-24 11:45:13'),
(49, 1, 25, 'hi!', '2022-10-24 11:45:13', '2022-10-24 11:45:13'),
...
(63, 25, 5, 'nothing special here!', '2022-10-23 21:29:04', '2022-10-23 21:29:04'),
(64, 5, 25, 'What if we meet next week?', '2022-10-22 08:16:18', '2022-10-22 08:16:18'),
(65, 5, 25, 'What if we meet next week?', '2022-10-22 08:16:18', '2022-10-22 08:16:18'),
...
(365, 2, 5, 'sechs', NULL, '2022-10-21 20:49:09'),
(366, 2, 5, 'sieben', NULL, '2022-10-21 20:49:10'),
(367, 2, 5, 'acht', NULL, '2022-10-21 20:50:18'),
(368, 2, 5, 'neun', NULL, '2022-10-21 20:50:19'),

msg_id is primary and incremental

This is the users table:

CREATE TABLE `users` (
  `user_id` int(11) NOT NULL,
  `username` varchar(25) COLLATE utf8_unicode_ci NOT NULL,
  `email` varchar(255) CHARACTER SET armscii8 NOT NULL,
  ...
  `image` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  ...
  `created_at` timestamp NOT NULL DEFAULT current_timestamp(),
  `updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  ...
  `active` tinyint(1) DEFAULT 0,
  `status` varchar(16) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'Not active',
  `last_chat_notification_id` int(11) DEFAULT NULL,
  ...
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

screenshot of output

The first users are correct (talking to me as I am logged in with the user_id 25)
But from the 6th user on it shows messages that are not between us.

The list shows every existing user but only the last message between them and the currently logged in user (or non if not applicable).
The current user should also be excluded from the list.

2

Answers


  1. Chosen as BEST ANSWER

    Introduced to more advanced SQL concepts and guided through various steps of data extraction by @Akina I came to the following solution to the question:

    WITH
    cte1 AS (
        SELECT *, incoming_msg_id AS user_id FROM chat
        WHERE @user_id IN (incoming_msg_id, outgoing_msg_id)
        UNION ALL
        SELECT *, outgoing_msg_id FROM chat
        WHERE @user_id IN (incoming_msg_id, outgoing_msg_id)
    ),
    cte2 AS (
        SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY msg_id DESC) rn
        FROM cte1
    ),
    cte3 AS (
        SELECT * FROM cte2 WHERE user_id <> @user_id AND rn = 1
    )
    SELECT *
    FROM users
    LEFT JOIN cte3 USING (user_id);
    

    This solution selects the last chat messages if there is any but nevertheless displays all existing users.


  2. Schematically:

    WITH 
    cte1 AS (
        SELECT *, user_from as user_id FROM messages
        UNION ALL
        SELECT *, user_to FROM messages
    ),
    cte2 AS (
        SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) rn
        FROM cte1
    )
    SELECT *
    FROM users
    JOIN cte2 USING (user_id)
    WHERE rn = 1
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search