I have this situation. I have a table Orders that is related to OrderStatus.
OrderStatus
id | orderId | created
I need to retrieve the Orders with its last status. I tried this query, what I don't know if it is performant. I need to know if there are better solutions.
select Orders.id, OrderStatus.status from Orders
inner join OrderStatus on OrderStatus.id =
(select top 1 id from OrderStatus where orderId = Order.id order by created desc)
Correlated subquery is usually bad news (sometimes SQL Server can optimize it away, sometimes it acts like a really slow loop). Also not sure why you think you need DISTINCT when you're only taking the latest status, unless you don't have any primary keys...
;WITH x AS
(
SELECT o.id, os.status,
rn = ROW_NUMBER() OVER (PARTITION BY os.orderId ORDER BY created DESC)
FROM dbo.Orders AS o
INNER JOIN dbo.OrderStatus AS os
ON o.id = os.orderId
)
SELECT id, status
FROM x
WHERE rn = 1;
You can use the Row_Number function:
WITH CTE AS
(
SELECT Orders.id, OrderStatus.status,
RN = ROW_NUMBER() OVER (
PARTITION BY OrderStatus.OrderId
ORDER BY created DESC)
FROM Orders
INNER JOIN OrderStatus ON OrderStatus.OrderId = Orders.id
)
SELECT id, status
FROM CTE WHERE RN = 1
I've used a common-table-expression since it enables to filter directly and it's also very readable.
Related
I'm trying to generate a query that will do the following:
I have an offer catalog table that has [Id, term, loanamount, interestrate], for every id there are multiple offers, i.e. every customer gets multiple offers. I'm trying to retrieve a dataset with the offer for each id that has [Id, max(term), max(loanamount)] and then the interest rate for the offer with the max(term) and max(loanamount) the way the I view it is the following:
SELECT a.Id as oppId,
a.MAX(Term) as maxTerm,
a.MAX(LoanAmount) as maxAmount,
b.InterestRate
FROM table a
JOIN ( SELECT InterestRate
FROM table
WHERE Id = oppId
AND Term = maxTerm
AND LoanAmount = maxAmount) b
GROUP BY Id
This doesn't seem to work so if anyone has a way to acomplish this it would be fantastic!
You can use row_number()
select * from
(
select *,row_number() over(partition by id order by Term desc, LoanAmount desc) as rn
from tablename
)A where rn=1
If I understand correctly, you can use row_number():
select t.*
from (select t.*,
row_number() over (partition by id order by term desc, loan_amount desc) as seqnum
from t
) t
where seqnum = 1
I have two tables,
table one name (tbl_brands) with these columns:
and second table name (tbl_loader_attachment) with these columns:
I used this MySQL code below to sort by DESC:
SELECT tbl_loader_attachment.*, tbl_brands.* FROM tbl_loader_attachment
INNER JOIN tbl_brands ON(tbl_brands.b_id=tbl_loader_attachment.b_id)
GROUP BY tbl_loader_attachment.b_id ORDER BY tbl_loader_attachment.la_id DESC
When I execute my code select the first row appear (test1) I want to select the last row get (test4)
One approach uses a GROUP BY query:
SELECT tla1.*, tb.*
FROM tbl_brands tb
INNER JOIN tbl_loader_attachment tla1
ON tb.b_id = tla1.b_id
INNER JOIN
(
SELECT b_id, MAX(la_id) AS max_la_id
FROM tbl_loader_attachment
GROUP BY b_id
) tla2
ON tla1.b_id = tla2.b_id AND
tla1.la_id = tla2.max_la_id;
If you are using MySQL 8+ (or should a future reader of this question be using MySQL 8+), then another option here is to use ROW_NUMBER:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY b_id ORDER BY la_id DESC) rn
FROM tbl_loader_attachment
)
SELECT tla.*, tb.*
FROM tbl_brands tb
INNER JOIN cte tla ON tb.b_id = tla.b_id
WHERE tla.rn = 1;
I am trying this query:
SELECT * FROM heath_check where cid = '1' and eid in('3','5','7','1','6')
My table structure:
I want distinct eid but all other data as it is. For example I have two entries with an eid of 1 my query fetched both, but I want one which is in the second column.
SELECT *
FROM heath_check AS hc
INNER JOIN (
SELECT MAX(id) AS lastId
FROM heath_check
WHERE cid = '1' and eid in('3','5','7','1','6')
GROUP BY eid) AS lastIDs
ON hc.id = lastIDs.lastId
;
You need a subquery, like the above, to find the records you want for each value. If you had wanted the first ones, you could use MIN(id) instead; if you cannot count on sequential ids, it becomes much more complex with use of potentially non-unique timestamps (if they are even available).
Create a RowNumber grouped by eid and filter the RowNumber = 1 to get the expected result.
SELECT id, eid, cid,weight, s_blood_pressure
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY eid ORDER BY id DESC) AS RowNumber
FROM heath_check
WHERE cid = '1' AND eid IN ('3','5','7','1','6')
) A
WHERE RowNumber = 1
This is the answers that I want to get.But if I used MAX() function in MySQL, it just return one record.How to handle it?
A_plus ID
2 12345
2 45678
As decribed above,The SQL I used as fellow,but it just return one record.
SELECT MAX(A_plus_Num) AS A_plus, ID FROM
(SELECT COUNT(grade) AS A_plus_Num,ID FROM take WHERE grade = 'A+'GROUP BY ID) AS temp
A_plus ID
2 12345
In MySQL, the query is a bit more complicated. One method is using two subqueries with aggregation:
select t.*
from (select t.id, count(*) as A_plus
from take t
where t.grade = 'A+'
group by t.id
) t
where t.A_plus = (select max(A_plus)
from (select t.id, count(*) as a_plus
from take t
where t.grade = 'A+'
group by t.id
)
);
MySQL seems to be unable to optimise a select with a GROUP BY subquery and ends up in long execution times. There must be a known optimisation for such common scenario.
Let's assume that we're trying to return all orders from the database, with a flag indicating if it is the first order for the customer.
CREATE TABLE orders (order int, customer int, date date);
Retrieving the first orders by customer is superfast.
SELECT customer, min(order) as first_order FROM orders GROUP BY customer;
However, it becomes very slow once we join this with the full order set using a subquery
SELECT order, first_order FROM orders LEFT JOIN (
SELECT customer, min(order) as first_order FROM orders GROUP BY customer
) AS first_orders ON orders.order=first_orders.first_order;
I hope there is a simple trick that we're missing, because otherwise it would be about 1000x faster to do
CREATE TEMPORARY TABLE tmp_first_order AS
SELECT customer, min(order) as first_order FROM orders GROUP BY customer;
CREATE INDEX tmp_boost ON tmp_first_order (first_order)
SELECT order, first_order FROM orders LEFT JOIN tmp_first_order
ON orders.order=tmp_first_order.first_order;
EDIT:
Inspired by #ruakh proposed option 3, there is indeed a less ugly workaround using INNER JOIN and UNION, which has acceptable performance yet does not require temporary tables. However, it is a bit specific to our case and I am wondering if a more generic optimisation exists.
SELECT order, "YES" as first FROM orders INNER JOIN (
SELECT min(order) as first_order FROM orders GROUP BY customer
) AS first_orders_1 ON orders.order=first_orders_1.first_order
UNION
SELECT order, "NO" as first FROM orders INNER JOIN (
SELECT customer, min(order) as first_order FROM orders GROUP BY customer
) AS first_orders_2 ON first_orders_2.customer = orders.customer
AND orders.order > first_orders_2.first_order;
Here are a few things you might try:
Removing customer from the subquery's field-list, since it's not doing anything anyway:
SELECT order,
first_order
FROM orders
LEFT
JOIN ( SELECT MIN(order) AS first_order
FROM orders
GROUP
BY customer
) AS first_orders
ON orders.order = first_orders.first_order
;
Conversely, adding customer to the ON clause, so it actually does something for you:
SELECT order,
first_order
FROM orders
LEFT
JOIN ( SELECT customer,
MIN(order) AS first_order
FROM orders
GROUP
BY customer
) AS first_orders
ON orders.customer = first_orders.customer
AND orders.order = first_orders.first_order
;
Same as previous, but using an INNER JOIN instead of a LEFT JOIN, and converting your original ON clause into a CASE expression:
SELECT order,
CASE WHEN first_order = order THEN first_order END AS first_order
FROM orders
INNER
JOIN ( SELECT customer,
MIN(order) AS first_order
FROM orders
GROUP
BY customer
) AS first_orders
ON orders.customer = first_orders.customer
;
Replacing the whole JOIN approach with an uncorrelated IN-subquery in a CASE expression:
SELECT order,
CASE WHEN order IN
( SELECT MIN(order)
FROM orders
GROUP
BY customer
)
THEN order
END AS first_order
FROM orders
;
Replacing the whole JOIN approach with a correlated EXISTS-subquery in a CASE expression:
SELECT order,
CASE WHEN NOT EXISTS
( SELECT 1
FROM orders AS o2
WHERE o2.customer = o1.customer
AND o2.order < o1.order
)
THEN order
END AS first_order
FROM orders AS o1
;
(It's very likely that some of the above will actually perform worse, but I think they're all worth trying.)
I would expect this to be faster when using a variable instead of the LEFT JOIN:
SELECT
`order`,
If(#previous_customer<>(#previous_customer:=`customer`),
`order`,
NULL
) AS first_order
FROM orders
JOIN ( SELECT #previous_customer := -1 ) x
ORDER BY customer, `order`;
That's what my example on SQL Fiddle returns:
CUSTOMER ORDER FIRST_ORDER
1 1 1
1 2 (null)
1 3 (null)
2 4 4
2 5 (null)
3 6 6
4 7 7