Selecting most recent record from multiple tables - sql

Newbie to SQL, just stepped out of my comfort zone. I'm using MySQL in a WPF application.
I have three tables in my database.
Patients:
ID | Name | ...
Referrals:
ID | FK_Patient_ID | ...
Visits:
ID | FK_Referral_ID | Date | FollowUpDate | FollowUpInterval | ...
The 'FK' fields are foreign keys into the other tables. So a visit belongs to a referral, and a referral belongs to a patient.
I want the get the most recent visit for each patient (or referral, since you can't have a visit without a referral) and get the following:
patients.ID | patients.Name | visits.FollowUpDate | visits.FollowUpInterval
What I'm trying to do is get a list of patients who have missed their follow up visits.
Hopefully this is a no brainer for you SQL people out there...

SELECT p1.ID
,p1.Name
,v1.FollowUpDate
,v1.FollowUpInterval
FROM Patients p1
INNER JOIN
Referals r1 ON p1.ID=r1.FK_Patient_ID
INNER JOIN
Visits v1 ON r1.ID=v1.FK_Referral_ID
INNER JOIN (
SELECT MAX(v.ID) AS ID
FROM Patients p
INNER JOIN
Referals r ON p.ID=r.FK_Patient_ID
INNER JOIN
Visits v ON r.ID=v.FK_Referral_ID
GROUP BY p.ID) v2 ON v1.ID=v2.ID

I'm not 100% sure if this works with MySQL, but here's one way that you could do it in SQL Server, and I think it's portable:
SELECT p.ID, p.NAME, v.FollowUpDate, v.FollowUpInterval
FROM Patients p
JOIN Referrals r ON p.ID = r.FK_PatientID
JOIN Visits v ON ON r.ID = v.FK_Referral_ID
JOIN (SELECT r.FK_Patient_ID, MAX(v.Date) AS [Date]
FROM Referrals r
JOIN Visits v ON r.ID = v.FK_Referral_ID
GROUP BY r.FK_Patient_ID
) x ON p.ID = x.FK_Patient_ID
AND v.Date = x.Date
Basically, you use a subquery to find the most recent visit by patient, and then join it back to your original tables to pull back the rows that match that value. This only works if there was one row with that date.

I used the script as below on PostgeSQL and it worked!
SELECT p.ID, p.NAME, v.FollowUpDate, v.FollowUpInterval
FROM Patients p
INNER JOIN Referrals r ON p.ID = r.FK_PatientID
INNER JOIN Visits v ON ON r.ID = v.FK_Referral_ID
INNER JOIN (SELECT r.FK_Patient_ID, MAX(v.Date) AS "Date"
FROM Referrals r
INNER JOIN Visits v ON r.ID = v.FK_Referral_ID
GROUP BY
r.FK_Patient_ID
) x ON p.ID = x.FK_Patient_ID AND v.Date = x.Date

Related

Full outer join doesn't returns the results from right table

My application has a list of users who can be booked for a bookings. We need to pay the users for these bookings. I need to get the list of users who needs to be paid. A user can be paid if they have bookings or with payment created which has no bookings.
I tried the below:
SELECT
users.id as user_id,
user_bookings.id as user_booking_id,
user_bookings.payment_id as user_booking_payment_id,
payments.id as payment_id
FROM
users
LEFT OUTER JOIN user_bookings ON user_bookings.user_id = user.id
FULL JOIN payments ON payments.id = user_bookings.payment_id
Where payments.issued = false;
But it doesn't list the users with the payment with no bookings. It only lists the users with the bookings created. How can I get the users with payment which doesn't have any bookings?
EDIT
I tried the below query:
User with 37271 has the below payment records:
select payments.id, payments.issued from payments where payments.user_id=37271;
id | issued
--------+--------
133046 | f
133045 | t
(2 rows)
Below are the bookings I have:
select user_bookings.id, user_bookings.payment_issued, user_bookings.payment_id from user_bookings where user_id=37271;
id | payment_issued | payment_id
--------+-------------------+---------------
541136 | t | 133045
541137 | t | 133045
(2 rows)
While running the below query:
SELECT u.id as user_id, ub.id as user_booking_id, ub.payment_id as user_booking_payment_id,
p.id as payment_id, p.issued as payments_issued
FROM users u LEFT OUTER JOIN
user_bookings ub
ON ub.user_id = u.id FULL JOIN
(SELECT p.*
FROM payments p
WHERE NOT p.issued
) p
ON p.id = ub.payment_id
where users.id=37271;
It doesn't returns the payment with no bookings but it returns issued payments:
user_id | user_booking_id | user_booking_payment_id | payment_id | payments_issued
---------+-----------------+-------------------------+------------+-----------------
37271 | 541137 | 133045 | |
37271 | 541136 | 133045 | |
(2 rows)
SELECT
users.id as user_id,
user_bookings.id as user_booking_id,
user_bookings.payment_id as user_booking_payment_id,
payments.id as payment_id,
FROM
users
LEFT OUTER JOIN user_bookings ON user_bookings.user_id = user.id
FULL JOIN payments ON payments.id = linguist_bookings.payment_id
Where payments.issued = false
OR payments.issued IS NULL;
If you use an outer join, you turn it into an inner join if you use it in a WHERE clause; because NULL = [something] is never true.
You are turning the outer join into an inner join. Dealing with filtering with full outer join is tricky. I recommend a subquery:
SELECT u.id as user_id, ub.id as user_booking_id, ub.payment_id as user_booking_payment_id
p.id as payment_id,
FROM users u LEFT OUTER JOIN
user_bookings ub
ON ub.user_id = u.id FULL JOIN
(SELECT p.*
FROM payments p
WHERE NOT p.issued
) p
payments p
ON p.id = ub.payment_id;
I would, however, question why you want a FULL JOIN here. You could get payment ids with no other information from the other tables.
I would expect a LEFT JOIN to be sufficient:
SELECT u.id as user_id, ub.id as user_booking_id, ub.payment_id as user_booking_payment_id,
p.id as payment_id
FROM users u LEFT JOIN
user_bookings ub
ON ub.user_id = u.id LEFT JOIN
payments p
ON p.id = ub.payment_id AND NOT p.issued;

Join Two Queries/Select Statement

I don't know how to explain it. But I am trying to join two select statements/queries. I need to include customer and supplier name in the same table.
Table 1 - j:
Job ID, Customer ID
Table 2 - jl:
Job_Line.Job_ID, Supplier_ID
Table 3 - p:
ID, Name
First Select statement - customer name:
Select name
From p
INNER JOIN j ON p.id = j.customer_id
Second Select statement - supplier name:
Select name
From p
INNER JOIN jl ON p.id = jl.supplier_id
Don't know how to join above two selects, so i could have a table like:
id, customer name, supplier name
I am new to SQL and learning online. I understand the basis, but getting stuck at this finding this complex!
This should do the trick
SELECT j.id, pc.name, ps.name
FROM j
INNER JOIN p pc ON j.customer_id = pc.id
INNER JOIN jl ON j.id = jl.job_id
INNER JOIN p ps ON jl.supplier_id = ps.id
Note, pc and ps are table aliases.

SQL: How to save order in sql query?

I have PostgreSQL database and I try to print all my users (Person).
When I execute this query
-- show owners
-- sorted by maximum cars amount
SELECT p.id
FROM car c JOIN person p ON c.person_id = p.id
GROUP BY p.id
ORDER BY COUNT(p.name) ASC;
I get all owners sorted by cars amount
Output: 3 2 4 1
And all order goes wrong when I try to link owner id.
SELECT *
FROM person p
WHERE p.id IN (
SELECT p.id
FROM car c JOIN person p ON c.person_id = p.id
GROUP BY p.id
ORDER BY COUNT(p.name) ASC);
Output: 1 2 3 4 and other data
You see than order is wrong. So here is my question how can I save that order?
Instead Of subquery use join. Try this.
SELECT p.*
FROM person p
JOIN (SELECT p.id,
Count(p.NAME)cnt
FROM car c
JOIN person p
ON c.person_id = p.id
GROUP BY p.id) b
ON p.id = b.id
ORDER BY cnt ASC
Untangle the mess. Aggregate first, join later:
SELECT p.*
FROM person p
JOIN (
SELECT person_id, count(*) AS ct
FROM car
GROUP BY person_id
) c ON c.person_id = p.id
ORDER BY c.cnt;
No need to join to person twice. This should be fastest if you count most or all rows.
For a small selection, correlated subqueries are faster:
SELECT p.*
FROM person p
ORDER BY (SELECT count(*) FROM car c WHERE c.person_id = p.id)
WHERE p.id BETWEEN 10 AND 20; -- some very selective predicate
As for your original: IN takes a set on the right hand, order of elements is ignored, so ORDER BY is pointless in the subuery.

sql - aggregation issue

I have two tables in my DB - products & orders. An order can only be of one kind of product.
Here's the basic idea:
What I'm trying to do is a query that given a copmany_id returns all the products (from that company) that have less than 10 orders (including 0)
my query looks like this:
SELECT p.*
FROM product p,
order o
WHERE p.company_id =?
AND o.product_id = p.id
GROUP BY p.id
HAVING Count(o.id) < 10
ORDER BY p.id DESC
The query works fine for products which have 0 < orders but doesn't return ones with 0 orders. What do I need to do to return them as well?
You're INNER JOINING your two tables, which means that only those products are returned for which there is at least one order.
You will need to LEFT OUTER JOIN the order table:
SELECT p.*
FROM product p
LEFT OUTER JOIN order o
ON o.product_id = p.id
WHERE p.company_id = ?
GROUP BY p.id
HAVING Count(o.id) < 10
ORDER BY p.id DESC
A left outer join will return every record on the left-hand side of the JOIN operation at least once, regardless if there is a matching record to the right-hand side of the JOIN operation.
try left outer join
SELECT p.*
FROM product p
LEFT OUTER JOIN order o
ON o.product_id = p.id
WHERE p.company_id =?
GROUP BY p.id
HAVING Count(o.id) < 10
ORDER BY p.id DESC
See this example for left outer join

Query extensibility with WHERE EXISTS with a large table

The following query is designed to find the number of people who went to a hospital, the total number of people who went to a hospital and the divide those two to find a percentage. The table Claims is two million plus rows and does have the correct non-clustered index of patientid, admissiondate, and dischargdate. The query runs quickly enough but I'm interested in how I could make it more usable. I would like to be able to add another code in the line where (hcpcs.hcpcs ='97001') and have the change in percentRehabNotHomeHealth be relfected in another column. Is there possible without writing a big, fat join statement where I join the results of the two queries together? I know that by adding the extra column the math won't look right, but I'm not worried about that at the moment. desired sample output: http://imgur.com/BCLrd
database schema
select h.hospitalname
,count(*) as visitCounts
,hospitalcounts
,round(count(*)/cast(hospitalcounts as float) *100,2) as percentRehabNotHomeHealth
from Patient p
inner join statecounties as sc on sc.countycode = p.countycode
and sc.statecode = p.statecode
inner join hospitals as h on h.npi=p.hospitalnpi
inner join
--this join adds the hospitalCounts column
(
select h.hospitalname, count(*) as hospitalCounts
from hospitals as h
inner join patient as p on p.hospitalnpi=h.npi
where p.statecode='21' and h.statecode='21'
group by h.hospitalname
) as t on t.hospitalname=h.hospitalname
--this where exists clause gives the visitCounts column
where h.stateCode='21' and p.statecode='21'
and exists
(
select distinct p2.patientid
from Patient as p2
inner join Claims as c on c.patientid = p2.patientid
and c.admissiondate = p2.admissiondate
and c.dischargedate = p2.dischargedate
inner join hcpcs on hcpcs.hcpcs=c.hcpcs
inner join hospitals as h on h.npi=p2.hospitalnpi
where (hcpcs.hcpcs ='97001' or hcpcs.hcpcs='9339' or hcpcs.hcpcs='97002')
and p2.patientid=p.patientid
)
and hospitalcounts > 10
group by h.hospitalname, t.hospitalcounts
having count(*)>10
You might look into CTE (Common Table Expressions) to get what you need. It would allow you to get summarized data and join that back to the detail on a common key. As an example I modified your join on the subquery to be a CTE.
;with hospitalCounts as (
select h.hospitalname, count(*) as hospitalCounts
from hospitals as h
inner join patient as p on p.hospitalnpi=h.npi
where p.statecode='21' and h.statecode='21'
group by h.hospitalname
)
select h.hospitalname
,count(*) as visitCounts
,hospitalcounts
,round(count(*)/cast(hospitalcounts as float) *100,2) as percentRehabNotHomeHealth
from Patient p
inner join statecounties as sc on sc.countycode = p.countycode
and sc.statecode = p.statecode
inner join hospitals as h on h.npi=p.hospitalnpi
inner join hospitalCounts on t.hospitalname=h.hospitalname
--this where exists clause gives the visitCounts column
where h.stateCode='21' and p.statecode='21'
and exists
(
select p2.patientid
from Patient as p2
inner join Claims as c on c.patientid = p2.patientid
and c.admissiondate = p2.admissiondate
and c.dischargedate = p2.dischargedate
inner join hcpcs on hcpcs.hcpcs=c.hcpcs
inner join hospitals as h on h.npi=p2.hospitalnpi
where (hcpcs.hcpcs ='97001' or hcpcs.hcpcs='9339' or hcpcs.hcpcs='97002')
and p2.patientid=p.patientid
)
and hospitalcounts > 10
group by h.hospitalname, t.hospitalcounts
having count(*)>10