How to remove count values "0" from result table? - sql

I'm trying to write a query that returns a table with columns name and numberOfClasses. This table includes the name of all students and the amount of classes the student follows. I use the following database tables:
╔══════════════╦═══════════╦══════════╗
║ TakesClasses ║ ║ ║
╠══════════════╬═══════════╬══════════╣
║ id ║ person_id ║ class_id ║
║ 99 ║ 1 ║ 40 ║
║ 98 ║ 1 ║ 41 ║
║ 97 ║ 1 ║ 42 ║
║ 96 ║ 1 ║ 43 ║
║ 95 ║ 2 ║ 44 ║
║ 94 ║ 2 ║ 45 ║
║ 93 ║ 2 ║ 46 ║
╚══════════════╩═══════════╩══════════╝
╔═════════╦═══════╦══╗
║ Persons ║ ║ ║
╠═════════╬═══════╬══╣
║ id ║ name ║ ║
║ 1 ║ Bart ║ ║
║ 2 ║ David ║ ║
║ 3 ║ Dani ║ ║
║ 4 ║ Erik ║ ║
╚═════════╩═══════╩══╝
I used the following query:
SELECT
name,
COUNT(T.person_id) AS numberOfClasses
FROM
Persons P
LEFT OUTER JOIN
TakesClasses T ON P.id = T.person_id
GROUP BY
P.id, P.name
And this is the output:
╔═══════╦═════════════════╗
║ name ║ numberOfClasses ║
╠═══════╬═════════════════╣
║ Bart ║ 4 ║
║ Dani ║ 3 ║
║ David ║ 0 ║
║ Erik ║ 0 ║
╚═══════╩═════════════════╝
How can I remove the entries with 0 from my the results table?
Thanks for the help in advance!

Just use JOIN instead of LEFT JOIN
select name, count(T.person_id) as amountOfClasses
from Persons P join
TakesClasses T
on P.id = T.person_id
group by P.id, P.name

If you want to filter on aggregated data like what comes out of GROUP BY you can use the HAVING clause
SELECT name,COUNT(T.person_id) as amountOfClasses
FROM Persons P LEFT OUTER JOIN TakesClasses T
on P.id = T.person_id
GROUP BY P.id, P.name
HAVING COUNT(T.person_id) > 0;
sqlFiddle: http://sqlfiddle.com/#!9/1108c5/8

Related

How to filter SQL dataset based off value in one column?

I have a table with this structure:
╔══════╦════╦═════════╗
║ Comp ║ ID ║ Desc ║
╠══════╬════╬═════════╣
║ 1 ║ 1 ║ Comp1-1 ║
║ 1 ║ 2 ║ Comp1-2 ║
║ 3 ║ 2 ║ Comp3-2 ║
║ 1 ║ 3 ║ Comp1-3 ║
║ 1 ║ 4 ║ Comp1-4 ║
║ 3 ║ 5 ║ Comp3-5 ║
╚══════╩════╩═════════╝
The dataset I'm creating should have a unique ID.
If an ID exists in Comp1, use that Desc.
If it does not exist in Comp1, use Comp3.
End result should look like this instead:
╔══════╦════╦═════════╗
║ Comp ║ ID ║ Desc ║
╠══════╬════╬═════════╣
║ 1 ║ 1 ║ Comp1-1 ║
║ 1 ║ 2 ║ Comp1-2 ║
║ 1 ║ 3 ║ Comp1-3 ║
║ 1 ║ 4 ║ Comp1-4 ║
║ 3 ║ 5 ║ Comp3-5 ║
╚══════╩════╩═════════╝
I've tried using NOT EXISTS and joining with a subquery but I'm not sure what to Join on.
Using not exists, it looks like:
select t.*
from t
where t.descr like 'Comp1-%' or
not exists (select 1
from t t2
where t2.id = t.id and t2.descr like 'Comp1-%'
);

How do I query every person that does not know anyone that takes classes?

I'm struggling with the following SQL problem:
I must query all persons that do not know anyone who takes classes, using the following tables:
╔═════════╦════════╦═══════════╦═════╗
║ Persons ║ ║ ║ ║
╠═════════╬════════╬═══════════╬═════╣
║ id ║ name ║ address ║ age ║
║ 1 ║ Teddy ║ Utrecht ║ 96 ║
║ 2 ║ Harun ║ Texas ║ 64 ║
║ 3 ║ Egbert ║ Rome ║ 68 ║
║ 4 ║ John ║ Amsterdam ║ 39 ║
╚═════════╩════════╩═══════════╩═════╝
╔═══════╦════════════╦════════════╗
║ Knows ║ ║ ║
╠═══════╬════════════╬════════════╣
║ id ║ personA_id ║ personB_id ║
║ 25 ║ 1 ║ 2 ║
║ 26 ║ 1 ║ 3 ║
║ 27 ║ 3 ║ 2 ║
║ 28 ║ 3 ║ 4 ║
╚═══════╩════════════╩════════════╝
╔══════════════╦═══════════╦══════════╗
║ TakesClasses ║ ║ ║
╠══════════════╬═══════════╬══════════╣
║ id ║ person_id ║ class_id ║
║ 35 ║ 1 ║ 50 ║
║ 36 ║ 1 ║ 51 ║
║ 37 ║ 1 ║ 52 ║
║ 38 ║ 1 ║ 53 ║
║ 39 ║ 2 ║ 54 ║
║ 40 ║ 2 ║ 55 ║
║ 41 ║ 2 ║ 56 ║
╚══════════════╩═══════════╩══════════╝
After a long time trying different queries, the following query is the closest I got to the desired result:
select distinct name
from Persons P
where P.id NOT IN
(select person_id
from TakesClasses T join Knows K
on T.person_id = K.personA_id
where class_id IS NOT NULL)
I seem to get more results back than is necessary for this query (Harun, Egbert and John), but I cannot find the mistake I'm making. Can someone please help me out?
use this:
select P.id, name
from Persons P
where P.id NOT IN
(select K.personA_id
from TakeClasses T join Knows K
on T.person_id = K.personB_id
)
This type of problem suggests not exists:
select p.*
from persons p
where not exists (select 1
from takesclasses tc join
knows k
on tc.personB_id = k.person_id
where k.personA_id = p.id
);
The correlation clause connects the subquery to the outer query.
Note that this assumes that the knows relationship is symmetric. So if A "knows" B then B "knows" A is also in the table.
EDIT:
I strongly advise you never to use NOT IN with subqueries. It does not behave as expected if any values returned in the subquery are NULL. In this case, though, you can do:
select p.*
from persons p
where p.id not in (select k.personA_id
from takesclasses tc join
knows k
on tc.personB_id = k.person_id
);

Sorting and Number results based in order, using multiple columns "order by" criteria

all
I've been trying to do, with data following the structure:
╔══════════════╦═══════════╦══╗
║ Alphabetical ║ Numerical ║ ║
╠══════════════╬═══════════╬══╣
║ A ║ 15 ║ ║
║ A ║ 30 ║ ║
║ E ║ 100 ║ ║
║ C ║ 45 ║ ║
║ F ║ 25 ║ ║
║ C ║ 65 ║ ║
║ B ║ 25 ║ ║
║ F ║ 35 ║ ║
║ C ║ 100 ║ ║
║ A ║ 10 ║ ║
║ C ║ 20 ║ ║
║ B ║ 5 ║ ║
║ E ║ 10 ║ ║
║ F ║ 85 ║ ║
║ D ║ 30 ║ ║
║ F ║ 1 ║ ║
╚══════════════╩═══════════╩══╝
To get the following:
╔══════════════╦══════╦═════════╗
║ Alphabetical ║ Rank ║ Numeric ║
╠══════════════╬══════╬═════════╣
║ A ║ 1 ║ 30 ║
║ A ║ 2 ║ 15 ║
║ A ║ 3 ║ 10 ║
║ B ║ 1 ║ 25 ║
║ B ║ 2 ║ 5 ║
║ C ║ 1 ║ 100 ║
║ C ║ 2 ║ 65 ║
║ C ║ 3 ║ 45 ║
║ C ║ 4 ║ 20 ║
║ D ║ 1 ║ 30 ║
║ E ║ 1 ║ 100 ║
║ E ║ 2 ║ 10 ║
║ F ║ 1 ║ 85 ║
║ F ║ 2 ║ 35 ║
║ F ║ 3 ║ 25 ║
║ F ║ 4 ║ 1 ║
╚══════════════╩══════╩═════════╝
Basically, to order the alphabetical field in ascending order, the numerical field in descending order and get the order or rank by using the order used for the numerical field, grouped by the alphabetical field.
I have only achieved it if I limit it to one specific value in the Alphabetical column, by using something like:
select ordered_src.*, ROWNUM Rank from (select src.* from Source src where alphabetical = 'A' order by Numeric desc) ordered_src;
But I have no idea how to get the result shown above. Any idea? Also, is there any alternative that will work also in mysql/mssql/etc?
Thanks!
Use row_number():
select s.*,
row_number() over (partition by alphabetical order by numerical desc) as rank
from source s
order by alphabetical, rank;

Selecting unique values from self-referencing table

Suppose we have the following data in the table named My_Tabel:
╔═══════════╦═════════════╦════════════╗
║ ID ║ Person_Name ║ Partner_ID ║
╠═══════════╬═════════════╬════════════╬
║ 101 ║ John ║ 3 ║
║ 100 ║ Miller ║ 0 ║
║ 3 ║ Ruby ║ 101 ║
║ 180 ║ Jack ║ 0 ║
║ 199 ║ George ║ 65 ║
║ 23 ║ Joseph ║ 0 ║
║ 34 ║ Fredrick ║ 117 ║
║ 117 ║ Jinan ║ 34 ║
║ 122 ║ Verena ║ 0 ║
║ 65 ║ Mary ║ 199 ║
╚═══════════╩═════════════╩════════════╝
Where 0 values in Partner_ID Column indicates that he/she is single.
We need to display partnered persons without repeating or duplication, the desired result should look like:
╔═════════════╦══════════════╗
║ Person_Name ║ Partner_Name ║
╠═════════════╬══════════════╬
║ John ║ Ruby ║
║ George ║ Mary ║
║ Fredrick ║ Jinan ║
╚═════════════╩══════════════╝
what is the best SQL query that returns the above results?
I'm using this code:
SELECT
t1.Name, t2.Name
FROM My_Tabel t1
INNER JOIN My_Tabel t2 ON (t2.ID = t1.Partner_ID)
but it the returned result is:
╔═════════════╦══════════════╗
║ Person_Name ║ Partner_Name ║
╠═════════════╬══════════════╬
║ John ║ Ruby ║
║ Ruby ║ John ║
║ George ║ Mary ║
║ Mary ║ George ║
║ Fredrick ║ Jinan ║
║ Jinan ║ Fredrick ║
╚═════════════╩══════════════╝
how the SQL statement should be updated (or replaced with another) to get the desired results?
Just add a condition to get one side of each pair:
SELECT t1.Name, t2.Name
FROM My_Table t1 INNER JOIN
My_Table t2
ON (t2.ID = t1.Partner_ID)
WHERE t1.ID < t2.ID;

Script to insert default values to MANY-TO-MANY relation in SQL Server 2008

I'm using SQL Server 2008. Consider the following tables:
Table STUDENT
╔════╦═══════════╦══════════╗
║ ID ║ FIRSTNAME ║ LASTNAME ║
╠════╬═══════════╬══════════╣
║ 1 ║ joe ║ Smith ║
║ 2 ║ frank ║ Smith ║
║ 3 ║ Scott ║ Smith ║
╚════╩═══════════╩══════════╝
Table COURSE
╔════╦═════════╦════════════════╗
║ ID ║ NAME ║ DESCRIPTION ║
╠════╬═════════╬════════════════╣
║ 1 ║ Physics ║ PHYSICS COURSE ║
║ 2 ║ MATH ║ COURSE ║
╚════╩═════════╩════════════════╝
TABLE STUDENT_COURSE
╔════════════╦═══════════╦═══════╗
║ STUDENT_ID ║ COURSE_ID ║ GRADE ║
╠════════════╬═══════════╬═══════╣
║ 1 ║ 1 ║ 9 ║
║ 2 ║ 1 ║ 8 ║
║ 3 ║ 1 ║ 6 ║
╚════════════╩═══════════╩═══════╝
I need to insert default grade 10 to all users for MATH course, if such grade does not exist for that user.
I'm not familiar with Microsoft SQL Server and T-SQL. Can you please help me with such query?
INSERT INTO STUDENT_COURSE (Student_ID, course_ID, grade)
SELECT a.ID as StudentID, b.ID as Course_ID, 10 AS grade
FROM Student a
CROSS JOIN Course b
LEFT JOIN
(
SELECT d.*
FROM STUDENT_COURSE d
INNER JOIN Course e
ON d.Course_ID = e.ID
WHERE e.Name = 'Math'
) c ON a.ID = c.student_ID AND
b.ID = c.course_ID
WHERE c.student_ID IS NULL AND
b.Name = 'Math'
SQLFiddle Demo
SQLFiddle Demo (added record which one student has already grade on MATH)
OUTPUT if you List all the records on table STUDENT_COURSE
MySQL> SELECT * FROM STUDENT_COURSE
╔════════════╦═══════════╦═══════╗
║ STUDENT_ID ║ COURSE_ID ║ GRADE ║
╠════════════╬═══════════╬═══════╣
║ 1 ║ 1 ║ 9 ║
║ 2 ║ 1 ║ 8 ║
║ 3 ║ 1 ║ 6 ║
║ 1 ║ 2 ║ 10 ║
║ 2 ║ 2 ║ 10 ║
║ 3 ║ 2 ║ 10 ║
╚════════════╩═══════════╩═══════╝