SQL join over five tables - sql

I have five tables:
models: id, name, specification
models_networks: id, model_id, network_id
networks: id, name, description
countries_networks: id, country_id, network_id
countries: id, countryName, etc, etc
the models table is connected to the networks table via models_networks with a many to many relation.
the networks table is connected to the countries table via countries_networks with a many to many relation
I need to do the following query, but I'm stuck:
Select all the models that will work in a specific country.
e.g.: say France has two networks. PigNetwork and CowNetwork. I want to get all the models that work on PigNetwork or CowNetwork, basically any that work in that country one way or the other.
If I've made myself clear, can someone help with the JOIN query please? I've only ever gone as far as joining two tables before. Thanks.

SELECT
m.name AS model_name,
c.countryName,
COUNT(*) AS network_count
FROM
models AS m
INNER JOIN models_networks AS mn ON mn.model_id = m.id
INNER JOIN networks AS n ON n.id = mn.network_id
INNER JOIN countries_networks AS cn ON cn.network_id = n.id
INNER JOIN countries AS c ON c.id = cn.country_id
WHERE
c.countryName = 'France'
GROUP BY
m.name,
c.countryName

Something along the lines of this should work...
SELECT M.Name As ModelName FROM Countries C
INNER JOIN Countries_Networks CN
ON C.CountryId = CN.CountryId
INNER JOIN Networks N
ON CN.NetworkId = N.NetworkId
INNER JOIN ModelNetworks MN
ON MN.NetworkId = N.NetworkId
INNER JOIN Model M
ON M.ModelId = MN.ModelId
WHERE C.CountryName = 'FRANCE'

SELECT m.id
FROM model m
WHERE EXISTS
(
SELECT NULL
FROM model_networks mn
JOIN countries_networks cn
ON cn.network_id = mn.network_id
AND cn.country_id = #code_of_france
WHERE mn.model_id = m.id
)
This is efficient since it returns a model right that moment it finds the first suitable network.
Make sure you have the following UNIQUE indexes:
model_networks (model_id, network_id)
country_network (country_id, network_id)

SELECT models.id, models.name, models.specifications JOIN models_networks ON models.id=models_networks.model_id WHERE models_networks.networks_id IN (1,2)
(replace 1,2 with your network ids )
Doesn't look like you need two joins if you just want the models. You only need more joins if you don't know the network ids or if you need columns out of the other tables. The syntax for that is the same though. You just start with JOIN <table> ON <field>=<field> before the WHERE statement

select models.id, models.name, models.specification from models inner join models_networks on models.id = models_network.network_id inner join countries_networks on models_network.network_id = countries_networks.network_id where countries_networks.countryName = 'France'

Related

How to inner join same column

Sorry for the confusing title, I don't really know how to word it. so what I'm trying to achieve is to define two different column data into one id. so teamID has the team ids, and I'm trying to define both the home ids and away ids on the same inner join, but don't know the proper script
SELECT *
FROM fixtures INNER JOIN
teams
ON fixtures.homeTeam = teams.teamID INNER JOIN
teams
ON fixtures.awayTeam = teams.teamID;
what I want to achieve is basically teamaway= teamID and homeaway = teamID and then TeamID = teamName
You need table aliases:
SELECT f.*, th.*, ta.*
FROM fixtures f INNER JOIN
teams th
ON f.homeTeam = th.teamID INNER JOIN
teams ta
ON f.awayTeam = ta.teamID;
Note . . . You should select the column that you want explicitly and give them meaningful names. For instance:
SELECT f.*, th.name as home_team, ta.name as away_team
FROM fixtures f INNER JOIN
teams th
ON f.homeTeam = th.teamID INNER JOIN
teams ta
ON f.awayTeam = ta.teamID;
Otherwise, you'll have duplicate column names which are a bit hard to understand.

Join with count

I need to write SQL query like:
Show all countries with more than 1000 users, sorted by user count.
The country with the most users should be at the top.
I have tables:
● Table users (id, email, citizenship_country_id)
● Table countries (id, name, iso)
Users with columns: id, email, citizenship_country_id
Countries with columns: id, name, iso
SELECT countries.name,
Count(users.citiizenship_country_id) AS W1
FROM countries
LEFT JOIN users ON countries.id = users.citizenship_country_id
GROUP BY users.citiizenship_country_id, countries.name
HAVING ((([users].[citiizenship_country_id])>2));
But this does not work - I get an empty result set.
Could you please tell me what I'm doing wrong?
A LEFT JOIN is superfluous for this purpose. To have 1000 users, you need at least one match:
SELECT c.name, Count(*) AS W1
FROM countries c JOIN
users u
ON c.id = u.citizenship_country_id
GROUP BY c.name
HAVING COUNT(*) > 1000;
Notice that table aliases also make the query easier to write and to read.
Group by country name and use HAVING Count(u.citiizenship_country_id)>1000, it filters rows after aggregation:
SELECT c.name,
Count(u.citiizenship_country_id) AS W1
FROM countries c
INNER JOIN users u ON c.id = u.citizenship_country_id
GROUP BY c.name
HAVING Count(u.citiizenship_country_id)>1000
ORDER BY W1 desc --Order top counts first
;
As #GordonLinoff pointed, you can use INNER JOIN instead of LEFT JOIN, because anyway this query does not return counries without users and INNER JOIN performs better because no need to pass not joined records to the aggregation.

Oracle SQL - How do i select tables with 0 or more values from other tables

Soo i need to make a consult that shows the id of the city, name, and how much clients that city have including cities that have 0 clients;
I was first trying to just get the cities that have clients but have no ideia on how to include cities that have no clients.
I have a table: CITIES that cointains ID_city, NAME, and REGION
and the table: CLIENTS that cointains ID_client, NAME and ID_city
query:
select l.name, l.ID_city, count(c.name) from clients c
JOIN cities l on l.ID_city = c.ID_city
GROUP BY l.name, l.ID_city;
use left join
select l.nomecidade, l.codcidade, count(c.nomecliente) from prova.clientes c
left JOIN prova.cidades l on l.codcidade = c.codcidade
GROUP BY l.nomecidade, l.codcidade
Use left join but make sure which table you considering first.
The code below should solve the problem:
select C1.name, C1.ID_city, count(C2.name) from cities C1
LEFT JOIN clients C2 on C1.ID_city = C2.ID_city
GROUP BY C1.name, C1.ID_city;

Fetching distinct rows from multiple joins SQL

I have one main table called deliveries and it has one to many relationship with deliveries_languages as dl, deliveries_markets dm and deliveries_tags dt having delivery_id as foreign key. These 3 tables have one to one relation with languages , markets and tags respectively. Additionaly, deliveries, table have one to one relation with companies and have company_is as foreign key. Following is a query that I have written:
SELECT deliveries.*, languages.display_name, markets.default_name, tags.default_name, companies.name
FROM deliveries
JOIN deliveries_languages dl ON dl.delivery_id = deliveries.id
JOIN deliveries_markets dm ON dm.delivery_id = deliveries.id
JOIN deliveries_tags dt ON dt.delivery_id = deliveries.id
JOIN languages ON languages.id = dl.language_id
JOIN markets ON markets.id = dm.market_id
JOIN tags ON tags.id = dt.tag_id
JOIN companies ON companies.id = deliveries.company_id
WHERE
deliveries.name ILIKE '%new%' AND
deliveries.created_by = '5f331347-fb58-4f63-bcf0-702f132f97c5' AND
deliveries.deleted_at IS NULL
LIMIT 10
Here I am getting redundant delivery_ids because for each delivery_id there are multiple languages, markets and tags. I want to use limit on distinct delivery_ids. So, limit 10 should not give me 10 records from above join query but 10 records where there is distinct delivery_id (deliveries.id). I probably can use derived table concept here but I am not sure how can I do that. Can someone please help me to resolve this issue.
In Postgres, you can use distinct on:
SELECT DISTINCT ON (d.id) d.*, l.display_name, m.default_name, t.default_name, c.name
FROM deliveries d JOIN
deliveries_languages dl
ON dl.delivery_id = d.id JOIN
deliveries_markets dm
ON dm.delivery_id = d.id JOIN
deliveries_tags dt
ON dt.delivery_id = d.id JOIN
languages l
ON l.id = dl.language_id JOIN
markets m
ON m.id = dm.market_id JOIN
tags
ON t.id = dt.tag_id JOIN
companies c
ON c.id = d.company_id
WHERE d.name ILIKE '%new%' AND
d.created_by = '5f331347-fb58-4f63-bcf0-702f132f97c5' AND
d.deleted_at IS NULL
ORDER BY d.id
LIMIT 10;
For larger amounts of data, this can be very inefficient. Multiple junction tables result in Cartesian products when used like this. However, for a smallish amount of data, this should solve your problem.

How would I join this query in one single SQL statement?

Let's say I have these tables, with these fields:
companies: company_id | name | num_employees
companies_countries: company_id | country_id
countries: country_id | country_iso_code
Assuming this is a 1:1 relationship:
How can I join the country_iso_code directly into the company recordset, when I fetch all the companies? I think I would need two joins here?
A simple example :
select c.name, n.country_iso_code
from companies c,
companies_countries x,
countries n
where x.company_id = c.company_id
and n.country_id = x.country_id
Edit
For a good intro to JOIN, have a look at SQL JOIN.
SELECT
companies.company_id,
companies.name,
companies.num_employees,
countries.country_iso_code
FROM
companies
LEFT JOIN
companies_countries ON (companies_countries.company_id = companies.company_id)
LEFT JOIN
countries ON (countries.country_id = companies_countries.country_id);
A query that uses a LEFT JOIN instead of an INNER JOIN or an implicit join, will return companies even when they have no country assigned. On the other hand, a query with an INNER JOIN would skip companies that do not have an assigned country in companies_countries.
Note that your design is implying that each company can be assigned more than one country. If you want to enforce only one country for each company, simply put a country_id column in your companies table. You would not need the companies_countries table.
select name, country_iso_code
from companies left join companies_countries
on (companies.company_id = companies_countries.company_id)
left join countries
on (companies_countries.country_id = countries.country_id)
SELECT co.*, cu.country_iso_code
FROM companies co
LEFT JOIN companies_countries cc ON cc.company_id = co.company_id
LEFT JOIN countries c ON c.country_id = cc.country_id
Why LEFT JOIN and not the WHERE condition like in other examples? A join table (companies_countries) is not typically used in 1:1 relationships - it is overnormalization.
When your relationship ceases to be 1:1:
SELECT co.*, GROUP_CONCAT(cu.country_iso_code)
FROM companies co
LEFT JOIN companies_countries cc ON cc.company_id = co.company_id
LEFT JOIN countries c ON c.country_id = cc.country_id
This will return results like
CompanyA | Canada,USA,Mexico
CompanyB | Ireland,UK,Japan
This is not a 1:1 relationship. A country can have more the one company.
This is either a 1:N relationship (for some reason implemented using two relational tables), or the M:N relationship which describes multinational companies.
If this is a 1:N relationship, you could just put the country_code field into the companies table, in which case one join would be enough:
SELECT *
FROM companies co
LEFT JOIN
countries cn
ON cn.country_code = co.country_code
Your design is viable for both 1:N and M:N relationships, in which case two joins are required:
SELECT co.*, cn.*
FROM companies co
LEFT JOIN
company_countries cc
ON cc.company_id = co.company_id
LEFT JOIN
countries cn
ON cn.country_code = cc.country_code
If this is a 1:N relationship, you should make company_id a PRIMARY KEY in the company_country table.
If this is a M:N relationship, you should make a composite PRIMARY KEY on company_country (company_id, country_code)
You may want to read this article in my blog about the difference between entity-relationship model and its relational implementation:
What is entity-relationship model?
Yes, you would need two joins.
You can create a view and fake a single join though...
select companies.* from countries, companies_countries, companies where countries.country_id=companies_countries and companies_countries.company_id=company_id and countries.country_iso_code='xxxx'
where xxxx is the iso code you want to match
select c.company_id, c.name, c.num_employees, co.county_iso_code from
companies c, companies_countries cc, countries co
where c.company_id = cc.company_id
and cc.country_id = co.country_id