I get the same object twice - sql

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.

Related

Find rows with fewer than X associations (including 0)

I have students associated to schools, and want to find all schools that have five or fewer (including zero) students that have has_mohawk = false.
Here's an Activerecord query:
School.joins(:students)
.group(:id)
.having("count(students.id) < 5")
.where(students: {has_mohawk: true})
This works for schools with 1 - 4 such students with mohawks, but omits schools where there are no such students!
I figured out a working solution and will post it. But I am interested in a more elegant solution.
Using Rails 5. I'm also curious whether Rails 6's missing could handle this?
find all schools that have five or fewer (including zero) students that have has_mohawk = false.
Here is an optimized SQL solution. SQL is what it comes down to in any case. (ORMs like Active Record are limited in their capabilities.)
SELECT sc.*
FROM schools sc
LEFT JOIN (
SELECT school_id
FROM students
WHERE has_mohawk = false
GROUP BY 1
HAVING count(*) >= 5
) st ON st.school_id = sc.id
WHERE st.school_id IS NULL; -- "not disqualified"
While involving all rows, aggregate before joining. That's faster.
This query takes the reverse approach by excluding schools with 5 or more qualifying students. The rest is your result - incl. schools with 0 qualifying students. See:
Select rows which are not present in other table
Any B-tree index on students (school_id) can support this query, but this partial multicolumn index would be perfect:
CREATE INDEX ON students (school_id) WHERE has_mohawk = false;
If there can be many students per school, this is faster:
SELECT sc.*
FROM schools sc
JOIN LATERAL (
SELECT count(*) < 5 AS qualifies
FROM (
SELECT -- select list can be empty (cheapest)
FROM students st
WHERE st.school_id = sc.id
AND st.has_mohawk = false
LIMIT 5 -- !
) st1
) st2 ON st2.qualifies;
The point is not to count all qualifying students, but stop looking once we found 5. Since the join to the LATERAL subquery with an aggregate function always returns a row (as opposed to the join in the first query), schools without qualifying students are kept in the loop, and we don't need the reverse approach.
About LATERAL:
What is the difference between LATERAL JOIN and a subquery in PostgreSQL?
In addition to the first query, write another to find schools where no students have mohawks (works in Rails 5).
School.left_outer_joins(:students)
.group(:id)
.having("max(has_mohawk::Integer) = 0")
You might think from this popular answer that you could instead just write:
School.left_outer_joins(:students)
.group(:id)
.where.not(student: {has_mohawk: true})
But that will include (at least in Rails 5) schools where there is any student with a has_mohawk value of false, even if some students have a has_mohawk value of true.
Explanation of max(has_mohawk::Integer) = 0
It converts the boolean to an integer (1 for true, 0 for false). Schools with any true values will have a max of 1, and can thus be filtered out.
Similiar: SQL: Select records where ALL joined records satisfy some condition

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.

Need to further refine my output to my SQL query

I am working on an assignment for class and I have hit a roadblock. This is my code that I am working with:
SELECT ANIM_ID, ANIM_NAME, FOOD_ITEM_ID, FOOD_ITEM_DESCRIPTION, FOOD_CATEGORY_DESCRIPTION, FOOD_PRICE
FROM ANIMAL
JOIN ANIMAL_DIETARY_REQUIREMENTS USING (ANIM_ID)
JOIN FOOD_ITEM USING (FOOD_ITEM_ID)
JOIN FOOD_CATEGORY USING (FOOD_CATEGORY_ID)
JOIN FOOD_ITEM_SUPPLIER USING (FOOD_ITEM_ID)
WHERE ANIM_NAME = 'Brianna' OR ANIM_NAME = 'Isaiah'
AND FOOD_PRICE IN
(SELECT MIN(FOOD_PRICE)
FROM FOOD_ITEM_SUPPLIER
GROUP BY FOOD_ITEM_ID);
And the result I am getting is this:(truncating only the columns I am concerned with)
"ANIM_NAME""FOOD_ITEM_ID""FOOD_ITEM_DESCRIPTION""FOOD_PRICE"
"Brianna" "121" "Super Mixed Seafood ""241.99"
"Brianna" "4" "Tasty Meat with Antioxi""384.76"
"Brianna" "4" "Tasty Meat with Antioxi""297.73"
"Isaiah" "9" "Nutritious Chicken" ""51.97"
"Brianna" "4" "Tasty Meat with Antioxi""48.4"
"Isaiah" "61" "Nutritious Mixed Seafoo""421.63"
I am getting all prices for Brianna and tasty meat instead of just the min price. I just need the rows that are in bold. I have been at this all day and I just can't see the answer and I am sure it is a simple line of code that I am just missing. This is my first Database class and my first time coding with SQL. Also this is my very first post so if I posted something in error I do apologize. Thanks for any help or feedback you guys might have for me.
First, you need parentheses around your animal name selection. As it was, you asked for all the prices for Brianna, or all the prices for Isaiah which were min prices, which was exactly what you got.
Second, you need to compare the min price for a given FOOD_ITEM_ID, as in:
SELECT ANIM_ID,
ANIM_NAME,
FOOD_ITEM_ID,
FOOD_ITEM_DESCRIPTION,
FOOD_CATEGORY_DESCRIPTION,
FOOD_PRICE
FROM ANIMAL
INNER JOIN ANIMAL_DIETARY_REQUIREMENTS
USING (ANIM_ID)
INNER JOIN FOOD_ITEM
USING (FOOD_ITEM_ID)
INNER JOIN FOOD_CATEGORY
USING (FOOD_CATEGORY_ID)
INNER JOIN FOOD_ITEM_SUPPLIER
USING (FOOD_ITEM_ID)
WHERE (ANIM_NAME = 'Brianna' OR
ANIM_NAME = 'Isaiah') AND
(FOOD_ITEM_ID, FOOD_PRICE) IN (SELECT FOOD_ITEM_ID, MIN(FOOD_PRICE)
FROM FOOD_ITEM_SUPPLIER
GROUP BY FOOD_ITEM_ID);
You could also replace the OR'd animal name selections with
ANIM_NAME IN ('Brianna', 'Isaiah')
which would be shorter and eliminate the ambiguity without parentheses.
It's suitable you use parentheses around your OR statement to be isolated, and include FOOD_ITEM_ID with FOOD_PRICE for comparison of MINIMIUM prices in the WHERE clause:
WHERE (ANIM_NAME = 'Brianna' OR ANIM_NAME = 'Isaiah')
AND (FOOD_ITEM_ID,FOOD_PRICE) IN
(SELECT FOOD_ITEM_ID,MIN(FOOD_PRICE)
FROM FOOD_ITEM_SUPPLIER
GROUP BY FOOD_ITEM_ID);

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.

Error on HQL Query distinct count of child collections (in elements error)

First of all, please forgive me for my vocabulary is a little limited with NHibernate so I might call something the wrong thing...here is my question:
Result I am looking for is a count of distinct students for a course. I have three classes:
Courses, Students, CourseDates.
Courses contains a HasMany relationship with CourseDates.
CourseDates is a collection of dates on which each class has occurred and contains a HasAndBelongsToMany relationship with the Students class.
What I need to do is a get a distinct count of the Students from all the dates a course has occurred. Here is an example of a SQL statement that I want to replicate. The result is a number (long). This specific example produces the result: 5
SELECT COUNT(DISTINCT dbo.Literacy_Course_DatesStudents.IDStudent) AS StudentCount FROM
Literacy_Course_DatesStudents INNER JOIN Literacy_Course_Dates ON Literacy_Course_DatesStudents.IDDate = Literacy_Course_Dates.IDDate WHERE (Literacy_Course_Dates.IDCourse = 28)
Below is the query written from within a new class that I created specifically for this report...but I keep getting an error: Exception of type 'Antlr.Runtime.MissingTokenException' was thrown. Usually I thought this error was thrown when I didn't have CourseEnrolledCount class imported into the other classes but I have done that.
Dim q As Castle.ActiveRecord.Queries.SimpleQuery(Of CourseEnrolledCount)
q = New Castle.ActiveRecord.Queries.SimpleQuery(Of CourseEnrolledCount)(GetType(Literacy.Courses.CourseDates), "select new CourseEnrolledCount(Count(Distinct o.IDStudent in elements(t.Students) as o)) from CourseDates t Where t.Course.IDCourse = :courseid")
Let me know if I need to provide additional information. I hope I am being clear in my question. Thank you in advance for your time.
Here is a HQL query that does what you want:
select count(disctinct student.id) from Course course
inner join course.courseDates courseDate
inner join courseDate.students student
where course.id = :courseId
I'll let you substitute the real names of the associations.
You could even make it shorter (and more similar to your SQL query) by avoiding the join on the course:
select count(disctinct student.id) from CourseDate courseDate
inner join courseDate.students student
where courseDate.course.id = :courseId