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

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.

Related

Query to combine data from 2 tables based on a condition for both the tables in SQL

I have 2 tables. ProfileInfo (ProfileID - PK) and EmployeeRole (EmpID - PK, ProfileID - FK). Both are connected using a ProfileID and both tables have LastUpdatedTimestamp Column. I need to fetch data from both the tables combined, using from and to lastupdated timestamp.
Sometimes both the tables get updated at the same time and most times
only one get updated
. Here is what i have tried but it bring up data which is updated on both tables. Firstly, I tried join but it didn't work as much as i thought it would
select emp.emp_id as EmpId from EmployeeRole emp
FULL OUTER JOIN ProfileInfo pi on emp.profile_id = pi.profile_id
where emp.LST_UPDT_TS between '2017-09-18' and '2017-09-20' and
pi.LST_UPDT_TS between '2017-09-18' and '2017-09-20';
This brought emp ids that had changes on both the tables alone.
Table Details:
EmployeeRole Emp_ID PK, Profile_id FK, LST_UPDT_TS TIMESTAMP
ProfileInfo Profile_Id PK, Profile_name, LST_UPDT_TS TIMESTAMP
Example: If 2 records of ProfileInfo gets updated and 1 record of EmployeeRole gets updated. I need to get 3 emp_id considering both the records from ProfileInfo is not related to EmployeeRole record. If in case one of the record is related then I have to get 2 emp_id only.
I searched for similar answers for a short period but nothing worked. Please help.
This is just an example, your conditions may vary
SELECT
-- common data
COALESCE(emp.profile_id, pi.profile_id) as profile_id
,COALESCE(emp.LST_UPDT_TS, pi.LST_UPDT_TS) as LST_UPDT_TS
-- emp role
,emp.emp_id as EmpId
-- profile
, pi.Profile_name
FROM (SELECT *
FROM EmployeeRole
WHERE LST_UPDT_TS between '2017-09-18' and '2017-09-20') emp
FULL OUTER JOIN (
SELECT *
FROM ProfileInfo
WHERE LST_UPDT_TS between '2017-09-18' and '2017-09-20') pi
-- rows matching predicate
ON emp.profile_id = pi.profile_id
AND emp.LST_UPDT_TS = pi.LST_UPDT_TS
This worked with some little tweeks from the initial query I changed.
select emp.emp_id as EmpId from EmployeeRole emp
JOIN ProfileInfo pi on emp.profile_id = pi.profile_id and
((emp.LST_UPDT_TS between '2017-09-18' and '2017-09-20') or
(pi.LST_UPDT_TS between '2017-09-18' and '2017-09-20'));
Thanks a lot #Serg and #Phil and #wildplasser

SQL getting the average count of diagnosis based on specialty

I have 2 tables one for appointments and one for doctors.
I want to select the average number of patients for each specialty, which is stored in the doctors table. The appointments table has the patients' id the doctors' id and the diagnosis, if a patient has had a diagnosis.
I tried this, but doesn't work.
SELECT AVG(patientAMKA)
FROM appointments
WHERE diagnosis IS NOT NULL
GROUP
BY doctor.specialty
EDIT: I just want to clarify that patientAMKA is the id of the patient.
EDIT2: I specifically mean how many patients (with a diagnosis) exist for each speciaity, then take the average of those numbers.
Sounds like it would be something like:
SELECT d.specialty, COUNT(*)
FROM doctor d
INNER JOIN appointments a ON d.id = a.doctor_id
WHERE diagnosis IS NOT NULL
GROUP BY d.specialty
SELECT doc.specialty, AVG(app.patientAMKA)
FROM doctor doc
JOIN appointments app ON doc.doctorid = app.doctorid
WHERE app.diagnosis IS NOT NULL
GROUP BY doc.specialty
Based on your clarifications, I believe what you want is:
SELECT
AVG(CountOfDiagnoses) as TheAverage
FROM
(
SELECT
d.specialty,
COUNT(1) as CountOfDiagnoses
FROM
appointments a
JOIN doctor d ON
d.doctor_id = a.doctor_id
WHERE
a.diagnosis IS NOT NULL
GROUP BY
d.specialty
) Counts

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

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'
)

Selecting columns from different tables

I need to display two columns from my attendance table (MEMBER_ID & MEETING_ID) and one column from my meeting table and finally two columns from my member table which displays the names that match with MEETING_ID.
The attendance table has a composite key (MEMBER_ID*, MEETING_ID*)
The member table's primary key is MEMBER_ID
Meeting table's primary key is MEETING_ID
My attempt is not working, can someone please help?
SELECT MEMBER_ID, MEETING_ID, MEETING_NAME MEMBER_FIRSTNAME, MEMBER_LASTNAME
FROM ATTENDANCE, MEMBER, MEETING
WHERE MEETING.MEMBER_ID = MEETING.MEMBER_ID;
End result needs to be:
MEMBER_ID MEETING_ID MEETING_NAME FIRSTNAME LASTNAME
0001 MEET0004 SPORTS DAY JOHN SMITH
May be you need this.
SELECT A.MEMBER_ID, A.MEETING_ID, M2.MEETING_NAME, M1.MEMBER_FIRSTNAME, M1.MEMBER_LASTNAME
FROM ATTENDANCE A, MEMBER M1, MEETING M2
WHERE M1.MEMBER_ID = A.MEMBER_ID
AND A.MEETING_ID = M2.MEETING_ID;
SELECT
a.MEMBER_ID
,a.MEETING_ID
,mt.MEETING_NAME
,mb.MEMBER_FIRSTNAME
,mb.MEMBER_LASTNAME
FROM
ATTENDANCE a
INNER JOIN MEMBER mb
ON a.MEMBER_ID = mb.MEMBER_ID
INNER JOIN MEETING mt
ON a.MEETING_ID = mt.MEETING_ID
;
Use Explicit Join Syntax and then setup your relationships using the ON conditions and the keys between the tables. Note I also used table aliases to shorten typying.

Update multiple row values to same row and different columns

I was trying to update table columns from another table.
In person table, there can be multiple contact persons with same inst_id.
I have a firm table, which will have latest 2 contact details from person table.
I am expecting the firm tables as below:
If there is only one contact person, update person1 and email1. If there are 2, update both. If there is 3, discard the 3rd one.
Can someone help me on this?
This should work:
;with cte (rn, id, inst_id, person_name, email) as (
select row_number() over (partition by inst_id order by id) rn, *
from person
)
update f
set
person1 = cte1.person_name,
email1 = cte1.email,
person2 = cte2.person_name,
email2 = cte2.email
from firm f
left join cte cte1 on f.inst_id = cte1.inst_id and cte1.rn = 1
left join cte cte2 on f.inst_id = cte2.inst_id and cte2.rn = 2
The common table expression (cte) used as a source for the update numbers rows in the person table, partitioned by inst_id, and then the update joins the cte twice (for top 1 and top 2).
Sample SQL Fiddle
I think you don't have to bother yourself with this update, if you rethink your database structure. One great advantage of relational databases is, that you don't need to store the same data several times in several tables, but have one single table for one kind of data (like the person's table in your case) and then reference it (by relationships or foreign keys for example).
So what does this mean for your example? I suggest, to create a institution's table where you insert two attributes like contactperson1 and contactperson2: but dont't insert all the contact details (like email and name), just the primary key of the person and make it a foreign key.
So you got a table 'Person', that should look something like this:
ID INSTITUTION_ID NAME EMAIL
1 100 abc abc#inst.com
2 101 efg efg#xym.com
3 101 ijk ijk#fg.com
4 101 rtw rtw#rtw.com
...
And a table "Institution" like:
ID CONTACTPERSON1 CONTACTPERSON2
100 1 NULL
101 2 3
...
If you now want to change the email adress, just update the person's table. You don't need to update the firm's table.
And how do you get your desired "table" with the two contact persons' details? Just make a query:
SELECT i.id, p1.name, p1.email, p2.name, p2.email
FROM institution i LEFT OUTER JOIN person p1 ON (i.contactperson1 = p1.id)
LEFT OUTER JOIN person p2 ON (i.contactperson2 = p2.id)
If you need this query often and access it like a "table" just store it as a view.