SQL Query using multiple JOINS without sub query - sql

Lets say I have three tables with these columns,
Players - id, name
Events - id, name
Games - first_player_id, second_player_id, event_id.
And I need the players details who are playing in a game which is happening in an event.
And I could write query like,
SELECT players.id, events.id as event_id,
(SELECT name as player_one_name from players where id = games.first_player_id),
(SELECT name as player_two_name from players where id = games.second_player_id),
games.id as game_id
FROM events
INNER JOIN games on events.id = games.event_id
INNER JOIN players on games.first_player_id = players.id;"
Here I am using two sub queries to fetch players name. And it gives correct results. Can this query be optimized? For ex, can I remove any subquery or innerjoin ?
FYI, I use PostgreSQL database.
Thanks.

If you do not want sub queries in your select statement then you must provide a join for each subset. Since your database is set oriented the two INNER JOINS would prove more efficient.
SELECT players.id, events.id as event_id,
player_one_name=player_one.name,
player_tow_name=player_two.name
FROM events
INNER JOIN games on events.id = games.event_id
INNER JOIN players player_one on games.first_player_id = player_one.id
INNER JOIN players player_two on games.second_player_id = player_two.id

You must do a join for each foreign key
SELECT players_a.id, events.id as event_id,
players_a.name as player_one_name,
players_b.name as player_two_name,
games.id as game_id
FROM events
INNER JOIN games on events.id = games.event_id
INNER JOIN players players_a on games.first_player_id = players.id
INNER JOIN players players_b on games.first_player_id = players.id

The currently accepted answer is right about joining the players table twice, but mostly wrong otherwise. This would work:
SELECT e.id AS event_id
,g.id AS game.id
,p1.name AS first_player
,p2.name AS second_player
FROM events e
LEFT JOIN games g ON g.event_id = e.id
LEFT JOIN players p1 ON p1.id = g.first_player_id
LEFT JOIN players p2 ON p2.id = g.second_player_id;
Use LEFT [OUTER] JOIN to cover the cases where an event does not have a game or a game does not have both players (yet).
Use table aliases to simplify your syntax. To join the same table twice you also need at least one table alias.
After attaching an alias to a table in the FROM list, only that alias is visible in your query, not the original name of the table.
Study the manual for details.

Related

Nested Query equivalent to inner join

I'm trying to better understand nested queries, and I was wondering what would get me the same result as this:
SELECT PLAYERS.PLAYER_TAG, PLAYERS.POSITION, PLAYERS.TOTAL_KILLS, TEAMS.TEAM_NAMES, TEAMS.GAMES_PLAYED, TEAMS.WINS
FROM PLAYERS
INNER JOIN TEAMS ON TEAMS.TEAM_NAMES = PLAYERS.TEAM
ORDER BY TEAM_NAMES ASC;
but using a nested query instead. Thanks for your help
This is not that kind of query when you need any nested ones (sub-queries) due to lack of any predicates (WHERE clause).
You would not use this and would just use the INNER JOIN query.
However, if a player will only ever be on one team then this query:
SELECT PLAYER_TAG,
POSITION,
TOTAL_KILLS,
( SELECT TEAM_NAMES
FROM teams
WHERE TEAMS.TEAM_NAMES = PLAYERS.TEAM ) AS teams_names,
( SELECT GAMES_PLAYED
FROM teams
WHERE TEAMS.TEAM_NAMES = PLAYERS.TEAM ) AS games_played,
( SELECT WINS
FROM teams
WHERE TEAMS.TEAM_NAMES = PLAYERS.TEAM ) AS wins
FROM PLAYERS
ORDER BY TEAM_NAMES ASC;
(If a player can be on multiple teams then the above query will raise an exception as the nested sub-queries return more than a single row.)
Would be the (inefficient) equivalent of:
SELECT PLAYERS.PLAYER_TAG,
PLAYERS.POSITION,
PLAYERS.TOTAL_KILLS,
TEAMS.TEAM_NAMES,
TEAMS.GAMES_PLAYED,
TEAMS.WINS
FROM PLAYERS
LEFT OUTER JOIN TEAMS
ON TEAMS.TEAM_NAMES = PLAYERS.TEAM
ORDER BY TEAM_NAMES ASC;
(Using a LEFT OUTER JOIN rather than an INNER JOIN.)
If you wanted an INNER JOIN then you can add an EXISTS filter to the outer query's WHERE clause.

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.

Select Query from 3 tables with foreign keys

I Have 3 Tables with foreign keys to each other.
I want to write a SQL Server Stored Procedure to select records from one of them.
Now, let's suppose that i want all the Winner records referring to the Player records referring to The Game with the ID=2, how can i proceed?
Thank you.
you have specified all the Winner records So that i have used the left join for player and game. But the Overall code works according to the where condition.
Try This,
select w.* from Winner w
left Join Player p on p.ID_player = w.player_FK
left join Game g on g.ID_game = p.Game_FK
where Game.ID_game = 2
You need to use a SELECT and INNER JOIN then to filter on GameID 2 you can use a WHERE clause.
SELECT ID_Winner, Name, Lastname, Player_FK
FROM Winner
INNER JOIN Player on Player.ID_Pplayer = Winner.Player_FK
INNER JOIN Game ON Game.ID_game = Player.Game_FK
WHERE Game.ID_game = 2

Left outer join two levels deep in Postgres results in cartesian product

Given the following 4 tables:
CREATE TABLE events ( id, name )
CREATE TABLE profiles ( id, event_id )
CREATE TABLE donations ( amount, profile_id )
CREATE TABLE event_members( id, event_id, user_id )
I'm attempting to get a list of all events, along with a count of any members, and a sum of any donations. The issue is the sum of donations is coming back wrong (appears to be a cartesian result of donations * # of event_members).
Here is the SQL query (Postgres)
SELECT events.name, COUNT(DISTINCT event_members.id), SUM(donations.amount)
FROM events
LEFT OUTER JOIN profiles ON events.id = profiles.event_id
LEFT OUTER JOIN donations ON donations.profile_id = profiles.id
LEFT OUTER JOIN event_members ON event_members.event_id = events.id
GROUP BY events.name
The sum(donations.amount) is coming back = to the actual sum of donations * number of rows in event_members. If I comment out the count(distinct event_members.id) and the event_members left outer join, the sum is correct.
As I explained in an answer to the referenced question you need to aggregate before joining to avoid a proxy CROSS JOIN. Like:
SELECT e.name, e.sum_donations, m.ct_members
FROM (
SELECT e.id AS event_id, e.name, SUM(d.amount) AS sum_donations
FROM events e
LEFT JOIN profiles p ON p.event_id = e.id
LEFT JOIN donations d ON d.profile_id = p.id
GROUP BY 1, 2
) e
LEFT JOIN (
SELECT m.event_id, count(DISTINCT m.id) AS ct_members
FROM event_members m
GROUP BY 1
) m USING (event_id);
IF event_members.id is the primary key, then id is guaranteed to be UNIQUE in the table and you can drop DISTINCT from the count:
count(*) AS ct_members
You seem to have this two independent structures (-[ means 1-N association):
events -[ profiles -[ donations
events -[ event members
I wrapped the second one into a subquery:
SELECT events.name,
member_count.the_member_count
COUNT(DISTINCT event_members.id),
SUM(donations.amount)
FROM events
LEFT OUTER JOIN profiles ON events.id = profiles.event_id
LEFT OUTER JOIN donations ON donations.profile_id = profiles.id
LEFT OUTER JOIN (
SELECT
event_id,
COUNT(*) AS the_member_count
FROM event_members
GROUP BY event_id
) AS member_count
ON member_count.event_id = events.id
GROUP BY events.name
Of course you get a cartesian product between donations and events for every event since both are only bound to the event, there is no join relation between donations and event_members other than the event id, which of course means that every member matches every donation.
When you do your query, you ask for all events - let's say there are two, event Alpha and event Beta - and then JOIN with the members. Let's say that there is a member Alice that participates on both events.
SELECT events.name, COUNT(DISTINCT event_members.id), SUM(donations.amount)
FROM events
LEFT OUTER JOIN profiles ON events.id = profiles.event_id
LEFT OUTER JOIN donations ON donations.profile_id = profiles.id
LEFT OUTER JOIN event_members ON event_members.event_id = events.id
GROUP BY events.name
On each row you asked the total for Alice's donations. If Alice donated 100 USD, then you asked for:
Alpha Alice 100USD
Beta Alice 100USD
So it's not surprising that when asking for the sum total Alice comes out as having donated 200 USD.
If you wanted the sum of all donations, you'd better doing with two distinct queries. Trying to do everything with a single query, while possible, would be a classical SQL Antipattern (actually the one in chapter #18, "Spaghetti Query"):
Unintended Products
One common consequence of producing all your
results in one query is a Cartesian product. This happens when two of
the tables in the query have no condition restricting their
relationship. Without such a restriction, the join of two tables pairs
each row in the first table to every row in the other table. Each such
pairing becomes a row of the result set, and you end up with many more
rows than you expect.

Need help with a simple Join

Oi
Right to the problem.
SELECT *,t.id AS threadid FROM threads t
LEFT JOIN players p on p.id = t.last_poster
WHERE t.boardid = $boardid
I have two fields in threads called posterid and lastposterid. Which are the IDs of the thread starter / last poster. What I want to do is to get their names from players table.
But how?
You just need to join to your players table twice, like this.
SELECT
threads.*,
starterPlayer.*,
lastPosterPlayer.*
FROM
threads
LEFT OUTER JOIN
players starterPlayer
ON
starterPlayer.id = threads.posterid
LEFT OUTER JOIN
players lastPosterPlayer
ON
lastPosterPlayer.id = threads.lastposterid
You can join to the same table twice and give the table a different alias.
This presumes that there always will be a first and last poster, if this is the case then you want an INNER JOIN rather than a LEFT JOIN, you will need to change the select statement to get the relevant fields.
SELECT t.id AS threadid, playerFirst.name AS FirstPoster, playerLast.name as LastPoster
FROM threads t
INNER JOIN
players playerFirst ON playerFirst.id = t.posterid
INNER JOIN
players playerLast ON playerLast.id = t.lastposterid
How about...
SELECT *,
(SELECT name
FROM players
WHERE players.id = threads.posterid) AS poster,
(SELECT name
FROM players
WHERE players.id = threads.lastposterid) AS last_poster
FROM threads;