select ids that doesn't have a specific values - mysql

I have a table with the schema below:
+---------+--------+
|studentId | course |
+---------+--------+
|1 | 2 |
|1 | 3 |
|1 | 4 |
|1 | 5 |
|2 | 4 |
|2 | 5 |
+---------+--------+
and I want to perform a query to get student Ids that don't have course 2 and 3
select * from students where course not in (2,3);
but it returns Students IDs 1 and 2 and I want it to return Student ID 2 only.
How do I do that?

You could do it like this:
select * from students where studentId not in -- exclude all students from course 2 & 3
(
--find all the students in course 2 & 3
select distinct studentId --could be duplicates might as well grab a distinct list.
from students
where course in (2,3)
)

This answers assumes that OP wants to filter out students that have either course 2 or course 3 or both of them set.
At first, find all students, who have course 2 or 3
SELECT DISTINCT studentId
FROM students
WHERE course IN (2,3)
Then, find all students, who are not in that list
SELECT *
FROM students
WHERE studentId NOT IN (...)
If you only want to return a list of studentIds, without their courses, replace * with DISTINCT studentId.
Put those together:
SELECT DISTINCT studentId
FROM students
WHERE studentId NOT IN (
SELECT DISTINCT studentId
FROM students
WHERE course IN (2,3)
)

Another query using having to filter out students that have courses 2 or 3
select studentId
from students
group by studentId
having sum(course in (2,3)) = 0

This should work:
select
*
from
students s
where
not exists ( select
1
from
students ss
where
ss.studentID = s.studentID
and ss.course in (2,3));

SELECT DISTINCT x.studentid
FROM student x
LEFT
JOIN
( SELECT studentid
FROM student
WHERE course IN(2,3)
GROUP
BY studentid
HAVING COUNT(*) = 2 ) y
ON y.studentid = x.studentid
WHERE y.studentid IS NULL;
(of course, it's highly unlikely that a table holding students and courses would be called student. enrolment might be a better title)

I would recommend using NOT IN and writing a subquery that pulls for each student who is taking courses 2 or 3. That is, if you are looking for students who are not taking course 2 or 3. If you are looking to exclude students who are taking BOTH courses, this will need to change a little bit. Let me know if that is the case.
Start by writing the subquery, which is easy enough:
SELECT *
FROM students
WHERE course = 2 OR course = 3
Then, you can select from your table again using the NOT IN operator:
SELECT DISTINCT studentid
FROM students
WHERE studentid NOT IN (SELECT studentid
FROM students
WHERE course = 2 OR course = 3);
And it works!

JR's query will perform poorly MySQL < 5.6 and only performs an OR not an AND on course.
Try this:
SELECT
id
FROM foo AS missingfoo
LEFT JOIN (
SELECT id FROM foo AS foo1
JOIN foo AS foo2 USING (id)
WHERE foo1.course=2
AND foo2.course=3
) AS z
USING (id) WHERE z.id IS NULL GROUP BY id;

Related

How to get list of students who have enrolled atleast once and then final status is not enrolled?

I've following table, It has log of students who enrolled and enrolled out datewise.
student_id | is_enrolled | created_at
-------------------------------------
1 | 1 | 2020-01-01
2 | 0 | 2020-01-02
3 | 0 | 2020-01-01
1 | 0 | 2020-01-02
4 | 1 | 2020-01-02
1 | 0 | 2020-01-03
3 | 0 | 2020-01-03
4 | 1 | 2020-01-04
If you see, the student 1 has enrolled on 2020-01-01 and then enrolled out on 2020-01-02. Student 2 and 3 have never enrolled. Student 4 enrolled multiple times but never enrolled out. Hence, not in the output.
Basically, I want to write a query whose output is students like 1, who have atleast enrolled once and final status is not enrolled. I was able to get all the enrolled students, but stuck after that point.
My queries,
SELECT DISTINCT student_id
FROM student
WHERE is_enrolled = 1
ORDER
BY student_id; # gives me 1 and 4
SQL fiddle
Ideally, a single query solution without nested query would be awesome. I'm, okay with multiple query solution as well.
Note: I was able to get the required output by using for-loops in my code, but I would like to learn can I do this just by SQL queries. I'm not looking for any programming language code.
SELECT DISTINCT x.*
FROM student x
JOIN
( SELECT student_id
, MAX(created_at) created_at
FROM student
GROUP
BY student_id
) y
ON y.student_id = x.student_id
AND y.created_at = x.created_at
JOIN student z
ON z.student_id = x.student_id
AND z.is_enrolled = 1
WHERE x.is_enrolled = 0;
As an aside, never use SELECT *, and in the absence of any aggregating functions, a GROUP BY clause is NEVER appropriate.
I'm not a DBA ( database expert ), but I'll normally use something like this for my MSSQL database:
WITH summary AS (
SELECT
student_id,
is_enrolled,
created_at
ROW_NUMBER() OVER(PARTITION BY s.student_id ORDER BY s.created_at DESC) AS rk
FROM student s)
SELECT s.*
FROM summary s
WHERE s.rk = 1
AND is_enrolled = 1
What I did was adding an extra column after the order by is done, you want to see if the latest created value has an is_enrolled value of 1.
The "With" part is used to define a sub query, with some extra logic in there.
You can use aggreation:
select student_id
from student s
group by student_id
having sum(is_enrolled) >= 1 and
max(created_at) = max(case when is_enrolled = 0 then created_at end);
The first condition checks that the student is enrolled at least once.
The second checks that the latest created_at is the latest created_at for an unenrolled record. That checks that the last status is "unenrolled".
Here is the SQL Fiddle.

How to get Database-table entries where a certain column is not linked to a certain value by another table?

I have a database with the following tables: Students, Classes, link_student_class. Where Students contains the information about the registered students and classes contains the information about the classes. As every student can attend multiple classes and every class can be attended by multiple students, I added a linking-table, for the mapping between students and classes.
Linking-Table
id | student_id | class_id
1 1 1
2 1 2
3 2 1
4 3 3
In this table both student_id as well as class_id will appear multiple times!
What I am looking for, is a SQL-Query that returns the information about all students (like in 'SELECT * FROM students') that are not attending a certain class (given by its id).
I tried the following SQL-query
SELECT * FROM `students`
LEFT JOIN(
SELECT * FROM link_student_class
WHERE class_id = $class_id
)
link_student_class ON link_student_class.student_id = students.student_id
Where $class_id is the id of the class which students i want to exclude.
In the returned object the students i want to include and those i want to exclude are different in the value of the column 'class_id'.
Those to be included have the value 'NULL' whereas those I want to exclude have a numerical value.
NOT EXISTS comes to mind:
select s.*
from students s
where not exists (select 1
from link_student_class lsc
where lsc.student_id = s.student_id and
lsc.class_id = ?
);
The ? is a placeholder for the parameter that provides the class.
you should check for NULL link_student_class.student_id
SELECT *
FROM `students`
LEFT JOIN(
SELECT *
FROM link_student_class
WHERE class_id = $class_id
) link_student_class ON link_student_class.student_id = students.student_id
where link_student_class.student_id is null
Or also a NOT IN predicate:
WITH
stud_class(id,stud_id,class_id) AS (
SELECT 1, 1,1
UNION ALL SELECT 2, 1,2
UNION ALL SELECT 3, 2,1
UNION ALL SELECT 4, 3,3
)
,
stud(stud_id,fname,lname) AS (
SELECT 1,'Arthur','Dent'
UNION ALL SELECT 2,'Ford','Prefect'
UNION ALL SELECT 3,'Tricia','McMillan'
UNION ALL SELECT 4,'Zaphod','Beeblebrox'
)
SELECT
s.*
FROM stud s
WHERE stud_id NOT IN (
SELECT
stud_id
FROM stud_class
WHERE class_id= 2
);
-- out stud_id | fname | lname
-- out ---------+--------+------------
-- out 3 | Tricia | McMillan
-- out 4 | Zaphod | Beeblebrox
-- out 2 | Ford | Prefect
-- out (3 rows)
-- out
-- out Time: First fetch (3 rows): 9.516 ms. All rows formatted: 9.550 ms

mysql "and" logic within result set

Say I have a data set like the following:
table foo
id | employeeType | employeeID
-------------------------
1 | Developer | 1
2 | Developer | 2
3 | Developer | 3
4 | Manager | 1
5 | Manager | 4
6 | Manager | 5
7 | CEO | 1
8 | CEO | 6
and I wanted to run a query that would return all the employeeids (along with the employeeTypes) where there is a common employee id between all employeeTypes (that's the 'and' logic. ONly employeeIDs that have all employeeTypes will return. employeeType = Developer and employeeType=Manager and employeeType=CEO). For the data above the example output would be
result table
id | employeeType | employeeID
-------------------------
1 | Developer | 1
4 | Manager | 1
7 | CEO | 1
I was able to do this when I only had only TWO employeeTypes by self joining the table like this.
select * from foo as fooOne
join foo as fooTwo
on fooOne.employeeID = fooTwo.employeeID
AND
fooOne.employeeType <> fooTwo.employeeType
that query returns a result set with values from fooTwo when the 'and' logic matches, but again, only for two types of employees. My real use case scenario dictates that I need to be able to handle a variable number of employeeTypes (3, 4, 5, etc...)
Any thoughts on this would be greatly appreciated.
This should return the rows that you want:
SELECT foo.*
FROM
foo
WHERE
employeeID IN (
SELECT employeeID
FROM foo
GROUP BY employeeID
HAVING COUNT(DISTINCT employeeType) =
(SELECT COUNT(DISTINCT employeeType)
FROM foo)
)
Please see a fiddle here.
The inner query will return the number of distinct employee types:
(SELECT COUNT(DISTINCT employeeType) FROM foo)
The middle query will return all the employee IDs that have the maximum number of employee types:
SELECT employeeID
FROM foo
GROUP BY employeeID
HAVING COUNT(DISTINCT employeeType) =
(SELECT COUNT(DISTINCT employeeType) FROM foo)
and the outer query will return the whole rows.
You can try a subquery to make it dynamic
SELECT employeeID, employeeType
FROM foo
WHERE employeeID IN (
SELECT employeeID
FROM foo
GROUP BY employeeID
HAVING COUNT(DISTINCT employeeType) = (SELECT COUNT(DISTINCT employeeType) FROM foo)
)
I agree that this might be looked down as a very inefficient/hacky way of doing things, but this should still get the job done. And frankly, I can't see any other way out of this.
SELECT * FROM (
SELECT EMPLOYEE_ID, GROUP_CONCAT(DISTINCT EmployeeType ORDER BY EmployeeType) AS Roles
FROM EMPLOYEES GROUP BY EMPLOYEE_ID
) EMPLOYEE_ROLES
WHERE EMPLOYEE_ROLES.Roles = 'CEO,Developer,Manager';
Note that the comma separated list of roles provided in the end is in the alphabetical order.

SQL "chained" queries?

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.

mysql select matching results

I'm trying to find the leagues (lid) where two users are apart of.
Here are my tables:
Table leagues:
*id* lname
--------------
1 Hard C
3 Fun
5 Crazy
Table match:
*userid* *lid*
-----------------
1 1
4 5
1 3
2 1
4 1
4 3
*Are primary keys
match.lid is foreign key to leagues.id (a user cannot not be part of the same league twice)
Here's what I have so far (a start):
SELECT t1.lid, t2.lname
FROM match t1
JOIN leagues t2 on t1.lid = t2.id
So far I managed to join the two tables and get the names. My ultimate goal is to show the lid's where two users are part of the same league, say userid 1 and 4.
userid 1 is a member of lid 1 and 3
userid 4 is a member of lid 5, 1, and 3
Both users meet in league(lid) 1 and 3
So I need a query that shows only the league where both users meet. Like this:
lid lname
--------------
1 Hard C
3 Fun
Since userid 1 and 4 meet in league 1 and 3, the results should show that. I can run two queries for each user and check which leagues both users meet via php, but I think it's more efficient to run one query.
SELECT m1.lid, l.lname FROM
`match` m1, `match` m2, leagues l
WHERE m1.lid = m2.lid AND m1.lid = l.id
AND m1.userid = 1
AND m2.userid = 4
There are a few ways. The most straightforward is:
SELECT id AS lid,
lname
FROM leagues
WHERE id IN
( SELECT lid
FROM match
WHERE userid = 1
)
AND id IN
( SELECT lid
FROM match
WHERE userid = 4
)
;
Another way, which is a bit less direct, but may perform better — you can try it and see — is to use JOIN:
SELECT id AS lid,
lname
FROM leagues
JOIN match AS match1
ON match1.lid = leagues.id
AND match1.userid = 1
JOIN match AS match2
ON match2.lid = leagues.id
AND match2.userid = 4
;