Join tables with specific order - mysql

I've got two tables, for example: Teacher and Pupil and table LastViewedPupil with fields who watched him and when (teacherId & pupilId). So I want to return the list of Pupils that was ordered by last viewed date, but there are not all pupils inside LastViewedPupil, but last few for example, I want to show after that ordered by date all left records no matter in wich order, how can I do that?
I can do without last part like
select * from Pupil as p, (
select * from LastViewedPupil lvp where lvp.teacherId = 5 ORDER BY lastViewDate
) as lvp where lvp.pupilId = p.pupilId;
Or should I add corresponding records in LastViewDatePupil for all pupils or need to Join table itself (sounds awkward)?

You should try this one:
SELECT p.*
LEFT JOIN LastViewedPupil lvp ON p.id = lvp
WHERE lvp.teacher_id = 5
ORDER BY lvp.lastViewDate DESC
I'm not sure if that query puts NULL at the beginning or at the end. If that doesn't order the results properly, try this other. I used a CASE for reordering data
SELECT p.*,
CASE WHEN lvp.lvp.lastViewDate IS NULL THEN 1 ELSE 0, END AS notNullfirst FROM Pupil p
LEFT JOIN LastViewedPupil lvp ON p.id = lvp
WHERE lvp.teacher_id = 5
ORDER BY notNullfirst, lvp.lastViewDate DESC

Related

Count total, active and inactive data by MySQL

My code sample for get total total_stamp, i need with active and inactive. My stamp table have current_status row for active and inactive
SELECT r.*
, COUNT(s.current_status) total_stamp
FROM tbl_registers r
LEFT
JOIN tbl_stamps s
ON r.register_id = s.register_id
WHERE r.ins_id = 1
GROUP
BY r.register_id
ORDER BY r.register_name_en ASC
, s.stamp_name_en ASC
Current output like that, I need another more column line one is total_active another is total inactive with single query.
SELECT r.*,
COUNT(s.current_status),
SUM(current_status='something meaning active') active,
SUM(current_status='something meaning inactive') inactive,
...
should do the trick. Why? because expressions like current_status='something meaning inactive' in MySQL have the value 0 meaning false, or 1 meaning true. So SUM() adds up the number of true items.
I think the best way to achieve this would be to split your LEFT JOIN up into two separate LEFT JOINS. One to the table where active and another where inactive. This way you will be able to sum the three separately. Does that make sense? Something like this:
SELECT r.*, sActive.total + sInactive.total as total_stamp, sActive.total as active_stamp, sInactive.total as inactive_stamp
FROM tbl_registers as r
LEFT JOIN (
SELECT register_id, COUNT(*) as total
FROM tbl_stamps
WHERE s.current_status = 'active'
GROUP BY register_id
) as sActive ON sActive.register_id = r.register_id
LEFT JOIN (
SELECT register_id, COUNT(*) as total
FROM tbl_stamps
WHERE s.current_status = 'inactive'
GROUP BY register_id
) as sInactive ON sInactive.register_id = r.register_id
GROUP BY r.register_id

How can I use DISTINCT with a JOIN?

I have those two tables:
users:
And playertimes:
(I know jumps and style should be int and not varchar() but that's not what I'm trying to solve)
I have a query, which is supposed to return the following results within the resultset:
name - retrieved using JOIN with auth
time
style
Here's my current query:
SELECT p.style, p.time, u.name FROM playertimes p JOIN users u ON p.auth = u.auth WHERE p.map = 'bhop_good' ORDER BY p.time ASC;
Here's the result I'm getting with the query above:
At this moment, the table can only contains two possible values of style which are 0 or 1. What I'm looking for, is to make style a DISTINCT, so in my case, I want to only get 2 results (one row per a value of style) for the query above, which should look like the following screenshot:
I'd like to receive help, thanks!
It looks like you want the style row for each distinct value of style having the smallest time value.
You get that like so. It takes two steps. The first determines the smallest time for each value
SELECT style, MIN(time) time
FROM playertimes
GROUP BY style
The second step gets the actual playertimes row corresponding to that time.
SELECT p.style, s.time, p.auth
FROM playertimes p
JOIN (
SELECT style, MIN(time) time
FROM playertimes
GROUP BY style
) s ON p.style = s.style AND p.time = s.time
Finally, you can join that lot to your users table to turn your auth column into a name column.
SELECT p.style, s.time, u.name
FROM playertimes p
JOIN (
SELECT style, MIN(time) time
FROM playertimes
GROUP BY style
) s ON p.style = s.style AND p.time = s.time
JOIN users.u ON p.auth = u.auth
And, of course, once you have a working query you can add a WHERE clause to it, like this one.
WHERE p.map = 'some constant'

LEFT JOIN to a single row in order of criteria in MySQL

Ok, I tried to simplify my question by abstracting away the details but I'm afraid I wasn't clear and didn't meet moderator requirements. So I will post the full query with my problem in more detail and the actual query I am struggling with. If the question is still inadequate, could you please comment with specifics about what is unclear and I will do my best to clarify.
First, here is the current query that returns all assignment rows for each bed:
SELECT
beds.bed_id,
beds.bedstatus,
beds.position as bed_position,
rooms.room_id,
rooms.room,
wings.wing_id,
wings.name as wing_name,
buildings.building_id,
buildings.name as building_name,
assignments.assignment_id,
assignments.student_id,
assignments.assign_dt,
assignments.assigned_by,
assignments.assignment_status,
assignments.expected_arrival_dt as arrival_dt,
assignments.room_charge_type,
students.first_name,
students.last_name,
meal_plans.name as meal_plan_name,
room_rates.rate_name
FROM
beds
LEFT JOIN
rooms ON (beds.room_id = rooms.room_id)
LEFT JOIN
wings ON (rooms.wing_id = wings.wing_id)
LEFT JOIN
buildings ON (wings.building_id = buildings.buildings_id)
LEFT JOIN assignments ON
((beds.bed_id=assignments.bed_id) AND (term_id = #term_id))
LEFT JOIN
students ON (assignments.student_id = students.student_id)
LEFT JOIN
meal_plans ON (assignments.meal_plan_id = meal_plans.meal_plan_id)
LEFT JOIN
room_rates ON (room_rate_id = room_rates.room_rate_id)
WHERE
(
(rooms.room IS NOT NULL) AND
(rooms.assignable = 1) AND
(buildings.active = 1) AND
(buildings.building_id = #building_id)
)
ORDER BY BY rooms.room;
The problem is that there may be multiple rows in the "assignments" table for each room distinguished by the "assignment_status" field and I want a single row for each assignment. I want to determine which assignment row to select based on the value in assignment_status. That is if the assignment status is "active", I want that row, otherwise, if there is a row with status "waiting approval" then I want that row, etc...
Barmar's suggestion is given here:
LEFT JOIN (SELECT *
FROM OtherTable
WHERE <criteria>
ORDER BY CASE status
WHEN 'Active' THEN 1
WHEN 'Waiting Approval' THEN 2
WHEN 'Canceled' THEN 3
...
END
LIMIT 1) other
This was very helpful and I attempted this approach:
SELECT
beds.bed_id,
beds.bedstatus,
beds.position as bed_position,
rooms.room_id,
rooms.room,
wings.wing_id,
wings.name as wing_name,
buildings.building_id,
buildings.name as building_name,
assign.assignment_id,
assign.student_id,
assign.assign_dt,
assign.assigned_by,
assign.assignment_status,
assign.expected_arrival_dt as arrival_dt,
assign.room_charge_type,
students.first_name,
students.last_name,
meal_plans.name as meal_plan_name,
room_rates.rate_name
FROM
beds
LEFT JOIN
rooms ON (beds.room_id = rooms.room_id)
LEFT JOIN
wings ON (rooms.wing_id = wings.wing_id)
LEFT JOIN
buildings ON (wings.building_id = buildings.buildings_id)
LEFT JOIN (SELECT *
FROM assignments
WHERE ((assignments.bed_id==beds.bed_id) AND (term_id = #term_id))
ORDER BY CASE assignment_status
WHEN 'Active' THEN 1
WHEN 'Waiting Approval' THEN 2
WHEN 'Canceled' THEN 3
END
LIMIT 1) assign
LEFT JOIN
students ON (assign.student_id = students.student_id)
LEFT JOIN
meal_plans ON (assign.meal_plan_id = meal_plans.meal_plan_id)
LEFT JOIN
room_rates ON (room_rate_id = room_rates.room_rate_id)
WHERE
(
(rooms.room IS NOT NULL) AND
(rooms.assignable = 1) AND
(buildings.active = 1) AND
(buildings.building_id = #building_id)
)
ORDER BY rooms.room;
But I realized, the problem here is that OtherTable (assignments) is joined to the parent query based on a FK:
((beds.bed_id=assignments.bed_id) AND (term_id = #term_id))
So I can't do the subselect as the beds.bed_id isn't in scope for the subselect. So as Barmar's comment indicates the join criteria needs to be outside the subselect--but I'm having trouble figuring out how to both restrict the results to a single row per room and move the join outside the subselect. I'm wondering if travelboy's suggestion to use GROUP BY may be more fruitful, but haven't been able to determine how the grouping should be done.
Let me know if I can provide additional clarification.
Original Question:
I need from Table A to do a LEFT JOIN on a SINGLE row in another table, Table B meeting certain criteria (there may be multiple or no rows in Table B that meet the criteria). If there are multiple rows I want to select which row in B to join based on the value of a field in Table B. For example, if there is a row in B with status column='Active', I want that row, if not, if there is a row with status='Waiting Approval', I want that row, if there is a row with status='Canceled', I want that row, etc... Can I do this without a sub select? With a sub select?
Use:
LEFT JOIN (SELECT *
FROM OtherTable
WHERE <criteria>
ORDER BY CASE status
WHEN 'Active' THEN 1
WHEN 'Waiting Approval' THEN 2
WHEN 'Canceled' THEN 3
...
END
LIMIT 1) other
In some cases (but not in all cases) you can do it without a sub-select. You would need to GROUP BY a unique field in table A, typically an ID. This ensures that you get only one (or none) row from table B. However, selecting the row you want is the tricky part. You need an aggregating function such as MAX(). If the field in B is a number, that's easy to do. If not, you can apply some SQL functions on the fields in B to calculate something like a score to sort by. For example, Active could correspond to a higher value than Cancelled etc. That will work without a sub-select and likely be faster on big data sets.
With a sub-select it's easy to do. You can either use Barmar's solution, or, if you only need one specific field from B, you can also put the sub-select within the SELECT clause of the outer query.
I need to follow up with some additional testing to make sure this is accomplishing my goal--but I think I've done this using travelboy's suggestion of a group by query combined with barmar's case logic (wish I could split the answer). Here's the query:
SELECT
beds.bed_id,
beds.bedstatus,
beds.position as bed_position,
rooms.room_id,
rooms.room,
wings.wing_id,
wings.name as wing_name,
buildings.building_id,
buildings.name as building_name,
assignments.assignment_id,
assignments.student_id,
assignments.assign_dt,
assignments.assigned_by,
assignments.assignment_status,
assignments.expected_arrival_dt as arrival_dt,
assignments.room_charge_type,
MIN(CASE assignments.assignment_status
WHEN 'Active' THEN 1
WHEN 'Waiting Approval' THEN 2
WHEN 'Canceled' THEN 3
END),
students.first_name,
students.last_name,
meal_plans.name as meal_plan_name,
room_rates.rate_name
FROM
beds
LEFT JOIN
rooms ON (beds.room_id = rooms.room_id)
LEFT JOIN
wings ON (rooms.wing_id = wings.wing_id)
LEFT JOIN
buildings ON (wings.building_id = buildings.building_id)
LEFT JOIN assignments
ON ((assignments.bed_id=beds.bed_id) AND (term_id = 28))
LEFT JOIN
students ON (assignments.student_id = students.student_id)
LEFT JOIN
meal_plans ON (assignments.meal_plan_id = meal_plans.meal_plan_id)
LEFT JOIN
room_rates ON (assignments.room_rate_id = room_rates.room_rate_id)
WHERE
(
(rooms.room IS NOT NULL) AND
(rooms.assignable = 1) AND
(buildings.active = 1)
)
GROUP BY
bed_id
ORDER BY rooms.room;

COUNT evaluate to zero if no matching records

Take the following:
SELECT
Count(a.record_id) AS newrecruits
,a.studyrecord_id
FROM
visits AS a
INNER JOIN
(
SELECT
record_id
, MAX(modtime) AS latest
FROM
visits
GROUP BY
record_id
) AS b
ON (a.record_id = b.record_id) AND (a.modtime = b.latest)
WHERE (((a.visit_type_id)=1))
GROUP BY a.studyrecord_id;
I want to amend the COUNT part to display a zero if there are no records since I assume COUNT will evaluate to Null.
I have tried the following but still get no results:
IIF(ISNULL(COUNT(a.record_id)),0,COUNT(a.record_id)) AS newrecruits
Is this an issue because the join is on record_id? I tried changing the INNER to LEFT but also received no results.
Q
How do I get the above to evaluate to zero if there are no records matching the criteria?
Edit:
To give a little detail to the reasoning.
The studies table contains a field called 'original_recruits' based on activity before use of the database.
The visits tables tracks new_recruits (Count of records for each study).
I combine these in another query (original_recruits + new_recruits)- If there have been no new recruits I still need to display the original_recruits so if there are no records I need it to evalulate to zero instead of null so the final sum still works.
It seems like you want to count records by StudyRecords.
If you need a count of zero when you have no records, you need to join to a table named StudyRecords.
Did you have one? Else this is a nonsense to ask for rows when you don't have rows!
Let's suppose the StudyRecords exists, then the query should look like something like this :
SELECT
Count(a.record_id) AS newrecruits -- a.record_id will be null if there is zero count for a studyrecord, else will contain the id
sr.Id
FROM
visits AS a
INNER JOIN
(
SELECT
record_id
, MAX(modtime) AS latest
FROM
visits
GROUP BY
record_id
) AS b
ON (a.record_id = b.record_id) AND (a.modtime = b.latest)
LEFT OUTER JOIN studyrecord sr
ON sr.Id = a.studyrecord_id
WHERE a.visit_type_id = 1
GROUP BY sr.Id
I solved the problem by amending the final query where I display the result of combining the original and new recruits to include the IIF there.
SELECT
a.*
, IIF(IsNull([totalrecruits]),consents,totalrecruits)/a.target AS prog
, IIf(IsNull([totalrecruits]),consents,totalrecruits) AS trecruits
FROM
q_latest_studies AS a
LEFT JOIN q_totalrecruitment AS b
ON a.studyrecord_id=b.studyrecord_id
;

Select parents based on all children statisfying condition

This is such a simple problem but for some reason I cannot get my head round it today.
I have two entities:- title and product each respectively named tbl_title and tbl_product. Each title can have many products.
The product table has a field called unwanted which can be either null, 0 or 1.
I wish to select all titles based on where all products (ALL) have unwanted set to 1. So in other words I wish to select the parent based upon all children filling a certain condition. So if a title has one product that is unwanted but another that is not I do not wish for this title to enter the result set.
When I try this the most I get out of my head is:
SELECT * FROM `tbl_title`
left join tbl_product on tbl_product.title_id = tbl_title.id
where tbl_product.unwanted = 1
group by tbl_title.id
Which obviously does not work.
So how do I code such a query?
select * from tbl_title
where id not in (select title_id from tbl_product where unwanted = 0)
In English, this query eliminates all titles that have a wanted product.
From a style point of view, it would be better to call your column wanted, because unwanted = 0 is a double-negative of wanted = 1. It's always easier to get your head around positives.
SELECT t.id
FROM `tbl_title` t
left join tbl_product p on p.title_id = t.id
group by t.id
having sum(p.unwanted = 0 or p.unwanted is null) = 0
Try using a subquery like this:
SELECT * FROM `tbl_title` AS t
WHERE EXISTS (SELECT 1 FROM products WHERE title_id = t.id AND unwanted = 1)
AND NOT EXISTS (SELECT 1 FROM products WHERE title_id = t.id AND (unwanted = 0 OR unwanted IS NULL))
Just for the fields in title table
SELECT *
FROM `tbl_title` AS t
JOIN tbl_product AS v ON t.id = v.title_id
WHERE NOT EXISTS(
SELECT *
FROM tbl_product
WHERE (t.id = title_id)
AND (unwanted = 0 OR unwanted IS NULL)
GROUP BY t.id