Oracle SQL Programming Query - sql

Apologies, but I have little experience with SQL programming and I need to figure out why my SQL query isn't working...I have been trying to no avail to work this out!
Database SQL file located here
Database schema as follows:
DEPARTMENT(deptnum, descrip, instname, deptname, state, postcode)
ACADEMIC(acnum, deptnum*, famname, givename, initials, title)
PAPER(panum, title)
AUTHOR(panum*, acnum*)
FIELD(fieldnum, id, title)
INTEREST(fieldnum*, acnum*, descrip)
Essentially I am trying to find out the following, and having some real issues:
Need to find the academics that have more than 1 research interest. I need to list the acnum, famname and givename of these academics, sorted by famname and then by givename.
select A.acnum, A.givename, A.famname, INTEREST.FIELDNUM
from ACADEMIC A, INTEREST
where A.ACNUM = INTEREST.ACNUM
having count (Interest.acnum) > 1;
Something like this?
I need to find if there are any research fields where no academics have no interest in? I need to print the total number of research fields like this. I believe the query uses a SET operator.
I need to find the research fields that have the largest number of interested academics. I need to output the fieldnum and number of interested academics.

The schema is not quite clear to me, but here is a starting point:
1) First one:
SELECT a.acnum, a.famname ,a.givename, count(i.fieldnum)
FROM ACADEMIC a INNER JOIN INTEREST i ON a.acnum = i.acnum
GROUP BY a.acnum, a.famname ,a.givename
HAVING COUNT(i.fieldnum) > 1
ORDER BY a.famname ,a.givename;
2) If you just need the number of them:
SELECT COUNT(1)
FROM FIELD f
WHERE NOT EXISTS(SELECT 1
FROM ACADEMIC a
INNER JOIN INTEREST i ON a.acnum = i.acnum
WHERE f.fieldnum = i.fieldnum);
3) It's a bit fuzzy, because I don't know what largest actually means, but here is the sorted list:
SELECT f.fieldnum, count(a.acnum) as number_of_interested_academics
FROM FIELD f
INNER JOIN INTEREST i ON f.fieldnum = i.fieldnum
INNER JOIN ACADEMIC a ON i.acnum = a.acnum
GROUP BY f.fieldnum
ORDER BY count(a.acnum) DESC;

Related

How can I finish my code based on the given question and what I have so far

Before I continue I do want to say this is FOR homework for my class. We are using SQL in Microsoft Access. With the transition to online classes because of covid-19 and my professor developing the virus last month we have not have had time to really dive much into how SQL works. I will also provide a picture of the ER Diagram I have of the database to hopefully provide a clear picture of what I'm trying to achieve. https://imgur.com/a/4QGRgRc
Now the question I'm given to solve is this:
Retrieve the phone number of the customer(s), who lives in Tyler, TX, booked two rooms on Jan 1, 2020.
The code I have written so far is as follows
SELECT CUSTOMER.Phone_No, CUSTOMER.Address, RESERVATION.Reservation_No, INCLUDES.Check_in, ROOM.Room_No
FROM CUSTOMER, RESERVATION, INCLUDES, ROOM
SELECT CUSTOMER.Phone_No, WHERE CUSTOMER.Address = 'Tyler, Tx' AND INCLUDES.Check_in < '12/31/2019' AND INCLUDES.Check_in > '01/01/2020'
I'm pretty stuck on where to go now or even if what I have so far will work. Could someone give me a point in the right direction on where I should go now?
If you just want customer info, use an aggregate query. Each field pulled must be included in GROUP BY clause:
SELECT Customer.ID, CustomerName, Phone_No FROM Customer INNER JOIN Reservation ON Customer.ID = Reservation.CustomerID WHERE Address = 'Tyler, Tx' AND Check_in BETWEEN #12/31/2019# AND #01/01/2020# GROUP BY Customer.ID, CustomerName, Phone_No HAVING Count(CustomerID) = 2;
Or take advantage of the power of nested query and IN() function:
SELECT * FROM Customer WHERE Customer.Address = 'Tyler, Tx' AND Customer.ID IN (SELECT CustomerID FROM Reservation WHERE Check_in BETWEEN #12/31/2019# AND #01/01/2020# GROUP BY CustomerID HAVING Count(*) = 2);
Or use DCount(). However, domain aggregate functions can slow query performance.
SELECT * FROM Customer WHERE Customer.Address = 'Tyler, Tx' AND DCount("*", "Reservation", "Check_in BETWEEN #12/31/2019# AND #01/01/2020# AND CustomerID=" & Customer.ID) = 2;

SQL: nested query with tuple constructor

I have some difficulties dealing with an SQL exercise for my Intro to Database course. The SQL standard we mainly use is the Oracle one (the one compatible with Apex).
I have the following SQL database (primary keys bold):
TEENAGER(SSN, Name, Surname, BirthDate, CityOfResidence, Sex)
ACTIVITY(ActivityCode, AName, Description, Category)
SUMMER-CAMP(CampCode, CampName, City)
SUBSCRIPTION-TO-ACTIVITY-IN-SUMMER-CAMP(SSN,ActivityCode, CampCode,
SubscriptionDate)
This is what the exercise asks:
"For each teenager, born before 2005, who subscribed to activities
organized by at least 5 different summer camps, show name, surname,
birth date of the teenager and the name of each summer camp to which
the teenager subscribed to all the different activities organized by
the camp."
I do not have any problem finding the SSNs of the teenagers born before 2005 and who subscribed to at least 5 camps and I am able to find the number of different activities organized by the camp. How do I manage to use this information to find the final result?
Now, this is my attempt to a solution (I added two in-line comments with "#" for clarity):
FROM TEENAGER T, SUMMER-CAMP SC, SUBSCRIPTION-TO-ACTIVITY-IN-SUMMER-CAMP STAISC
WHERE T.SSN = STAISC.SSN AND STAISC.CampCode = SC.CampCode
AND SSN IN (SELECT T.SSN #born before 2005 and at least 5 camps
FROM TEENAGER T, SUBSCRIPTION-TO-ACTIVITY-IN-SUMMER-CAMP STAISC
WHERE T.BirthDate < TO_DATE('01/01/2005', 'DD/MM/YYYY')
AND T.SSN = STAISC.SSN
GROUP BY T.SSN
HAVING COUNT(DISTINCT STAISC.CampCode) > 4)
GROUP BY STAISC.CampCode, T.SSN
HAVING (STAISC.CampCode, COUNT(DISTINCT ActivityCode)) IN (SELECT CampCode, COUNT(DISTINCT ActivityCode) #number of activities in camps
FROM SUBSCRIPTION-TO-ACTIVITY-IN-SUMMER-CAMP
GROUP BY CampCode)```
As you can see, I am using a tuple constructor in the outer-most query in a HAVING clause to try and use the information about the total number of activities organised in a camp. Am I allowed to do that and would it work? (The professor did not give us any database since in the exam we will have to write down the query without being able to run it).
Thanks in advance!
I answer to my own question since I found a correct solution:
SELECT T.SSN, SC.CampCode, T.Name, T.Surname, T.BirthDate, SC.CampName
FROM TEENAGER T, SUMMER-CAMP SC, SUBSCRIPTION-TO-ACTIVITY-IN-SUMMER-CAMP STAISC
WHERE BirthDate < TO_DATE('01/01/2005', 'DD/MM/YYYY')
AND T.SSN = STAISC.SSN AND STAISC.CampCode = SC.CampCode
AND T.SSN IN(SELECT SSN FROM SUBSCRIPTION-TO-ACTIVITY-IN-SUMMER-CAMP
GROUP BY SSN
HAVING COUNT(DISTINCT CampCode))
GROUP BY T.SSN, SC.CampCode
HAVING COUNT(DISTINCT STAISC.ActivityCode) = (SELECT COUNT(ActivityCode)
FROM SUBSCRIPTION-TO-ACTIVITY-IN-SUMMER-CAMP STAISC2
WHERE STAISC.CampCode = STAISC2.CampCode)

How would you explain this query in layman terms?

Here is the database I'm using: https://drive.google.com/file/d/1ArJekOQpal0JFIr1h3NXYcFVngnCNUxg/view?usp=sharing
select distinct
AC1.givename, AC1.famname, AC2.givename, AC2.famname
from
academic AC1, author AU1, academic AC2, author AU2
where
AC1.acnum = AU1.acnum
and AC2.acnum = AU2.acnum
and AU1.panum = AU2.panum
and AU2.acnum > AU1.acnum
and not exists (select *
from Interest I1, Interest I2
where I1.acnum = AC1.acnum
and I2.acnum = AC2.acnum);
Output:
I'm having trouble explaining this output of the subquery and query in layman terms(Normal english).
Not sure if my explanation is right:
"The subquery finds the interested fields where two authors have no common field of interest.
The whole query finds the first and last names of the authors of papers which have at least two authors, and have no common field of interest."
As it currently stands, the subquery will produce rows if each academic has at least one interest.
So overall, the query is "produce pairs of academics who co-authored at least one paper and where at least one of them has no interests whatsoever". It's difficult to believe that that was the intent, and if it was, there are clearer ways of writing it that make it more clear that that is what we're looking for.
If that's the query we want, though, I'd write it as:
SELECT
AC1.givename, AC1.famname, AC2.givename, AC2.famname
FROM
academic AC1
inner join
academic AC2
on
AC1.acnum < AC2.acnum
WHERE EXISTS
(select * from author au1 inner join author au2 on au1.panum = au2.panum
where au1.acnum = ac1.acnum and au2.acnum = ac2.acnum)
AND
(
NOT EXISTS (select * from interest i where i.acnum = ac1.acnum)
OR
NOT EXISTS (select * from interest i where i.acnum = ac2.acnum)
)
If, as is more likely, we wanted pairs of co-authors who have no interests in common, we would write something like:
SELECT
AC1.givename, AC1.famname, AC2.givename, AC2.famname
FROM
academic AC1
inner join
academic AC2
on
AC1.acnum < AC2.acnum
WHERE EXISTS
(select * from author au1 inner join author au2 on au1.panum = au2.panum
where au1.acnum = ac1.acnum and au2.acnum = ac2.acnum)
AND NOT EXISTS
(select * from interest i1 inner join interest i2 on i1.field = i2.field
where i1.acnum = ac1.acnum and i2.acnum = ac2.acnum)
Notice how neither of my queries uses distinct, because we've made sure that the outer query isn't joining additional rows where we only care about the existence or absence of those rows - we've moved all such checks into EXISTS subqueries.
I generally see distinct used far too often when the author is getting multiple results when they only want a single result and they're unwilling to expend the effort to discover why they're getting multiple results. In this case, it would be situations where the same pairs of academics have co-authored more than one paper.

I get the same object twice

I am trying to get all the lessons of the students that have a grade that contains a certain term.
The orange relations are the relevant relations:
The query:
SELECT
tg.nhsColor AS cellColor,
tg.nhsTgradeName AS LessonName,
lsons.nhsLessonID AS LessonID,
lsons.nhsTgradeID AS TgradeID,
lsons.nhsDay AS nhsDay,
lsons.nhsHour AS nhsHour,
tg.nhsTeacherID AS TeacherID
FROM
nhsTeacherGrades AS tg,
nhsLessons AS lsons,
nhsLearnGroups,
nhsMembers AS mem,
nhsGrades AS grd
WHERE
tg.nhsTgradeID = lsons.nhsTgradeID
AND nhsLearnGroups.nhsTgradeID = tg.nhsTgradeID
AND mem.nhsUserID = nhsLearnGroups.nhsStudentID
AND mem.nhsGradeID = grd.nhsGradeID
AND grd.nhsGradeName LIKE '%"+gradePart+"%'
The query works, yet, i get the same lesson twice from this query.
You can get duplicates for at least two reasons:
the same lessons can occur in different teacher grades followed by a certain student
different students can follow the same teacher grade
The following (untested) nested SQL could solve this. It gets the teacher grade ID of each lesson and checks which of these have at least one viable student linked to it:
SELECT tg.nhsColor AS cellColor,
tg.nhsTgradeName AS LessonName,
lsons.nhsLessonID AS LessonID,
lsons.nhsTgradeID AS TgradeID,
lsons.nhsDay AS nhsDay,
lsons.nhsHour AS nhsHour,
tg.nhsTeacherID AS TeacherID
FROM nhsLessons AS lsons
INNER JOIN nhsTeacherGrades AS tg
ON tg.nhsTgradeID = lsons.nhsTgradeID
WHERE tg.nhsTgradeID IN (
SELECT grp.nhsTgradeID
FROM (nhsLearnGroups grp
INNER JOIN nhsMembers AS mem
ON mem.nhsUserID = grp.nhsStudentID)
INNER JOIN nhsGrades AS grd
ON mem.nhsGradeID = grd.nhsGradeID
WHERE grd.nhsGradeName LIKE '%"+gradePart+"%'
)
Note that I used the JOIN syntax, which is considered better practice than placing join conditions in the WHERE clause. MS Access is quite pesky about using parentheses in the JOIN clauses, so you might need to play with those a bit to make it work.

sql join and minus

I seem to be having problem getting a certain query to work. I know I'm so close. Here's a copy of my er diagram
I think I am so close to achieving what I want to do with this code, only I get invalid identifier when trying to run it. I think its because the practice is being changed somehow after joining, as I am only getting invalid identifier on row 5?
SELECT staffid, staff_firstname, staff_surname, practice.practice_name, practice.practice_city
from staff
join practice on staff.practiceid = practice.practiceid
MINUS
SELECT staffid, staff_firstname, staff_surname, practice.practice_name, practice.practice_city
from staff
where role = 'GP';
Basically I'm trying to use the minus construct to find practices which do not employ a GP and include some information such as the CITY and practice_address.
I can use the minus construct to find out how many staff do not have the role of GP like so:
SELECT staffid, staff_firstname, staff_surname
from staff
MINUS
SELECT staffid, staff_firstname, staff_surname
from staff
where role = 'GP';
where I get the results:
STAFFID STAFF_FIRS STAFF_SURN
__________ __________ __________
8 NYSSA THORNTON
9 MONA BRADSHAW
10 GLORIA PENA
I'm struggling to use the join with the minus construct to get information about the GP's practice address and city etc.
Any help would be greatly appreciated!
The second select, after the minus, is referring to columns from the practice table - but it doesn't join to it:
SELECT staffid, staff_firstname, staff_surname,
practice.practice_name, practice.practice_city
from staff
join practice on staff.practiceid = practice.practiceid
MINUS
SELECT staffid, staff_firstname, staff_surname,
practice.practice_name, practice.practice_city
from staff
join practice on staff.practiceid = practice.practiceid
where role = 'GP';
That isn't going to give you what you want though, it will just remove the rows for staff that are GPs, not all trace of practices that have any GPs - non-GP staff at all practices will still be shown.
if you don't want the remaining staff details you only need to include the columns from the practice table in the select lists, and the minus would then give you what you want (and Gordon Linoff has shown two alternatives to minus in that case). If you do want the remaining staff details then you can use a not-exists clause rather than a minus - something like:
select s.staffid, s.staff_firstname, s.staff_surname,
p.practice_name, p.practice_city
from staff s
join practice p on s.practiceid = p.practiceid
where not exists (
select 1
from staff s2
where s2.practice_id = p.practice_id
and s2.role = 'GP
);
This is similar to Gordon's second query but has an extra join to staff for the details. Again, if you don't want those, use Gordon's simpler query.
You could also use an aggregate check, or could probably do something with an analytic function if you've learned abput those, to save having to hit the tables twice.
Your original query only operates on the level of "staff", not "practice". I would be inclined to solve this using aggregation:
select p.practice_name, p.practice_city
from staff s join
practice p
on s.practiceid = p.practiceid
group by p.practice_name, p.practice_city
having sum(case when s.role = 'GP' then 1 else 0 end) = 0;
Or, even better:
select p.*
from practice p
where not exists (select 1
from staff s
where s.practiceid = p.practiceid and s.role = 'GP'
);
I think this is the simplest and most direct interpretation of your question.