Query to display specific columns using a Set operator - sql

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.)

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.

The simplest illustration of using a set operator to find countries without cities would be:
select country_id from countries
minus
select country_id from locations
COUNTRY_ID
----------
1
4
As you need the country name, you just need to look it up:
select country_name from countries
minus
select c.country_name from locations l
join countries c on c.country_id = l.country_id;
COUNTRY_NAME
-----------------
England
Italy
Cities without a country (or with an invalid country code) is simpler as a left join and filter:
select l.city, l.country_id
from locations l
left join countries c on c.country_id = l.country_id
where c.country_id is null
CITY
-----------------
Berlin
Tokyo
If the requirement really is to do this using set operators, you would (conceptually) look for cities whose country_id is in the set of (location countries minus city countries):
select l.location_id, l.city, l.country_id
from locations l
where l.country_id in
( select country_id from locations
minus
select country_id from countries )
However this wouldn't give you locations whose country_id was null.

Related

self join after an inner join

I am finding what cities have the same name in different states. The city name and state name are in seperate tables (cities and states) and can be inner joined over a seperate common column.
select c1.city, c1.state, c2.city, c2.state
from cities
inner join states on cities.commonid = states.commonid
After inner joining i need to self join to perform a function as follows
select c1.city, c1.state, c2.city, c2.state
from *joined table* c1 join
*joined table* c2
on c1.city = c2.city and c1.state <> c2.state
i am wondering how i can self join a table that is the result of another join in the same query
output will be like this
+----------+-------+--------+--------+
| city1 | state1|city2 |state2 |
+----------+-------+--------+--------+
| x | melb | x | syd |
| y | bris | y | ACT |
+----------+-------+--------+--------+
I assume that the table cities has a column like state_id that references a column state_id in the table states (change the names to the actual names of the columns).
First do a self join for cities with the conditions:
c1.city = c2.city AND c1.state_id < c2.state_id
The < operator makes sure that each pair of cities wil be returned only once.
Then join 2 copies of states, because each of them will be used to get the name of the state for each of the 2 cities:
SELECT c1.city city1, s1.state state1,
c2.city city2, s2.state state2
FROM cities c1
INNER JOIN cities c2 ON c1.city = c2.city AND c1.state_id < c2.state_id
INNER JOIN states s1 ON s1.state_id = c1.state_id
INNER JOIN states s2 ON s2.state_id = c2.state_id
ORDER BY city1
You can do select of your inner join query and give it alias.
Then it will become something like below...
Select c.city,c.state from
(Select City,state from cities inner join states where cities.id = states.id) as c
Now make a self join for c.
I would perform a pre-query of all cities that have more than one state. Then join to the states table to see which states they are encountered.
select
Duplicates.city,
s.state
from
( select c1.city
from cities c1
group by c1.city
having count(*) > 1 ) Duplicates
JOIN cities c2
on Duplicates.city = c2.city
JOIN states s
on c2.commonid = s.commonid
order by
Duplicates.city,
s.state
By NOT trying to do cross-tab of just two city/states, you would get a single list. If one city name exists in 5 states, how would you plan on showing that. This way you would see all alphabetized.
I would suggest a CTE:
with cs as (
select c.name as city_name, s.name as state_name
from cities c join
states s
on c.commonid = s.commonid
)
select cs1.*, cs2.*
from cs cs1 join
cs cs2
on cs1.name = cs2.name and cs1.state <> cs2.state;

Group by Country with bridge table

I am not quite sure with the tables below to write a query that returns countries that have communities. The CommunityLocation bridge table is where I can look for companies and their locations but not quite sure how to really mold this query.
Community
community_id
Location
location_id
country_id
Country
country_id
CommunityLocation
community_id
location_id
So I just need a list of country IDs that have communities.
I think this is what you need:
select distinct
c.country_id
from
countires c
join Location l
on c.country_id = l.country_id
join CommunityLocation cl
on cl.location_id = l.location_id
I would recommend exists:
select c.country_id
from countries c
where exists (select 1
from Location l join
CommunityLocation cl
on cl.location_id = l.location_id
where c.country_id = l.country_id
);
This avoids extra processing for removing duplicates -- and that is usually a performance gain.

Using a subquery with inner join in the from clause?

I'm trying to create a table that with one column containing the number of countries and the next column being the number of official countries. So basically, one row might say there are 32 countries that have 2 official languages, 28 with 3 official languages, etc.
So far I've made a table that counts the number of official languages per each individual country.
select c.name, count(l.language) number_of_languages from world.country c
inner join (select countrycode, language, isofficial from
world.countrylanguage where isofficial='T') l on c.code=l.countrycode group
by c.name order by count(l.language)
Here's a sample of the result:
NAME NUMBER_OF_LANGUAGES
---------------------------------------------------- -------------------
Lesotho 2
Sri Lanka 2
Canada 2
Singapore 3
Belgium 3
Bolivia 3
First, your query can be simplified. It doesn't need to use a subquery:
select c.name, count(cl.language) as number_of_languages
from world.country c inner join
world.countrylanguage cl
on c.code = cl.countrycode
where cl.isofficial = 'T'
group by c.name
order by count(cl.language);
Next, use this as a subquery:
select number_of_languages, count(*)
from (select c.name, count(cl.language) as number_of_languages
from world.country c inner join
world.countrylanguage cl
on c.code = cl.countrycode
where cl.isofficial = 'T'
group by c.name
) cl
group by number_of_languages
order by number_of_languages;

Retrive counts of two columns from two diffrent tables with third table using join query in SQL

I have 3 tables: COUNTRY, STATE, CITY
This is my Country table with two columns:
CountryID, Name
This is my State table:
This is my City table:
I want to retrieve the count of states and cities according to the country table using join query.
Skipping the fact that your question is not asked well - try this query, it should work for you:
WITH
tab_a AS (
SELECT c.countryid, COUNT (s.stateid) AS state_num
FROM country c
LEFT JOIN state s ON c.countryid = s.countryid
GROUP BY c.countryid
),
tab_b AS (
SELECT c.countryid, COUNT (cc.cityid) city_num
FROM country c
LEFT JOIN state s ON c.countryid = s.countryid
LEFT JOIN city cc ON s.stateid = cc.stateid
GROUP BY c.countryid
)
SELECT a.countryid,
a.state_num,
b.city_num
FROM tab_a a JOIN tab_b b ON a.countryid=b.countryid

Getting Parent of Parent in Self Join Table

I have self join table. This table is being used to join up to 4 level, i.e.;
Region -> Country -> County -> Town
How can I get Parent of Parent of Town. To do this up to two level this is the query
SELECT t.ShortName AS Town,
(SELECT c.ShortName FROM Locations c
WHERE c.LocationId = t.ParentId) AS County
FROM Locations t
WHERE t.LocationId = 100
Now want to get Country which is parent of County.
Either hardcode another join or use a recursive CTE.
;with locs as
(
select 1 as level, ShortName, ParentId
from Locations
WHERE LocationId = 100
UNION ALL
SELECT level + 1, l.ShortName, l.ParentId
FROM Locations l
JOIN locs ON locs.ParentId = l.LocationId
)
SELECT * FROM locs;
Just pretend its 4 separate tables, using nicely named aliases:
SELECT town.ShortName as TownName,
county.ShortName as CountyName,
country.ShortName as CountryName,
region.ShortName as RegionName
FROM Locations town
INNER JOIN Locations county ON town.ParentID = county.LocationID
INNER JOIN Locations country ON county.ParentID = country.LocationID
INNER JOIN Locations region ON country.ParentID = region.LocationID
WHERE town.LocationID = 100
If not every town has a county, country, and region, then some of those might need to be LEFT OUTER joins.