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

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"
);

Related

How to sum up max values from another table with some filtering

I have 3 tables
User Table
id
Name
1
Mike
2
Sam
Score Table
id
UserId
CourseId
Score
1
1
1
5
2
1
1
10
3
1
2
5
Course Table
id
Name
1
Course 1
2
Course 2
What I'm trying to return is rows for each user to display user id and user name along with the sum of the maximum score per course for that user
In the example tables the output I'd like to see is
Result
User_Id
User_Name
Total_Score
1
Mike
15
2
Sam
0
The SQL I've tried so far is:
select TOP(3) u.Id as User_Id, u.UserName as User_Name, SUM(maxScores) as Total_Score
from Users as u,
(select MAX(s.Score) as maxScores
from Scores as s
inner join Courses as c
on s.CourseId = c.Id
group by s.UserId, c.Id
) x
group by u.Id, u.UserName
I want to use a having clause to link the Users to Scores after the group by in the sub query but I get a exception saying:
The multi-part identifier "u.Id" could not be bound
It works if I hard code a user id in the having clause I want to add but it needs to be dynamic and I'm stuck on how to do this
What would be the correct way to structure the query?
You were close, you just needed to return s.UserId from the sub-query and correctly join the sub-query to your Users table (I've joined in reverse order to you because to me its more logical to start with the base data and then join on more details as required). Taking note of the scope of aliases i.e. aliases inside your sub-query are not available in your outer query.
select u.Id as [User_Id], u.UserName as [User_Name]
, sum(maxScore) as Total_Score
from (
select s.UserId, max(s.Score) as maxScore
from Scores as s
inner join Courses as c on s.CourseId = c.Id
group by s.UserId, c.Id
) as x
inner join Users as u on u.Id = x.UserId
group by u.Id, u.UserName;

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

How to join few table and connect id with name column from another table

I have these tables with the following column names:
players:
id first_name last_name age position salary hire_date skills_data_id team_id
Skills:
id, dribbling, pace, passing, shooting, speed, strength
towns:
id, name, country_id
teams:
id name established fan_base stadium_id
On this base, I have found the players with the max speed in terms of towns where their team played.
At the same time, I have to skip players that played in team ‘Devify’.
At the moment I have tried with this code, but the final result is not correct.
select max(s.speed) as `max_speed`,tt.name as `town_name`
from skills_data as s
right join players as p on s.id = p.skills_data_id
inner join teams as t on p.team_id = t.id
inner join towns as tt on p.team_id = tt.id
where t.name not like 'Devify'
group by s.id
order by max(s.speed) desc, town_name;
The result should be something like that:
max_speed town_name
97 Smolensk
92 Bromma
92 Lühua
...
NULL Zavolzh’ye
My result is:
max_speed town_name
97 Montréal-Ouest
92 Dalubian
92 Samsan
Thank you in advance.
There is a problem in your group by clause: you should be grouping by town rather than by skill id.
I am also quite suspicious about the join condition that brings in the towns (see my comment under your question): but if you are getting results for your existing query, it must be right...
Other changes to your query:
changed your right join to an inner join
used more meaningful table aliases
used an inequality condition instead of not like to exclude the unwanted team
New query:
select
max(sk.speed) as max_speed,
to.name as `town_name`
from skills_data as sk
inner join players as pl on sk.id = pl.skills_data_id
inner join teams as te on pl.team_id = te.id and te.name <> 'Devify'
inner join towns as to on pl.team_id = to.id
group by to.id, to.name
order by max_speed desc, town_name;

Select count field of rows that are related to another table

I'm struggling with this issue from a long time and don't know how to solve it. It's hard for me to describe, so please be patient. There are two tables:
Table "Users"
UserId PK
Gender
Table "Forms"
FormId PK
UserId1 FK
UserId2 FK
Type
Forms are always related to two users, but not all users have related forms. Now I want to count specified gender only of those users, who have related forms.
So as a result, I want to have sth. like this:
# | Gender | GenderCount
1 | male | 43
2 | female | 12
3 | trans | 2
I tried the following SQL-Script but the result isn't distinct (the sum of all GenderCount is greater then the actual number of users)
SELECT u.Gender AS 'Gender', COUNT(u.Gender) AS 'GenderCount'
FROM Users u, Forms f
WHERE ((f.UserId1 = u.UserId)
OR (f.UserId2 = u.UserId))
AND (Type = 'Foo')
GROUP BY Gender
ORDER BY GenderCount
DESC
Any tips to solve this?
Let's take a look at what you want:
How many of each gender answered any form?
Note: each user should only be counted once, no matter how many forms they've filled out.
Phrased like this, the answer becomes fairly obvious, at least in pseudo-code:
SELECT
u.Gender,
COUNT(u.Gender)
FROM
Users u
WHERE
[User has answered a form]
GROUP BY
u.Gender
The easiest way to determine if a user has answered a form depends on the specific flavour of SQL being used. You'll need to use a subquery. There are a couple of options for how to access it.
IN is the most common method:
SELECT
u.Gender Gender,
COUNT(u.Gender) GenderCount
FROM
Users u
WHERE
u.id IN (
SELECT f.UserId1 user_id FROM Forms f WHERE Type = 'Foo'
UNION
SELECT f.UserId2 user_id FROM Forms f WHERE Type = 'Foo'
)
GROUP BY
Gender
ORDER BY
GenderCount DESC
Where available, EXISTS is more natural to read, and is sometimes faster:
SELECT
u.Gender Gender,
COUNT(u.Gender) GenderCount
FROM
Users u
WHERE
EXISTS(
SELECT '1'
FROM Forms f
WHERE
(f.UserId1 = u.id OR f.UserId2 = u.id)
AND Type = 'Foo'
)
GROUP BY
Gender
ORDER BY
GenderCount DESC
Regarding speed: The query optimiser will often convert IN to EXISTS where possible, to avoid selecting extra rows unnecessarily. However, the use of multiple columns necessitates either an OR or a UNION, so it may be pretty even in this case. ie: neither OR nor UNION play nicely with indexes.
SELECT u1.Gender AS 'Gender', COUNT(*) AS 'GenderCount'
FROM
Users u1
INNER JOIN
(SELECT DISTINCT u.UserId
FROM
Users u
INNER JOIN Forms f ON ((f.UserId1 = u.UserId)
OR (f.UserId2 = u.UserId))
AND (f.Type = 'Foo')) T ON T.UserId = u1.UserId
GROUP BY Gender
ORDER BY GenderCount DESC
Skip the join which is generating multiple rows per user:
SELECT Gender, COUNT(Gender) AS 'GenderCount'
FROM Users
WHERE UserId IN (SELECT UserId1 FROM Forms WHERE Type = 'Foo'
UNION
SELECT UserId2 FROM Forms WHERE Type = 'Foo')
GROUP BY Gender
ORDER BY GenderCount DESC
Or if you prefer to avoid a UNION (which is perfectly valid in this scenario BTW) you can use OR like this:
SELECT Gender, COUNT(Gender) AS 'GenderCount'
FROM Users
WHERE UserId IN (SELECT UserId1 FROM Forms WHERE Type = 'Foo')
OR UserId IN (SELECT UserId2 FROM Forms WHERE Type = 'Foo')
GROUP BY Gender
ORDER BY GenderCount DESC
As others have pointed out, there are ways to do this using a JOIN as well. However, a JOIN adds needless complexity for the DBMS engine as it will first need to match up the rows, and then reduce to DISTINCT values.
You should use
count(distinct u.UserId)
that way users only get counted once: count(distinct field_name) counts the number of unique values contained in field_name, so counting distinct on the primary key gives you the number of unique users, which is what you're looking for.
Also, instead of joining, you probably would be better off using an in clause like this
select Gender, count(distinct UserId) as GenderCount
from Users
where u.UserId in (select UserId1 from Forms) or u.UserId in (select UserId2 from Forms)
It's probably also going to be slightly faster.

SQL: one to many, select just one

I am writing a small tool for managing the people registered to an association.
I have a table USERS and a table CARDS. Each user can have more than one card (because cards expire).
With one query I would like to extract ALL user information plus ALL card information only for the most recent card (field: ISSUEDATE).
Here is a very basic example:
USERS table
IDUSER NAME SURNAME
------------------------
1 Robert Hill
CARDS table
IDCARD IDOWNER ISSUEDATE DESC
----------------------------------------
1 1 2010-01-01 'CARD ONE'
2 1 2010-08-18 'CARD TWO'
Basically I would like to get back from my query these fields:
IDUSER NAME SURNAME IDCARD IDOWNER ISSUEDATE DESC
---------------------------------------------------------------
1 Robert Hill 2 1 2010-08-18 'CARD TWO'
I tried, but I cannot manage :(
EDIT:
What happens if there is NO CARD linked to the user? (one user is registered but the association did not give him/her any card yet). Is there any way to adapt this query in order to get back user information anyway? The idea is to get all possible information for each user. This information is later used via JSON on ad ExtJS application.
Thanks for your answers.
Option 1 with NOT EXISTS query:
select u.IDUSER, u.NAME, u.SURNAME,
c.IDCARD, c.IDOWNER, c.ISSUEDATE, c.DESC
from USERS u
join CARDS c on u.IDUSER = c.IDOWNER
where not exists (select 1 from CARDS where IDOWDER = u.IDUSER and ISSUEDATE > c.ISSUEDATE)
Option 2 with LIMIT
select u.IDUSER, u.NAME, u.SURNAME,
c.IDCARD, c.IDOWNER, c.ISSUEDATE, c.DESC
from USERS u
join CARDS c on u.IDUSER = c.IDOWNER
order by c.ISSUEDATE desc
LIMIT 1
select u.IDUSER, u.NAME, u.SURNAME,
c.IDCARD, c.IDOWNER, c.ISSUEDATE, c.DESC
from USERS u
join CARDS c on u.IDUSER = c.IDOWNER and c.IDCARD in (
/* select biggest IDCARD numbers that has same IDOWNER thus eliminating multiple rows and remaining only 1 for each IDOWNER */
select MAX(IDCARD) as IDCARD
from USERS join CARDS on USERS.IDUSER = CARDS.IDOWNER
group by CARDS.IDOWNER
)
Altho LIMIT is not generally alowed in mysql subqueries, LIMIT 1 is an exception, so
"Select just one" is a perfect for that case.
LEFT JOIN insures that you will also have users without card data (card data will be NULL, you can use ifnull(desc, 'No Card Issued') to display that at your will)
and subquery insures that only the most recent card data for the user will be selected.
SELECT *
FROM users u
LEFT JOIN cards c
ON c.idcard = (SELECT idcard
FROM cards c1
WHERE c1.idowner = u.iduser
ORDER BY issuedate DESC
LIMIT 1)