Is there any alternative way to write this t-sql query? - sql

I have these 3 tables:
For each car I need to visualize the data about the last (most recent) reservation:
the car model (Model);
the user who reserved the car (Username);
when it was reserved (ReservedOn);
when it was returned (ReservedUntil).
If there is no reservation for a given car, I have to show only the car model. Other fields must be empty.
I wrote the following query:
SELECT
Reservations.CarId,
res.MostRecent,
Reservations.UserId,
Reservations.ReservedOn,
Reservations.ReservedUntil
FROM
Reservations
JOIN (
Select
Reservations.CarId,
MAX(Reservations.ReservedOn) AS 'MostRecent'
FROM
Reservations
GROUP BY
Reservations.CarId
) AS res ON res.carId = Reservations.CarId
AND res.MostRecent = Reservations.ReservedOn
This first one works but I got stuck to obtain the result that I need. How could I write complete the query?

It looks like a classic top-n-per-group problem.
One way to do it is to use OUTER APPLY. It is a correlated subquery (lateral join), which returns the latest Reservation for each row in the Cars table. If such reservation doesn't exist for a certain car, there will be nulls.
If you create an index for Reservations table on (CarID, ReservedOn DESC), this query should be more efficient than self-join.
SELECT
Cars.CarID
,Cars.Model
,A.ReservedOn
,A.ReservedUntil
,A.UserName
FROM
Cars
OUTER APPLY
(
SELECT TOP(1)
Reservations.ReservedOn
,Reservations.ReservedUntil
,Users.UserName
FROM
Reservations
INNER JOIN Users ON Users.UserId = Reservations.UserId
WHERE
Reservations.CarID = Cars.CarID
ORDER BY
Reservations.ReservedOn DESC
) AS A
;
For other approaches to this common problem see Get top 1 row of each group
and Retrieving n rows per group

With not exists:
select r.* from reservations r
where not exists (
select 1 from reservations
where carid = r.carid and reservedon > r.reservedon
)
You can create a CTE with the above code and join it to the other tables:
with cte as (
select r.* from reservations r
where not exists (
select 1 from reservations
where carid = r.carid and reservedon > r.reservedon
)
)
select c.carid, c.model, u.username, cte.reservedon, cte.reserveduntil
from cars c
left join cte on c.carid = cte.carid
left join users u on u.userid = cte.userid
If you don't want to use a CTE:
select c.carid, c.model, u.username, t.reservedon, t.reserveduntil
from cars c
left join (
select r.* from reservations r
where not exists (
select 1 from reservations
where carid = r.carid and reservedon > r.reservedon
)
) t on c.carid = t.carid
left join users u on u.userid = t.userid

Related

JOIN 2 tables ORDER BY SUM value

I have 2 tables: 1st is comment, 2nd is rating
SELECT * FROM comment_table a
INNER JOIN (SELECT comment_id, SUM(rating_value) AS total_rating FROM rating_table GROUP BY comment_id) b
ON a.comment_id = b.comment_id
ORDER BY b.total_rating DESC
I tried the above SQL but doesn't work!
Object is to display a list of comments order by rating points of each comments.
SELECT s.* FROM (
SELECT * FROM comment_table a
INNER JOIN (SELECT comment_id, SUM(rating_value) AS total_rating FROM rating_table GROUP BY comment_id) b
ON a.comment_id = b.comment_id
) AS s
ORDER BY s.total_rating DESC
Nest it inside an another select. It will then output the data in the correct order.

JOIN only one row from second table and if no rows exist return null

In this query I need to show all records from the left table and only the records from the right table where the result is the highest date.
Current query:
SELECT a.*, c.*
FROM users a
INNER JOIN payments c
ON a.id = c.user_ID
INNER JOIN
(
SELECT user_ID, MAX(date) maxDate
FROM payments
GROUP BY user_ID
) b ON c.user_ID = b.user_ID AND
c.date = b.maxDate
WHERE a.package = 1
This returns all records where the join is valid, but I need to show all users and if they didn't make a payment yet the fields from the payments table should be null.
I could use a union to show the other rows:
SELECT a.*, c.*
FROM users a
INNER JOIN payments c
ON a.id = c.user_ID
INNER JOIN
(
SELECT user_ID, MAX(date) maxDate
FROM payments
GROUP BY user_ID
) b ON c.user_ID = b.user_ID AND
c.date = b.maxDate
WHERE a.package = 1
union
SELECT a.*, c.*
FROM users a
--here I would need to join with payments table to get the columns from the payments table,
but where the user doesn't have a payment yet
WHERE a.package = 1
The option to use the union doesn't seem like a good solution, but that's what I tried.
So, in other words, you want a list of users and the last payment for each.
You can use OUTER APPLY instead of INNER JOIN to get the last payment for each user. The performance might be better and it will work the way you want regarding users with no payments.
SELECT a.*, b.*
FROM users a
OUTER APPLY ( SELECT * FROM payments c
WHERE c.user_id = a.user_id
ORDER BY c.date DESC
FETCH FIRST ROW ONLY ) b
WHERE a.package = 1;
Here is a generic version of the same concept that does not require your tables (for other readers). It gives a list of database users and the most recently modified object for each user. You can see it properly includes users that have no objects.
SELECT a.*, b.*
FROM all_users a
OUTER APPLY ( SELECT * FROM all_objects b
WHERE b.owner = a.username
ORDER BY b.last_ddl_time desc
FETCH FIRST ROW ONLY ) b
I like the answer from #Matthew McPeak but OUTER APPLY is 12c or higher and isn't very idiomatic Oracle, historically anyway. Here's a straight LEFT OUTER JOIN version:
SELECT *
FROM users a
LEFT OUTER JOIN
(
-- retrieve the list of payments for just those payments that are the maxdate per user
SELECT payments.*
FROM payments
JOIN (SELECT user_id, MAX(date) maxdate
FROM payments
GROUP BY user_id
) maxpayment_byuser
ON maxpayment_byuser.maxdate = payments.date
AND maxpayment_byuser.user_id = payments.user_id
) b ON a.ID = b.user_ID
If performance is an issue, you may find the following more performant but for simplicity you'll end up with an extra "maxdate" column.
SELECT *
FROM users a
LEFT OUTER JOIN
(
-- retrieve the list of payments for just those payments that are the maxdate per user
SELECT *
FROM (
SELECT payments.*,
MAX(date) OVER (PARTITION BY user_id) maxdate
FROM payments
) max_payments
WHERE date = maxdate
) b ON a.ID = b.user_ID
A generic approach using row_number() is very useful for "highest date" or "most recent" or similar conditions:
SELECT
*
FROM users a
LEFT OUTER JOIN (
-- determine the row corresponding to "most recent"
SELECT
payments.*
, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY date DESC) is_recent
FROM payments
) b ON a.ID = b.user_ID
AND b.is_recent = 1
(reversing the ORDER BY within the over clause also enables "oldest")

Fetch most recent records as part of Joins

I am joining 2 tables customer & profile. Both the tables are joined by a specific column cust_id. In profile table, I have more than 1 entry. I want to select the most recent entry by start_ts (column) when joining both the tables. As a result I would like 1 row - row from customer and most recent row from profile in the resultset. Is there a way to do this ORACLE SQL?
I would use window functions:
select . . .
from customer c join
(select p.*,
row_number() over (partition by cust_id order by start_ts desc) as seqnum
from profile
) p
on c.cust_id = p.cust_id and p.seqnum = 1;
You can use a left join if you like to get customers that don't have profiles as well.
One way (which works for all DB engines) is to join the tables you want to select data from and then join against the specific max-record of profile to filter out the data
select c.*, p.*
from customer c
join profile p on c.cust_id = p.cust_id
join
(
select cust_id, max(start_ts) as maxts
from profile
group by cust_id
) p2 on p.cust_id = p2.cust_id and p.start_ts = p2.maxts
Here is another way (if there exists no newer entry then it's the newest):
select
c.*,
p.*
from
customer c inner join
profile p on p.cust_id = c.cust_id and not exists(
select *
from profile
where cust_id = c.cust_id and start_ts > p.start_ts
)

Counting associations from multiple tables

I want to see how many association each of my records in a given table have. Some of these association have some conditions attached to them
So far I have
-- Count app associations
SELECT
distinct a.name,
COALESCE(v.count, 0) as visitors,
COALESCE(am.count, 0) AS auto_messages,
COALESCE(c.count, 0) AS conversations
FROM apps a
LEFT JOIN (SELECT app_id, count(*) AS count FROM visitors GROUP BY 1) v ON a.id = v.app_id
LEFT JOIN (SELECT app_id, count(*) AS count FROM auto_messages GROUP BY 1) am ON a.id = am.app_id
LEFT JOIN (
SELECT DISTINCT c.id, app_id, count(c) AS count
FROM conversations c LEFT JOIN messages m ON m.conversation_id = c.id
WHERE m.visitor_id IS NOT NULL
GROUP BY c.id) c ON a.id = c.app_id
WHERE a.test = false
ORDER BY visitors DESC;
I run into problem with the last join statement for conversations. I want to count the number of conversations that have at least 1 message where the visitor_id is not null. For some reason, I get multiple records for each app, ie. the conversations are not being grouped properly.
Any ideas?
My gut feeling, based on limited understanding of the big picture: in the nested query selecting from conversations,
remove DISTINCT
remove c.id from SELECT list
GROUP BY c.app_id instead of c.id
EDIT: try this
...
LEFT JOIN (
SELECT app_id, count(*) AS count
FROM conversations c1
WHERE
EXISTS (
SELECT *
FROM messages m
WHERE m.conversation_id = c1.id and
M.visitor_id IS NOT NULL
)
GROUP BY c1.app_id) c
ON a.id = c.app_id

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