How to count the teams with no foreign players - mysql

I have a table for the football clubs of a country. The fields are "teamName", "playerName", and "country".
I'd like to count the clubs that all their players are foreigners
. I tried the following query but I think it's not working since it seems that it counts when we have at least one foreigner but I want it to count if all the players of a team are foreigners!
SELECT COUNT(DISTINCT teamName)
FROM teams
WHERE country not like '%England%'
Please advise. Thanks!

One way would be:
SELECT COUNT(DISTINCT teamName)
FROM teams T1
WHERE NOT EXISTS
( select * from teams T2
WHERE T1.teamName=T2.teamName and T2.country like '%England%')

Hmm even join seems to need an inner query:
SELECT COUNT(*)
FROM (
SELECT teamName,
SUM(country like '%England%') AS Locals
FROM teams
GROUP BY teamName
) AS t
WHERE Locals = 0;
Seems like there should be a shorter answer though...

Quick and dirty answer, and it does only one pass through the data, no subquery. This selects teams that are all foreign. You can play with the CASE expression if that is not what you want.
SELECT team_name
FROM teams
GROUP BY team_name
HAVING COUNT(country)=
SUM(CASE country != 'England' WHEN TRUE THEN 1 ELSE 0 END);
Longer answer: Your schema is not normalized, but should be. You want want table of teams and a second table of players, which includes a foreign key into the team table for that player's current team. This is basic DB normalization. However, replacing the single table in the FROM with the join of those two tables, the same GROUP BY/HAVING trick works.

Related

Subquery has been wrong all this time what do I do?

So I have the following table structure for a Sports Event system
TEAMS TABLE
team_id
game_id
team_name
team_logo
PLAYERS TABLE
player_id
team_id
player_name
player_mobile
player_email
So whenever a player submits a team registration details get saved on both tables. Events could be something like Cricket, Basketball, Netball, etc. Sometimes they dont fill in players details and sometimes they resubmit their team again which means same team name is submitted.
So whenever I need to check the accurate details of the team list I have been using this:
SELECT team_id FROM `teams` WHERE `game_id`= 35 GROUP BY `team_name
To get a list of the people in these teams that are the same name I was using this:
SELECT team_id, player_name FROM `player` WHERE team_id IN (SELECT team_id FROM `teams` WHERE `game_id`= 35 GROUP BY `team_name`) AND player_name IS NOT NULL AND player_name <> ''
The problem is the query on top gives me different results to what I am getting on the bottom. What I need to do is to get a list of current teams whenever i need. Duplicates of teams should be not there. Then I need a list of the players of these teams.
Currently stumped :( Help me pls.
TL;DR
You can get the desired results with a JOIN and DISTINCT
SELECT DISTINCT t.team_name, P.player_name
FROM teams AS t
INNER JOIN Players AS p
ON p.team_id = t.team_id;
FULL EXPLANATION
The following query is not deterministic, that is to say, you could run the same query on the same data multiple times and get different results:
SELECT team_id
FROM `teams`
WHERE `game_id`= 35
GROUP BY `team_name`;
Many DBMS would not even allow this query to run. You have stated that some teams are duplicated, so consider the following dummy data:
team_id team_name game_id
------------------------------------
1 The A-Team 35
2 The A-Team 35
3 The A-Team 35
When you group by team_name you are end up with one group, so if we start with a valid query:
SELECT team_name
FROM `teams`
WHERE `game_id`= 35
GROUP BY `team_name`;
We would expect one result:
team_name
--------------
The A-Team
When you add team_id in to the select, with no aggregate function, you need to pick one value for team_id, but the query engine has 3 different values to chose from, and none of them are more correct than any other. This is why anything in the select statement, must be contained within the group by (or functionally dependent on something that is), or part of an aggregate function.
The MySQL Docs state:
In standard SQL, a query that includes a GROUP BY clause cannot refer to nonaggregated columns in the select list that are not named in the GROUP BY clause. For example, this query is illegal in standard SQL because the name column in the select list does not appear in the GROUP BY:
SELECT o.custid, c.name, MAX(o.payment)
FROM orders AS o, customers AS c
WHERE o.custid = c.custid
GROUP BY o.custid;
For the query to be legal, the name column must be omitted from the select list or named in the GROUP BY clause.
MySQL extends the use of GROUP BY so that the select list can refer to nonaggregated columns not named in the GROUP BY clause. This means that the preceding query is legal in MySQL. You can use this feature to get better performance by avoiding unnecessary column sorting and grouping. However, this is useful primarily when all values in each nonaggregated column not named in the GROUP BY are the same for each group.
The reason this clause exists is valid, and can save some time, consider the following query:
SELECT t.team_id, t.team_name, COUNT(*) AS Players
FROM teams AS t
LEFT JOIN Players AS p
ON p.team_id = t.team_id
GROUP BY t.team_id;
Here, we can include team_name in the select list even though it is not in the group by, but we can do this safely since team_id is the primary key, therefore it would be impossible to have two different values of team_name for a single team_id.
Anyway, I digress, the problem you are most likely having is that the value returned for team_id in each of your queries will likely be different depending on the context of the query and the execution plan chosen.
You can get a distinct list of players and teams using DISTINCT:
SELECT DISTINCT t.team_name, P.player_name
FROM teams AS t
INNER JOIN Players AS p
ON p.team_id = t.team_id;
This is essentially a hack, and while it does remove duplicate records it does not resolve the underlying issue, of duplicate records, and potentially a sub-optimal data structure.
If it is not too late, I would reconsider your design and make a few changes. If team names are supposed to be unique, then make them unique with a unique constraint, so instead of working around duplicate entries, you prevent them completely.
You should probably be using junction tables for players and games, i.e. have your main tables
Team (team_id, team_name, team_logo etc)
Game (game_id, game_name, etc)
Player (player_id, player_name, player_email, player_mobile etc)
Then tables to link them
Team_Game (team_id, game_id)
Team_Player (team_id, player_id)
This then allows one player to play for multiple teams, or one team to enter multiple events.
Select t.team_id , p.player_name from player p
JOIN teams t
ON t.team_id = p.team_id
Where t.game_id = 35 AND p.player_name IS NOT NULL AND p.player_name <> ''
GROUP BY(t.team_name)
```
You should do a unique constraint on the team_name column, this way you are not allowing duplicate teams
Ps. I did not test the query but it should work

SQL Comment Grouping

I have two table in MySQL
Table 1: List of ID's
--Just a single column list of ID's
Table 2: Groups
--Group Titles
--Members **
Now the member field is basically a comments field where all the ID's that are part of that group are listed. So for instance one whole field of members looks like this:
"ID003|ID004|ID005|ID006|ID007|ID008|... Etc."
There they can be up to 500+ listed in the field.
What I would like to do is to run a query and find out which ID's appear in only three or less groups.
I've been taking cracks at it, but honestly I'm totally lost. Any ideas?
Edit; I misunderstood the question the first time, so I'm changing my answer.
SELECT l.id
FROM List_of_ids AS l
JOIN Groups AS g ON CONCAT('|', g.members, '|') LIKE CONCAT('%|', l.id, '|%')
GROUP BY l.id
HAVING COUNT(*) <= 3
This is bound to perform very poorly, because it forces a table-scan of both tables. If you have 500 id's and 500 groups, it must run 250000 comparisons.
You should really consider if storing a symbol-separated list is the right way to do this. See my answer to Is storing a delimited list in a database column really that bad?
The proper way to design such a relationship is to create a third table that maps id's to groups:
CREATE TABLE GroupsIds (
memberid INT,
groupid INT,
PRIMARY KEY (memberid, groupid)
);
With this table, it would be much more efficient by using an index for the join:
SELECT l.id
FROM List_of_ids AS l
JOIN GroupsIds AS gi ON gi.memberid = l.id
GROUP BY l.id
HAVING COUNT(*) <= 3
select * from
(
select ID,
(
select count(*)
From Groups
where LOCATE(concat('ID', a.id, '|'), concat(Members, '|'))>0
) as groupcount
from ListIDTable as a
) as q
where groupcount <= 3

mysql count one value display multiple columns

I couldnt find this, i'm sure its simple.
Table (196 rows)
tech, name, area, manager
------------------------------
Joe,mary,pa,sally
Kim,brad,ga,tim
kelly,Joe,pa,sally
Joe,jose,pa,sally
a tech is assigned to multiple name/area/managers. I want to do a report that shows all the rows of the table with a column showing the number of assignments for the tech.
My desired results
tech, name, area, manager, (count of number of tech assignments)
Joe,mary,pa,sally,2
Kim,brad,ga,tim,1
kelly,Joe,pa,sally,1
Joe,jose,pa,sally,2
I'm guessing you want a subquery on the SELECT clause:
SELECT
name,
area,
manager,
(SELECT COUNT(*) FROM tablename WHERE tech = x.tech) AS assignments
FROM tablename x
And here is a possibly more efficient way to do the same thing:
SELECT
t.name,
t.area,
t.manager,
sub.assignments
FROM tablename t
INNER JOIN (
SELECT tech, COUNT(*) AS assignments
FROM tablename
GROUP BY tech
) sub
ON sub.tech = t.tech
select
a.tech, a.name, a.area, a.manager,
b.cnt
from table a, (
select count(*) cnt, tech
from table
group by tech) b
where a.tech=b.tech;
Is this what you want?
SELECT tech, count(name) AS total_records FROM db_table GROUP BY tech
Not sure this is what you're looking for though.
Oh yeah, looks like you really need to use subquery.
Thanks for the examples, that helped.
SELECT tech, name, area, manager, assignments
FROM table
INNER JOIN (
SELECT tech, COUNT(*) AS assignments
FROM table
GROUP BY tech
) t
USING (tech)
Explanation:
Select all columns from table and join them with table containing tech column and count of rows from table having particular tech. Keyword USING says how those two table merge.

MYSQL View and summing fields

I need some help I have been scouring the web and haven't been able to find something too similar. I have a MYSQL database for my Golf League. I need to display standings by creating a view from this database. There are 2 people per team, my primary key in 'players' is 'id' there is also a teamID (numerical value 1 - 20, 20 teams) for each player which corresponds to their teammates. Basically what I need is a view that contains 'teamID', both players 'LName' (maybe an 'LNameA','LNameB'), and a sum of the two players 'points' field. I have never summed a field from one person and another or created a view in MYSQL.
EDIT:
I was trying something like
CREATE
VIEW standings1
AS SELECT teamID, LName, points
FROM players
but need teamID to be the primaryKey of the view which will contain each players last name, and their points summed together.
Try this:
create view standings as
select teamId, group_concat(lname separator ', ') as TeamMembers,
sum(points) TotalPoints from players
group by teamId
Oh, one more thing. If you want to have the names of the players in different fields (group_concat just separate them by commas, but it is still a single field) you can use this query:
create view standings as
select a.teamId, a.lname as player1, b.lname as player2,
a.points + b.points TotalPoints
from players a
join players b ON a.teamId = b.teamId AND a.id >= b.id
group by a.teamId, a.id
having count(*) = 2
That way you can play better with the names in PHP without having to parse the ", "
If I understand your table structure, you will need a JOIN against the table's own teamID. I'm assuming the teamID refers to a team, and is not the id of the player. The trick here is to join two copies of the table on the same teamID, but where the player ids are non-equal. That should produce the pair of players per team.
CREATE VIEW standings AS
(
SELECT
p1.teamID AS teamID,
p1.id AS p1id,
p2.id AS p2id,
p1.LName AS p1LName,
p2.LName AS p2LName,
p1.score + p2.score AS totalScore
FROM
/* JOIN on matching teamID and non-matching player-id (so you don't get the same player twice) */
players p1 JOIN players p2 ON p1.teamID = p2.teamID and p1.id <> p2.id
);

MySQL: count records by value and then update another column with their count

Three very related SQL questions (I am actually using mySQL):
Assuming a people table with two columns name, country
1) How can I show the people who have fellow citizens? I can display the count of citizens by country:
select country, count(*) as fellow_citizens
from people
group by country
But I can't seem to show those records for which fellow_citizens is > 1. The following is invalid:
select name, country, count(*) as fellow_citizens
from people
group by country
where fellow_citizens > 1;
... and would not be what I want any way since I don't want to group people.
2) To solve the above, I had the idea to store fellow_citizens in a new column.
This sounds like a variation on questions such as MySQL: Count records from one table and then update another. The difference is that I want to update the same table.
However the answer given there doesn't work here:
update people as dest
set fellow_citizens =
(select count(*) from people as src where src.country = dest.country);
I get this error message:
You can't specify target table 'dest' for update in FROM clause
It seems like I need to through another temporary table to do that. Can it be done without a temporary table?
3) As a variant of the above, how can I update a column with a people counter by country? I found I can update a global counter with something like:
set #i:=0; update people set counter = #i:=#i+1 order by country;
But here I would like to reset my #i counter when the value of country changes.
Is there a way to do that in mySQL without going to full-blown procedural code?
Your select query should look something like:
SELECT name, country, count(*) as fellow_citizens
FROM people
GROUP BY country
HAVING fellow_citizens > 1;
Recommended solution
You don't want to group, you just want to repeat the count for every person.
This data is not normalized and code like this is not a great way to do things.
However the following select will give you all the people and their counts:
SELECT p.*
, s.fellow_citizens
FROM people p
INNER JOIN (
SELECT country, count(*) as fellow_citizens
FROM people
GROUP BY country
HAVING count(*) > 1) s ON (s.country = p.country)
If you don't want the actual count, just people from countries with more than 1 citizen, another way to write this query is (this may be faster than previous method, you have to test with your data):
SELECT p.*
FROM people p
WHERE EXISTS
( SELECT *
FROM people p2
WHERE p2.country = p.country
AND p2.PK <> p.PK -- the Primary Key of the table
)
Only do this if you are facing slowness
If you want to include the counts into the table using a update statement I suggest you use a separate count table, because at least that's somewhat normalized.
INSERT INTO people_counts (country, pcount)
SELECT p.country, count(*) as peoplecount
FROM people p
GROUP BY p.country
ON DUPLICATE KEY pcount = VALUES(pcount)
Then you can join the counts table with the people data to speed up the select.
SELECT p.*, c.pcount
FROM people p
INNER JOIN people_counts c ON (p.country = c.country)
1 ->
select country, count(*) as fellow_citizens
from people
group by country
having fellow_citizens > 1
Sorry, but could not really understand points 2 and 3.
I would do this.
create view citizencount as
select country, count(*) citizens
from people
group by country
Then your Query becomes (Something like)
select p.personid, p.country, cc.citizens -1 fellow_citizens
from people p
join citizencount cc on
cc.country = p.country
You might then want to add
where fellow_citizens > 0
Have you tried this for both of your problem
select name, fellow_citizens
from people left join
(select country, count(*) as fellow_citizens
from people group by country
having fellow_citizens > 1) as citizens
on people.country = citizens.country
I am not very good at writing sql query; but I think this can solve your problem