I have 3 MYSQL tables, one with members information (with ids), one with subscription years (also with ids), and a join table to save every year each member have been subscribed (using years and members ids). I managed to get the list of all the members related to a specific year using the MYSQL code below, but I can't figure out how to get the list of members that are not related to the same years (the ones not included on the first list) using MYSQL code.
I already tried to search for the members without the specific year id, using the code below, but this will return a list with all the members except for the ones that were subscribed only on that year.
The code to extract all the member subscribed on a year knowing its year id (x):
SELECT DISTINCT m.id, first_name, last_name
FROM members_years sc
INNER JOIN members m ON m.id = sc.member_id
WHERE sc.year_id = x
While the nonworking code to extract the list of members not related with a subscription year knowing its year id (x):
SELECT DISTINCT m.id, first_name, last_name
FROM members_years sc
INNER JOIN members m ON m.id = sc.member_id
WHERE sc.year_id != x
To resume I need to extract using a single MYSQL code the list of all members that are not related with a specific subscription year.
Here an example of tables:
Members:
Id | First_name | Last_name
——————————————————
1 | John | Smith
——————————————————
2 | John | Doe
——————————————————
3 | Jane | Doe
Years:
Id | Year
——————
1 | 2013
——————
2 | 2014
——————
3 | 2015
Members_years:
Id | member_id | year_id
———————————————
1 | 1 | 1
———————————————
2 | 1 | 2
———————————————
3 | 2 | 3
———————————————
4 | 3 | 1
———————————————
5 | 3 | 2
———————————————
6 | 3 | 3
With the 3 previous tables, searching with x=3 (2015) with the first code we will get John Doe and Jane Doe, that is fine.
But with second code, using x=3, we get John Smith and Jane Doe instead of only Jonh Smith.
One way to do what you want is with a left join. You want to find all rows which don't have a particular relation for a year x
SELECT m.id, first_name, last_name
FROM members m
LEFT JOIN members_years sc ON (m.id = sc.member_id and sc.year_id = x)
WHERE sc.year_id IS NULL;
So, you LEFT JOIN members_years looking for rows with your target year for each member in the ON clause.
Any member which doesn't have that year would have NULL values from that join, so the WHERE clause looks for those and we end up with just the members you need.
If you're unfamiliar with LEFT JOIN, you might find it instructive to leave off the WHERE clause and include some columns from sc:
SELECT m.id, first_name, last_name, sc.year_id
FROM members m
LEFT JOIN members_years sc ON (m.id = sc.member_id and sc.year_id = x);
You'll get a row for every member - some with a value for year_id, some with NULL.
I think #IVO GELOV’s comment contains the code you are looking for. The reason you are seeing John Doe and Jane Doe pop up in the results of your second query is because both of those users have a record on members_years where year_id Is Not equal to 3. (Although John Doe doesn’t actually have a record where year_id Is Not equal to 3 in your example data, I am assuming that on the actual data you are querying against this is true). Your query needs to instead determine what users DO have a record where year_id Is Equal to 3, and then return every other user besides the users that meet this criteria in your search result.
Related
I am trying to find one query that returns all people including the company they belong to and companies that do not have any person assigned yet.
Company
cid | cname
--------------
1 Company 1
2 Company 2
Person
pid | pname | fk_company
---------------------------
1 Person 1 1
2 Person 2 1
desired result
pid | pname | fk_company | cid | cname
----------------------------------------------
1 Person 1 1 1 Company 1
2 Person 2 1 1 Company 1
NULL NULL NULL 2 Company 2
Thanks in advance
If you want everything from both tables, regardless of match left AND right, you need a FULL JOIN:
SELECT *
FROM person
FULL JOIN company
ON person.fk_company = company.cid
edit: Apparently mysql doesn't support FULL JOIN. You'll have to do both LEFT JOINS by hand and UNION ALL them.
You should mention something you tried. Anyway, I will explain the method so you can work on it.
SELECT <column_names>FROM <table1_name> LEFT JOIN <table2_name>ON
<table1.column_name> = <table2.column_name>;
For more explanations please refer this link.
SQL Left Join
I am working in MySQL. I have the following query:
SELECT tar.ID, COUNT(tsr.StudentID) AS Students
FROM Teacher_ApplicationRecord tar
LEFT JOIN Teacher_StudentRecord tsr ON sar.ID = tsr.TeacherID AND tsr.Session = 1 AND tsr.Year = 2017
WHERE tar.ApplicationYear = 2017
AND tar.Session1 = 1
This query returns no results. However if I take the COUNT(tsr.StudentID) out of the SELECT statement, it returns all the teacher ID's with NULL for the tsr table.
What I want is a query that returns all the teacher ID's and a count of the students assigned to that teacher, with 0 if the result is NULL.
I have tried COALESCE(COUNT(tsr.StudentID), 0) AND IFNULL(COUNT(tsr.StudentID), 0) with no success so far. Any other thoughts?
UPDATE:
The tsr table has 4 columns: TeacherID, StudentID, Year, Session. It has no records yet. It will be populated next year when students are assigned to teachers.
The tar table has a list of TeacherID's in it with some other data, such as year and faculty.
I want my results to look like below:
+-----------+-----------------+
| TeacherID | COUNT(StudentID)|
+-----------+-----------------+
| 1 | 0 |
+-----------+-----------------+
| 2 | 0 |
+-----------+-----------------+
etc.
As students are assigned to teachers, the COUNT(StudentID) numbers will go up. Hope this helps.
UPDATE 2
The tar table looks like this:
+---------+---------------+
|TeacherID|ApplicationYear|
+---------+---------------+
| 1 | 2017 |
+---------+---------------+
| 2 | 2017 |
+---------+---------------+
| 3 | 2017 |
+---------+---------------+
It has other columns but they are not relevant to the question.
The tsr table looks like this:
+---------+---------+----+-------+
|TeacherID|StudentID|Year|Session|
+---------+---------+----+-------+
| 1 | 10 |2017| 1 |
+---------+---------+----+-------+
| 1 | 11 |2017| 1 |
+---------+---------+----+-------+
| 2 | 12 |2017| 1 |
You can try joining the teacher table to the student table (in that order), and then using GROUP BY to count the number of students per teacher:
SELECT tsr.ID,
COUNT(sar.ID) AS numOfStudents -- count no matching students as zero
FROM Teacher_StudentRecord tsr
LEFT JOIN Student_ApplicationRecord sar
ON tsr.TeacherID = sar.ID AND
tsr.Session = 1 AND
tsr.Year = 2017
GROUP BY tsr.ID
The usefulness of a LEFT JOIN here is the edge case where a teacher has no matching students. In this case, the result set, before aggregation happens, would have a single record for that teacher, and the student ID value would be NULL, which be ignored by COUNT, resulting in a correct zero count.
Note that I removed the WHERE clause, whose intended logic is already contained in the ON clause. This WHERE clause was throwing off your results by removing teacher records before they could even be aggregated.
Update:
Please try the following query to see if you get results:
SELECT tsr.ID,
COUNT(sar.ID) AS numOfStudents
FROM Teacher_StudentRecord tsr
LEFT JOIN Student_ApplicationRecord sar
ON tsr.TeacherID = sar.ID
GROUP BY tsr.ID
If this gives you no results, then no teachers are actually connected to any students, and your data has a problem.
Here is a demo using your sample data. It works as expected:
SQLFiddle
Apologies for the title; my question is much clearer with the specific example.
I have two tables - one for people, and one for addresses. I've grouped them together and selected the relevant parts:
ID | Address | Address_Status
-------------------------------
1 | 2 Main St | Active
1 | 19 Elm Rd | Inactive
1 | 7 Red Ave | Active
2 | 9 Gold St | Inactive
2 | 3 Rich St | Inactive
I'm trying to select IDs of people who have all Inactive addresses. This means that in the output, the only record should be ID = 2. I've tried grouping them by ID, then using a having (count(*) - count(Address_Status = 'I')) = 0, which is as close as I've come.
If a user has no addresses at all they should be omitted yes?
In that case I'd favor something like:
SELECT People.ID, People.Name,
SUM(IF(Addresses.Address_Status = 'I', 1, 0)) AS InactiveCount,
SUM(IF(Addresses.Address_Status != 'I', 1, 0)) AS ActiveCount,
FROM People JOIN Addresses ON Addresses.personID = People.ID
GROUP BY People.ID
HAVING ActiveCount = 0 AND InactiveCount > 0
One possibility would be to join the address table and return only values with no active record existed:
Select P.* from People P
left join (Select * from addresses where Address_status="ACTIVE") A
on P.id=A.pid
where A.pid is null;
The drawback here is that you would also return any user that had no address registered to their account, which might not be the intended behavior.
I have two tables.
I am a total newbie to SQL. Using mysql at the moment.
I have the following setup for a school-related db:
Table A contains students records.
Student's id, password,name, lastname and so on.
Table B contains class attendancy records.
Each record goes like this: date, student id, grade
I need to gather all the student info of students that attended classes in a certain date range.
Now, the stupid way would be
first I SELECT all classes from Table B with DATE IN BETWEEN the range
then for each class, I get the student id and SELECT * FROM students WHERE id = student id
What I can't wrap my mind around is the smart way.
How to do this in one query only.
I am failing at understanding the concepts of JOIN, UNION and so on...
my best guess so far is
SELECT students.id, students.name, students.lastname
FROM students, classes
WHERE classes.date BETWEEN 20140101 AND 20150101
AND
classes.studentid = students.id
but is this the appropriate way for this case?
Dont add the join statement in the where clause. Do it like this:
SELECT s.id, s.name, s.lastname,c.date,c.grade
FROM classes c
inner join students s
on c.studentid=s.id
WHERE c.date BETWEEN '01/01/2014' AND '01/01/2015'
This sounds like an assignment so I will attempt to describe the problem and give a hint to the solution.
An example of a union would be;
SELECT students.name, students.lastname
FROM students
WHERE students.lastname IS NOT NULL
UNION
SELECT students.name, 'N/A'
FROM students
WHERE students.lastname IS NULL;
+--------------+--------------+
| name | lastname |
+--------------+--------------+
| John | Doe | <- First two rows came from first query
| Jill | Smith |
| Bill | N/A | <- This came from the second query
+--------------+--------------+
The usual use case for a union is to display the same columns, but munge the data in a different way - otherwise you can usually achieve similar results through a WHERE clause.
An example of a join would be;
SELECT authors.id, authors.name, books.title
FROM authors LEFT JOIN books ON authors.id = books.authors_id
+--------------+--------------+------------------+
| id | name | title |
+--------------+--------------+------------------+
| 1 | Mark Twain | Huckleberry Fin. |
| 2 | Terry Prat.. | Good Omens |
+--------------+--------------+------------------+
^ First two columns from ^ Third column appended
from authors table from books table linked
by "author id"
Think of a join as appending columns to your results, a union is appending rows with the same columns.
In your situation we can rule out a union as you don't want to append more student rows, you want class and student information side by side.
I've got THREE MYSQL TABLES (innoDB) :
NAMES
id nid version fname lname birth
RELATIONS
id rid version idname idperson roleid
ROLES
id role
I want to select the last version of each RELATIONS joined to the last version of their related NAMES for a particular idperson (and the name of the ROLE)
Of course, idperson will have 0, 1 or more relations and there will be one or more versions of RELATIONS and NAMES
I wrote something like :
SELECT A.id,A.nid,MAX(A.version),A.idname,A.idperson,A.roleid,B.id,B.role
FROM RELATIONS A
INNER JOIN
ROLES
ON A.roleid = B.id
INNER JOIN
(SELECT id,nid,MAX(version),fname,lname,birth FROM NAMES) C
ON A.idname = C.id
WHERE A.idperson = xx
It doesn't work maybe because MAX() seems to return only one line...
How to get the maximum value for more than one line in this joining context?
PS: how do you generate this kind of nice data set?
i.e. :
id home datetime player resource
---|-----|------------|--------|---------
1 | 10 | 04/03/2009 | john | 399
2 | 11 | 04/03/2009 | juliet | 244
5 | 12 | 04/03/2009 | borat | 555
8 | 13 | 01/01/2009 | borat | 700
Adding a GROUP BY statement, both in the subquery you have, as well as in the outer query should allow the MAX function to generate the result that you're looking for.
This is untested code, but should give you the result that you're looking for:
SELECT A.id,A.nid,MAX(A.version),A.idname,A.idperson,A.roleid,B.id,B.role
FROM RELATIONS A
INNER JOIN
ROLES
ON A.roleid = B.id
INNER JOIN
(SELECT id,nid,MAX(version),fname,lname,birth FROM NAMES GROUP BY fname,lname) C
ON A.idname = C.id
WHERE A.idperson = xx
GROUP BY fname,lname
Alternatively, if it works better for your database architecture, you can use any unique identifier for the employees you'd like (possibly nid?).
As to the question that you've posed in your PS, I'm unsure as to what you're asking. I don't seem a home, datetime, player, or resource field in the examples of your tables that you've provided. If you could clarify, I'd be happy to try and help you with that as well.