Inner join on the same row twice - sql

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

Related

SQL - Add query parameter with join

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.

How to get the count of a particular category for each year?

I am trying out a problem which states me to find " For each year, count the number of movies in that year that had only female actors".
Table schema is as follows:
-------------------- ----------------------- ----------------------
| Movie | | Person | | Cast |
-------------------- ------------------------ ----------------------
| MovieID | year | | PersonID | Gender | | MovieID | PersonID |
-------------------- ------------------------ ----------------------
Running the following query:
SELECT M.YEAR, COUNT(M.MID) NUMBER_OF_FEMALE_ONLY_MOVIES FROM MOVIE M
WHERE M.MID IN (SELECT X.MID FROM (SELECT AX.MID, COUNT(AX.PID) TOTAL_CAST
FROM M_CAST AX GROUP BY AX.MID) X
WHERE
X.TOTAL_CAST = (SELECT COUNT(A.PID) FROM M_CAST A, PERSON B WHERE A.MID =
X.MID AND
TRIM(B.PID) = TRIM(A.PID) AND B.GENDER = 'Female')) GROUP BY M.YEAR
My results are :
---------------------------------------
| year | NUMBER_OF_FEMALE_ONLY_MOVIES |
---------------------------------------
| 1999 | 1 |
| 2005 | 1 |
| 2009 | 1 |
| 2012 | 1 |
| 2018 | 1 |
----------------------------------------
But I need to return 0 as count for the years which do not have any such movies.
Eg.
2013 0
WITH
PERSON_CAST_MERGE AS
(
SELECT P.PID,C.MID,GENDER
FROM PERSON P
INNER JOIN M_CAST C ON C.PID = P.PID
),
MALE_COUNT AS
(
SELECT F.MID FROM PERSON_CAST_MERGE F
WHERE TRIM(F.GENDER) NOT LIKE "%FEMALE%"
),
FEMALE_COUNT AS
(
SELECT F.MID FROM PERSON_CAST_MERGE F
WHERE TRIM(F.GENDER) LIKE "%FEMALE%"
),
ONLY_FEMALE AS
(
SELECT F.MID FROM FEMALE_COUNT F
WHERE F.MID NOT IN (SELECT M.MID FROM MALE_COUNT M)
),
TEST AS
(
SELECT M.YEAR,COUNT(M.MID) AS NO_OF_MOVIES
FROM ONLY_FEMALE F
INNER JOIN MOVIE M ON M.MID = F.MID
GROUP BY M.YEAR
)
SELECT M.YEAR,
CASE
WHEN M.YEAR IN (SELECT F.YEAR FROM TEST F) THEN
(SELECT F.NO_OF_MOVIES FROM TEST F WHERE F.YEAR = M.YEAR)
WHEN M.YEAR <> (SELECT F.YEAR FROM TEST F) THEN
0
END
AS NO_OF_MOVIES
FROM MOVIE M
GROUP BY M.YEAR
I'd suggest exploring the data within the CTE to get a better understanding.
First CTE (all_cast): Return the entire movie cast
Second CTE (male_present): Return movie id's from all_cast where there exists male actors.
Result: Return movies from all_cast where movie id is not present in male_present
WITH all_cast AS (
SELECT SUBSTR(m."year",-4) as 'Year', m.title, trim(m.MID) as MID, p.Name, trim(p.Gender) as Gender
FROM Movie m
JOIN M_Cast mc
ON m.MID = mc.MID
JOIN Person p
ON trim(mc.PID) = p.PID
),
male_present AS (
SELECT year, mid, name
FROM all_cast
WHERE Gender = 'Male'
)
SELECT year, COUNT(DISTINCT mid) as 'All Female Cast'
FROM all_cast a
WHERE NOT EXISTS (SELECT * FROM male_present WHERE a.mid = mid)
GROUP BY year
You need only the group by with subquery as you require reference to the movieids of personids with gender as female in person
SELECT YEAR, COUNT(*) FROM
MOVIE
Where MovieId IN (SELECT MOVIEId
from CAST WHERE PERSONID IN
(Select PersonId from Person Where
Gender ='FEMALE'))
Group by Year
Try this- A DISTINCT MovieID is required as there may have multiple Female casting for a single movie. Distinct will provide the actual count of movies.
SELECT
M.Year,
COUNT(DISTINCT MovieID)
FROM Movie M
INNER JOIN Cast C ON M.MovieID = C.MovieID
INNER JOIN Person P ON C.PersonID = P.PersonID
WHERE P.Gender = 'Female'
GROUP BY M.Year;
I think the problem can be solved by joining all tables and filtering on WHERE clause for female actors. In this case joining tables will also give better performance rather than sub-querying.
Please try the following code:
Select year, count(*)
from movie
join Cast on movie.movieid = cast.movieid
join person on person.personid = cast.personid
where person.gender = 'Female'
group by year
Please let me know if that works fine for you.
By merging your query with the Movie table using the outer left join, you can get the desired results. The time taken will be very low compared to the answer posted by #Lucky
WITH FEMALE_ONLY AS
(SELECT M.YEAR,
COUNT(M.MID) COUNT_ALL_FEMALE
FROM MOVIE M
WHERE M.MID IN
(SELECT Q.MID
FROM
(SELECT MC.MID,
COUNT(MC.PID) total
FROM M_CAST MC
GROUP BY MC.MID) Q
WHERE Q.total =
(SELECT COUNT(A.PID)
FROM M_CAST A,
PERSON B
WHERE A.MID = Q.MID
AND TRIM(B.PID) = TRIM(A.PID)
AND B.Gender = 'Female'))
GROUP BY M.YEAR)
SELECT DISTINCT M.year,
coalesce(FO.COUNT_ALL_FEMALE, 0) FEMALE_ONLY_MOVIES
FROM Movie M
LEFT OUTER JOIN FEMALE_ONLY FO ON M.year = FO.year
ORDER BY M.year;
You can do like this
select z.year, count(*)
from Movie z
where not exists (select *
from Person x, M_Cast xy
where x.PID = xy.PID and xy.MID = z.MID and x.gender!='Female')
group by z.year;

SQL: Find country name of the team having the most players who have never scored a goal

I have the following tables:
create table Players (
id integer,
name varchar(50) not null,
birthday date,
memberOf integer not null,
position varchar(20).
primary key (id),
foreign key (memberOf) references Teams(id)
);
create table Goals (
id integer,
scoredIn integer not null,
scoredBy integer not null,
timeScored integer not null,
rating varchar(20),
primary key (id),
foreign key (scoredIn) references Matches(id),
foreign key (scoredBy) references Players(id)
);
create table Teams (
id integer,
country varchar(50) not null,
primary key (id)
);
I have the following data in the above tables:
PLAYERS:
id | name | birthday | memberof | position
7 Mina 1997-01-20 1 Captain
9 John 1997-09-01 1 Quarterback
2 Minnie 1995-10-13 3 Goalkeeper
13 Lisa 1997-03-27 4 Captain
12 Rina 1995-01-03 2 Fullback
11 Jasper 2002-09-22 1 Halfback
17 Rose 1997-02-11 1 Goalkeeper
22 Parvin 1993-03-09 3 Goalkeeper
25 Nasom 1996-12-29 3 Fullback
GOALS:
id | scoredin | scoredby | timescored | rating
1 10 7 60 amazing
2 10 7 30 okay
3 10 7 90 amazing
4 20 9 119 nice
5 20 9 80 amazing
6 20 9 75 amazing
7 30 2 30 nice
8 30 2 90 amazing
9 40 13 110 amazing
TEAMS:
id | country
1 Australia
2 Malaysia
3 Japan
4 Thailand
I am trying to output the country name of the team which has the most players who have never scored a goal. The output should be:
Country | Players
Australia 2
Japan 2
I have the following view, which gives the count of players who have never scored a goal for each country:
create or replace view zerogoals as
select t.country, count(*)
from (
select distinct p.id, p.name, p.memberof, g.scoredby
from players p
full outer join goals g
on p.id = g.scoredby where scoredby is null
) s
inner join teams t on t.id = s.memberof group by t.country;
The above query gives me the following output:
country | count
Australia 2
Japan 2
Malaysia 1
I tried using the max function to get the desired output:
select country, max(count)
from zerogoals
group by country;
However I get the following output:
country | max
Australia 2
Japan 2
Malaysia 1
I am not sure how to get the tuples in the view zerogoals with the maximum value for the attribute count. Any insights are appreciated.
You can use a CTE:
with cte as (
select
t.id, t.country, count(*) players
from teams t inner join (
select * from players
where id not in (select scoredby from goals)
) p on p.memberOf = t.id
group by t.id, t.country
)
select country, players
from cte
where players = (select max(players) from cte)
order by country
See the demo.
Results:
country | players
Australia | 2
Japan | 2
You could try using a inner join between the player, team and the list of not in goals ordered by count and limit to 1
select t.name , count(*)
from player p
INNER JOIN team t ON t.id = p.memberof
inner join (
select p.id
from PLAYERS p
where p.id NOT IN (
select scoredby
from GOALS
) ) t1 on t1.id = p.id
group by t.name
order by count(*) desc
limit 1
if you want all the max then
select t.name , count(*)
from player p
INNER JOIN team t ON t.id = p.memberof
inner join (
select p.id
from PLAYERS p
where p.id NOT IN (
select scoredby
from GOALS
) t1 on t1.id = p.id
group by t.name
having count(*) = (
select t.name , count(*)
from player p
INNER JOIN team t ON t.id = p.memberof
inner join (
select p.id
from PLAYERS p
where p.id NOT IN (
select scoredby
from GOALS
) t1 on t1.id = p.id
group by t.name
order by count(*)
limit 1
)
To get the number of players per country with no goal, you can use:
select t.name, count(*) as num_players_no_goal
from team.t join
player p
on t.id = p.memberof
where not exists (select 1
from goals g
where g.scoredby = p.id
)
group by t.name;
To limit this to just the maximum number, use window functions:
select name, num_players_no_goal
from (select t.name, count(*) as num_players_no_goal,
rank() over (order by count(*) desc) as seqnum
from team.t join
player p
on t.id = p.memberof
where not exists (select 1
from goals g
where g.scoredby = p.id
)
group by t.name
) t
where seqnum = 1;
One slight caveat is that this returns no teams if all players on all teams have scored goals. It is easily modified for that situation, but I'm guessing that you would rather return zero teams than all teams if that were the case.

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