0

So I've got the following tables setup:

thread:

  • id
  • created_at

thread_users:

  • id
  • thread_id -> FK thread
  • user_id -> FK users (the user who is in the thread)
  • joined_at

users:

  • id
  • name
  • etc...other relevant details for user

Current functionality: When a user clicks on another user (e.g there FRIEND in the app), it will open the message thread between the 2 users (it should grab the msgs between the 2 users). To determine WHICH thread to use (we need to find the thread_id between the 2 users), we must query the thread_users table using the HTTP_REQUEST_USER (user who sent the GET request) & and the user_id PARAM passed in the http GET request.

Once I do this: select * from thread_users where user_id = HTTP_REQUEST_USER_ID OR user_id = GET_REQUEST_PARAM_USER_ID

It will return a bunch of threads that the users are in, I then group the returned results using the lodash groupBy method: (which basically MOVES all the results into there corresponding thread objects - E.g 6: [ { user1 }, { user2 } ] - This represents the thread_id 6)

const groupedThreads = _.groupBy(foundThreadUsers, 'thread_id');

From here I loop through each of the found threads (groupedThreads will contain objects THAT contain the arrays of user in each of the threads)

Object.keys(groupedThreads).forEach((key) => {
      const thr = groupedThreads[key].filter(tu => tu.user_id === currentUserId || tu.user_id === userId);
      console.log(thr, 'FOUND THREAD inside loop after filter');
      /**
       * @TODO add something here which will take into account pluralbot
       */
      if (thr.length === 2) {
        foundThread = thr[0];
      }
    });

However, this bit of logic doesn't work:

if (thr.length === 2) {
   foundThread = thr[0];
}

Because the select * from ... (query listed above ^^), only returns the thread_users that match the user ids passed into the where user_id = x OR user_id = y.

I want to be able to select * from thread_users where user_id = HTTP_REQUEST_USER_ID OR user_id = GET_REQUEST_PARAM_USER_ID BUT also return the other users WHO match the found thread_users.thread_id. Is this possible? Or is there another way I could find the thread between the 2 users?

James111
  • 15,378
  • 15
  • 78
  • 121
  • Reformulated: you need the most recent thread in which both users participated. – joop Jul 03 '17 at 09:29
  • @joop Yeah. But it's been setup so that threads won't be deleted. So you'll always have the same thread between the two users. – James111 Jul 04 '17 at 00:53

3 Answers3

2

This is the trivial solution using a normal relational data model. (you could combine the two EXISTS() conditions) :


SELECT * 
FROM thread th
WHERE EXISTS (
        SELECT * 
        FROM user_thread ut
        WHERE ut.thread_id = th.id
        AND ut.user_id = ***user1***
        )
AND EXISTS (
        SELECT *
        FROM user_thread ut
        WHERE ut.thread_id = th.id
        AND ut.user_id = ***user2***
        )
        ;
joop
  • 4,330
  • 1
  • 15
  • 26
  • Ahh awesome! I thought there would be a better way todo this :) So basically if I wanted to find a group between more users, I could chain more `EXISTS` to the query? Also is this an expensive query to run? E.g if I had 40,000 users & 29,000 of them had multiple threads, would it be an issue? – James111 Jul 04 '17 at 11:43
  • 1
    Check the plan! No it is *not* an expensive operation, given you have both supporting indexes for the FK in the bridge-table (user_thread) – joop Jul 04 '17 at 12:14
1

This will get you the answer you're looking for:

SELECT DISTINCT  -- Use DISTINCT to avoid getting duplicates for inverse pairing
  T.id
FROM thread T
  INNER JOIN thread_users U1
    ON T.id = U1.thread_id
  INNER JOIN thread_users U2
    ON T.id = U2.thread_id
WHERE U1.user_id = @FirstUserID
  AND U2.user_id = @SecondUserID
Joel Brown
  • 14,123
  • 4
  • 52
  • 64
0

Okay so what I ended up doing was adding a new field to the thread table named: thread_name. The thread name consists of the 2 user ids joined together, e.g: 445-123. The reason for this is so I can easily query for a thread between 2 users! I'm not sure if I will do this for group messages though (we will see when the time comes).

Example usage:

const name = [http_req_userId, otherUserId].sort().join('-'); // Returns something like 123-323
select * from threads where thread_name = name;

Note that I'm sorting the 2 IDS before joining them together so I don't create duplicate threads between users.

I got the idea for this off this SO question (see comments of below answer): thread messaging system database schema design

EDIT

Do not use this approach - I will leave this here as a reference for what NOT to do.

James111
  • 15,378
  • 15
  • 78
  • 121
  • 2
    IMHO this is a terrible design. It combines two FK's into a composed *text* field(you can never enforce constraints on the FKs ... ); it even violates 1NF ... – joop Jul 04 '17 at 10:49
  • @James111 Joop is right. This is not a very supportable approach. You could use his answer, which works, or you could simplify it by using a simple join (two of them, actually) instead of EXISTS. – Joel Brown Jul 04 '17 at 10:56
  • @JoelBrown : a simple join *could* generate duplicate entries in the outer query. Mine doesn't. (your sulution uses DISTINCT to avoid this, but selects only thread.id... could be distinct ON) – joop Jul 04 '17 at 11:01
  • @joop Yeah I didn't think it was the best idea at the time but couldn't find any other solution. Thanks for the answer! – James111 Jul 04 '17 at 11:46