Search code examples
sqlpostgresqlexcept

How to replace 2 queries with except


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 .


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