Search code examples
sqloraclejoinselectleft-join

Query to display specific columns using a Set operator


I am trying to use a Set operator to show country names(Table A col.) without a city(Table B col.) and cities(B) without a country(A). I have also tried to write this query using LEFT JOINS, which I show below and I included Table C(Regions) because I am not sure whether to use that primary key in a LEFT JOIN.

Table A (Countries):

Column_name   |   Column_id
              |
COUNTRY_ID    |     1
COUNTRY_NAME  |     2
REGION_ID     |     3

Table B (Locations):

Column_name   |   Column_id
              |
LOCATION_ID   |     1
   CITY       |     4
COUNTRY_ID    |     6

Table C(Regions):

Column_name   |   Column_id
              |
 REGION_ID    |     1

I have tried the following:

SELECT c.country_name, l.city
FROM Countries c
LEFT JOIN Locations l ON c.country_id = l.country_id

UNION

SELECT c2.country_name, l2.city
FROM Countries c2
LEFT JOIN Locations l2 ON c2.country_id = l2.country_id;

The SQL statement above returned all Table A values, and Table A values that do not contain Table B values (Countries that do not have Cities).

I also tried this statement below and got the exact same result:

SELECT c.country_name, l.city
FROM Countries c
LEFT JOIN Locations l ON c.country_id = l.country_id
LEFT JOIN Regions r ON r.region_id = c.region_id;

The one thing it is missing is Table A values not found in Table B (Countries not found in Cities.)


Solution

  • There are a lot of options to get your desired result. One way is to use LEFT JOIN to get the countries without city and RIGHT JOIN to get the cities without country:

    SELECT c.country_name, l.city
    FROM countries c
    LEFT JOIN locations l ON c.country_id = l.country_id
    WHERE l.city IS NULL
    UNION ALL
    SELECT c.country_name, l.city
    FROM countries c
    RIGHT JOIN locations l ON c.country_id = l.country_id
    WHERE c.country_name IS NULL;
    

    Another possibility is to use two LEFT JOIN, but starting from the opposite table, like this:

    SELECT c.country_name, l.city
    FROM countries c
    LEFT JOIN locations l ON c.country_id = l.country_id
    WHERE l.city IS NULL
    UNION ALL
    SELECT c.country_name, l.city
    FROM locations l
    LEFT JOIN countries c ON c.country_id = l.country_id
    WHERE c.country_name IS NULL;
    

    If you don't like using JOIN at all, you can do this using NOT IN:

    SELECT c.country_name, NULL city
    FROM countries c
    WHERE country_id NOT IN (SELECT country_id FROM locations)
    UNION ALL
    SELECT NULL country_name, l.city
    FROM locations l
    WHERE country_id NOT IN (SELECT country_id FROM countries);
    

    Or if you prefer NOT EXISTS, this will work, too:

    SELECT c.country_name, NULL city
    FROM countries c
    WHERE NOT EXISTS (SELECT 1 FROM locations WHERE country_id = c.country_id)
    UNION ALL
    SELECT NULL country_name, l.city
    FROM locations l
    WHERE NOT EXISTS (SELECT 1 FROM countries WHERE country_id = l.country_id);
    

    I created an example that shows all these queries will produce the identic outcome: db<>fiddle

    Add ORDER BY c.country_name and ORDER BY l.city to the queries in case you want the result set to be sorted by them.

    A last, but important note: As you see, I used UNION ALL instead of UNION because I don't see a reason why to use UNION in your use case. UNION ALL is much faster, so I recommend to use that unless there is a really convincing reason to do not use it. The only advantage of UNION is that it does not show duplicate rows, but I think they are very unlikely in your situation, so it should not be required.