Selecting relation with exact keys in a many to many - sql

I have an M2M relation on two tables in an SQL database, as follows:
Players
-------
Name
ID
Teams
------
Name
ID
PlayersTeams
------
PlayerID
TeamID
A team consists of 1 or more players.
I would like to query for a team given its player IDs, and return only the team that contains exactly those players, no more, no less. So querying for a team with players (1,2,3) would only return team with players 1,2,3, and not a team with players 1,2,3,4.
Is this possible in a single query?
I've got an sqlfiddle I'm trying to work it out in here: http://sqlfiddle.com/#!2/27799/8
in that example i'd like to be able to select the team "john and mick" by querying with player IDs 1 and 2...
update
in this sqlfiddle http://sqlfiddle.com/#!2/27799/69 I can select the team ID 2 ("john and mick") but it also gets team ID 4 ("john, mick and trev"). Need to filter it down to JUST 2.

select TeamId
from PlayersTeams
group by TeamId
having count(*) = sum(case when playerid in (1,2) then 1 else 0 end)
and count(*) = 2
Not familiar with mysql so I don;t know how to get the players list length (the count(*) =2) to make is fully dynamic but you get the point.

I added player 3 in team 3 to create all possible cases. This is my answer:
SELECT t.*
FROM playersteams a,
teams t
WHERE a.teamid = t.id
AND (SELECT COUNT(*) FROM playersteams c
WHERE c.teamid = a.teamid
AND c.playerid = 2) = 1
AND (SELECT COUNT(*) FROM playersteams c
WHERE c.teamid = a.teamid
AND c.playerid = 3) = 1
GROUP BY t.id, t.name
HAVING COUNT(a.playerid) = (SELECT COUNT(*) FROM players WHERE id IN (2,3));
SQL Fiddle: demo

Related

Sql query for the lowest score per country

I have a database with 3 tables. In table country I have id and name columns. The sport table also has id and name columns. Finally, the table match has id, player1, and player2(that are ids of country that play one against other), winner_id (id of country that won the match) and sport_id of the sport which was played. The least wins means that I just need in which sport country had the least wins, no matther on played matches.
I want to show the sport per country with the least wins. It should look like this:
Country
Sport
Wins
France
Basketball
2
How can I construct this query? I'm using SQL Server.
Data in table look like this. Table countries:
country_id
name
1
France
2
England
Table sport:
sport_id
name
1
Footbal
2
Basketball
Table match:
match_id
player1
player2
winner_id
sport_id
1
3
1
3
1
2
6
4
4
2
I want to note that the used wording with least wins is not clear, in my solution with least wins means most matches played with least wins.
To get this ranking, we need to know how many matches a country has played in each sport and how many of those have been won.
SELECT
country.name AS country,
sport.name AS sport,
sport_wins.wins
FROM
country
OUTER APPLY (
SELECT TOP 1
t.match_count,
COALESCE(t.wins, 0) AS wins,
t.sport_id
FROM (
SELECT
COUNT(*) AS match_count,
m_c.sport_id,
t.wins
FROM match m_c
OUTER APPLY (
SELECT
COUNT(*) AS wins,
match.sport_id
FROM match
WHERE country.country_id = match.winner_id
AND match.sport_id = m_c.sport_id
GROUP BY match.sport_id
) t
WHERE country.country_id IN (m_c.player1, m_c.player2)
GROUP BY m_c.sport_id, t.wins
) t
ORDER BY t.wins ASC, t.match_count DESC
) sport_wins
JOIN sport ON sport.sport_id = sport_wins.sport_id
Please, check a demo.
If you do not take into account losses, but only the number of wins is of interest, you can use a query like this one
WITH cte AS (
SELECT
country.country_id,
sport.sport_id,
SUM(CASE WHEN match.winner_id = country.country_id THEN 1 ELSE 0 END) AS wins
FROM country
CROSS JOIN sport
JOIN match ON match.sport_id = sport.sport_id
AND country.country_id IN (match.player1, match.player2)
GROUP BY country.country_id, sport.sport_id
)
SELECT
country.name,
sport.name,
t.min_wins AS wins
FROM (
SELECT
country_id,
MIN(wins) AS min_wins
FROM cte
GROUP BY country_id
) t
JOIN cte ON cte.country_id = t.country_id AND cte.wins = min_wins
JOIN country ON cte.country_id = country.country_id
JOIN sport ON cte.sport_id = sport.sport_id
This query takes into account the fact that the country participates in matches in sport, so if a country does not compete in a sport, that sport will not be included in the statistics as it will have 0 wins and this will be the minimum value.
Please, check a demo
You need to first cross-join the sports with the countries, then get the total.
Then you can use a row-number approach to get the bottom country in each sport
SELECT
c.Country,
c.Sport,
c.Wins
FROM (
SELECT
c.name Country,
s.name Sport,
COUNT(m.winner_id) Wins,
ROW_NUMBER() OVER (PARTITION BY s.sport_id, s.name ORDER BY COUNT(m.winner_id)) rn
FROM country c
CROSS JOIN sport s
LEFT JOIN [match] m
ON s.sport_id = m.sport_id AND m.winner_id = c.country_id
GROUP BY
s.sport_id,
s.name,
c.country_id,
c.name
) c
WHERE c.rn = 1;

Select the number of English players from team which scored the most goals at home on a match

TEAM
ID
TEAM_NAME
1
Arsenal
MATCH
ID
DATE
HOME_TEAM_ID
AWAY_TEAM_ID
HOME_SCORE
AWAY_SCORE
1
2018-08-10
10
7
2
1
NATION
ID
NATION_NAME
1
Spain
PLAYER
ID
NAME
NATION_ID
TEAM_ID
100
David de Gea
1
10
GOAL
GOAL_ID
MATCH_ID
PLAYER_ID
GOAL_ORDER
OWN_GOAL
1
1
106
1
False
I tried to calculate the number of English players from that team which scored the most goals at home, from the database above (SQL Server).
My query:
SELECT TOP 1 COUNT(PLAYER.PLAYER_ID), MAX(HOME_SCORE)
FROM PLAYER
JOIN NATION ON PLAYER.NATION_ID = NATION.NATION_ID
JOIN MATCH ON MATCH.HOME_TEAM_ID = PLAYER.TEAM_ID
WHERE NATION.NATION_NAME = 'England'
GROUP BY PLAYER.PLAYER_ID
ORDER BY MAX(MATCH.HOME_SCORE) DESC
It gives correct answer for the max(home_score), but the count of English players in that team isn't correct.
You need to first get the highest scoring team, then left-join NATION to PLAYER, then count the number of non-null NATION_ID.
You are also grouping by the wrong values
SELECT TOP (1)
p.TEAM_ID,
NumPlayers = COUNT(*),
NumEnglishPlayers = COUNT(n.NATION_ID),
HighestScore = m.HOME_SCORE
FROM (
SELECT TOP (1) *
FROM MATCH m
ORDER BY
m.HOME_SCORE DESC
) m
JOIN PLAYER p ON m.HOME_TEAM_ID = p.TEAM_ID
LEFT JOIN NATION n ON p.NATION_ID = n.NATION_ID
AND n.NATION_NAME = 'England'
GROUP BY
m.HOME_TEAM_ID,
m.HOME_SCORE;
db<>fiddle
Just an untested notepad scribble.
Since I can't verify it without sample data.
SELECT TOP 1
match.match_id
, match.home_score
, COUNT(CASE
WHEN player_nation.nation_name = 'England'
THEN player.player_id
END) AS EnglishPlayers
, COUNT(goal.goal_id) AS TotalGoals
FROM GOAL AS goal
JOIN PLAYER AS player
ON player.player_id = goal.player_id
JOIN MATCH AS match
ON match.match_id = goal.match_id
AND match.home_team_id = player.team_id
LEFT JOIN NATION AS player_nation
ON player_nation.nation_id = player.nation_id
GROUP BY match.match_id
ORDER BY TotalGoals DESC

How to count the difference between two values in SQL?

Sorry if my title is not detailed
I have two tables
Game table:
homeTeam int
awayTeam int
homePoints int
awayPoints int
Team
tid int
name varchar(20)
I am trying to find the number of games won at home by a specific team, lets say 'cops', with Team.tid = Game.homeTeam and wins are counted if homePoints > awayPoints
I want to end up with
Team HomeWins
-----------------
Cops 20
How do I go about that?
EDIT: #
I Managed to get my answer using
SELECT t.name, count(CASE WHEN homePoints > awayPoints then 1 ELSE NULL END) as "Home Wins"
from Team t
JOIN Game g
ON t.tid = g.homeTeam
where t.name = 'Patriots'
GROUP BY t.name
some of the other answers were giving me the following errors
Column 'team.name' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
You should join the tables to be able to get both name and homePoints and use COUNT by homePoints and group by using team id to see a result for each team.
SELECT
T.name, COUNT(G.homePoints)
FROM
team T
INNER JOIN
game G ON G.homeTeam = T.tid
WHERE
G.homePoints > G.awayPoints
GROUP BY T.tid, T.name;
OR if you need the result for a specific team by providing it's id, you may drop the GROUP BY and add condition in the WHERE, e.g.
SELECT
T.name, COUNT(T.tid)
FROM
team T
INNER JOIN
game G ON G.homeTeam = T.tid
WHERE
G.homePoints > G.awayPoints and T.tid = :request_team_id;
First SELECT the columns that we want to display and use AS to specify they headings.
SELECT
team.name as 'Team',
COUNT(game.homePoints) AS 'HomeWins'
FROM dbo.team
Then we use INNER JOIN to only include the entries in game table, which match the Team ID in the homeTeam column vs what we selected from the team table.
INNER JOIN game on team.tid = game.homeTeam
Then we add a WHERE clause to limit it to only the team we ask for team.name = 'cops' and only include wins by that team
WHERE
team.name = 'cops'
AND game.homePoints > game.awayPoints
So all together your script should look like this;
SELECT
team.name as 'Team',
COUNT(game.homePoints) AS 'HomeWins'
FROM dbo.team
INNER JOIN game on team.tid = game.homeTeam
WHERE
team.name = 'cops'
AND game.homePoints > game.awayPoints
GROUP BY team.name

Sql Query Join in Oracle

I have Parent table and multiple child tables with foreign key constraint.
School Table
School ID EduDetails Genders Address_id EDUTYPE
1 2 M 3 FGN
And the child tables like
Education Details
EDU ID EducationType
2 Online
AKA Name
School Id AKA Name
1 Test School
1 School Test
Gender Table
Gender ID Gender Desc
M Male
I am using Left outer join for the parent and school table to fetch the results.
But My issue is, If AKA table has 5 counts matching the school Id and Gender table has only 1 records for that school Id.
So the results comes with 5 duplicate rows with school Information and also other child table information.
Is there any workaround to fix this issue. I tried using subquery and row_number over by function. But it is not working for me. Can anybody help me to solve this issue.
Thanks in advance for your time in looking this issue.
My required output should be like this
School_id AKA Name GenderDesc EductaionType
1 Test School Male Online
1 School Test
So I need to have Null values for the not matching records.
Since you want all the records in the AKA Name table, I've joined on that getting a Row_Number for each row. Then using that Row_Number, LEFT JOIN on the other tables.
SELECT S.SchoolId,
SA.AKAName,
G.GenderName,
ED.EducationType
FROM School s
JOIN
(SELECT SchoolId,
AKAName,
ROW_NUMBER() OVER (PARTITION BY SchoolId ORDER BY AKAName) rn
FROM SchoolAKA
) SA ON S.SchoolID = SA.SchoolId
LEFT JOIN
(SELECT EDUID,
EducationType,
ROW_NUMBER() OVER (ORDER BY EducationType) rn
FROM EduDetails
) ED ON S.EDUID = ED.EDUID AND SA.rn = ED.rn
LEFT JOIN
(SELECT GenderId,
GenderName,
ROW_NUMBER() OVER (ORDER BY GenderName) rn
FROM Genders
) G ON S.GenderId = G.GenderId AND SA.rn = G.rn
Here is the SQL Fiddle.
And here are the results:
SCHOOLID AKANAME GENDERNAME EDUCATIONTYPE
1 School Test Male Online
1 Test School (null) (null)

how to write this query in sql

how to write this query in sql :
For every player that has played more than two games, list the player name, total amount of winnings and number of games played for each player". The result should be sorted by the winnings in descending order.
and i have in player table these attributes:
playerId,playerName,age
and in games table these attrubites:
gameId,playerId,results note the results attrubie is filled either by (first or second or third or,..,or no show) the winner is the one who has the result= first
this is my weak query i didn't got the right answer ,but that all what i can do . any idea
select playerName,count(*),count(*)
from games,player
where games.playerId=player.playerId
group by games.results
You want to look into GROUP BY and HAVING in conjunction with COUNT. Something like this would probably do (untested):
SELECT
p.playerName
,COUNT(g.*)
,SUM(g.Winnings) -- you didn't name this column
FROM
games g
INNER JOIN ON g.playerId = p.playerId
WHERE
g.results = 1 -- whatever indicates this player was the winner
GROUP BY
p.playerName
HAVING
COUNT(g.*) > 2
*Try this (pretty much as you said it in English...
(if "winnings" is amount won in the game), then:
Select playerName, count(*) Games, -- Number of game records per player
Sum(g.Winnings) Winnings -- Sum of a Winnings attribute (dollars ??)
from player p Join Games g -- from the two tables
On g.PlayerId = p.PlayerId -- connected using PlayerId
Group by p.playerName -- Output in one row per Player
Having Count(*) > 2 -- only show players w/more than 2 games
Order By Sum(g.Winnings) -- sort the rows based on Player Winnings
if by "Winnings" you mean the number of games won, then...
Select playerName, Count(*) Games, -- Number of game records per player
Sum(Case g.WonTheGame -- or whatever attribute is used
When 'Y' Then 1 -- to specify that player won
Else 0 End) Wins -- Output in one row per Player
From player p Join Games g -- from the two tables
On g.PlayerId = p.PlayerId -- connected using PlayerId
Group by p.playerName -- Output in one row per Player
Having Count(*) > 2 -- only show players w/more than 2 games
Order By Sum(Case g.WonTheGame -- Sort by Number of games Won
When 'Y' Then 1
Else 0 End)
Try this :
SELECT playerName, COUNT(g.PlayerID) as NumberOfPlays
FROM games g ,player p
WHERE g.playerId=p.playerId
GROUP BY g.PlayerID
HAVING COUNT(g.PlayerID) > 1
ORDER BY g.results DESC
SELECT - the data you want to display
FROM - the tables
WHERE - both IDs match each other
GROUP BY - Games PlayerID, so all the counts are correct
HAVING - Make sure they played more then one game
ORDER BY - Order the results the way you want them.
it's tough to glean exactly what you need from your question but try something like this:
select playerName, count(*)
from games g
join player p ON g.playerId = p.playerId
group by playerName
having count(*) > 2
order by games.results DESC
select
playerName,
sum(if(games.result = 'first',1,0)) as wins,
count(*) as gamesPlayed
from player
join games on games.playerId = player.playerId
group by games.results
having count(*) > 2
order by count(*) desc;