Show earliest reservations where books are available - mysql

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

Related

Combining three tables when two have the same foreign key

Hello I am making some accounting software that allows you to store transactions across many months (stored in table transactions) and categorize those transactions based on a list of available categories (stored in table categories). Finally each month the user can create a budget where they build a list (from a subset of categories) and assign a goal to each entry. These lists are stored in table budget.
I need a query that returns a spending and budget summary for a specified month. This summary would be a table of categories.name, budget.goal, and sum(transactions.amount).
Sometimes a user would have a budget item for the specified month but hasn't made any transactions with that category yet (Out to Eat example). Sometimes a user would have an unexpected expense they didn't budget for (Auto Repair Example) and there will be some categories (like vacation) where the user didn't budget that item and there were no expenses of that category.
SELECT categories.id, categories.name, SUM(transactions.amount)
FROM categories
LEFT JOIN transactions ON categories.id=transactions.category_id
WHERE transactions.date LIKE '2019-08-%' GROUP BY categories.id;
gets me half of what I want and
SELECT categories.id, categories.name, budgets.goal
FROM categories
LEFT JOIN budgets ON categories.id=budgets.category_id
WHERE budgets.date LIKE '2019-08-%' GROUP BY categories.id;
gets me the other half of what I want. Is there a single query that can return results like what I have pictured above? I would be even more thrilled if we could exclude results where both goal and sum are NULL.
If you know that there can be at most one budget per category and month, then you can (LEFT) JOIN both (child) tables to categories:
SELECT
c.id,
categories.name,
MAX(b.goal) as budget_goal,
SUM(t.amount) as total_txn_amount
FROM categories c
LEFT JOIN budgets b
ON c.id = b.category_id
AND b.date LIKE '2019-08-%'
LEFT JOIN transactions t
ON c.id = t.category_id
GROUP BY categories.id
HAVING COALESCE(budget_goal, total_txn_amount) IS NOT NULL;
Note that while we know that there can be only one budget per group, the engine doesn't, and might claim that b.goal must be either in the GROUP BY clause or used in an aggregate function. So we use MAX(b.goal) to avoid that error.
To improve the performance of the first JOIN I would change
AND b.date LIKE '2019-08-%'
to
AND b.date >= '2019-08-01'
AND b.date < '2019-08-01' + INTERVAL 1 MONTH
and create a composite index on (category_id, date).
Also to force the uniqueness of category and month combination in the budgets table, I would create a virtual column like
year_month VARCHAR(7) as (LEFT(date, 7))
and a UNIQUE KEY on (category_id, year_month)
Then you can use
LEFT JOIN budgets b
ON c.id = b.category_id
AND b.date = '2019-08'
You can try below query to find the proper result :
SELECT c.name,b.goal,sum(ct.amount) FROM era.categories c left join budget b on b.cat_id=c.id left join transactions ct on ct.cat_id=c.id WHERE b.date LIKE '2019-08-%' group by ct.cat_id;
Thanks.

How to join on a row with max value

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 = ?

SQL How to Get Total Number of Films Out On Rent?

I'm practicing SQL with given results using MySQL Sakila database.
Here's one of the problems I'm struggling with now.
How many films are out on rent?
Correct Result: 183
I'm inner joining inventory,film, and rental tables for this.
In inventory table, I have inventory_id, film_id, store_id, and last_update
In film table, I havefilm_id, title, description, release_year, language_id, original_language_id, rental_duration, rental_rate, length, replacement_cost, rating, special_features, last_update
And for rental table, I have rental_id, rental_date, inventory_id, customer_id, return_date, staff_id, last_update
And here's my SQL statement
SELECT
inv.film_id, COUNT(f.title) as numOfDVDsOnRent
FROM
rental AS r
INNER JOIN
inventory AS inv ON r.inventory_id = inv.inventory_id
inner join film as f on inv.film_id=f.film_id
What I've got for the result so far was rows of total number of DVDs out on rent for each film...Something like this:
So, how can I get the correct result?
Please try the following...
SELECT COUNT( inventory_id ) as numOfDVDsOnRent
FROM rental
WHERE return_date IS NULL;
All the information to determine how many films are out on rent is included in rental. We do not need to refer to the details about each film or about each item of inventory to determine this, so I have dumped the INNER JOINs. Note : Where JOIN is not preceeded by a join type, an INNER JOIN is performed, so you can just type JOIN if you wish.
Thus all we need to do is count all the entries in rental where the entry has not been returned date.
If you have any questions or comments, then please feel free to post a Comment accordingly.

Creating a query in mysql linking 2 tables and only displaying certain information

I have 2 tables, tbl_students & tbl_inv. The student table holds all information for students. I am creating an invoicing system to create and keep records for invoices (monthly) for the students. The tbl_inv can have multiple invoices for a single student but with a different date. I am trying to create an array which will list all the students with the latest invoice, total and status ordered by the newest invoice date at the top. I am fairly new to programming with mysql and php, so my apologies if my question sounds silly. This is what I have for the query.....
$query = "SELECT * FROM tbl_student
LEFT JOIN tbl_inv ON tbl_student.sid = tbl_inv.inv_sid
GROUP BY tbl_student.sid
ORDER BY tbl_inv.inv_date DESC";
This creates an array which has one line per student but doesn't display the latest invoice date and details.
If anyone can help I would be much appreciated :-)
(Addition pull from direct comments to existing answer)
This is the final query that works..
SELECT
S.*,
TI2.*
FROM
tbl_student S
LEFT JOIN ( SELECT
TI.inv_sid,
MAX(TI.inv_id) LatestInvoice
FROM
tbl_inv TI
GROUP BY
TI.inv_sid ) PreQuery
ON S.sid = PreQuery.inv_sid
LEFT JOIN tbl_inv TI2
ON PreQuery.inv_sid = TI2.inv_sid
AND PreQuery.LatestInvoice = TI2.inv_id
ORDER BY
TI2.inv_id ASC
I have students link with each other my using a field called f_link on the student table. If a student has a family member linked to them the f_link field shows the master id no. If there is no family link the field just shows a 0. With my invoicing system I am creating an invoice for a student and any family member linked to them. The current query will display no data for invoices for the family member, but they have been invoiced. I want to narrow this query down to only display students who have 0 in the f_link field.
Keep looking and learning querying... formatting and readability is a big plus for future maintenance. For this type of problem, ask what is the FIRST thing you need... in your case, on a per-student basis, what is the last invoice they had (regardless of the data).
select
TI.inv_sid,
MAX( TI.inv_date ) LatestInvoiceDate
from
tbl_inv TI
group by
TI.inv_sid
The above will give you one piece of the puzzle, and that is how many complex queries get built. From this, you then want the student's name, and invoice details about this most recent invoice. To do, the above query will be used in the next phase... in this case, it will get an "alias" for its result set to get the rest of the details.
select
S.*,
TI2.*
from
tbl_Student S
LEFT JOIN ( select
TI.inv_sid,
MAX( TI.invoicenumber ) LatestInvoiceNumber,
count(*) totalInvoicesForStudent,
sum( TI.Total ) as TotalAllInvoices,
sum( TI.Total - TI.AmountPaid) as OutstandingBalances
from
tbl_inv TI
group by
TI.inv_sid ) PreQuery
on S.sid = PreQuery.inv_sid
LEFT JOIN tbl_inv TI2
on PreQuery.inv_sid = TI2.inv_sid
AND PreQuery.LatestInvoiceNumber = TI2.invoiceNumber
where
s.F_Link = 0
order by
TI2.inv_date desc
So, to clarify above query
For EVERY Student (table listed first),
do a LEFT JOIN (all students regardless of ever yet having an invoice) to the "PreQuery" result set that has all students with their most recent invoice date).
If such an invoice record WAS found, do another LEFT JOIN to the invoice table AGAIN, but this time on the PreQuery "student id" and the "LatestInvoiceDate" determined for that user to the original invoice table.
Once those are all joined together, get all the fields from both the student table and the SECOND instance (TI2) invoice table for the invoice details.
Now, if you want ONLY students who HAD A MINIMUM of at least 1 invoice, just leave as "JOIN" instead of "LEFT JOIN".

join on sub query returns fails

Trying to join a table "fab_qouta.qoutatype" to at value inside a sub query "fab_status_members.statustype" but it returns nothing.
If I join the 2 tables directly in a query the result is correct.
Like this:
select statustype, takst
from
fab_status_members AS sm
join fab_quota as fq
ON fq.quotatype = sm.statustype
So I must be doing something wrong, here the sub query code, any help appreciated
select
ju.id,
name,
statustype,
takst
from jos_users AS ju
join
( SELECT sm.Members AS MemberId, MaxDate , st.statustype
FROM fab_status_type AS st
JOIN fab_status_members AS sm
ON (st.id = sm.statustype) -- tabels are joined
JOIN
( SELECT members, MAX(pr_dato) AS MaxDate -- choose members and Maxdate from
FROM fab_status_members
WHERE pr_dato <= '2011-07-01'
GROUP BY members
)
AS sq
ON (sm.members = sq.members AND sm.pr_dato = sq.MaxDate)
) as TT
ON ju.id = TT.Memberid
join fab_quota as fq
ON fq.quotatype = TT.statustype
GROUP BY id
Guess the problem is in the line: join fab_quota as fq ON fq.quotatype = TT.statustype
But I can't seem to look through it :-(
Best regards
Thomas
It looks like you are joining down to the lowest combination of per member with their respective maximum pr_dato value for given date. I would pull THIS to the FIRST query position instead of being buried, then re-join it to the rest...
select STRAIGHT_JOIN
ju.id,
ju.name,
fst.statustype,
takst
from
( SELECT
members,
MAX(pr_dato) AS MaxDate
FROM
fab_status_members
WHERE
pr_dato <= '2011-07-01'
GROUP BY
members ) MaxDatePerMember
JOIN jos_users ju
on MaxDatePerMember.members = ju.ID
JOIN fab_status_members fsm
on MaxDatePerMember.members = fsm.members
AND MaxDatePerMember.MaxDate = fsm.pr_dato
JOIN fab_status_type fst
on fsm.statustype = fst.id
JOIN fab_quota as fq
on fst.statusType = fq.quotaType
I THINK I have all of what you want, and let me reiterate in simple words what I think you want. Each member can have multiple status entries (via Fab_Status_Members). You are looking for all members and what their MOST RECENT Status is as of a particular date. This is the first query.
From that, whatever users qualify, I'm joining to the user table to get their name info (first join).
Now, back to the complex part. From the first query that determined the most recent date status activity, re-join back to that same table (fab_status_members) and get the actual status code SPECIFIC to the last status date for that member (second join).
From the result of getting the correct STATUS per Member on the max date, you need to get the TYPE of status that code represented (third join to fab_status_type).
And finally, from knowing the fab_status_type, what is its quota type.
You shouldn't need the group by since the first query is grouped by the members ID and will return a single entry per person (UNLESS... its possible to have multiple status types in the same day in the fab_status_members table... unless that is a full date/time field, then you are ok)
Not sure of the "takst" column which table that comes from, but I try to completely qualify the table names (or aliases) they are coming from, buy my guess is its coming from the QuotaType table.
... EDIT from comment...
Sorry, yeah, FQ for the last join. As for it not returning any rows, I would try them one at a time and see where the break is... I would start one at a time... how many from the maxdate query, then add the join to users to make sure same record count returned. Then add the FSM (re-join) for specific member / date activity, THEN into the status type... somewhere along the chain its missing, and the only thing I can think of is a miss on the status type as any member status would have to be associated with one of the users, and it should find back to itself as that's where the max date originated from. I'm GUESSING its somewhere on the join to the status type or the quota.