select data that belongs to category - sql

I have three tables like this:
table player:
id, name
table matchevent:
id, player_id, eventcategory_id, description
table eventcategory:
id, name
Relations are:
player : matchevent - 1:N
matchevent : eventcategory - N:1
In table player I have football player names and in eventcategory events, like yellow card, substitution etc. In table matchevent I store player events which belongs to some category.
And now I want to retrieve COUNT of events that all players have. For example:
first player has COUNT of yellow cards 0, substitution 0 and goals 0
second player has 1 yellow card, 0 substitution and 2 goals
third player have 0 yellow cards, 0 substitution 0 goals
etc
How can I do that in DQL? I tried LEFT JOIN and IN, but it doesn't work. It selects only players with events, not all players.
->createQuery('SELECT p, i
FROM MyBundle:player p
LEFT JOIN p.matchevent i
WHERE i.eventcategory IN (:eventcategory)
ORDER BY p.player ASC
')
->setParameters(array(
'eventcategory' => $eventcategory,
))

Use a right join that should force it to return all players even if they don't have events.
'SELECT p, i
FROM MyBundle:player p
RIGHT JOIN p.matchevent i
WHERE i.eventcategory IN (:eventcategory)
ORDER BY p.player ASC
')

Related

Recursive CTE Query to make groups without duplicates

I need to build a sql query to find the least expensive squad (salary-wise) for teams in a European volleyball league.
For each team, the squad consists of 6 players. The entire team has 10 players (but could have more).
Positions are:
Libero
Opposite
Setter
Middle
Outside Hitter
Defensive Specialist
Here is a query result showing Players, Positions and Salaries. Note that some players can play multiple positions.
Salaries are in Euros, and are divided by 1000 to make things simpler.
Player Position Salary
--------------------------
Player A, Libero, 100
Player A, Defensive Specialist,100
Player B, Opposite, 200
Player C, Middle, 150
Player D, Outside Hitter, 175
Player D, Opposite, 150
Player E, Setter, 100
Player F, Setter, 150
Player G, Middle, 125
Player G, Opposite, 100
Player H, Libero, 75
Player I, Outside Hitter, 150
Player J, Defensive Specialist, 200
Since some players can fill multiple positions, I need weigh the benefit of moving them to another position to reduce the payroll, even if they are less expensive in the first position.
For example, if a player can fill Libero and Defensive Specialist positions, and as a Libero they are less expensive than other Liberos on the team, as a Defensive Specialist, they may even make the squad less expensive than if they were in the Libero position.
My first instinct is to generate all possible 6-person squads with all of the players (making sure that the same player is not in the same squad twice) - then ordering by the sum of salaries.
Doing this would find all combinations, and give me the least-expensive squad. But depending on the size of the team, this could be a very intensive task, so I wonder if there's a more efficient way.
I can find the first squad like this (though this doesn't check for duplicate player names in different positions):
WITH Squads AS
(
SELECT PlayerName, Salary, Position,
ROW_NUMBER() OVER (PARTITION BY Position ORDER BY Position) AS RowNumber
FROM Players
)
SELECT * FROM Squads WHERE RowNumber = 1 ORDER BY Position, Salary DESC, PlayerName
I believe I would then want to add a UNION ALL or a CROSS APPLY in the CTE to recurse through all players and positions, making sure that each squad has a player in each of the 6 positions.
What's the best method to do this?
This answer stores the position data in a separate table, and then recursively iterates over each position ID, joining previously unselected players who are listed under that position to the result. The running salary is stored, along with the players currently chosen for the given squad:
with recursive cte(pid, position, player, players, salary) as (
select 2, p.position, p1.player pl1, p1.player, p1.salary
from positions p join players p1 on p1.position = p.position where p.id = 1
union all
select c.pid + 1, p.position, p1.player, c.players||','||p1.player, c.salary + p1.salary
from cte c join positions p on p.id = c.pid join players p1 on p1.position = p.position where not c.players ~ p1.player
),
final_team as (
select c.players, c.salary from cte c where c.pid = 7
)
select f.* from final_team f where f.salary = (select min(f1.salary) from final_team f1)
See fiddle
The accepted answer works well. I had forgotten to mention what SQL flavor I was using - SQL Server 2019. The answer used PostgreSQL. Here is the SQL Server equivalent (also with Text fields changed to NVARCHAR):
with cte (pid, position, player, players, salary) as (
select 2, p.position, p1.player pl1, p1.player, p1.salary
from positions p join players p1 on p1.position = p.position where p.id = 1
union all
select c.pid + 1, p.position, p1.player, CONVERT(nvarchar(200),c.players+','+p1.player), c.salary + p1.salary
from cte c join positions p on p.id = c.pid join players p1 on p1.position = p.position where not (p1.player LIKE '%'+c.players+'%')
),
final_team as (
select c.players, c.salary from cte c where c.pid = 7
)
select f.* from final_team f where f.salary = (select min(f1.salary) from final_team f1)

Finding records where sum of count() from two different relations is atleast 2

I'm trying to create an SQL query that will return the names of non-captain players that have at least 2 fans.
My difficulty is that the fans are split into favourite teams and favourite players, so I have to count the players that are favourited as a team and individually.
Here is what i've come up with, but it doesn't return the correct records:
select players.name
from players
-- join relevant relations
join teams
on teams.name = players.team
join favplayers
on players.name = favplayers.player
join favteams
on players.team = favteams.team
-- conditions
where players.team <> teams.captain
group by players.name
having (count(favplayers.player) + count(favteams.team)) > 1;
Here is the relational model for reference:
Any ideas?
Output:
NAME COUNT(DISTINCTFAVPLAYERS.FAN) COUNT(DISTINCTFAVTEAMS.FAN)
-------------------- ----------------------------- ---------------------------
Arthurs 3 1
Becker 1 1
Bryan 0 3
Greul 0 2
Since it's possible to have favourite players who are members of a team that is nobody's favourite (and vice versa), the joins to the "favourite" tables need to be outer joins. Also, the non-captain condition should be on the name of the player, not the name of the team:
select players.name
from players
-- join relevant relations
join teams
on teams.name = players.team
left join favplayers
on players.name = favplayers.player
left join favteams
on players.team = favteams.team
-- conditions
where players.name <> teams.captain -- not players.team
group by players.name
having (count(favplayers.player) + count(favteams.team)) > 1;
This should correctly return players that are "favourited" more than once, although if you wanted to include a count of the number of times a player was "favourited", the having expression would overcount where there was more than one fan for both the player and the player's team - a better expression would be count(distinct favplayers.fan) + count(distinct favteams.fan). Note also that it would include players where a single fan had "favourited" both the player and their team.

SQL get rows containing two rows from bridge table

That title is presumably awfully worded. I have some PostgreSQL tables. There is a bridge table, that also holds extra data, so not strictly a bridge, but acts that way also. It goes something like so:
player_game
===========
player_game_id PK
player_id FK -> player
game_id FK -> game
other stuff.
I want to compile a list of all such game_ids that contain two players of my choice.
So I could find for example, games in which player 1234 played with player 9876.
There can be between 2 and 10 players in a game.
select pg1.game_id from player_game pg1
inner join player_game pg2 on pg1.game_id = pg2.game_id
where pg1.player_id = 1234
and pg2.player_id = 9876
group by pg1.game_id -- or : AND pg1.player_game_id < pg2.player_game_id
You need to join the table to itself using an alias. (for both tables) and then specify the two ids as needed
select *
from player_game as A
inner join player_game as B on A.game_id = B.game_id
where A.player_id = '' and B.player_id = ''

How to count users owning a certain game using one SQL query?

I have a table of games, like this:
ID | game name
1 legend of zelda
2 metal gear solid
3 resident evil
And another table of users owning those games, like this:
ID | User ID | Game ID
1 510 2
2 879 2
3 213 3
I need to make a list of games with a number of users owning them. From the above, the result would be:
legend of zelda (0 users)
metal gear solid (2 users)
resident evil (1 user)
How do I do the above using only 1 SQL query?
SELECT g."game name" AS name, COUNT(u.ID) AS users
FROM games AS g LEFT JOIN game_users AS u
ON g.ID = u."Game ID"
GROUP BY g."game name"
I've used SQL standard double quotes around the space-containing delimited identifiers since you didn't identify which sub-species of SQL you are using.
This gives you two columns of output - the game name and a simple count. If you want the decorative '(0 users)' and '(1 user)' notations, then you are into some more serious pain unless your DBMS provides a convenient function to handle the correct inflections for different numbers of an object in your language (apparently English - but the rules vary by language).
Doing the simple-minded computerese:
SELECT TRIM(r.name) || ' (' || r.users || ' users)'
FROM (SELECT g."game name" AS name, COUNT(u.ID) AS users
FROM games AS g LEFT JOIN game_users AS u
ON g.ID = u."Game ID"
GROUP BY g."game name") AS r
Or, slightly more sophisticated (but English-only):
SELECT TRIM(r.name) || ' (' || r.users || ' user' ||
CASE r.users WHEN 1 THEN '' ELSE 's' END || ')'
FROM (SELECT g."game name" AS name, COUNT(u.ID) AS users
FROM games AS g LEFT JOIN game_users AS u
ON g.ID = u."Game ID"
GROUP BY g."game name") AS r
However, in my book, SQL is for the data; presentation is best done in the application. Therefore, I'd probably use the first query.
Join them, group on game id, and select the count(*) of rows, or count(distinct users). If one guy owns the game twice, and you want that to count as only one owner, distinct users.
SELECT game.name, count(*)
FROM games
JOIN ownershipInfo
ON games.id = ownershipInfo.id
GROUP BY game.name --missing from original answer, oops
That will give you the number of rows.
Since you want to include where there are zero, do "LEFT JOIN" instead of plain join. If you want the count not of rows but distinct owners:
SELECT game.name, count(distinct ownershipInfo.ownerID)
FROM games
JOIN ownershipInfo
ON games.id = ownershipInfo.id
GROUP BY game.name --missing from original answer, oops
As jonathon points out you need a GROUP BY to get the aggregate information. And here I just took the expedient of grouping by name - usually you'd group by the game's ID, and do something more like:
SELECT max(game.id), count(distinct ownershipInfo.ownerID)
FROM games
LEFT JOIN ownershipInfo
ON games.id = ownershipInfo.id
GROUP BY games.id
That way if two games have the same name, you'd get a row for both (of course it wouldn't be self explanatory but maybe you'd also have MAX(publisher) or something to clarify which one was which)
SELECT g."game name" AS name, COUNT(*) AS users
FROM games AS g
INNER JOIN game_users AS u
ON g.ID = u."Game ID"
GROUP
BY g."game name"
UNION
SELECT g."game name" AS name, 0 AS users
FROM games AS g
WHERE NOT EXISTS (
SELECT *
FROM game_users AS u
WHERE g.ID = u."Game ID"
);

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;