SQL - Add query parameter with join - sql

There're 3 tables. Is it possible by Persons.Id to get not only the city person lives in but also all the cities all her/his friends live in?
Eg for person with Id = 1 we get 2 items: Paris(he lives in) and New York(his friend Michele lives in)
Is it possible to make it one query using PostreSQL?
Persons
Id Name CityId
1 Mike 2
2 Bred 3
3 Michele 3
FriendsConnections
Id Person1 Person2
1 1 3
2 2 3
Cities
Id Title
1 London
2 Paris
3 New York

-- Get the city for Person 2
select c.Title
from Persons p
inner join Cities c on p.CityId = c.Id
where p.Id = 2
union
-- Get the friends cities for Person 2
select c.Title
from Persons p1
inner join FriendsConnections fc on p1.id = fc.Person1
inner join Persons p2 on fc.Person2 = p2.id
inner join Cities c on p2.CityId = c.Id
where p1.Id = 2

You can try this:
SELECT
Persons.Name AS Name,
Cities.Title AS City,
Friends.Name AS Friend,
FriendsCities.Title AS FriendsCity
FROM Persons
-- Join the friends
LEFT JOIN FriendsConnections
ON FriendsConnections.Person1 = Persons.Id
OR FriendsConnections.Person2 = Persons.Id
LEFT JOIN Persons AS Friends
ON (Friends.Id = FriendsConnections.Person1
OR Friends.Id = FriendsConnections.Person2)
AND Friends.Id != Persons.Id
-- Join the city
LEFT JOIN Cities
ON Cities.Id = Persons.CityId
-- Join the friends' cities
LEFT JOIN Cities AS FriendsCities
ON FriendsCities.Id = Friends.CityId
Maybe you prefer this one:
SELECT
Friend1.Name AS Friend1,
City1.Title AS City1,
Friend2.Name AS Friend2,
City2.Title AS City2
FROM FriendsConnections
-- Join the friends
LEFT JOIN Persons AS Friend1
ON Friend1.Id = FriendsConnections.Person1
LEFT JOIN Persons AS Friend2
ON Friend2.Id = FriendsConnections.Person2
-- Join the cities
LEFT JOIN Cities AS City1
ON City1.Id = Friend1.CityId
LEFT JOIN Cities AS City2
ON City2.Id = Friend2.CityId
You can maybe change the JOIN type, depending on the foreign keys you have.

So Michel is friend of Mike and Bred, and lives in New York, Mike lives in paris and Bred in New York.
I Believe you expect a result such as:
Name | CityName | FriendName | FriendCityName
Mike | Paris | Michel | New York
Bred | New York | Michel | New York
The SQL code valid for any relational database should be as follow:
SELECT p.name, pc.title as PersonCity, f.name as FriendName, fc.title as FriendCity
FROM Persons p
-- Getting all persons friends (one to many)
INNER JOIN FriendsConnections f
ON f.person1 = p.id
-- Getting all persons cities (one to one)
INNER JOIN Cities pc
ON pc.id = f.person1
-- Getting all friend cities (one to one)
INNER JOIN Cities fc
ON fc.id = f.person2
Hope this helps.

Since you mentioned to wanted to get a list of cities, the following query should work:
Demo on DB Fiddle
select c.title city
from persons p
inner join
(
select id, cityid
from persons
union
select pm.id, pf.cityid
from persons pm
inner join friendsconnections fc on fc.person1 = pm.id
inner join persons pf on pf.id = fc.person2
union
select pm.id, pf.cityid
from persons pm
inner join friendsconnections fc on fc.person2 = pm.id
inner join persons pf on pf.id = fc.person1
) p_to_c on p_to_c.id = p.id
left join cities c on c.id = p_to_c.cityid
where p.id = 1;
Result:
city
--------
New York
Paris

Following query will give you the expected output.
select name,city,friend,D.title as friendCity from
(select A.name as name,C.title as city,B.cityid as friendcityid,B.name as friend from persons A join
(select * from friendsconnections join persons on friendsconnections.person2=persons.id)
B on A.id=B.person1
join cities C on A.cityid=C.id
)tmp
join cities D on tmp.friendcityid=D.id
Output
name city friend friendCity
Mike Paris Michele New York
Bred New York Michele New York

I would recommend using exists or in for the matching. That is, the outer select is only on cities -- that way, you don't need to incur overhead to remove duplicates:
select c.*
from cities c
where exists (select p.cityId
from persons p
where p.id = 1 and c.id = p.cityId
) or
exists (select p.cityId
from persons p join
FriendsConnections fc
on p.id = fc.person2
where fc.person1 = 1 and c.id = p.cityId
);
Here is a db<>fiddle.

Related

Get two tables from same table SQL

I have two tables
Cities:
id name
------------
1 Helsinki
2 Tukholma
3 Oslo
4 Turku
Flights
id where_id to_id
---------------------
1 1 2
2 1 3
3 2 3
4 2 4
I want to get this result
Helsinki Tukholma
Helsinki Oslo
Tukholma Oslo
Tukholma Turku
How do I compose the query? Result has two name columns and I can't get around it?
You can join twice:
select c1.name where_city, c2.name to_city
from flights f
inner join cities c1 on c1.id = f.where_id
inner join cities c2 on c2.id = f.to_id
You need two joins:
select f.*, cw.name, ct.name
from flights f join
cities cw
on f.where_id = cw.id join
cities ct
on f.to_id = ct.id;
I found this solution. Pretty clear. No JOINs
SELECT A.name, B.name FROM cities A, cities B, flights F WHERE F.where_id=A.id AND L.to_id=B.id;

Inner join on the same row twice

I have the following tables:
games:
id | tournament | player1 | player 2
1 | 1 | 1 | 2
2 | 1 | 3 | 4
players
id | name
1 | Johnson
2 | Smith
tournaments
id | name
1 | Tournament 1
Now I want to extract all information in the table 'games'.
I've used:
SELECT t.name, g.player1, g.player2
FROM tournaments AS t
INNER JOIN games AS g ON g.tournament = t.id
This works for extracting the information for the tournament - but I'm looking for the same for the players as well. The only solution I could find was after my loop to do another SQL to extract the information from the player:
SELECT * FROM players where id = player1variable
Is this the best solution? Is it possible to include this information in the first SQL?
You can do this by JOINing to the players table twice, once for each player:
SELECT t.name, p1.*, p2.*
FROM tournaments AS t
INNER JOIN games AS g ON g.tournament = t.id
INNER JOIN players AS p1 ON p1.id = g.player1
INNER JOIN players AS p2 ON p2.id = g.player2
Output:
name id name id name
Tournament 1 1 Johnson 2 Smith
Demo on dbfiddle
Note that this will give you columns with the same name, which may cause issues in your application. You can work around this by using column aliases:
SELECT t.name, p1.name AS player1, p2.name AS player2
FROM tournaments AS t
INNER JOIN games AS g ON g.tournament = t.id
INNER JOIN players AS p1 ON p1.id = g.player1
INNER JOIN players AS p2 ON p2.id = g.player2
Output:
name player1 player2
Tournament 1 Johnson Smith
Demo on dbfiddle
Use SubQuery.
SELECT t.name
, g.player1
, (select m.name from players m where m.id = g.player1) as player1_name
, g.player2
, (select m.name from players m where m.id = g.player2) as player2_name
FROM tournaments AS t
INNER JOIN games AS g ON g.tournament = t.id

Trying to count particular column from three different tables

Master table
SerNo HospitalId CityId
1 1 1
2 1 1
3 2 2
4 3 2
5 1 1
HospitalMaster
HospitalId HospitalName
1 ABC
2 XYZ
CityMaster
CityId City
1 Delhi
2 Bombay
Result
I need something like this
City TotalHospital
Delhi 1
Bombay 2
I tried joining the tables but I keep getting the total rows of the columns and not of the hospitals.
Thank you.
Left join the city master table to a subquery which finds the hospital counts for each city. Note carefully that we only count distinct hospitals, because a hospital city relationship may appear more than once in the master table.
SELECT t1.City, COALESCE(t2.cnt, 0) AS TotalHospital
FROM CityMaster t1
LEFT JOIN
(
SELECT CityId, COUNT(DISTINCT HospitalId) cnt
FROM Master
GROUP BY CityID
) t2
ON t1.CityId = t2.CityId;
Demo
Try this:
SELECT C.City,COUNT(DISTINCT HospitalID)TotalHospital
FROM CityMaster C
JOIN Master_table M ON M.CityId=C.CityId
GROUP BY C.City
You could apply join
select M.City,count(distinct M.HospitalId) from CityMaster C inner join Master M ON C.CityId = M.CityId
group by M.City
You can do it with using JOIN
Just replace #city, #hospital, #table with your table names.
select C.City,T.CityId from #city C,#hosp H,#table T WHERE T.CityId = C.CityId AND T.HospitalId = H.HospitalId Group by C.City,T.CityId
As we need city name along with count, we can get by join city master and master tables.
select max(C.cityname), count(distinct M.HospitalId)
from CityMaster C
inner join Master M
on C.Cityid = M.CityId
group by M.cityid

How to use UNION with COUNT

I have this table structure:
TABLE: PERSON TABLE: CAR
PersonID PersonID | CarID
------ ---------|---------
1 1 | 51
1 | 52
TABLE: PET TABLE: AGE
PersonID | PetID Person | AgeID
---------|---- -------|----
1 | 81 1 | 20
1 | 82
1 | 81
One person can have many cars and pets, but only one age.
I want to count the number of cars someone has, count the number of pets someone has, and list their age.
This is what I have so far:
select
car.personid as person,
count(car.carid) as cars,
null as pets
from car
where car.personid = 1
group by car.personid
union all
select
pet.personid as person,
null as cars,
count(pet.petid) as pets
from pet
where pet.personid = 1
group by pet.personid
This produces:
Person | Cars | Pets
-------|------|-----
1 | 2 | null
1 | null | 3
But I'd like the results to look like this:
Person | Cars | Pets | Age
-------|------|------|----
1 | 2 | 3 | 20
There's a fiddle here: http://sqlfiddle.com/#!3/f584a/1/0
I'm completely stuck on how to bring the records into one row and add the age column.
SQL Fiddle
Query 1:
SELECT p.PersonID,
( SELECT COUNT(1) FROM CAR c WHERE c.PersonID = p.PersonID ) AS Cars,
( SELECT COUNT(1) FROM PET t WHERE t.PersonID = p.PersonID ) AS Pets,
a.AgeID AS Age
FROM PERSON p
LEFT OUTER JOIN
AGE a
ON ( p.PersonID = a.PersonID )
Results:
| PersonID | Cars | Pets | Age |
|----------|------|------|-----|
| 1 | 2 | 3 | 20 |
Query 2:
WITH numberOfPets AS (
SELECT PersonID,
COUNT(1) AS numberOfPets
FROM PET
GROUP BY PersonID
),
numberOfCars AS (
SELECT PersonID,
COUNT(1) AS numberOfCars
FROM CAR
GROUP BY PersonID
)
SELECT p.PersonID,
COALESCE( numberOfCars, 0 ) AS Cars,
COALESCE( numberOfPets, 0 ) AS Pets,
AgeID AS Age
FROM PERSON p
LEFT OUTER JOIN AGE a ON ( p.PersonID = a.PersonID )
LEFT OUTER JOIN numberOfPets t ON ( p.PersonID = t.PersonID )
LEFT OUTER JOIN numberOfCars c ON ( p.PersonID = c.PersonID )
Results:
| PersonID | Cars | Pets | Age |
|----------|------|------|-----|
| 1 | 2 | 3 | 20 |
Should work with duplicate Petid or duplicate carid
SqlFiddle Demo
WITH person_cte
AS (SELECT *
FROM person),
car_count
AS (SELECT Count(1) AS car,
p.personid
FROM person_cte p
LEFT OUTER JOIN car c
ON p.personid = c.personid
GROUP BY p.personid),
pet_count
AS (SELECT Count(1) AS Pet,
p.personid
FROM person_cte p
LEFT OUTER JOIN pet c
ON p.personid = c.personid
GROUP BY p.personid)
SELECT c.personid,
c.car,
p.pet,
a.ageid
FROM car_count c
INNER JOIN age a
ON c.personid = a.personid
INNER JOIN pet_count p
ON p.personid = c.personid;
If there wont be any duplicates in Carid or Petid then use this
SqlFiddle Demo
SELECT p.personid,
a.ageid,
Count(DISTINCT carid) as carid,
Count(DISTINCT petid) as petid
FROM person p
INNER JOIN age a
ON p.personid = a.personid
LEFT OUTER JOIN car c
ON p.personid = c.personid
LEFT OUTER JOIN pet pe
ON p.personid = pe.personid
GROUP BY p.personid,
a.ageid
One issue I see with most of these responses is that they will only include people who own a car. What if the person doesn't have a vehicle, but has pets? What if they haven't entered their age, yet? You'd lose that metric.
Tie the person table into this as the main requirement. To get the rest of the numbers you could take various approcahes, such as a simple series of left outer joins on the other tables and count their result.
Also note that tagging "ID" at the end of values is a misnomer and considered bad design practice. If it's an age, just call it "age" or "age_value", but not "AgeID". I would also suggest denormalizing your AGE and PERSON tables and make Age (not AgeID) a nullable field.
E.G.
SELECT
PERSON.PersonID,
AgeID AS Age,
CarCount,
PetCount
FROM
#PERSON AS PERSON
LEFT OUTER JOIN AGE AS AGE
ON AGE.PersonID = PERSON.PersonID
LEFT OUTER JOIN
( SELECT PersonID, COUNT( 1 ) AS CarCount FROM CAR GROUP BY PersonID ) AS CAR
ON CAR.PersonID = PERSON.PersonID
LEFT OUTER JOIN
( SELECT PersonID, COUNT( 1 ) AS PetCount FROM PET GROUP BY PersonID ) AS PET
ON PET.PersonID = PERSON.PersonID
You need to be joining on single values, so do your counts within subqueries
select c.PersonID,a.CarID,b.PetID,c.AgeID from (
select person.PersonID, COUNT(car.CarID) as CarID
from Person INNER JOIN Car on Person.PersonID = Car.PersonID
group by Person.PersonID) a
inner join (
select person.PersonID, COUNT(Pet.PetID) as PetID
from Person INNER JOIN Pet on Person.PersonID = Pet.PersonID
group by Person.PersonID) b
on a.PersonID = b.PersonID
inner join (select PersonID,AgeID from Age) c
on a.PersonID = c.PersonID
Another method is
select person,
sum(cars) as cars,
sum(pets) as pets
from
(
select
car.personid as person,
count(car.carid) as cars,
null as pets
from car
where car.personid = 1
group by car.personid
union all
select
pet.personid as person,
null as cars,
count(pet.petid) as pets
from pet
where pet.personid = 1
group by pet.personid
) as t
group by person
Do you want to count distinct number of cars/pets? If so, add a distinct within the count.
select
person.personid as person,
count(car.carid) as cars,
count(pet.petid) as pets
age.ageID
from person
left outer join pet on pet.personid = person.personid
left outer join car on car.personid = person.personid
left outer join age on age.personid = person.personid
where car.personid = 1
group by car.personid, age.ageID;

Return 1 result per left join

Currently I am performing a left join on two tables. The first table has an id and a persons name, the second table has an id, the id of a person from table 1, and then a timestamp (of the last flight they had).
People Flights
id | name id | person_id | time
------------ ---------------------------
1 Dave 1 1 1284762115
2 Becky 2 1 1284787352
3 2 1284772629
4 2 1286432934
5 1 1289239480
When I perform my left join, I get a list of people and their flight times, but what I would like is just the list of people with their last flight times.
So SELECT p.id, p.name, f.time FROM People p LEFT JOIN Flights f ON p.id = f.person_id
Returns
1 Dave 1284762115
1 Dave 1284787352
1 Dave 1289239480
2 Becky 1284772629
2 Becky 1286432934
I would like to see just:
1 Dave 1289239480
2 Becky 1286432934
So I need to return only the match with the highest f.id or the highest f.time
SELECT
p.id, p.name, MAX(f.time) AS LastFlight
FROM
People p
LEFT JOIN Flights f ON p.id = f.person_id
GROUP BY
p.id, p.name
SELECT p.id, p.name, f.time FROM People p LEFT JOIN
(select person_id, max(time) time from flights group by person_id) f
ON p.id = f.person_id
Try this:
;with LastFlightTimes as
(
select person_id, max(id) maxid
from Flights f
group by person_id
)
SELECT p.id, p.name, f.time FROM People p
LEFT JOIN LastFlightTimes lft ON p.id = lft.person_id
left join Flights f on f.id = lft.maxid