Selecting students who have not made any payment in a given period (term) - sql

I have two tables: students and payments.
students has the columns:
first_name
last_name
student_id
class_name
payments table has the columns:
full_name
student_id
term
session
amount_paid
class_fee
The details of every student is in the students table, but only those who have made either full or part payment enters the payments table. I have written a query to select those who have paid.
The question now is how to write a query that will select those in a particular class that have made no payment at all in a given period (term).

You are probably using a JOIN to select those who have paid. That will work because there are matching rows in both tables. To find those who have not paid, you can use a LEFT JOIN. It will give you NULL if rows do not match.
SELECT students.*
FROM students
LEFT JOIN payments ON students.student_id = payments.student_id
AND term = 'whatever'
WHERE amount_paid IS NULL
(Note: the term = 'whatever' needs to be in the ON clause, not the WHERE)
You can also do this using a subquery and the NOT EXISTS clause. NOT EXISTS will return true if the subquery returns zero rows.
SELECT *
FROM students
WHERE NOT EXISTS(
SELECT amount_paid
FROM payments
WHERE students.student_id = payments.student_id
AND term = 'whatever'
)

Related

Updating fields with indirectly related data

I have four tables:
student table with id_student, firstname and lastname fields
rent table with id_student and id_book
book table with id_book, book_name, author_name
booking table with id_book, current_student
The rent table only has IDs and links between student and book tables.
How can I update the booking.current_student field with a concatenation of firstname and lastname fields from student table (like 'john doe' ) - for each id_book in booking table, update booking.curent_student from student.firstname and student.lastname.
Since the booking table has no id_student column, how can I update booking.current_student from the student table?
You need a correlated update, which uses a subquery to get the new value to use in the set clause for each row; and that subquery needs to join the rent and student tables:
update booking b
set current_student = (
select s.firstname || ' ' || s.lastname
from rent r
join student s on s.id_student = r.id_student
where r.id_book = b.id_book
);
The 'correlation' part is that the subquery filters on r.id_book = b.id_book, so it correlates with the outer booking (b) table which is being updated.
If there are any rows in booking which don't have a matching rent row then they will be set to null. And if you have multiple booking rows for the same book ID then they will all be updated to the same student name; and if you have multiple rent rows for the same book ID it will error as the subquery will return multiple rows.
It's generally not a good idea to duplicate data like this. It would require less maintenance if you used a view instead:
create view booking (id_book, current_name) as
select r.id_book, s.firstname || ' ' || s.lastname
from rent r
join student s on s.id_student = r.id_student;
Then as rows are added to or removed from the rent table, or if a student changes their name, the view will automatically reflect the changes without you having to do anything.

Condition for finding rows with same value in one colum and null in another column

I have a table with columns student ID, subject, Enrolled. I have a row for each subject against a student. I am inserting this data to a temp table for reporting. I have another column named reporting in this temp table. I need to update reporting false when enrolled is null for all the rows imported for a student.
Here is the approach:
Select from all students
Left outer join to student-course (outer join is important here)
Group by student ID, and get min course ID
Use rows with null course ID to decide what temp rows to update
In SQL this query would look like this:
SELECT StudentId, MIN(CourseId)
FROM Student s
LEFT OUTER JOIN StudentCourse sc
ON s.StudentId=sc.StudentId
GROUP BY s.StudentId
HAVING MIN(CourseId) IS NULL

Query to select related data from two tables in which one table has no related fields in third table

I have three tables in my Oracle db:
Peoples:
IdPerson PK
Name
Surname
Earnings:
IdEarning PK
IdPerson
EarningValue
Awards:
IdAward PK
IdPerson FK
AwardDescription
So one person can have many earnings, one earning or can have no any earnings. Also one person can have many awards, one award, or no any award.
I have to select Surname and AwardDescription but only for people who have any earnings, because it is possible to have some award but, also don't have any earning!
My problem is to make a correct group by statement. I use query posted below and I am selecting surname of person with a description of award, but it is duplicating for each row in Earnings for this person.
SELECT AwardDescription, Surname
FROM Awards
INNER JOIN People ON People.IdPerson= Awards.IdPerson
INNER JOIN Earnings ON Earnings .IdPerson= People.IdPerson;
How to group it and avoid duplicating rows for each earning of person?
One person can be in many rows, but with different awards.
You could add DISTINCT to your query:
SELECT DISTINCT AwardDescription, Surname
FROM Awards
INNER JOIN People ON People.IdPerson= Awards.IdPerson
INNER JOIN Earnings ON Earnings .IdPerson= People.IdPerson;
Or another option is to use EXISTS:
SELECT AwardDescription, Surname
FROM Awards
INNER JOIN People P ON P.IdPerson= Awards.IdPerson
WHERE EXISTS (
SELECT 1
FROM Earnings E
WHERE P.IdPerson = E.IdPerson);
Do a left outer join among the tables like
SELECT p.Surname, a.AwardDescription, e.EarningValue
FROM People p
LEFT JOIN Awards a ON p.IdPerson= a.IdPerson
LEFT JOIN Earnings e ON e.IdPerson= p.IdPerson
WHERE a.AwardDescription IS NOT NULL
OR e.EarningValue IS NOT NULL;

SQL Query: how to select records, but if a parent record exists select the most recent child of it

I have a complicated query I need to figure out but I'm not well versed enough in writing queries and sub queries.
The problem: I need to retrieve unique Patient records but if a record has a non null master_patient_id, I need to subquery or join on that master_patient table and query for the most recent (created_at desc limit 1) child patient of that master_patient.
The reason for this is that our system will create a new patient record for the same patient if they were readmitted to the same facility. Upon creating the 2nd record for a given patient we also create a master_patient record to associate the 2 patient records with it so that the system can know they are the same patient.
Now, I need to show a list of non duplicate patients. So I need to have a query that will get patients from the patient record, but query the master_patient table and only retrieve the latest patient associated to its master_patient.
Patient Table has: id, name, master_patient_id
and the patient belongs_to master_patient but isn't required to be present.
Master Patient table just has an id and has_many patients.
Desired results: should be unique patient records, but the only way to find out if patients are unique among themselves is to query the master_patient table to see if any patients belong_to it and then just retrieve the latest patient (child of master_patient).
I can't base my query off master_patient because those don't exist for patients that only have 1 record. Should I use some type of join or subquery?
Update: Thanks to #τεκ I was able to tweak his query to work in Postgres:
Update 2: 1 more tiny tweak to the query to make it shorter and correct a null id being returned:
SELECT MAX(patients.id) as id, *
FROM "patients"
JOIN (
SELECT MAX(created_at) AS created_at,
patient_master_id,
COALESCE(patient_master_id, id) pm_id
FROM patients
GROUP BY patient_master_id,
COALESCE(patient_master_id, id)
) s
ON (s.pm_id = patients.id or s.patient_master_id = patients.patient_master_id)
AND s.created_at = patients.created_at
GROUP BY patients.id, s.created_at, s.patient_master_id, s.pm_id
select max(id) as id from patient p
join (select
max(created_at) as created_at,
master_patient_id,
case when master_patient_id is null then id else null end as id
from patient
group by master_patient_id, case when master_patient_id is null then id else null end
) s on (s.id = p.id or s.master_patient_id = p.master_patient_id) and s.created_at = p.created_at
There's probably a simpler, postgres-specific way to do it, but I don't know postgres. In T-SQL it's cross apply.

How to get distinct and lastest record with inner join query

I have two table named: customers and bill. customer and bill have one to many relation.
Customer table contains record of customer mobileNo,bikeNo etc
Bill table contain record of customer bill with bikeNo(foreign key),billdate etc.
I have query for that:
SELECT customer.mobileNo, bill.iDate AS Expr1
FROM (customer INNER JOIN
bill ON customer.bikeNo = bill.bikeNo)
ORDER BY bill.iDate;
Now How i get distinct and latest billdate record and mobileNo with this query?
Use GROUP BY and MAX():
SELECT customer.mobileNo, MAX(bill.iDate) AS iDate
FROM (customer INNER JOIN
bill ON customer.bikeNo = bill.bikeNo)
GROUP BY customer.mobileNo
ORDER BY iDate