I need help writing a MySQL query. So far, none of the questions out there seem to fit my needs.
I have an order table and an order_log table. In the order_log table I make a record every time an order's status is changed. I need to display a list of the most recent status changes from the order_log table. The query I'm currently using does a JOIN on the two tables and grabs everything where order.status = order_log.status.
The problem with this is that some times an order will pass through the same status more than once. When that occurs, my query grabs every entry in the order_log table for that order and that status, but I only want the most recent log.
I tried writing a new JOIN query to grab the Max of the order_log date entry, but it's only returning 1 entry. This is what I have.
SELECT *
FROM order_status_log AS l
JOIN orders AS o ON ( l.order_id = o.id )
WHERE l.status = o.status
AND l.date = (
SELECT MAX( date )
FROM order_status_log l2
JOIN orders AS o2 ON ( l2.order_id = o2.id )
)
Any ideas?
there are many ways to do it, one is to have a separate subquery the gets the latest entry of each record: order_ID.
The result of the subquery is then joined back with the original table but has multiple conditions: that it matches the order_ID and also the latest date.
SELECT a.*, b.*
FROM `order` a
INNER JOIN order_log b
ON a.id = b.order_ID
INNER JOIN
(
SELECT order_ID, MAX(date) max_date
FROM order_log
GROUP BY order_ID
) c on b.order_ID = c.order_ID AND
b.date = c.max_date
That may help;
select olg.column1,o.column2,max(olg.date) from --You can add other columns as well
order_status_log olg
join orders o
on olg.id = o.order_id
group by olg.column1,o.column2
Related
Consider the following DB structure
customer (id)
invoice (id, customer_id)
invoice_item (id, invoice_id, warranty_expiry)
I need to select all customers, where all their items are expired. Here is what I have so far
select * from customer c
inner join invoice i on c.id = i.customer_id
inner join invoice_item ii on i.id = ii.invoice_id
where ii.warranty_expiry < NOW()
group by c.id
having COUNT(ii.id) // <---
It feels like I should put something in HAVING clause, but I don't have an exact count of items for each client.
You can indeed use a having clause to ensure that the given customer has all their items expired. This works by moving the check on warranty_expiry from the where clause to the having clause, as follows:
select c.id
from customer c
inner join invoice i on c.id = i.customer_id
inner join invoice_item ii on i.id = ii.invoice_id
group by c.id
having max(ii.warranty_expiry >= NOW()) = 0
Note that select * and group by do not go along well (although older versions of MySQL do allow it by default). You should enumerate the columns that you want to retain in the select clause and in the group by clause.
You can simplify the query, because you don't need the customer table. Then I would go for:
select i.customer_id
from invoice i join
invoice_item ii
on i.id = ii.invoice_id
group by i.customer_id
having max(ii.waranty_expiry) < now();
This assumes that warnty_expiry is not null. If that is possible, then:
having max(ii.waranty_expiry) < now() and sum(ii.waranty_expiry is null) = 0;
I'm trying to do a right join in MySQL like so:
SELECT customers.id,customers.firstname,customers.lastname,customers.email,orders.time,orders.notes,pendings.date_updated,pendings.issue,appointments.closed,appointments.job_description,backup_plans.expiration FROM customers
RIGHT JOIN orders
ON customers.id = orders.customer_id
ORDER BY orders.time DESC LIMIT 1
RIGHT JOIN pendings
ON customers.id = pendings.customer_id
ORDER BY pendings.date_updated DESC LIMIT 1
RIGHT JOIN appointments
ON customers.id = appointments.customer_id
ORDER BY appointments.closed DESC LIMIT 1
RIGHT JOIN backup_plans
ON customers.id = backup_plans.customer_id
ORDER BY backup_plans.expiration DESC LIMIT 1
My intent is this: to select customers' name and email, along with the most recent order, pending, appointment, and backup plan exploration. When I execute this I get a syntax error:
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RIGHT JOIN pendings
ON customers.id = pendings.customer_id
ORDER BY pendings.d' at line 5
I'm unfamiliar with joins and would appreciate any help.
EDIT 1:
It seems that I need to make a subquery per DanK's suggestion like so:
SELECT customers.id,customers.firstname,customers.lastname,customers.email,orderstmp.time,orderstmp.notes FROM customers
RIGHT JOIN (
SELECT orders.time,orders.notes,orders.customer_id FROM orders ORDER BY orders.time DESC LIMIT 1
) as orderstmp ON orderstmp.customer_id = customers.id
But when I do this, I only get one row result, whereas I want all the customer information.
EDIT 2:
Per Tom H's suggestion, I've built this query:
SELECT
customers.id,
SQ_O.time,
SQ_O.notes
FROM customers
LEFT JOIN (
SELECT
customers.id,
orders.time,
orders.notes
FROM customers
LEFT JOIN orders ON orders.customer_id = customers.id
ORDER BY orders.time DESC LIMIT 1
) AS SQ_O ON SQ_O.id = customers.id
which has all blank time and notes fields
and
SELECT
customers.id,
O1.time,
O1.notes
FROM customers
LEFT JOIN orders AS O1 ON O1.customer_id = O1.id
LEFT JOIN orders AS O2 ON O2.customer_id = customers.id AND O2.time > O1.time WHERE O2.customer_id IS NULL
Which reaches max execution time. I'm guessing this is due to my lack of familiarity with what's possible in MySQL in comparison to other dialects.
I also tried Correlated subqueries like this:
SELECT
customers.firstname,
customers.lastname,
customers.email,
(
SELECT CONCAT(orders.time,': ',orders.notes)
FROM orders
WHERE orders.customer_id = customers.id
ORDER BY orders.time DESC LIMIT 1
) as last_order
FROM customers
But the "last_order" column comes up blank.
FINAL, DISAPPOINTING EDIT
After trying a number of really stellar suggestions that helped me learn SQL significantly, I decided to write a PHP script to get me what I want. The project's under a bit of a deadline so whatever works, works. Thanks everyone!
You can only have one ORDER BY statement per query. You can of course use subqueries and refer to a result set as a virtual table but ultimately in a single SELECT you can only have one ORDER BY.
For instance:
SELECT something
FROM table
ORDER BY something -- One order By
With a subquery as a virtual table:
SELECT something
FROM (SELECT anotherthing, something
FROM table
ORDER BY anotherthing) -- this is an order by in a separate select statement..
ORDER BY something -- still only one Order by
------EDIT--------
For assistance with your join syntax, try something like this:
SELECT --fields,
FROM customers
RIGHT JOIN orders ON customers.id = orders.customer_id
RIGHT JOIN pendings ON customers.id = pendings.customer_id
RIGHT JOIN appointments ON customers.id = appointments.customer_id
RIGHT JOIN backup_plans ON customers.id = backup_plans.customer_id
ORDER BY orders.time DESC, pendings.date_updated DESC, appointments.closed DESC, backup_plans.expiration DESC
LIMIT 1
Try this:
SELECT customers.id,customers.firstname,customers.lastname,customers.email,orders.time,orders.notes,pendings.date_updated,pendings.issue,appointments.closed,appointments.job_description,backup_plans.expiration FROM customers
RIGHT JOIN orders
ON customers.id = orders.customer_id
RIGHT JOIN pendings
ON customers.id = pendings.customer_id
RIGHT JOIN appointments
ON customers.id = appointments.customer_id
RIGHT JOIN backup_plans
ON customers.id = backup_plans.customer_id
ORDER BY orders.time DESC, pendings.date_updated DESC, appointments.closed DESC, backup_plans.expiration DESC LIMIT 1
You can accomplish this through subqueries or with additional JOINs. Here's an example of each. (NOTE: I use SQL Server, so it's possible that some of the syntax that I'm used to isn't supported in the same way in MySQL). I'm only doing these example with the Orders, but hopefully you can extend the ideas to the other tables.
Using subqueries:
SELECT
C.id,
SQ_O.time,
SQ_O.notes
FROM
Customers C
LEFT OUTER JOIN
(
SELECT
C2.Customer_ID,
O.time,
O.notes
FROM
Customers C2
LEFT OUTER JOIN Orders O ON O.customer_id = C2.id
ORDER BY
O.time DESC LIMIT 1
) SQ_O ON SQ_O.customer_id = C.id
Using multiple JOINs:
SELECT
C.id,
O1.time,
O1.notes
FROM
Customers C
LEFT OUTER JOIN Orders O1 ON O1.customer_id = C.id
LEFT OUTER JOIN Orders O2 ON O2.customer_id = C.id AND O2.time > O1.time
WHERE
O2.customer_id IS NULL -- Basically we're excluding any rows where another order was found with a later time than O1
If exact matches in Orders.time are possible than you'll need additional criteria on which one to choose.
As long as you can rely on no customer having their two most recent orders having the same time, this should work:
SELECT c.firstname, c.lastname, c.email, o.*
FROM customers AS c
LEFT JOIN (
SELECT customer_id, MAX(`time`) AS maxTime
FROM orders
GROUP BY customer_id
) AS lastO ON c.id = lastO.customer_id
LEFT JOIN orders AS o
ON lastO.customer_id = o.customer_id
AND lastO.maxTime = o.`time`
;
As long as the other tables can also be relied upon to have only one MAX value per customer, you should be able to append similar JOINs for them. The issue with multiple of the same "last" time\date_updated\closed\etc.. for a customer is that they will multiply results. For example, pairs of the same time in orders and pairs of date_updated in pending on the same customer will result in 4 rows instead of two as every "last" row for that customer in orders is paired up with every "last" row in pending.
I have three tables, libraryitems, copies and loans.
A libraryitem hasMany copies, and a copy hasMany loans.
I'm trying to get the latest loan entry for a copy only; The query below returns all loans for a given copy.
SELECT
libraryitems.title,
copies.id,
copies.qruuid,
loans.id AS loanid,
loans.status,
loans.byname,
loans.byemail,
loans.createdAt
FROM copies
INNER JOIN libraryitems ON copies.libraryitemid = libraryitems.id AND libraryitems.deletedAt IS NULL
LEFT OUTER JOIN loans ON copies.id = loans.copyid
WHERE copies.libraryitemid = 1
ORDER BY copies.id ASC, loans.createdAt DESC
I know there needs to be a sub select of some description in here, but struggling to get the correct syntax. How do I only return the latest, i.e MAX(loans.createdAt) row for each distinct copy? Just using group by copies.id returns the earliest, rather than latest entry.
Image example below:
in the subquery , getting maximum created time for a loan i.e. latest entry and joining back with loans to get other details.
SELECT
T.title,
T.id,
T.qruuid,
loans.id AS loanid,
loans.status,
loans.byname,
loans.byemail,
loans.createdAt
FROM
(
SELECT C.id, C.qruuid, L.title, MAX(LN.createdAt) as maxCreatedTime
FROM Copies C
INNER JOIN libraryitems L ON C.libraryitemid = L.id
AND L.deletedAt IS NULL
LEFT OUTER JOIN loans LN ON C.id = LN.copyid
GROUP BY C.id, C.qruuid, L.title) T
JOIN loans ON T.id = loans.copyid
AND T.maxCreatedTime = loans.createdAt
A self left join on loans table will give you latest loan of a copy, you may join the query to the other tables to fetch the desired output.
select * from loans A
left outer join loans B
on A.copyid = B.copyid and A.createdAt < B.createdAt
where B.createdAt is null;
This is your query with one simple modification -- table aliases to make it clearer.
SELECT li.title, c.id, c.qruuid,
l.id AS loanid, l.status, l.byname, l.byemail, l.createdAt
FROM copies c INNER JOIN
libraryitems li
ON c.libraryitemid = li.id AND
li.deletedAt IS NULL LEFT JOIN
loans l
ON c.id = l.copyid
WHERE c.libraryitemid = 1
ORDER BY c.id ASC, l.createdAt DESC ;
With this as a beginning let's think about what you need. You want the load with the latest createdAt date for each c.id. You can get this information with a subquery:
select l.copyid, max(createdAt)
from loans
group by l.copyId
Now, you just need to join this information back in:
SELECT li.title, c.id, c.qruuid,
l.id AS loanid, l.status, l.byname, l.byemail, l.createdAt
FROM copies c INNER JOIN
libraryitems li
ON c.libraryitemid = li.id AND
li.deletedAt IS NULL LEFT JOIN
loans l
ON c.id = l.copyid LEFT JOIN
(SELECT l.copyid, max(l.createdAt) as maxca
FROM loans
GROUP BY l.copyid
) lmax
ON l.copyId = lmax.copyId and l.createdAt = lmax.maxca
WHERE c.libraryitemid = 1
ORDER BY c.id ASC, l.createdAt DESC ;
This should give you the most recent record. And, the use of left join should keep all copies, even those that have never been leant.
I have 2 tables, restaurants and orders, orders table have restaurant_id, status and date fields, each day is a separate line in orders table.
I need to filter the restaurants and show only the restaurants that have at least one reservation for some date range, but I have to show it on a calendar, it should look like a list of restaurants with information.
Smth like this
So, it means that if at least one day with reserved status condition is satisfied in that date range, all the orders for that date range for that restaurant should be fetched as well.
I can not make a usual inner join,
SELECT r.`id`, r.`name`, o.`date`, o.`status`
FROM restaurants r
INNER JOIN orders o ON r.id = o.restaurant_id AND o.date BETWEEN '2013-08-10' AND '2013-08-31'
AND status = 'reserved'
because, in this case, for example, I will not have the order information for August 31, for restaurant2, because though it is in date rage from 10 - 31, but its status is 'closed'.
So, I am thinking to make a left join like this
SELECT r.`id`, r.`name`, o.`date`, o.`status`, o.`id` order_id
FROM restaurants r
LEFT JOIN orders o ON r.id = o.restaurant_id AND
o.date BETWEEN '2013-08-10' AND '2013-08-31'
WHERE o.`id` IS NOT NULL
But I need to also add one condition to ensure that there is at least one row with order's status = 'reserved', I tried to add where clause like COUNT(o.status = 1) > 0 but it gives an error.
THanks
I think the best way to achieve this (assuming things like 'N/A' and 'reserved' are stored in the orders table is to join to all orders in the first instance, and then use a second join to limit it to only restaurants that have a reservation within that period:
SELECT r.`id`, r.`name`, o.`date`, o.`status`, o.`id` order_id
FROM restaurants r
INNER JOIN orders o
ON r.id = o.restaurant_id
AND o.date BETWEEN '2013-08-10' AND '2013-08-31'
INNER JOIN
( SELECT DISTINCT o2.Restaurant_ID
FROM orders o2
WHERE o2.date BETWEEN '2013-08-10' AND '2013-08-31'
AND o2.Status = 'Reserved'
) o2
ON r.id = o2.restaurant_id;
Example on SQL Fiddle
I am having to set up a query that retrieves the last comment made on a customer, if no one has commented on them for more than 4 weeks. I can make it work using the query below, but for some reason the comment column won't display the latest record. Instead it displays the oldest, however the date shows the newest. It may just be because I'm a noob at SQL, but what exactly am I doing wrong here?
SELECT DISTINCT
customerid, id, customername, user, MAX(date) AS 'maxdate', comment
FROM comments
WHERE customerid IN
(SELECT DISTINCT id FROM customers WHERE pastdue='1' AND hubarea='1')
AND customerid NOT IN
(SELECT DISTINCT customerid FROM comments WHERE DATEDIFF(NOW(), date) <= 27)
GROUP BY customerid
ORDER BY maxdate
The first "WHERE" clause is just ensuring that it shows only customers from a specific area, and that they are "past due enabled". The second makes sure that the customer has not been commented on within the last 27 days. It's grouped by customerid, because that is the number that is associated with each individual customer. When I get the results, everything is right except for the comment column...any ideas?
Join much better to nested query so you use the join instead of nested query
Join increase your speed
this query resolve your problem.
SELECT DISTINCT
customerid,id, customername, user, MAX(date) AS 'maxdate', comment
FROM comments inner join customers on comments.customerid = customers.id
WHERE comments.pastdue='1' AND comments.hubarea='1' AND DATEDIFF(NOW(), comments.date) <= 27
GROUP BY customerid
ORDER BY maxdate
I think this might probably do what you are trying to achieve. If you can execute it and maybe report back if it does or not, i can probably tweak it if needed. Logically, it ' should' work - IF i have understood ur problem correctly :)
SELECT X.customerid, X.maxdate, co.id, c.customername, co.user, co.comment
FROM
(SELECT customerid, MAX(date) AS 'maxdate'
FROM comments cm
INNER JOIN customers cu ON cu.id = cm.customerid
WHERE cu.pastdue='1'
AND cu.hubarea='1'
AND DATEDIFF(NOW(), cm.date) <= 27)
GROUP BY customerid) X
INNER JOIN comments co ON X.customerid = co.customerid and X.maxdate = co.date
INNER JOIN customer c ON X.customerid = c.id
ORDER BY X.maxdate
You need to have subquery for each case.
SELECT a.*
FROM comments a
INNER JOIN
(
SELECT customerID, max(`date`) maxDate
FROM comments
GROUP BY customerID
) b ON a.customerID = b.customerID AND
a.`date` = b.maxDate
INNER JOIN
(
SELECT DISTINCT ID
FROM customers
WHERE pastdue = 1 AND hubarea = 1
) c ON c.ID = a.customerID
LEFT JOIN
(
SELECT DISTINCT customerid
FROM comments
WHERE DATEDIFF(NOW(), date) <= 27
) d ON a.customerID = d.customerID
WHERE d.customerID IS NULL
The first join gets the latest record for each customer.
The second join shows only customers from a specific area, and that they are "past due enabled".
The third join, which uses LEFT JOIN, select all customers that has not been commented on within the last 27 days. In this case,only records without on the list are selected because of the condition d.customerID IS NULL.
But tomake your query shorter, if the customers table has already unique records for customer, then you don't need to have subquery on it.Directly join the table and put the condition on the WHERE clause.
SELECT a.*
FROM comments a
INNER JOIN
(
SELECT customerID, max(`date`) maxDate
FROM comments
GROUP BY customerID
) b ON a.customerID = b.customerID AND
a.`date` = b.maxDate
INNER JOIN customers c
ON c.ID = a.customerID
LEFT JOIN
(
SELECT DISTINCT customerid
FROM comments
WHERE DATEDIFF(NOW(), date) <= 27
) d ON a.customerID = d.customerID
WHERE d.customerID IS NULL AND
c.pastdue = 1 AND
c.hubarea = 1
Two of your table columns are not contained in either an aggregate function or the GROUP BY clause. for example suppose that you have two data rows with the same customer id and same date, but with different comment data. how SQL should aggregate these two rows? :( it will generate an error...
try this
select customerid, id, customername, user,date, comment from(
select customerid, id, customername, user,date, comment,
#rank := IF(#current_customer = id, #rank+ 1, 1),
#current_customer := id
from comments
where customerid IN
(SELECT DISTINCT id FROM customers WHERE pastdue='1' AND hubarea='1')
AND customerid NOT IN
(SELECT DISTINCT customerid FROM comments WHERE DATEDIFF(NOW(), date) <= 27)
order by customerid, maxdate desc
) where rank <= 1