Question

I have two queries:

SELECT 
      users.id, 
      users.gender, 
      users.status, 
      users.icon_id, 
      users.image_name, 
      coords.lat, 
      coords.lng, 
      users.mess_count 
FROM 
    users 
INNER JOIN 
    coords ON users.id = coords.user_id

then I select blocked users:

SELECT 
      first_user, 
      second_user 
FROM 
    blocks 
WHERE 
    first_user = $1 OR second_user = $1

From first table I need to select all users which has coordinates and not blocked, I also need some public information(gender and etc.). Then because I need two side blocking. I have to select is user blocked him, or he was blocked by that user. So $1 is current user, and I select is my id in block table, if it is - I exclude another user from first query.

Then using string operations in my programming language I transform my string to exclude results I get from second query.

I probably can do it with EXCEPT, but I can't do it, because I have only 2 column selected with second query, and I need much more, in final result: users.id, users.gender, users.status, users.icon_id, users.image_name, coords.lat, coords.lng, users.mess_count .

Was it helpful?

Solution

There are several ways to do it, the only mildly compromising factor is that I believe you want to exclude users that appear in either of two columns in the blocks table.

SQL traditionally has weaker performance with OR logic, which the following query attempts to work around. (In part due to its ability to make better use of indexes)

SELECT 
  users.id, 
  users.gender, 
  users.status, 
  users.icon_id, 
  users.image_name, 
  coords.lat, 
  coords.lng, 
  users.mess_count 
FROM 
  users 
INNER JOIN
  coords
    ON users.id=coords.user_id
WHERE
      NOT EXISTS (SELECT * FROM blocks WHERE first_user  = users.id AND second_user = $1)
  AND NOT EXISTS (SELECT * FROM blocks WHERE second_user = users.id AND first_user  = $1)


Depending on the version of PostgreSQL, the optimiser may be less efficient with correlated sub-queries, such as those I have used above. In such a case, the following may be more performant still. (It still avoids using OR.)

SELECT 
  users.id, 
  users.gender, 
  users.status, 
  users.icon_id, 
  users.image_name, 
  coords.lat, 
  coords.lng, 
  users.mess_count 
FROM 
  users 
INNER JOIN
  coords
    ON users.id=coords.user_id
LEFT JOIN
(
   SELECT first_user  AS user_id FROM blocks WHERE second_user = $1
   UNION
   SELECT second_user AS user_id FROM blocks WHERE first_user  = $1
)
  AS blocks
    ON blocks.users_id = users.id
WHERE
  blocks.user_id IS NULL

OTHER TIPS

You could left join with the blocks table, then use the is null check.

SELECT 
      users.id, 
      users.gender, 
      users.status, 
      users.icon_id, 
      users.image_name, 
      coords.lat, 
      coords.lng, 
      users.mess_count 
FROM 
      users 
INNER JOIN coords ON users.id=coords.user_id
LEFT JOIN blocks ON users.id = blocks.first_user OR users.id = blocks.second_user
WHERE blocks.first_user IS NULL

Check this out, I wrote the query using NOT IN.

SELECT 
  users.id, 
  users.gender, 
  users.status, 
  users.icon_id, 
  users.image_name, 
  coords.lat, 
  coords.lng, 
  users.mess_count 
FROM 
  users 
  INNER JOIN coords ON users.id=coords.user_id
  Where 
   users.id NOT IN (SELECT first_user FROM blocks WHERE first_user=$1)
   AND
   users.id NOT IN (SELECT second_user FROM blocks WHERE second_user=$1)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top