sql query for fetching leaderboard from two tables - mysql

I have two MySql table user and marks
User table:
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| firstName | varchar(255) | YES | | NULL | |
| lastName | varchar(255) | YES | | NULL | |
| email | varchar(55) | YES | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
Marks Table
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| user_id | int | NO | | NULL | |
| subject_id | varchar(255) | YES | | NULL | |
| score | int | YES | | NULL | |
| subject_name | varchar(225) | YES | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
I want to fetch details(userid, firstName and lastName) of top 10 users with the highest marks in descending order.
Marks of the user is defined as sum of all scores a user has in different subjects.
I am really confused which join is to be used here, I am new to MySql and this query is kind of challenging for me, Hope you understood the problem.
Please let me know if you have any suggestion, Thank You

You can join the two tables together by id=user_id, then you group the result by id, sort by the total marks per id, then take the top 10 results.
If you wanted a result even if the user had no marks at all, change JOIN to LEFT JOIN, this will still give you a result from the first table even if there are no results from the second.
SELECT u.id, u.firstName, u.lastName, SUM(m.score) AS TotalScore
FROM [User] AS u
JOIN Marks AS m ON m.user_id = u.id
GROUP BY u.id, u.firstName, u.lastName
ORDER BY SUM(m.score) DESC
LIMIT 10;

You should inner join the table Marks on the id from table User with user_id from the table Marks:
SELECT user_id, firstName, lastName
FROM User
INNER JOIN Marks
ON User.id = Marks.user_id
Here is a helpful resource for SQL joins

Related

How to return NULL values when joining multiple tables

here are the contents of the tables.
mysql> desc student;
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| name | varchar(20) | NO | | NULL | |
| sex | enum('F','M') | NO | | NULL | |
| student_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
+------------+------------------+------+-----+---------+----------------+
mysql> desc grade_event;
+----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+------------------+------+-----+---------+----------------+
| date | date | NO | | NULL | |
| category | enum('T','Q') | NO | | NULL | |
| event_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
+----------+------------------+------+-----+---------+----------------+
mysql> desc score;
+------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+-------+
| student_id | int(10) unsigned | NO | PRI | NULL | |
| event_id | int(10) unsigned | NO | PRI | NULL | |
| score | int(11) | NO | | NULL | |
+------------+------------------+------+-----+---------+-------+
What I'm trying to accomplish is to display which students missed a quiz/test, found under 'category' in the grade_event table.
Here's what I've come up with, but am not generating any results;
select name, category, sc.event_id
from student s
join score sc on s.student_id=sc.student_id
join grade_event ge on sc.event_id=ge.event_id
where score is NULL
group by name, event_id;
I've also gone the route of attempting a subquery;
select name, category, sc.event_id
from student s
join score sc on s.student_id=sc.student_id
join grade_event ge on sc.event_id=ge.event_id
where score not in (select score from score)
group by name, event_id;
Any help would be appreciated.
I think you should just replace your join by left join, join is an inner join in MySQL : http://dev.mysql.com/doc/refman/5.7/en/join.html
And take care with group by event_id, it can be useful to precise group by sc.event_id. I don't know in MySQL but in sql server it wouldn't work.
Your second query is necessarily empty as you ask a column to have its values not in its values :)
You need to use an outer join instead of an inner join to get list of students that do not have a corresponding record in the events after creating a carthesian join of students and events:
select name, category, ge.event_id
from (student s
join grade_event ge) --no join condition creates a carthesian join
left join score sc on s.student_id=sc.student_id and sc.event_id=ge.event_id
where sc.score is NULL

EAV - Add rows with null value if row does not exist

I have the following table storing data in the EAV model:
+-------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+-------+
| user_id | int(11) | NO | MUL | NULL | |
| question_id | int(11) | NO | MUL | NULL | |
| answer | blob | YES | | NULL | |
+-------------+---------+------+-----+---------+-------+
With a table to hold the different types of question:
+----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| question | blob | YES | | NULL | |
+----------+---------+------+-----+---------+----------------+
As well as a users table:
+------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_name | varchar(128) | YES | | NULL | |
+------------------+--------------+------+-----+---------+----------------+
How would I write a query to insert a row with a null value for answer for each question_id that each user does not currently have a row for?
For example, if I have question_ids 1,2,3,4 and my table storing data looks like:
+--------------+---------------+--------+
| user_id | question_id | answer |
+--------------+---------+-----+--------+
| 1 | 1 | example|
| 1 | 3 | example|
| 1 | 4 | example|
+--------------+---------+-----+--------+
I want to insert a row that looks like :
+--------------+---------------+--------+
| user_id | question_id | answer |
+--------------+---------+-----+--------+
| 1 | 2 | NULL |
+--------------+---------+-----+--------+
I tried something like this:
INSERT INTO profile_answers
(
user_id,
question_id,
answer
)
SELECT
id,
profile_answers.question_id,
null
FROM users
LEFT JOIN profile_answers ON profile_answers.user_id = users.id
WHERE NOT EXISTS (
SELECT answer
FROM profile_answers
WHERE user_id = id
AND question_id IN (
SELECT GROUP_CONCAT(id SEPARATOR ',') FROM profile_questions
)
)
But I ended up inserting rows with a question id of 0.
I've given this some thought, but I couldn't find anything better than using a cartesian product between users and questions, and a filtering subquery:
SELECT u.id, q.id
FROM users u,
questions q
WHERE NOT EXISTS(
SELECT *
FROM profile_answers a
WHERE a.question_id = q.id AND a.user_id = u.id
);
Demo

Getting a SQL query to print 0 for null count results across 3 tables

I'm trying to get a SQL query to give me the results of a count but I need the result to include rows where the count is 0. What I found for solutions to this was to use IFNULL(COUNT(*), 0) in place of COUNT(*) however that had no effect on the result. I also tried using a LEFT JOIN but SQL gave me a syntax error if I tried to put in those. Here's my table setup
User
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| UserID | mediumint(9) | NO | PRI | NULL | auto_increment |
| firstName | varchar(15) | NO | | NULL | |
| lastName | varchar(15) | NO | | NULL | |
| Protocol | varchar(10) | NO | | NULL | |
| Endpoint | varchar(50) | NO | | NULL | |
| UsergroupID | mediumint(9) | NO | MUL | NULL | |
+-------------+--------------+------+-----+---------+----------------+
Subscription
+----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+----------------+
| SubscriptionID | mediumint(9) | NO | PRI | NULL | auto_increment |
| TopicID | mediumint(9) | NO | MUL | NULL | |
| UserID | mediumint(9) | NO | MUL | NULL | |
+----------------+--------------+------+-----+---------+----------------+
Topic
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| TopicID | mediumint(9) | NO | PRI | NULL | auto_increment |
| Name | varchar(50) | NO | | NULL | |
| FBName | varchar(30) | YES | | NULL | |
| FBToken | varchar(255) | YES | | NULL | |
| TWName | varchar(10) | YES | | NULL | |
| TWToken | varchar(50) | YES | | NULL | |
| TWSecret | varchar(50) | YES | | NULL | |
+----------+--------------+------+-----+---------+----------------+
My SQL query to try and get the COUNT is :
SELECT Topic.TopicID as ID, Topic.Name AS TopicName, COUNT(*) AS numSubscriptions
FROM User, Topic, Subscription
WHERE Subscription.UserID = User.UserID
AND Subscription.TopicID = Topic.TopicID
GROUP BY Topic.TopicID;
I've tried replacing COUNT(*) with IFNULL(COUNT(*), 0) and I've tried to replace User,Topic,Subscription with User JOIN Subscription JOIN Topic and I also tried User LEFT JOIN Subscription LEFT JOIN Topic but that got a SQL error.
The output I'm getting is:
+----+-----------+------------------+
| ID | TopicName | numSubscriptions |
+----+-----------+------------------+
| 2 | test | 2 |
| 3 | test2 | 1 |
+----+-----------+------------------+
I need to be getting
+----+-----------+------------------+
| ID | TopicName | numSubscriptions |
+----+-----------+------------------+
| 2 | test | 2 |
| 3 | test2 | 1 |
| 4 | test3 | 0 |
+----+-----------+------------------+
By default, outer joins are left to right. So, the trick is to start with Topic:
SELECT Topic.TopicID as ID, Topic.Name AS TopicName,
COUNT(User.UserID) AS numSubscriptions
FROM Topic
LEFT JOIN Subscription
ON Subscription.TopicID = Topic.TopicID
JOIN User
ON User.UserID = Subscription.UserID
GROUP BY Topic.TopicID
This allows for multiple subscriptions per user and requires that the user record exists to be considered in the count.
COUNT(NULL) evaluates to 0, so any topic records without a corresponding subscription and user record will show as 0.
If you're not concerned whether the user record exists, you could simplify it to the following:
SELECT Topic.TopicID as ID, Topic.Name AS TopicName,
COUNT(Subscription.TopicID) AS numSubscriptions
FROM Topic
LEFT JOIN Subscription
ON Subscription.TopicID = Topic.TopicID
GROUP BY Topic.TopicID
The example below should do what you're after. The column in the COUNT() can be any column of the subscription table, but using its ID is a good practice.
Using the left join ensures that all entries of the user table will show up in the results, even if there are no matching subscriptions.
SELECT User.firstName,
User.lastName,
Topic.Name AS TopicName,
COUNT(Subscription.SubscriptionId) AS numSubscriptions
FROM USER
LEFT OUTER JOIN Subscription ON Subscription.UserID=USER.UserID
LEFT OUTER JOIN Topic ON Subscription.TopicID=Topic.TopicID
GROUP BY User.firstName, User.lastName, Topic.Name;

MySQL joining data from 2 tables based on foreign key in a third, without duplicates or group by

I have three tables that look like this:
People:
+------------+-------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| fname | varchar(32) | NO | | NULL | |
| lname | varchar(32) | NO | | NULL | |
| dob | date | NO | | 0000-00-00 | |
| license_no | varchar(24) | NO | | NULL | |
| date_added | timestamp | NO | | CURRENT_TIMESTAMP | |
| status | varchar(8) | NO | | Allow | |
+------------+-------------+------+-----+-------------------+----------------+
Units:
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| number | varchar(3) | NO | | NULL | |
| resident | int(11) | NO | MUL | NULL | |
| type | varchar(16) | NO | | NULL | |
+----------+-------------+------+-----+---------+----------------+
Visits:
+----------+-----------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-----------+------+-----+---------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| vis_id | int(11) | NO | MUL | NULL | |
| unit | int(11) | NO | MUL | NULL | |
| time_in | timestamp | NO | | CURRENT_TIMESTAMP | |
| time_out | timestamp | NO | | 0000-00-00 00:00:00 | |
+----------+-----------+------+-----+---------------------+----------------+
There are multiple foreign keys linking these tables:
units.resident -> people.id
visits.unit -> units.id
visits.vis_id -> people.id
I am able to run this query to find all residents ie - everyone from people that are referenced by the units.resident foreign key:
SELECT concat(p.lname, ', ', p.fname) as 'Resident', p.dob as 'Birthday',
u.number as 'Unit #'
from people p, units u
where p.id = u.resident
order by u.number
It returns the results I want... However, it'd be useful to do the opposite of this to find all the people who are not residents ie- everyone from people who aren't referenced by the units.resident foreign key.
I've tried many different queries, most notably some inner and left joins, but I'm getting waaaaay too many duplicate entries (from what I've read here, this is normal). The only thing I've found that works is using a group by license_no, because as of now the "residents" don't have this information, like this:
SELECT p.id, concat(p.lname, ', ', p.fname) as 'Visitor',
p.license_no as 'License', u.number from people p
left join units u on u.number <> p.id
group by p.license_no order by p.id;
This works for all but one resident, who's u.number is displayed on ALL results. The residents will soon have license_no entries, and I can't have that one odd entry in the returned results all the time, so this query won't work as a long-term solution.
How can I structure a query without a group by that will return the results I want?
This should work
SELECT
p.id
, P.fname
, P.lname
FROM
people AS p
LEFT JOIN
units AS u
ON
p.id = u.resident
WHERE
u.resident IS NULL
Extra hint.
Table people should be called person.
By u.resident you mean a person. so it should be a person_id there in the unit table...
Better logic helps to write SQL better, if your name convention is clear to use.
Use a NOT EXISTS clause to exclude those people who are residents.
SELECT P.id
,P.fname
,P.lname
,etc...
FROM People P
WHERE NOT EXISTS (SELECT 1 FROM Units U WHERE U.resident = P.id)

SQL query help: How can i get the column value from other table?

mysql> describe posts;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | | NULL | |
| title | varchar(255) | NO | | NULL | |
| body | text | YES | | NULL | |
| created | datetime | YES | | NULL | |
| modified | datetime | YES | | NULL | |
| category_id | int(11) | NO | | NULL | |
| tags | varchar(50) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
8 rows in set (0.00 sec)
mysql> describe users;
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(255) | NO | UNI | NULL | |
| password | char(40) | NO | | NULL | |
| group_id | int(11) | NO | | NULL | |
| created | datetime | YES | | NULL | |
| modified | datetime | YES | | NULL | |
+----------+--------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
I'm trying to select column id,title from posts table and username from users table where posts.user_id = users.id.
If i select user_id from posts i will get only the integer value.
But i want to get the equivalent varchar value from users table.
It would be a joining sql query.
How can i do this?
Thanks in advance.
this should do the trick
SELECT p.id, p.title, u.UserName
FROM posts p
INNER JOIN users u ON p.user_id = u.id
or
SELECT p.id, p.title, u.UserName
FROM posts p,users u
WHERE p.user_id = u.id
or
SELECT p.id, p.title, (select u.UserName FROM users u WHERE p.user_id = u.id)
FROM posts p
In order of preference
You indeed need to use a join (most likely an inner join, here) between your two tables :
select posts.id, posts.title, users.username
from posts
inner join users on users.id = posts.user_id
With this, for each post, you'll get the corresponding user's informations -- and you can add whatever field you want from users to the select part of the query.
Try this query:
SELECT Posts.ID, Posts.Title, Users.UserName
FROM Posts INNER JOIN Users ON Posts.User_ID = Users.ID
Try this:
select posts.id, title, username
from posts join users
on posts.user_id = users.id
If the column name is ambiguous (e.g., id), you can prefix it with the table name, as shown above.