mysql left join question - sql

I've got two tables, one holds reservations for a room, and the other is a "mid" table to hold the dates that the room is reserved on (since a reservation could have multiple non-sequential dates).
It looks something like:
Res_table:
id, room_id, owner_id
Res_table_mid:
id, res_id, date
The res_id column in the res_table_mid references the id of the res_table. I need to get the start and end date of the reservation.
So the query looks something like this:
SELECT * FROM res_table a
LEFT JOIN (SELECT min(date) as start_date, res_id FROM res_table_mid) AS min ON a.id = min.res_id
LEFT JOIN (SELECT max(date) as end_date, res_id FROM res_table_mid) AS max ON a.id = max.res_id
This works as expected, unless the tables are empty or there are no results, in which case it errors with
#1048 - Column 'res_id' cannot be null
Is there a way to write this so that I get the data I need but if there's no results there's also no error?
Thanks!

Select id, room_id, owner_id
From Res_table
Left Join (
Select R2.res_id, Min(R2.Date), Max(R2.Date)
From Res_table_mid As R2
Group By R2.res_id
) As MinMax
On MinMax.res_Id = Res_table.Id
In your original query, neither derived table indicates the Group By column. Instead, you are relying on MySQL to guess that it should group by res_id. If I had to wager a guess, I'd say that this might be the source of the problem.

SELECT a.id,
a.room_id,
a.owner_id,
MAX(m.date) AS end_date ,
MIN(m.date) AS start_date
FROM res_table a
LEFT JOIN res_table_mid m
ON a.id = m.res_id
GROUP BY a.id,
a.room_id,
a.owner_id;

SELECT min(date) AS start_date FROM (
SELECT * FROM res_table a
LEFT JOIN res_table_mid AS b
ON a.id = b.res_id
WHERE a.id = #reservation)
SELECT max(date) AS end_date FROM (
SELECT * FROM res_table a
LEFT JOIN res_table_mid AS b
ON a.id = b.res_id
WHERE a.id = #reservation)

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.

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

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

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

SQL MAX() value across 2 or more queries

This seems like a basic action in SQL, but it has me stumped.
I have about 2 different subqueries, each grouped by LOCATION_ID that contain a date column. For example, one query includes a listing of WORKORDER records while another query pulls records from the NOTE table. Both of these queries includes a join to the LOCATION table allowing me to group by LOCATION_ID.
My goal is to pull the latest date of contact at that particular location and that can be in the form of a workorder, note date, followup date, etc. which are stored in different tables. So ideally I would have a query grouped by LOCATION_ID that shows the latest date of contact for that location.
I would post SQL but I don't have anything that is currently working for me. Any ideas on how to approach this type of scenario?
Thanks!
SELECT
L.LOCATION_ID, Max(MaxDate)
FROM
LOCATION AS L
LEFT JOIN
(SELECT
LOCATION_ID, Max(dbo.LeadNote.NoteDate) AS MaxDate
FROM
LeadNote
INNER JOIN
LOCATION ON LeadNote.LOCATION_ID = LOCATION.LOCATION_ID
GROUP BY
LOCATION_ID) T1 ON L.LOCATION_ID = T1.CONTACTLOCATION_LOCATION_ID
LEFT JOIN
(SELECT
LOCATION_ID, Max(dbo.WORKORDER.WORKORDER_DATECREATED) AS MaxDate
FROM
WORKORDER
INNER JOIN
LOCATION ON LOCATION_ID = WORKORDER_LOCATION_ID
GROUP BY
LOCATION_ID) T2 ON L.LOCATION_ID = T2.CONTACTLOCATION_LOCATION_ID`
Perhaps you could try using UNION to get a single sql result, then wrap it and give it an alias, and then apply a MAX on the field you wish, which both queries return. Keep in mind that to use UNION both queries must return the same set of field names.
Ex:
Query A:
Select a, b, c from T1 where....
Query B:
Select a, f, e from T2 where...
you would have:
SELECT MAX(e)
FROM
(
(Select a, b, c, NULL as f, NULL as e from T1 where....)
UNION
(Select a, NULL as b, NULL as c, f, e from T2 where...)
) t
If you need to use a join you can use a case statement to fetch the larger date.
SELECT
L.LOCATION_ID,
(CASE WHEN(T2.MaxDate IS NULL OR T1.MaxDate > T2.MaxDate)
THEN T1.MaxDate
ELSE T2.MaxDate
END) MaxDate
...
Try:
SELECT
L.LOCATION_ID, Max(MaxDate)
FROM
(
(
SELECT
LOCATION_ID, Max(LeadNote.NoteDate) AS MaxDate
FROM
LeadNote
JOIN
LOCATION
ON
LeadNote.LOCATION_ID = LOCATION.LOCATION_ID
GROUP BY
LOCATION_ID
)
UNION
(
SELECT
LOCATION_ID, Max(WORKORDER.WORKORDER_DATECREATED) AS MaxDate
FROM
WORKORDER
JOIN
LOCATION ON LOCATION_ID = WORKORDER_LOCATION_ID
GROUP BY
LOCATION_ID
)
)
It may need a little tweaking...but kudos to the comment by #DrCopyPaste if this works :)
You can do this with a simple join and a case statement:
SELECT
L.LOCATION_ID,
CASE WHEN(Max(LeadNote.NoteDate) IS NULL OR Max(LeadNote.NoteDate) > Max(WORKORDER.WORKORDER_DATECREATED)
THEN Max(LeadNote.NoteDate)
ELSE Max(WORKORDER.WORKORDER_DATECREATED) end AS maxDate
FROM
LOCATION AS L
LEFT JOIN LeadNote ON LeadNote.LOCATION_ID = LOCATION.LOCATION_ID
LEFT JOIN WORKORDER ON L.LOCATION_ID = WORKORDER_LOCATION_ID
GROUP BY L.LOCATION_ID

Problems with SQL Inner join

Having some problems while trying to optimize my SQL.
I got 2 tables like this:
Names
id, analyseid, name
Analyses
id, date, analyseid.
I want to get the newest analyse from Analyses (ordered by date) for every name (they are unique) in Names. I can't really see how to do this without using 2 x nested selects.
My try (Dont get confused about the names. It's the same principle):
SELECT
B.id,
B.chosendatetime,
vStockNames.name
FROM
vStockNames
INNER JOIN
(
SELECT TOP 1
vAnalysesHistory.id,
vAnalysesHistory.chosendatetime,
vAnalysesHistory.companyid
FROM
vAnalysesHistory
ORDER BY
vAnalysesHistory.chosendatetime DESC
) AS B
ON
B.companyid = vStockNames.stockid
In my example the problem is that i only get 1 row returned (because of top 1). But if I exclude this, I can get multiple analyses of the same name.
Can you help me ? - THanks in advance.
SQL Server 2000+:
SELECT (SELECT TOP 1
a.id
FROM vAnalysesHistory AS a
WHERE a.companyid = n.stockid
ORDER BY a.chosendatetime DESC) AS id,
n.name,
(SELECT TOP 1
a.chosendatetime
FROM vAnalysesHistory AS a
WHERE a.companyid = n.stockid
ORDER BY a.chosendatetime DESC) AS chosendatetime
FROM vStockNames AS n
SQL Server 2005+, using CTE:
WITH cte AS (
SELECT a.id,
a.date,
a.analyseid,
ROW_NUMBER() OVER(PARTITION BY a.analyseid
ORDER BY a.date DESC) AS rk
FROM ANALYSES a)
SELECT n.id,
n.name,
c.date
FROM NAMES n
JOIN cte c ON c.analyseid = n.analyseid
AND c.rk = 1
...without CTE:
SELECT n.id,
n.name,
c.date
FROM NAMES n
JOIN (SELECT a.id,
a.date,
a.analyseid,
ROW_NUMBER() OVER(PARTITION BY a.analyseid
ORDER BY a.date DESC) AS rk
FROM ANALYSES a) c ON c.analyseid = n.analyseid
AND c.rk = 1
You're only asking for the TOP 1, so that's all you're getting. If you want one per companyId, you need to specify that in the SELECT on vAnalysesHistory. Of course, JOINs must be constant and do not allow this. Fortunately, CROSS APPLY comes to the rescue in cases like this.
SELECT
B.id,
B.chosendatetime,
vStockNames.name
FROM
vStockNames
CROSS APPLY
(
SELECT TOP 1
vAnalysesHistory.id,
vAnalysesHistory.chosendatetime,
vAnalysesHistory.companyid
FROM
vAnalysesHistory
WHERE companyid = vStockNames.stockid
ORDER BY
vAnalysesHistory.chosendatetime DESC
) AS B
You could also use ROW_NUMBER() to do the same:
SELECT
B.id,
B.chosendatetime,
vStockNames.name
FROM
vStockNames
INNER JOIN
(
SELECT
vAnalysesHistory.id,
vAnalysesHistory.chosendatetime,
vAnalysesHistory.companyid,
ROW_NUMBER() OVER (PARTITION BY companyid ORDER BY chosendatetime DESC) AS row
FROM
vAnalysesHistory
) AS B
ON
B.companyid = vStockNames.stockid AND b.row = 1
Personally I'm a fan of the first approach. It will likely be faster and is easier to read IMO.
Will something like this work for you?
;with RankedAnalysesHistory as
(
SELECT
vah.id,
vah.chosendatetime,
vah.companyid
,rank() over (partition by vah.companyid order by vah.chosendatetime desc) rnk
FROM
vAnalysesHistory vah
)
SELECT
B.id,
B.chosendatetime,
vsn.name
FROM
vStockNames vsn
join RankedAnalysesHistory as rah on rah.companyid = vsn.stockid and vah.rnk = 1
It seems to me that you only need SQL-92 for this. Of course, explicit documentation of the joining columns between the tables would help.
Simple names
SELECT B.ID, C.ChosenDate, N.Name
FROM (SELECT A.AnalyseID, MAX(A.Date) AS ChosenDate
FROM Analyses AS A
GROUP BY A.AnalyseID) AS C
JOIN Analyses AS B ON C.AnalyseID = B.AnalyseID AND C.ChosenDate = B.Date
JOIN Names AS N ON N.AnalyseID = C.AnalyseID
The sub-select generates the latest analysis for each company; the join with Analyses picks up the Analyse.ID value corresponding to that latest analysis, and the join with Names picks up the company name. (The C.ChosenDate in the select-list could be replaced by B.Date AS ChosenDate, of course.)
Complicated names
SELECT B.ID, C.ChosenDateTime, N.Name
FROM (SELECT A.CompanyID, MAX(A.ChosenDateTime) AS ChosenDateTime
FROM vAnalysesHistory AS A
GROUP BY A.CompanyID) AS C
JOIN vAnalysesHistory AS B ON C.CompanyID = B.CompanyID
AND C.ChosenDateTime = B.ChosenDateTime
JOIN vStockNames AS N ON N.AnalyseID = C.AnalyseID
Same query with systematic renaming (and slightly different layout to avoid horizontal scrollbars).