Select maximum of groups - mysql

we have a table as the following which contains an order status:
history
---------------------------------
order_id status date
25 1 2014-10-01
25 3 2014-10-02
26 1 2014-10-01
27 2 2014-10-01
26 4 2014-10-03
I need to select the order_id of columns with a specified status, but it has to be the latest status.
This is possible with the group by function:
SELECT status, order_id, MAX(date)
FROM history GROUP BY order_id HAVING status = 4 OR status = 2
But this is not suitable for me, because I ONLY want the order_id, because this is a part of a subquery (SELECT ... WHERE order_id IN ([above query])). However, MySQL doesn't allow this.
Is there any alternative way to solve this?

This problem is a variation of the standard groupwise max problem, widely discussed here and elsewhere - and even with its own dedicated chapter in the mysql manual.
The standard response to the standard problem is the employment of an uncorrelated subquery, as follows...
SELECT x.*
FROM history x
JOIN
( SELECT order_id, MAX(date) max_date FROM history GROUP BY order_id ) y
ON y.order_id = x.order_id
AND y.max_date = x.date;
Your problem appears to be more complicated because of the additional requirement. However, this is a red herring. The correct answer using this method then is as follows...
SELECT x.*
FROM history x
JOIN
( SELECT order_id, MAX(date) max_date FROM history GROUP BY order_id ) y
ON y.order_id = x.order_id
AND y.max_date = x.date
WHERE status IN (2,4);

use SELF JOIN
SELECT h1.`order_id` FROM `history` h1
LEFT JOIN `history` h2
ON (h1.`order_id`= h2.`order_id` AND h1.`status` > h2.`status`)
-- WHERE h1.status IN (1,2,3) < -- here you can use your status condition
GROUP BY h1.`order_id`
Reference

Related

mysql finding the sum of subgroup maximums

If I have the following table in MySQL:
date type amount
2017-12-01 3 2
2018-01-01 1 100
2018-02-01 1 50
2018-03-01 2 2000
2018-04-01 2 4000
2018-05-01 3 2
2018-06-01 3 1
...is there a way to find the sum of the amounts corresponding to the latest dates of each type? There are guaranteed to be no duplicate dates for any given type.
The answer I'd be looking to get from the data above could broken down like this:
The latest date for type 1 is 2018-02-01, where the amount is 50;
The latest date for type 2 is 2018-04-01, where the amount is 4000;
The latest date for type 3 is 2018-06-01, where the amount is 1;
50 + 4000 + 1 = 4051
Is there a way to arrive directly at 4051 in a single query? This is for a Django project using MySQL if that makes a difference; I wasn't able to find an ORM-related solution either, so figured a raw SQL query might be a better place to start.
Thanks!
Not sure for Django but in raw sql you could use a self join to pick latest row for each type based on latest date and then aggregate your results to get the sum of amounts for each type
select sum(a.amount)
from your_table a
left join your_table b on a.type = b.type
and a.date < b.date
where b.type is null
Demo
Or
select sum(a.amount)
from your_table a
join (
select type, max(date) max_date
from your_table
group by type
) b on a.type = b.type
and a.date = b.max_date
Demo
Or by using a correlated subuery
select sum(a.amount)
from your_table a
where a.date = (
select max(date)
from your_table
where type = a.type
)
Demo
For Mysql 8 you can use window functions to get you desired result as
select sum(amount)
from (select *, row_number() over (partition by type order by date desc) as seq
from your_table
) t
where seq = 1;
Demo

Need help on MySQL query, i need to get the starting balance and the end balance by date group by stock_id

I need to get the starting balance from the earliest date and the ending balance from month end and group by stock_id.
My table:
id stock_id balance transact_at
1 1 100 2018-06-15
2 1 70 2018-06-16
3 1 30 2018-06-31
4 2 50 2018-06-01
5 2 10 2018-03-31
I want output:
stock_id start_balance ending_balance
1 100 30
2 50 10
Try this one. In this one two inner queries are fetching starting balance and closing balance by getting minimum and maximum transact_at corresponding to a stock_id and then the parent query is combing the two queries to get starting and closing balance in an single row. I have also shared fiddle link below to try.
select
tabledata1.stock_id,
startBalance,
closingBalance
from (
select
table1.stock_id,
balance as startBalance
from table1 join
(
select stock_id,
min(transact_at) as transact_at
from Table1 group by stock_id
) startTransaction
on Table1.stock_id = startTransaction.stock_id and
Table1.transact_at = startTransaction.transact_at
) tabledata1
join (
select
table1.stock_id,
balance as closingBalance
from table1 join
(
select stock_id,
max(transact_at) as transact_at
from Table1 group by stock_id
) endTransaction
on Table1.stock_id = endTransaction.stock_id
and Table1.transact_at = endTransaction.transact_at
) tabledata2
on tabledata1.stock_id = tabledata2.stock_id;
Demo
One approach in MySQL would be to aggregate by stock_id once and find the opening and closing dates. Then, self-join twice to pull in the actual balances which occurred on those opening and closing dates.
SELECT
t1.stock_id,
t2.balance AS start_balance,
t3.balance AS ending_balance
FROM
(
SELECT
stock_id,
MIN(transact_at) AS min_transact_at,
MAX(transact_at) AS max_transact_at
FROM my_table
GROUP BY stock_id
) t1
INNER JOIN my_table t2
ON t1.stock_id = t2.stock_id AND t2.transact_at = t1.min_transact_at
INNER JOIN my_table t3
ON t1.stock_id = t3.stock_id AND t3.transact_at = t1.max_transact_at;
Demo
Note: For posterity's sake, when MySQL 8+ becomes the norm, we could make use of things like ROW_NUMBER here, which might make it easier to get the result we want.
Try This One.
SELECT stock_id,MAX(balance) as start_balance, MIN(balance) as ending_balance FROM tbl_balance GROUP BY stock_id

Get values of first record and last record by date in mysql subquery

I know that for some MySQL pro, this is reasonably straightforward. I further realize that the answer could likely be figured out from other answers, however I've spent some real time trying to build this query, and I can't seem to figure out how to apply those solutions to my situation.
Mine seems different than others who want the "min and max" of a field - but I need the value from another field based on the "min and max" of the date field.
Given the following structure - a "user" table, and an "entries" table:
Data Sample (for "entries" table):
id | user_id | date | value
---+---------+--------------+-------
1 1 2018-02-01 125
2 5 2018-01-15 220
3 1 2017-12-31 131
4 4 2018-01-01 77
3 1 2017-12-15 133
I'd like to know value of the first entry (by date), the value of the last entry (by date), and the user_id.
The results should be:
user_id | first_date | first_value | last_date | last_value
--------+------------+-------------+------------+-----------
1 2017-12-15 133 2018-02-01 125
4 2018-01-01 77 2018-01-01 133
5 2018-01-15 220 2018-01-15 220
While I want the best solution, what I've been working on revolves around combining some queries like so:
SELECT user_id, l.date AS last_date, l.value AS last_value, f.date AS first_date, f.value AS first_value
FROM user AS u
LEFT JOIN (SELECT user_id, date, value FROM entries ORDER BY date ASC LIMIT 1) AS f ON f.user_id = u.user_id
LEFT JOIN (SELECT user_id, date, value FROM entries ORDER BY date DESC LIMIT 1) AS l ON l.user_id = u.user_id
NOTE: This doesn't work. If I wanted the "first entry" for someone, I would write a query that was SELECT user_id, date, value FROM entries ORDER BY date ASC LIMIT 1 - however, using it in the subqueries doesn't have the desired effect.
I've also tried some GROUP BY queries, with no success as well.
SQL Fiddle: http://sqlfiddle.com/#!9/71599
The following query gives you the expected result, but it's done without using a LEFT JOIN. So the NULL values are excluded.
SELECT
u.id AS user_id,
e1.date AS first_date,
e1.value AS first_value,
e2.date AS last_date,
e2.value AS last_value
FROM
users u,
(SELECT * FROM entries e ORDER BY date ASC) e1,
(SELECT * FROM entries e ORDER BY date DESC) e2
WHERE
e1.user_id = u.id
AND
e2.user_id = u.id
GROUP BY
u.id
And here's a working fiddle - http://sqlfiddle.com/#!9/71599/8
Also, it's worth noting that the LIMIT in your attempt would limit the results to 1 for all joined results, not each joined result. Either way, the LEFT JOIN didn't work. If anyone knows why, I'd be interested to understand.
Edit: Here's another attempt, this time utilising MIN() and MAX(), rather than ORDER BY. Unfortunately, you need to join the entries table multiple times for this to work though.
SELECT
u.id AS user_id,
e1.date AS first_date,
e1.value AS first_value,
e2.date AS last_date,
e2.value AS last_value
FROM users u
INNER JOIN entries e1 ON (u.id = e1.user_id)
INNER JOIN entries e2 ON (u.id = e2.user_id)
INNER JOIN (
SELECT user_id, MIN(date) AS date
FROM entries
GROUP BY user_id
) e3 ON (e1.user_id = e3.user_id AND e1.date = e3.date)
INNER JOIN (
SELECT user_id, MAX(date) AS date
FROM entries
GROUP BY user_id
) e4 ON (e2.user_id = e4.user_id AND e2.date = e4.date)
GROUP BY u.id
Another working fiddle: http://sqlfiddle.com/#!9/71599/18

How do optimise sql query using join between multiple tables

i have two tables having following structure
Table A
itemId categoryId orderDate
==========================================
1 23 2016-11-08
1 23 2016-11-12
1 23 2016-11-16
Table B have the structure
categoryId stock price
==========================================
23 500 600
However mine desired output should be as like
Result C
price stock orderdate qty
600 500 2016-11-08 (first order date) 3 (3 time appearance in first table)
Here is what i have tried so far
select b.price,b.stock from B b, A a
where b.categoryId = (
select a.categoryId
from A
GROUP BY categoryId
HAVING COUNT(categoryId)>1
)
and (a.orderdate = (
select MIN(orderdate)
from A
where categoryId = b.categoryId)
)
i have following result
price stock orderdate
600 500 2016-11-08
i have no idea how do find qty as it is appeared 3 times in first table.
I think you want the records in table a grouped by item id and category id, so include these two in your group by statement. Then the other columns you have to aggregate using MIN, MAX, AVG, SUM, etc. I use MIN which will give you the smallest number in the group for that particular column, although it shouldn't matter in this case whether you use MIN or MAX or AVG - it's all the same. Then COUNT(*) will just count the number of recrods in the group.
Also, joins are generally preferred over listing tables with commas.
SELECT a.itemid, a.categoryid, MIN(b.price), MIN(b.stock), min(a.orderdate), count(*) as qty
FROM a
INNER JOIN b ON a.categoryid = b.categoryid
GROUP BY a.itemid, a.categoryid
You also need to select COUNT(*)
how about use following sql
select min(price), min(stock), min(orderDate), COUNT(categoryId)
from A,B where A.categoryId = B.categoryId
GROUP by A.categoryId
You could create views for your subqueries and give them meaningful names e.g. CategoriesUsedInMultipleOrders, MostRecentOrderByCategory. This would 'optimize' you query by abstracting away complexity and making it easier for the human reader to understand.
This is the Query with the appropriate join method see Result:
SELECT B.price, B.stock, MIN( A.orderDate ) AS orderdate, COUNT( * ) AS qty
FROM TableA A, TableB B
WHERE A.categoryId = B.CategoryId
GROUP BY A.categoryId, B.price, B.stock

add a column in MySQL rank by deal by day

I am just learning MySQL. I need to find out rank of deals by day. Here I am adding the corresponding MYSQL query for my requirement that currently ranks all sales highest to lowest by day. Please help me to add a column that gives the rank to the deal highest to lowest and resetting the next day.
Here is my current working query,..
single day with title, price
SELECT
DATE(order_items.created_at) AS the_day,
order_items.deal_id,
SUM(order_items.item_total) AS daily_total,
SUM(order_items.qty) AS units_sold,
deals.price,
deals.title
FROM
order_items JOIN deals ON order_items.deal_id = deals.id
WHERE
order_items.created_at >= '2016-01-01 00:00:00' AND order_items.created_at < '2016-01-30 00:00:00'
AND
order_items.status=1
AND
order_items.paid=1
GROUP BY
order_items.deal_id
ORDER BY
the_day,
daily_total DESC;
The easiest way to do is that:
Use your existing SQL - I guess you need to update your SQL, make sure any non-aggregated columns in select should be in group by as well
Use PHP to loop (1-5), it works for multiple days
If you are happy to get top 5 for a single day, you can add limit 5 at end of your SQL
If you need top 5 results for each day in multiple days in one SQL, you need to update SQL to be more complicated. And here is a hint to use row id see example:
select increment counter in mysql
OK - Since you updated your question to return top 1 result per day, this is easier:
Step 1: get each day, each deal, report:
SELECT deal_id, date(created_at) ymd, sum(item_total) daily_total, sum(qty) units_sold
FROM order_items
WHERE substr(created_at,1,7) = '2016-01'
AND status = 1
AND paid = 1
GROUP BY 1,2
Step 2: Find the best deal of each day from step 1:
SELECT aa.ymd, max(aa.daily_total) max_total
FROM (
SELECT deal_id, date(created_at) ymd, sum(item_total) daily_total, sum(qty) units_sold
FROM order_items
WHERE substr(created_at,1,7) = '2016-01'
AND status = 1
AND paid = 1
GROUP BY 1,2
) as aa
GROUP BY 1;
Please note that max(item_total) not necessary same row as max(unit_sold), so you need to choose one, and cannot run togather
Step 3: Join step 2 with step 1 and deals to find out the rest of information:
SELECT aa.*, deals.price, deal.title
FROM (
SELECT aa.ymd, max(aa.daily_total) max_total
FROM (
SELECT deal_id, date(created_at) ymd, sum(item_total) daily_total, sum(qty) units_sold
FROM order_items
WHERE substr(created_at,1,7) = '2016-01'
AND status = 1
AND paid = 1
GROUP BY 1,2
) as aa
GROUP BY 1
) as bb
JOIN (
SELECT deal_id, date(created_at) ymd, sum(item_total) daily_total, sum(qty) units_sold
FROM order_items
WHERE substr(created_at,1,7) = '2016-01'
AND status = 1
AND paid = 1
GROUP BY 1,2
) as aa ON bb.ymd = aa.ymd and bb.max_total = aa.daily_total
JOIN deals ON aa.deal_id = deals.id
ORDER BY aa.ymd, aa.max_total