How to join on a row with max value - mysql

I have three tables: households, voters, door_knocks
Each household can have several voters associated with it. Each household can also have several door knocks associated with it.
I'm trying to pull together all the voters in a household and the date of the last door_knock from the door_knocks table and I'm having trouble figuring out the proper query syntax. Here is my latest attempt:
SELECT households.hh_id, voters.id
FROM households
INNER JOIN voters ON households.hh_id = voters.hh_id
INNER JOIN ( SELECT MAX(dk.date), dk.hh_id FROM door_knocks dk GROUP BY dk.date) dks
ON dks.hh_id = households.hh_id
WHERE households.street = ?
The above query pulls up one result for each door knock, however. I just want the the date from the last door knock.

So, what it sounds like you're hoping for conceptually is a table that lists the last date of a knock for each houshold.
You'd like to join against that table and combine it with the voters and the households.
what your query does is give you a table of all the dates (group by dk.date) and for each date list all the households.
If you group by hh_id instead, then you will get the max date for each given household.
SELECT households.hh_id, voters.id, dks.max_date
FROM households
INNER JOIN voters ON households.hh_id = voters.hh_id
INNER JOIN ( SELECT MAX(dk.date) as max_date, dk.hh_id FROM door_knocks dk GROUP BY dk.hh_id dks
ON dks.hh_id = households.hh_id
WHERE households.street = ?

Related

MySQL inner join query select from same table multiple times

I believe I have formed this question title correctly because I wasn't sure how to form it. As an example, I have summarized my query below.
I have an order table which saves order details like customer id, address and product ids and quantity ordered for each order in a row. So multiple inventory/product ids are saved in a single row.
so my query looks like: this is a summarized query for an easier explanation I have omitted various other fields.
SELECT customer.name,customer.address,tbl_order.order_date,tbl_order.product1_id,tbl_order.product2_id,inventory.product1_name,inventory.product2_name
FROM tbl_order
INNER JOIN customer ON tbl_order.customer_id = customer.id
INNER JOIN inventory on tbl_order.product1_id = inventory.id
INNER JOIN inventory on tbl_order.product2_id = inventory.id
where YEAR(tbl_order.order_date)='$year'
So my question is how to get the inventory details from the inventory table based on each product id from tbl_order. I am running a while loop to show all data for a year
while($row=mysqli_fetch_assoc($sql1))
I can divide this query into 2 and run the inventory query individually but then how to combine the while loop, as sometimes there could also be empty query when some products are not in order table (depending on order to order, not all products are ordered) so this doesn't work
while($row=mysqli_fetch_assoc($sql1)) and ($row1=mysqli_fetch_assoc($inv1)) and ($row2=mysqli_fetch_assoc($inv2))
and so one for 10 products
First, of all you have bad DB design and I kindly advice to normalize your DB.
Second, if you can not re-design the DB you can use multiple joins with aliases like:
SELECT
customer.name, customer.address, tbl_order.order_date,
tbl_order.product1_id, inv1.product1_name,
tbl_order.product2_id, inv2.product2_name
FROM tbl_order
INNER JOIN customer ON tbl_order.customer_id = customer.id
INNER JOIN inventory AS inv1 ON tbl_order.product1_id = inv1.id
INNER JOIN inventory AS inv2 ON tbl_order.product2_id = inv2.id
WHERE YEAR(tbl_order.order_date)='$year'

Use SELECT through three table

I tried to write a query, but unfortunately I didn't succeed.
I want to know how many packages delivered over a given period by a person.
So I want to know how many packages were delivered by John (user_id = 1) between 01-02-18 and 28-02-18. John drives another car (another plate_id) every day.
(orders_drivers.user_id, plates.plate_name, orders.delivery_date, orders.package_amount)
I have 3 table:
orders with plate_id delivery_date package_amount
plates with plate_id plate_name
orders_drivers with plate_id plate_date user_id
I tried some solutions but didn't get the expected result. Thanks!
Try using JOINS as shown below:
SELECT SUM(o.package_amount)
FROM orders o INNER JOIN orders_drivers od
ON o.plate_id=od.plate_id
WHERE od.user_id=<the_user_id>;
See MySQL Join Made Easy for insight.
You can also use a subquery:
SELECT SUM(o.package_amount)
FROM orders o
WHERE EXISTS (SELECT 1
FROM orders_drivers od
WHERE user_id=<user_id> AND o.plate_id=od.plate_id);
SELECT sum(orders.package_amount) AS amount
FROM orders
LEFT JOIN plates ON orders.plate_id = orders_drivers.plate_id
LEFT JOIN orders_driver ON orders.plate_id = orders_drivers.plate_id
WHERE orders.delivery_date > date1 AND orders.delivery_date < date2 AND orders_driver.user_id = userid
GROUP BY orders_drivers.user_id
But seriously, you need to ask questions that makes more sense.
sum is a function to add all values that has been grouped by GROUP BY.
LEFT JOIN connects all tables by id = id. Any other join can do this in this case, as all ids are unique (at least I hope).
WHERE, where you give the dates and user.
And GROUP BY userid, so if there are more records of the same id, they are returned as one (and summed by their pack amount.)
With the AS, your result is returned under the name 'amount',
If you want the total of packageamount by user in a period, you can use this query:
UPDATE: add a where clause on user_id, to retrieve John related data
SELECT od.user_id
, p.plate_name
, SUM(o.package_amount) AS TotalPackageAmount
FROM orders_drivers od
JOIN plates p
ON o.plate_id = od.plate_id
JOIN orders o
ON o.plate_id = od.plate_id
WHERE o.delivery_date BETWEEN convert(datetime,01/02/2018,103) AND convert(datetime,28/02/2018,103)
AND od.user_id = 1
GROUP BY od.user_id
, p.plate_name
It groups rows on user_id and plate_name, filter a period of delivery_date(s) and then calculate the sum of packageamount for the group

mysql Left Join, group by with count

I have two tables - one for courses and one for people attending the course.
Both are joined by course_id
table 1: firstaid - has all the course names
table 2: first_aid_att - records attendees and captures first aid
course id
I would like to get a count of attendees per course.
I have got a join working but don't know how to do the group by and count.
SELECT *
FROM firstaid
LEFT JOIN first_aid_att ON firstaid.course_id = first_aid_att.course_id
ORDER BY `sortDate` ASC
Try this:
SELECT f1.*, COUNT(f2.course_id)
FROM firstaid f1
LEFT JOIN first_aid_att f2 ON (f1.course_id = f2.course_id)
GROUP BY f1.course_id
ORDER BY f2.sortDate

Show earliest reservations where books are available

I have three tables: loans, reservations and books. The books table has an attribute for "noOfCopies", and the total number of loans and reservations for that book can not exceed that figure.
The reservations table has a "timestamp" column which is just the timestamp that the reservation was made. The idea is that when a book is returned, the earliest reservation gets the book next.
Here is what I need help with: I need to create an SQL view that will show all the earliest reservations for each book, but only where that book is available.
Can anyone give me the SQL for this? Thanks in advance.
Here is the SQL I already had: I thought it showed all the reservations where the books were available and was about to move on to figuring out how to show the earliest - but then I got nowhere with earliest and then realised that this doesn't actually work anyway:
CREATE VIEW `view_bookLoans` AS
SELECT count(l.`id`) as loanCount, l.`bookISBN`,b.`noOfCopies` FROM
loans l INNER JOIN books b
ON l.`bookISBN` = b.`ISBN`
GROUP BY l.`bookISBN`;
CREATE VIEW `view_reservationList` AS
SELECT
r.`timestamp`,
b.`title` as `bookTitle`,
r.`readerID`,
bl.`loanCount`
FROM
`reservations` r INNER JOIN `books` b
ON r.`bookISBN` = b.`ISBN`
LEFT JOIN
view_bookLoans bl
ON bl.`bookISBN` = b.`ISBN`
WHERE
(b.`noOfCopies` - bl.`loanCount`) > 0;
With exception to the comments I put in the question about how to exclude reservations already filled, or loans already returned... This SHOULD do it for you.
I would start the list by only looking at those books that people have reserved. Why query and entire list of books where nobody is interested in them... THEN, expand your criteria. The inner query starts directly with the books on the reservations list. That is LEFT JOINED to the loans table (in case there are none remaining on loan). I'm extracting the ealiest time stamp for the reservation per book, and also getting the total count of LOAN book entries grouped by ISBN.
From that result, I immediately re-join to the reservations to match based on the ISBN and timestamp of the earliest to get the WHO wanted it.
Now the finale... JOIN (not a LEFT JOIN) to the books table on both the ISBN AND your qualifier that the number of copies available... less how many are already loaned out is > 0.
You can obviously add an order by clause however you want.
ENSURE that you have an index on the reservations table on (bookISBN, timestamp) for query optimization. Also, the loans table too should have an index on ISBN.
SELECT
B.Title,
R2.readerID,
R2.timeStamp,
Wanted.AlreadyLoandedOut,
B.noOfCopies - Wanted.AlreadyLoanedOut as ShouldBeAvailable
FROM
( select
R.bookISBN,
MIN( R.timeStamp ) EarliestReservation,
COALESCE( COUNT( L.ID ), 0 ) as AlreadyLoanedOut
from
reservations R
LEFT JOIN Loans L
ON R.bookISBN = L.bookISBN
group by
R.bookISBN ) as Wanted
JOIN reservations R2
ON Wanted.bookISBN = R2.bookISBN
AND Wanted.EarliestReservation = R2.timeStamp
JOIN books B
ON Wanted.bookISBN = B.ISBN
AND B.noOfCopies - Wanted.AlreadyLoanedOut > 0

When to use a left outer join?

I don't understand the concept of a left outer join, a right outer join, or indeed why we need to use a join at all! The question I am struggling with and the table I am working from is here: Link
Question 3(b)
Construct a command in SQL to solve the following query, explaining why it had to employ the
(outer) join method. [5 Marks]
“Find the name of each staff member and his/her dependent spouse, if any”
Question 3(c) -
Construct a command in SQL to solve the following query, using (i) the join method, and (ii) the
subquery method. [10 Marks]
“Find the identity name of each staff member who has worked more than 20 hours on the
Computerization Project”
Can anyone please explain this to me simply?
Joins are used to combine two related tables together.
In your example, you can combine the Employee table and the Department table, like so:
SELECT FNAME, LNAME, DNAME
FROM
EMPLOYEE INNER JOIN DEPARTMENT ON EMPLOYEE.DNO=DEPARTMENT.DNUMBER
This would result in a recordset like:
FNAME LNAME DNAME
----- ----- -----
John Smith Research
John Doe Administration
I used an INNER JOIN above. INNER JOINs combine two tables so that only records with matches in both tables are displayed, and they are joined in this case, on the department number (field DNO in Employee, DNUMBER in Department table).
LEFT JOINs allow you to combine two tables when you have records in the first table but might not have records in the second table. For example, let's say you want a list of all the employees, plus any dependents:
SELECT EMPLOYEE.FNAME as employee_first, EMPLOYEE.LNAME as employee_last, DEPENDENT.FNAME as dependent_last, DEPENDENT.LNAME as dependent_last
FROM
EMPLOYEE INNER JOIN DEPENDENT ON EMPLOYEE.SSN=DEPENDENT.ESSN
The problem here is that if an employee doesn't have a dependent, then their record won't show up at all -- because there's no matching record in the DEPENDENT table.
So, you use a left join which keeps all the data on the "left" (i.e. the first table) and pulls in any matching data on the "right" (the second table):
SELECT EMPLOYEE.FNAME as employee_first, EMPLOYEE.LNAME as employee_last, DEPENDENT.FNAME as dependent_first, DEPENDENT.LNAME as dependent_last
FROM
EMPLOYEE LEFT JOIN DEPENDENT ON EMPLOYEE.SSN=DEPENDENT.ESSN
Now we get all of the employee records. If there is no matching dependent(s) for a given employee, the dependent_first and dependent_last fields will be null.
example (not using your example tables :-)
I have a car rental company.
Table car
id: integer primary key autoincrement
licence_plate: varchar
purchase_date: date
Table customer
id: integer primary key autoincrement
name: varchar
Table rental
id: integer primary key autoincrement
car_id: integer
bike_id: integer
customer_id: integer
rental_date: date
Simple right? I have 10 records for cars because I have 10 cars.
I've been running this business for 10 years, so I've got 1000 customers.
And I rent the cars about 20x per year per cars = 10 years x 10 cars x 20 = 2000 rentals.
If I store everything in one big table I've got 10x1000x2000 = 20 million records.
If I store it in 3 tables I've got 10+1000+2000 = 3010 records.
That's 3 orders of magnitude, so that's why I use 3 tables.
But because I use 3 tables (to save space and time) I have to use joins in order to get the data out again
(at least if I want names and licence plates instead of numbers).
Using inner joins
All rentals for customer 345?
SELECT * FROM customer
INNER JOIN rental on (rental.customer_id = customer.id)
INNER JOIN car on (car.id = rental.car_id)
WHERE customer.id = 345.
That's an INNER JOIN, because we only want to know about cars linked to rentals linked to customers that actually happened.
Notice that we also have a bike_id, linking to the bike table, which is pretty similar to the car table but different.
How would we get all bike + car rentals for customer 345.
We can try and do this
SELECT * FROM customer
INNER JOIN rental on (rental.customer_id = customer.id)
INNER JOIN car on (car.id = rental.car_id)
INNER JOIN bike on (bike.id = rental.bike_id)
WHERE customer.id = 345.
But that will give an empty set!!
This is because a rental can either be a bike_rental OR a car_rental, but not both at the same time.
And the non-working inner join query will only give results for all rentals where we rent out both a bike and a car in the same transaction.
We are trying to get and boolean OR relationship using a boolean AND join.
Using outer joins
In order to solve this we need an outer join.
Let's solve it with left join
SELECT * FROM customer
INNER JOIN rental on (rental.customer_id = customer.id) <<-- link always
LEFT JOIN car on (car.id = rental.car_id) <<-- link half of the time
LEFT JOIN bike on (bike.id = rental.bike_id) <<-- link (other) 0.5 of the time.
WHERE customer.id = 345.
Look at it this way. An inner join is an AND and a left join is a OR as in the following pseudocode:
if a=1 AND a=2 then {this is always false, no result}
if a=1 OR a=2 then {this might be true or not}
If you create the tables and run the query you can see the result.
on terminology
A left join is the same as a left outer join.
A join with no extra prefixes is an inner join
There's also a full outer join. In 25 years of programming I've never used that.
Why Left join
Well there's two tables involved. In the example we linked
customer to rental with an inner join, in an inner join both tables must link so there is no difference between the left:customer table and the right:rental table.
The next link was a left join between left:rental and right:car. On the left side all rows must link and the right side they don't have to. This is why it's a left join
You use outer joins when you need all of the results from one of the join tables, whether there is a matching row in the other table or not.
I think Question 3(b) is confusing because its entire premise wrong: you don't have to use an outer join to "solve the query" e.g. consider this (following the style of syntax in the exam paper is probably wise):
SELECT FNAME, LNAME, DEPENDENT_NAME
FROM EMPLOYEE, DEPENDENT
WHERE SSN = ESSN
AND RELATIONSHIP = 'SPOUSE'
UNION
SELECT FNAME, LNAME, NULL
FROM EMPLOYEE
EXCEPT
SELECT FNAME, LNAME, DEPENDENT_NAME
FROM EMPLOYEE, DEPENDENT
WHERE SSN = ESSN
AND RELATIONSHIP = 'SPOUSE'
In general:
JOIN joints two tables together.
Use INNER JOIN when you wanna "look up", like look up detailed information of any specific column.
Use OUTER JOIN when you wanna "demonstrate", like list all the info of the 2 tables.