SQL query or sub-query? - sql

I've got a table of student information in MySQL that looks like this (simplified):
| age : int | city : text | name : text |
-----------------------------------------------------
| | | |
I wish to select all student names and ages within a given city, and also, per student, how many other students in his age group (that is, how many students share his age value).
I managed to do this with a sub-query; something like:
select
name,
age as a,
(select
count(age)
from
tbl_students
where
age == a)
from
tbl_students
where
city = 'ny'
But it seems a bit slow, and I'm no SQL-wiz, so I figure I'd ask if there's a smarter way of doing this. The table is indexed by age and city.

select
t1.name,
t1.age as a,
count(t2.age) NumberSameAge
from
tbl_students t1 inner join tbl_students t2
on t1.age=t2.age
where
city = 'ny'
group by t1.name, t1.age
not tested, but something like that. I.o.w. a groupby on a join. This sometimes can be faster as the query you're running is doing a nested subquery for every row returned, and the query I posted above (or at least, the structure with a join and a groupby) performs a query on the related students just once.

It might be easier to grab a sub-query that grabs everything at once (vs. 1000 rows where it runs the sub-query 1000 times).
SELECT Age, count(*) AS SameAge FROM tbl_students
Making the full query:
SELECT t.Name, t.Age, s.SameAge
FROM tbl_students t
INNER JOIN (
SELECT Age, count(*) AS SameAge FROM tbl_students
) AS s
ON (t.Age = s.Age) -- m:1
WHERE t.City = 'NY'

Related

Oracle SQL - Return rows with a value based on multiple values in a second field

I need to return the rows that contain the employee names (in one field) who are only classified as Managers (not as Workers, or as Managers and Workers).
Managers and Workers values are in a second field.
So it might look like this:
+----------+------------+
| 'Miller' | 'Manager' |
| 'Jones' | 'Manager' |
| 'Jones' | 'Worker' |
+----------+------------+
In this instance I just want it to return 'Miller'.
I can get one or both, but not the ones where an employee is only classified as a Manager.
Any thoughts?
One method uses aggregation:
select name
from t
group by name
having min(classification) = max(classification) and min(classification) = 'manager';
Count the number of titles. If they have a title of 'Manager' and there's only one title, select the individual:
SELECT *
FROM PEOPLE p
INNER JOIN (SELECT NAME, COUNT(TITLE) AS TITLE_COUNT
FROM PEOPLE
GROUP BY NAME) c
ON c.NAME = p.NAME
WHERE p.TITLE = 'Manager' AND
c.TITLE_COUNT = 1;
dbfiddle here
Method with a subquery which should work well when there are not only 'Managers' and 'Workers' in the table:
SELECT t1.name FROM t t1
WHERE
t1.classification='Manager'
AND NOT EXISTS (
SELECT 1 FROM t t2 WHERE t1.name=t2.name AND t2.classification='Worker'
)

MSSQL: Display Rows for a Select with Case and Count even if Count = 0

i hope i can explain my Problem in detail, so you guys can understand me.
Ive created a small example.
I have a Table which looks like this:
City | Name
Berlin | Mike
Berlin City| Peter
Stuttgart | Boris
here is my Query:
SELECT CASE
WHEN City like '%Berlin%' THEN 'Count Person in Berlin:'
WHEN City like '%Stuttgart%' THEN 'Count Person in Stuttgart:'
WHEN City like '%Dresden%' THEN 'Count Person in Dresden:'
ELSE 'unknown'
END AS Text,
COUNT(Name) AS countPersons
FROM tblTest
GROUP BY City
This is the result:
Count Person in Berlin: 2
Count Person in Stuttgart: 1
But my desired result is:
Count Person in Berlin: 2
Count Person in Stuttgart: 1
Count Person in Dresden: 0
how can i get my desired result? hope you can help me.
Thanks in advance.
SQL Fiddle Demo
If you don't have a table with the list of cities, then you can use a subquery. The key to solving this type of problem is left outer join:
select cities.city, count(t.city) as numpeople
from (select 'Berlin' as city union all
select 'Stuttgart' union all
select 'Dresden'
) cities left outer join
tbltest t
on t.city = cities.city
group by cities.city;
If you want to have 'unknown' as well, then full outer join can be used:
select coalesce(cities.city, 'unknown') as city, count(t.city) as numpeople
from (select 'Berlin' as city union all
select 'Stuttgart' union all
select 'Dresden'
) cities full outer join
tbltest t
on t.city = cities.city
group by coalesce(cities.city, 'unknown');

How to fetch the non matching rows in Oracle

Can anyone help me fetch the non matching rows from two tables in Oracle?
Table: Names
Class_id Stud_name
S001 JAMES
S001 PETER
S002 MARK
Table: Course
Course_id Stud_name
S001 JAMES
S001 KEITH
S002 MARK
Output
I need the rows to display as
CLASS ID STUD_NAME_FROM_NAME_TABLE STUD_NAME_FROM_COURSE_TABLE
---------------------------------------------------------------------
S001 PETER KEITH
I have used Oracle joins to fetch the non matching names:
SELECT *
FROM Names, Course
WHERE Names.Class_id=Course.Course_id
AND Names.Stud_name<>Course.Stud_name
This query is returning duplicate rows.
If you insist on Join you can use this one:
SELECT *
FROM Names
FULL OUTER JOIN Course ON Names.Class_id=Course.Course_id
AND Names.Stud_name = Course.Stud_name
WHERE Names.Stud_name IS NULL or Course.Stud_name IS NULL
Fetches unmatched rows in Names table
SELECT * FROM Names
WHERE
NOT EXISTS
(SELECT 'x' from Course
WHERE
Names.Class_id = Course.Course_id AND
Names.Stud_name = Course.Stud_name)
Fetches unmatched rows in Names and Course too!
SELECT Names.Class_id,Names.Stud_name,C1.Stud_name
FROM Names , Course C1
WHERE Names.Class_id = C1.Course_id AND
NOT EXISTS
(SELECT 'x' from Course C2
WHERE
Names.Class_id = C2.Course_id AND
Names.Stud_name = C2.Stud_name);
When you ask for unmatching rows I assume that you want rows that exist in names but not in course.
If this is the case you're probably after
select * from names
where (class_id, stud_name ) not in
(select course_id, stud_name from course);
Your query returned duplicate rows beacuse for each row in names it selected all rows in course that satisfied the where condition.
So, for the row S001, PETER in names it faound that S001, JAMES and S001, KEITH matched that condition, thus, that row was "returned" twice.
EDIT Since it is not clear if stud_name is a primary key, or unique (and on second sight I think it's not), you'd probably want a
select * from names
where not exists (
select 1 from course where
names.class_id = course.course_id and
names.stud_name <> course.stud_name
)
Edit II if you insist on using a join (as per your comment) you might want to try a
select distinct names.* from...
Hope it helps you
with not_in_class as
(select a.*
from Names a
where not exists ( select 'x'
from course b
where b.Course_id = a.class_id
and a.Stud_name = b.Stud_name)),
not_in_course as
(select b.*
from course b
where not exists ( select 'x'
from Names a
where b.Course_id = a.class_id
and a.Stud_name = b.Stud_name))
select x.class_id,
x.Stud_name NOT_IN_CLASS,
y.stud_name NOT_IN_COURSE
from not_in_class x, not_in_course y
where x.class_id = y.course_id
Output
| CLASS_ID | NOT_IN_CLASS | NOT_IN_COURSE |
|----------|--------------|---------------|
| S001 | PETER | KEITH |
Only problem is that if multiple mismatches are there in both the tables for a given id, it works for single mismatch for a particular id. You need to rework if multiple mismatches are there for the same id.
Well, I am not sure if I understand correctly what you are asking. I think you want a list of all IDs where the student list in class table and course table differs. Then you want to show the id and the students that are in class but not in course and the students that are in course but not in class.
To do so you would full outer join the tables. That gives you students that are both in class and course, students that are in class and not in course, and students that are in course and not in class. Filter your results where either class_id or course_id is null then to get the students missing in course or class. At last group by id and list the students.
select coalesce(class.class_id, course.course_id) as id
, listagg(class.stud_name, ',') within group (order by class.stud_name) as missing_in_course
, listagg(course.stud_name, ',') within group (order by course.stud_name) as missing_in_class
from class
full outer join course
on (class.class_id = course.course_id and class.stud_name = course.stud_name)
where class.class_id is null or course.course_id is null
group by coalesce(class.class_id, course.course_id);
Here is the SQL fiddle showing how it works: http://sqlfiddle.com/#!4/8aaaa/2
EDIT: In Oracle 9i there is no listagg. You can use the inofficial function wm_concat instead:
select coalesce(class.class_id, course.course_id) as id
, wm_concat(class.stud_name) as missing_in_course
, wm_concat(course.stud_name) as missing_in_class
from class
full outer join course
on (class.class_id = course.course_id and class.stud_name = course.stud_name)
where class.class_id is null or course.course_id is null
group by coalesce(class.class_id, course.course_id);

Creating table using select statement from multiple tables

I have this university task to create table using SELECT statement from multiple tables, but it's not as simple... here's basic info:
I'm using 2 tables -
CITY(city_ID, name);
PERSON(person_ID, name, surname, city_ID);
--city_ID is FK indicating in which city person was born.
Now my task is to create new table
STATISTICS(city_ID, city_name, number_of_births);
--number_of_births is basically a count of people born in each city
Problem is that I have to use only SELECT statement to do so.
I've tried something like this: (I'm well aware that this cannot possibly work but as to give you a better idea where I'm stuck)
CREATE TABLE Statistics AS
(SELECT city.city_ID, city.name as "city_name", number_of_births AS
(SELECT COUNT(*) FROM person WHERE person.city_id = city.city_id)
FROM city, person);
For SQL Server you can do SELECT * INTO. Something like this:
SELECT
*
INTO Statistics
FROM (
SELECT
city.city_ID,
city.name as "city_name",
(SELECT COUNT(*) FROM person WHERE person.city_id = city.city_id) as 'number_of_births'
FROM city
inner join person on city.city_id = person.city_id
) t1
(Posted on behalf of the question author).
Ok, this got really messy. Dave Zych's answer was correct when rewritten in Oracle dialect.
CREATE TABLE Statistics AS SELECT * FROM (
SELECT DISTINCT
city.city_ID,
city.name AS "City_name",
(SELECT COUNT(*) FROM person WHERE person.city_ID = city.city_ID) AS "number_of_births"
FROM city INNER JOIN person ON city.city_ID = person.city_ID);

Get distinct result

I was asked this trick question:
Table: Student
ID NAME
1 JOHN
2 MARY
3 ROBERT
4 DENNIS
Table: Grade
ID GRADE
1 A
1 A
1 F
2 B
3 A
How do you write SQL query to return DISTINCT name of all students who has never received grade 'F' OR who has never taken a course (meaning, their ID not present in Grade table)?
Trick part is, you're not allowed to use OUTER JOIN, UNION or DISTINCT. Also, why this is a big deal?
Expected result is MARY, ROBERT, DENNIS (3 rows).
SELECT name FROM Student
WHERE
NOT EXISTS (SELECT * FROM Grade WHERE Grade.id = Student.id AND grade = 'F')
OR
NOT EXISTS (SELECT * FROM Grade WHERE Grade.id = Student.id);
You may use GROUP BY in order to fake a distinct.
SELECT name FROM student
WHERE (SELECT COUNT(*) FROM grade WHERE grade = 'F'
AND id = student.id) = 0
at least this is the shortest answer so far ...
Something like this could work, if you're allowed to use subqueries.
SELECT `NAME`
FROM Student
WHERE 'F' NOT IN
(SELECT GRADE FROM Grade WHERE ID = Student.ID)
Hmm, my homework sense is tingling... Not that my questions have never related to homework though...
You could use the GROUP BY and aggregate functions in order to fake a distinct.
You want to exclude everyone who has both taken a course and received a grade of 'F'. Something like this might work:
SELECT NAME
FROM Student
WHERE 0 = (SELECT COUNT(*)
FROM Student
LEFT JOIN Grade
USING (ID)
WHERE GRADE='F')
GROUP BY NAME
SELECT name
FROM grade G, student S
WHERE (S.id = G.id AND 'F' NOT IN (SELECT G1.grade
FROM grade G1
WHERE G1.id = G.id))
OR
S.id NOT IN (SELECT id
FROM grade)
GROUP BY name
A reason why they might not want you to use UNION, JOIN or DISTINCT is that some of those queries might be slow if you try to force an "optimized" solution.
I'm not too familiar with query optimization techniques but usually if you use some of those aggregators and JOINs, you might slow down your query rather than just letting the query optimizations run through and organize your SQL based on your table structure and contents.