SQL - Teams and Their Player Count - sql

I'd need some help here. Im stuck on this SQL Query.
I have 2 Tables team and function. Team contains ID,Teamname. Function contains ID,Role
role decides whether a person is a player or a coach (0 for player, 1 for coach).
I am supposed to list all teams that exists, but they should only count players and even teams with no players should be listed to. There's teams that only have coaches for example.
i can't find any way to count everyone within a team.
This is the closest I've come:
select teamname,
count(*)
from function left outer join team on team.id = function.id
WHERE role=0
group by teamname;

Here's one way to get the result:
SELECT t.teamname
, COUNT(1) AS player_count
FROM team t
LEFT
JOIN function f
ON f.id = t.id
AND f.role = 0
GROUP BY t.teamname;
-- or --
SELECT t.teamname
, IFNULL(SUM(f.role=0),0) AS player_count
FROM team t
LEFT
JOIN function f
ON f.id = t.id
GROUP BY t.teamname;
These queries use the team table as the driver, so we get rows from team even when there isn't a matching row in function. In the first query, we include the role=0 condition as a join predicate, but it's an outer join. In the second query, it's still an outer join, but we only "count" rows that have role=0.
NOTE It looks as if something is missing from your model; the id=id join predicate looks odd; it's odd that we'd have a column named id in the function table which is a foreign key referencing team.id.
Normally, id is the surrogate primary key in a table, and it's unique in that table. If it were unique in the function table, then that implies a one-to-one relationship between team and function, and the query will never return a count greater than 1.
Again, I strongly suspect that the "member" entity is missing from the model.
What we'd expect is something like this::
team
id PK
teamname
member
id PK
team_id FK references team.id
role 0=player, 1=coach
such that the query would look something like this:
SELECT t.teamname
, IFNULL(SUM(m.role=0),0) AS player_count
, IFNULL(SUM(m.role=1),0) AS coach_count
FROM team t
LEFT
JOIN member m
ON m.team_id = t.id

You can't select a single column when an aggregate function exists in the SELECT clause.
To get a count of all rows in the table and get the name of the team, you must use only aggregated columns or functions in the SELECT clause. For example:
SELECT COUNT(*), team.teamname
FROM function LEFT OUTER JOIN team ON team.id = function.id
WHERE function.role = 0
GROUP BY team.teamname
Gives you a total of all rows where role is equal to 0 plus the name of the team.

Use the following instead:
SELECT teamname,SUM(IF(role=0,1,0)) players
FROM function
LEFT JOIN team USING (id)
GROUP BY teamname;
This counts only those entries from 'function' that have player role (0).

Related

SQL - count of number of times a foreign key appears in a table

Schema Info:
3 tables are concerned: SIGHTING, SPOTTER, AND ORG
SIGHTING references SPOTTER through FK SpotterId.
SPOTTER references ORG through FK OrgId.
I would like a query to return two columns; one for a list of ORG.OrgName, and another for the respective total count of Spotter_ID appearances in SIGHTINGS for the corresponding ORG.OrgID.
What I have done below returns the incorrect counts for each row returned:
SELECT ORG.ORG_NAME AS ORG_NAME,
(SELECT COUNT(SIGHTINGS.SPOTTER_ID)
FROM SIGHTINGS
, SPOTTERS
WHERE SIGHTINGS.SPOTTER_ID = SPOTTERS.SPOTTER_ID
AND SPOTTERS.ORG_ID=ORG.ORG_ID) AS ORG_COUNT
FROM ORG;
It seems that you only need aggregation:
SELECT COUNT(1), orgName
FROM SIGHTING
INNER JOIN SPOTTER USING (spotterId)
INNER JOIN ORG USING (orgId)
GROUP BY orgName
This is simple aggregation, but you only need one JOIN:
select o.orgname, count(*) as numSpotters
from org o join
spotters s
on o.orgId = s.orgId
group by o.orgname;
Note: If you want all organizations, even those with no spotters, then use left join instead of join.

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.

Crafting a query for distinct data from two fields where one is often NULL

I have a table for players, a table for teams, and a table for team_players (using SQLServer 2005). Not all players are on a team. I would like to craft a query that will return one row for each non-team player, and one row for each team (i.e. there is one result row representing all 20 players on the Knicks, and that result row contains just the Knicks' team_id, but all non-team players get their own rows with unique player_id in the results set).
I am currently trying to have my result set consist of just one column, and am doing it like so:
SELECT DISTINCT ISNULL(tp.team_id, p.player_id) FROM players p
LEFT JOIN team_players tp ON tp.player_id = p.id
My question is: how can I allow this query to be ordered by teams with the most players DESC, and then by player name alphabetical for the non-team players? Is this possible with my current query base? Should I use a different method, like perhaps a UNION, to make this possible?
As in Martin's case, this is untested:
;with cteCombineData as (
select t.id, null as player_name, count(*) as player_count
from team t
inner join team_players tp
on t.id = tp.team_id
group by t.id
union all
select p.id, p.player_name, 0 as player_count
from players p
left join team_players tp
on p.id = tp.player_id
where tp.player_id is null
)
select id
from cteCombineData
order by player_count desc, player_name

SQL Logical AND operator for bit fields

I have 2 tables that have a many to many relationship; An Individual can belong to many Groups. A Group can have many Individuals.
Individuals basically just have their Primary Key ID
Groups have a Primary Key ID, IndividualID (same as the ID in the Individual Table), and a bit flag for if that group is the primary group for the individual
In theory, all but one of the entries for any given individual in the group table should have that bit flag set to false, because every individual must have exactly 1 primary group.
I know that for my current dataset, this assumption doesn't hold true, and I have some individuals that have the primary flag for ALL their groups set to false.
I'm having trouble generating a query that will return those individuals to me.
The closest I've gotten is:
SELECT * FROM Individual i
LEFT JOIN Group g ON g.IndividualID = i.ID
WHERE g.IsPrimaryGroup = 0
but going further than that with SUM or MAX doesn't work, because the field is a bit field, and not a numeric.
Any suggestions?
Don't know your data...but....that LEFT JOIN is an INNER JOIN
what happens when you change the WHERE to AND
SELECT * FROM Individual i
LEFT JOIN Group g ON g.IndividualID = i.ID
AND g.IsPrimaryGroup = 0
Here try running this....untested of course since you didn't provide any ample data
SELECT SUM(convert(int,g.IsPrimaryGroup)), i.ID
FROM Individual i
LEFT JOIN [Group] g ON g.IndividualID = i.ID
AND g.IsPrimaryGroup = 0
GROUP BY i.ID
HAVING COUNT(*) > 1
Try not using a bit field if you need to do SUM and MAX - use a TINYINT instead. In addition, from what I remember bit fields can not be indexed, so you will loose some performance in your joins.
Update: Got it working with a subselect. Select IndividualID from Group where the primary group is false, and individualID NOT IN (select IndividualID from Group where primary group is true)
You need to include the IsPrimaryGroup condition into the JOIN clause. This query finds all individuals with no PrimaryGroup set:
SELECT * FROM Individual i
LEFT OUTER JOIN Group g ON g.IndividualID = i.ID AND g.IsPrimaryGroup = 1
WHERE g.ID IS NULL
However, the ideal way to solve your problem (in terms of relational db) is to have a PrimaryGroupID in the Individual table.
SELECT COUNT(bitflag),individualId
FROM Groups
WHERE bitflag = 1
GROUP BY individualId
ORDER BY SUM(bitFlag)
HAVING COUNT(bitFlag) <> 1
That will give you each individual and how many primary groups they have
I don't know if this is optimal from a performance standpoint, but I believe something along these lines should work. I'm using OrgIndividual as the name of the resolution table between the Individal and the Group.SELECT DISTINCT(i.IndividualID)
FROM
Individual i INNER JOIN OrgIndividual oi
ON i.IndividualID = oi.IndividualID AND oi.PrimaryOrg = 0
LEFT JOIN OrgIndividual oip
ON oi.IndividualID = oip.IndividualID AND oi.PrimaryOrg = 1
WHERE
oi2.IndividualID IS NULL
SELECT IndividualID
FROM Group g
WHERE NOT EXISTS (
SELECT NULL FROM Group
WHERE PrimaryOrg = 1
AND IndividualID = g.IndividualID)
GROUP BY IndividualID