skip to Main Content

I’m trying to prepare a query for a user, which should return other users that this user hasn’t interacted with yet.

Suppose the following:

create schema if not exists bv;

create table if not exists bv.user (
  id serial primary key,
  user_name varchar(15) not null,
  is_male boolean not null
);

create table if not exists bv.chat (
  id serial primary key
);

create table if not exists bv.chat_message (
  id serial primary key,
  chat_id bigint references bv.chat (id),
  user_id bigint references bv.user (id),
  message_text varchar(255) not null
);

create table if not exists bv.junction_user_chat (
  chat_id bigint references bv.chat (id),
  user_id bigint references bv.user (id)
);

create table if not exists bv.user_coordinates (
  id serial primary key,
  user_id bigint references bv.user (id)
);

create table if not exists bv.user_like (
  id serial primary key,
  source_user_id bigint references bv.user (id),
  target_user_id bigint references bv.user (id)
);
insert into bv.user values
  (100, 'Mike', true),
  (101, 'John', true),
  (102, 'Jane', false),
  (103, 'George', true),
  (104, 'Lance', true);
insert into bv.chat values
  (10);
insert into bv.junction_user_chat values
  (10, 101),
  (10, 102);
insert into bv.chat_message values
  (500, 10, 101, 'hello'),
  (501, 10, 102, 'how are you?');
insert into bv.user_coordinates values
  (777, 100),
  (778, 101),
  (779, 102),
  (780, 103),
  (781, 104);
insert into bv.user_like values
  (1, 101, 102),
  (2, 102, 101),
  (3, 100, 102),
  (4, 103, 102),
  (5, 102, 104);

Let’s assume I am user with ID 102 (Jane). I would expect my query to return,

┌──────┬────────────────────┬──────────┐
│ id   │ user_name          │ is_male  │
├──────┼────────────────────┼──────────┤
│ 100  │ Mike               │ true     │
│ 103  │ George             │ true     │
└──────┴────────────────────┴──────────┘

Some remarks:

  • Users 102 and 101 already have a chat (and chat messages) between them, which why user 101 is not returned in the query,
  • User 102 does not exist as the source_user_id in bv."user_like" — if they did exist in that table, then user 103 would not be returned in the query
  • Users 100 and 103 are returned because,
    • they’ve both "liked" user 102,
    • user 102 not exist as bv."user_like"."souce_user_id",
    • a chat doesn’t exist for neither users 102 - 100 nor 102 - 103
  • User 104 is not returned because user 102 already "liked" them (even though user 104 hasn’t liked user 102 back at the moment)

Again, the idea here is to return all users that the current user(with id 102) hasn’t interacted with. By "interaction" I mean a record in bv."user_like", bv."chat" or bv."chat_message".

Here is my query:

SELECT DISTINCT ON (u.id)
    u.*
FROM bv.user_coordinates uc
JOIN bv.user u
    ON u.id = uc.user_id
JOIN (
    SELECT
      user_id,
      chat_id
    FROM bv.junction_user_chat
) chat_ids
  ON chat_ids.user_id = 102
LEFT JOIN bv.user_like ul
    ON u.id = ul.target_user_id
    AND ul.source_user_id = 102
LEFT JOIN bv.chat_message cm 
    ON cm.chat_id = any(chat_ids)
    AND (cm.user_id = u.id OR cm.user_id = 102)
WHERE u.id != 102
AND uc.user_id = u.id
AND u.is_male = true
AND ul.id is null
AND cm.id is null
GROUP BY u.id
ORDER BY u.id ASC

The problem is that I am unsure of how to make use of junction_user_chat here. Ideally, I’d like to form an array chat_ids (as I’ve tried to do), and then check to see if user ID 102 is present; if it is, then I’d like to omit it from the result.

How can I get this to work, is my approach even in the correct direction? Postgres’ array methods and data types are new to me in general, I’m much more used to junction tables, so I’d much more prefer that over the current attempt with the nested join and ANY comparison.

I will finally mention that this is a severely slimmed-down version of the actual query, which is why you are seeing a select...from bv.user_coordinates. This part can change, but I kept it in here just in case.

2

Answers


  1. Can you try the following ?

    with chats_of_user as (
      select distinct chat_id
      from bv.user userA
      join bv.junction_user_chat chatA
      on userA.id = chatA.user_id
      where userA.id= 102
    ),
    users_not_involed_in_chat as (
      select id
      from bv.user
      where bv.user.id NOT IN (
        select distinct userChatA.user_id
        from chats_of_user
        join bv.junction_user_chat userChatA
        on userChatA.chat_id = chats_of_user.chat_id
      )
    ),
    not_liked_users as (
      select id
      from bv.user
      where id NOT IN (
        select distinct userLike.target_user_id
        from bv.user userA
        join bv.user_like userLike
        on userA.id = userLike.source_user_id
        where userA.id = 102
    
      ) 
    )
    select users_not_involed_in_chat.id
    from users_not_involed_in_chat 
    join not_liked_users
    on users_not_involed_in_chat.id = not_liked_users.id
    ;

    This is working in the above scenario. You can check for other cases extensively. Please also check the perf once using explain analyze

    Login or Signup to reply.
  2. You can summarize this problem as:

    • getting the id of your chosen user
    • getting user_fans ids (people he is being liked from)
    • getting user_talkers ids (people he has talked to)
    • getting fans he has not talked to (user_fans except user_talkers)
    • retrieve user data for the remaining fans
    WITH chosen_user AS (
        SELECT id AS user_id
        FROM bv.user
        WHERE user_name = 'Jane'
    ), user_fans AS (
        SELECT source_user_id AS user_id
        FROM       bv.user_like 
        INNER JOIN chosen_user
                ON user_like.target_user_id = chosen_user.user_id
    ), user_talkers AS (
        SELECT user_chatters.user_id
        FROM       bv.junction_user_chat user_chats
        INNER JOIN chosen_user
                ON user_chats.user_id = chosen_user.user_id
        INNER JOIN bv.junction_user_chat user_chatters
                ON user_chatters.chat_id = user_chats.chat_id
        WHERE NOT user_chatters.user_id = chosen_user.user_id 
    ), fans_to_interact_with AS (
        SELECT * FROM user_fans
        EXCEPT ALL
        SELECT * FROM user_talkers
    )
    SELECT bv.user.*
    FROM       fans_to_interact_with
    INNER JOIN bv.user
            ON fans_to_interact_with.user_id = bv.user.id
    

    Output:

    id user_name is_male
    100 Mike true
    103 George true

    Check the demo here.


    If you are filtering already on the chosen user id you need, you can simplify this solution into:

    WITH user_fans AS (
        SELECT source_user_id AS user_id
        FROM bv.user_like 
        WHERE target_user_id = 102
    ), user_talkers AS (
        SELECT user_chatters.user_id
        FROM       bv.junction_user_chat user_chats
        INNER JOIN bv.junction_user_chat user_chatters
                ON user_chatters.chat_id = user_chats.chat_id
        WHERE user_chats.user_id = 102
          AND NOT user_chatters.user_id = 102
          
    ), fans_to_interact_with AS (
        SELECT * FROM user_fans
        EXCEPT ALL
        SELECT * FROM user_talkers
    )
    SELECT bv.user.*
    FROM       fans_to_interact_with
    INNER JOIN bv.user
            ON fans_to_interact_with.user_id = bv.user.id
    

    Check the demo here.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search