How to find MIN,MAX,FIRST,LAST in one MysQl query? - mysql

I have a table like below
products
id price date
-------------------------------
1 1 2018-04-27 12:10:15
2 2 2018-04-27 12:10:15
3 5 2018-04-27 12:10:18
4 3 2018-04-27 12:10:18
5 4 2018-04-27 12:11:25
6 3 2018-04-27 12:11:25
Note : I have to find min price,max price, starting price,ending price of every minute.
My expected output is
firstPrice lastPrice minPrice maxPrice
---------------------------------------
1 3 1 5 --> Grouped for (2018-04-27 12:10)
4 3 3 4 --> Grouped for (2018-04-27 12:11)
My query is
SELECT b.lastPrice,c.firstPrice,min(a.price) as minPrice,max(a.price) as maxPrice from products as a left join (select price as lastPrice,date from products order by date desc) as b on a.date = b.date left join (select price as firstPrice,date from products order by date asc) as c on a.date = c.date where a.date >= '2018-04-27 12:10:00'
I don't know what to do to get the expected output.

You can use the following:
SELECT
(SELECT price FROM products WHERE DATE_FORMAT(p.`date`, '%Y%m%d%H%i') = DATE_FORMAT(`date`, '%Y%m%d%H%i') ORDER BY `date` ASC, id ASC LIMIT 1) AS firstPrice,
(SELECT price FROM products WHERE DATE_FORMAT(p.`date`, '%Y%m%d%H%i') = DATE_FORMAT(`date`, '%Y%m%d%H%i') ORDER BY `date` DESC, id DESC LIMIT 1) AS lastPrice,
MIN(price) AS minPrice, MAX(price) AS maxPrice
FROM products p
GROUP BY DATE_FORMAT(`date`, '%Y%m%d%H%i')
demo: http://sqlfiddle.com/#!9/8a989/15/0

Possibly this where first and last price are acquired in sub queries using a limit clause and grouped by date.
drop table if exists t;
create table t(id int, price int , dt datetime);
insert into t values
( 1 , 1 , '2018-04-27 12:10:15'),
( 2 , 2 , '2018-04-27 12:10:15'),
( 3 , 5 , '2018-04-27 12:10:18'),
( 4 , 3 , '2018-04-27 12:10:18'),
( 5 , 4 , '2018-04-27 12:11:25'),
( 6 , 3 , '2018-04-27 12:11:25');
select concat(date(dt),' ',hour(dt), ':',minute(dt) ,':00') Timeslot,
(select price from t t1 where concat(date(t1.dt),' ',hour(t1.dt), ':',minute(t1.dt) ,':00') = concat(date(t.dt),' ',hour(t.dt), ':',minute(t.dt) ,':00') order by id limit 1) firstprice,
(select price from t t1 where concat(date(t1.dt),' ',hour(t1.dt), ':',minute(t1.dt) ,':00') = concat(date(t.dt),' ',hour(t.dt), ':',minute(t.dt) ,':00') order by id desc limit 1) lastprice,
min(price),max(price)
from t
group by concat(date(dt),' ',hour(dt), ':',minute(dt) ,':00');
+---------------------+------------+-----------+------------+------------+
| Timeslot | firstprice | lastprice | min(price) | max(price) |
+---------------------+------------+-----------+------------+------------+
| 2018-04-27 12:10:00 | 1 | 3 | 1 | 5 |
| 2018-04-27 12:11:00 | 4 | 3 | 3 | 4 |
+---------------------+------------+-----------+------------+------------+
2 rows in set (0.00 sec)

Related

How to fetch rows where count in sub query is max for multiple ids

I have a table as below
s_no medicine_id store_id
1 85 1
2 10 1
3 51 2
4 85 2
5 85 1
6 85 3
7 85 1
8 51 2
9 10 3
10 10 3
11 10 3
I want to fetch max count of medicine_id for each store.I have used below select query and could find half of the result.
SELECT store_id, MAX( mycount ) , medicine_id
FROM (
SELECT medicine_id, store_id, COUNT( medicine_id ) AS mycount
FROM medicines
GROUP BY medicine_id, store_id
) AS t
GROUP BY medicine_id, store_id
From this query i get result as below
medicine_id store_id mycount
85 1 3
85 2 1
85 3 1
51 2 2
10 1 1
10 3 3
What result i want is fetch rows where mycount is max for each medicine_id
medicine_id store_id mycount
85 1 3
51 2 2
10 3 3
There is another solution, in a way similar to Slava Rozhnev's.
Just do a LEFT JOIN to the same selection WHERE the count is higher and then take the records where the "higher" value is null
SELECT first.*
FROM (
SELECT medicine_id, store_id, COUNT(medicine_id) AS mycount
FROM medicines
GROUP BY medicine_id, store_id
) first
LEFT JOIN (
SELECT medicine_id, store_id, COUNT(medicine_id) AS mycount
FROM medicines
GROUP BY medicine_id, store_id
) second ON first.medicine_id = second.medicine_id AND first.mycount < second.mycount
WHERE second.mycount IS NULL
Returns:
+-------------+----------+---------+
| medicine_id | store_id | mycount |
+-------------+----------+---------+
| 10 | 3 | 3 |
| 51 | 2 | 2 |
| 85 | 1 | 3 |
+-------------+----------+---------+
3 rows in set (0.00 sec)
If your version of MySql is 8.0+ then you can do it with FIRST_VALUE() and MAX() window functions:
SELECT DISTINCT
medicine_id,
FIRST_VALUE(store_id) OVER (PARTITION BY medicine_id ORDER BY COUNT(*) DESC) store_id,
MAX(COUNT(*)) OVER (PARTITION BY medicine_id ORDER BY COUNT(*) DESC) AS mycount
FROM medicines
GROUP BY medicine_id, store_id
See the demo.
Results:
> medicine_id | store_id | mycount
> ----------: | -------: | ------:
> 10 | 3 | 3
> 51 | 2 | 2
> 85 | 1 | 3
The next bit a complicate query should to solve this problem:
SELECT tbl0.*
FROM (
SELECT medicine_id, store_id, COUNT(medicine_id) AS mycount
FROM medicines
GROUP BY medicine_id, store_id
) tbl0
-- join give stores with max count of medicine_id
JOIN (
-- get max count for each medicine_id
SELECT medicine_id, MAX(mycount) maxcount
FROM (
-- get medicine_id count for each store_id
SELECT medicine_id, store_id, COUNT(medicine_id) AS mycount
FROM medicines
GROUP BY medicine_id, store_id
) tbl1 group BY medicine_id
) tbl2 ON tbl0.medicine_id = tbl2.medicine_id AND tbl0.mycount = tbl2.maxcount;
Here you can try it SQLize.online
This query can be used for MySQL 5.* and MySQL 8.* as well.
Since MySQL 8.0 window function can be used for reduce query complexity

Group all rows after nth row together

I have the current table:
+----------+-------+
| salesman | sales |
+----------+-------+
| 1 | 142 |
| 2 | 120 |
| 3 | 176 |
| 4 | 140 |
| 5 | 113 |
| 6 | 137 |
| 7 | 152 |
+----------+-------+
I would like to make a query to retrieve the 3 top salesman, and an "Other" column, that would be the sum of everyone else. The expected output would be:
+----------+-------+
| salesman | sales |
+----------+-------+
| 3 | 176 |
| 7 | 152 |
| 1 | 142 |
| Others | 510 |
+----------+-------+
I am using MySQL, and I am experienced about it, but i can't imagine a way of doing this kind of GROUP BY.
A tried UNION with 2 SELECT, one for the top 3 salesman and another select for the "Others", but I couldn't figure a way of excluding the top 3 from the 2nd SELECT
You can do this by LEFT JOINing your table to a list of the top 3 salesmen, and then grouping on the COALESCEd salesman number from the top 3 table (which will be NULL if the salesman is not in the top 3).
SELECT COALESCE(top.sman, 'Others') AS saleman,
SUM(sales) AS sales
FROM test
LEFT JOIN (SELECT salesman AS sman
FROM test
ORDER BY sales DESC
LIMIT 3) top ON top.sman = test.salesman
GROUP BY saleman
ORDER BY saleman = 'Others', sales DESC
Output:
saleman sales
3 176
7 152
1 142
Others 510
Demo on dbfiddle
Using UNION, ORDER BY, LIMIT, OFFSET AND GROUP BY statements you should do the trick:
SELECT salesman, sales
FROM t
ORDER BY sales DESC LIMIT 3
UNION
SELECT 'Others', SUM(sales)
FROM (SELECT salesman, sales
FROM t
ORDER BY sales DESC LIMIT 3, 18446744073709551615) AS tt;
The big number at the end is the way to apply limit until the end of the table, as suggested here
This is a pain in MySQL:
(select salesman, count(*) as cnt
from t
group by salesman
order by count(*), salesman
limit 3
) union all
(select 'Others', count(*)
from t left join
(select salesman, count(*) as cnt
from t
group by salesman
order by count(*)
limit 3
) t3
on t3.salesman = t.salesman
where t3.salesman is null
);
This should be the fastest one if appropriate indexes are present:
(
SELECT salesman, sales
FROM t
ORDER BY sales DESC
LIMIT 3
)
UNION ALL
(
SELECT 'Other', SUM(sales) - (
SELECT SUM(sales)
FROM (
SELECT sales
FROM t
ORDER BY sales DESC
LIMIT 3
) AS top3
)
FROM t
)
ORDER BY CASE WHEN salesman = 'Other' THEN NULL ELSE sales END DESC
this will work:
select salesman,sales from tablename a where a.salesman in (3,7,1)
union all
select 'others' as others,sum(a.sales) as sum_of_others from tablename a where
a.salesman not in (3,7,1) group by others;
check https://www.db-fiddle.com/f/73GjFXL3KsZsYnN26g3rS2/0

MySQL : collect the sum of the associated values

I have three tables in database:
Table: article
id | code | name | quantity | stock_date
--------------------------------------------------
1 1dfod Article name 10 2016-04-01
Table: selling
id | client_id | selling_type_id | selling_date | selling_status
----------------------------------------------------------------
1 1 1 2016-04-02 1
2 1 1 2016-04-03 1
3 1 1 2016-04-04 1
Table: selling_detail
id | selling_id | article_id | quantity
-------------------------------------
1 1 1 2
2 1 1 3
3 1 1 1
4 2 1 3
5 3 1 1
at the end I would have a stock record for this article like this:
date | in_stock (item in stock) | out_stock (sum of item sold)
----------------------------------------------------------------------
2016-04-01 10 0
2016-04-02 0 6
2016-04-03 0 3
2016-04-04 0 1
All mysql queries to my knowledge do not give me this result.
Here is my code:
SELECT SUM(sd.quantity) out_stock, s.search_date, ifnull(ss.quantity, 0) in_stock
FROM selling_detail sd JOIN selling s ON (sd.selling_id = s.id)
LEFT JOIN shop_stock ss ON (ss.search_date = s.search_date) WHERE (sd.shop_stock_id = 1)
GROUP BY s.search_date;
SELECT date,SUM(in_stock) in_stock,SUM(out_stock) out_stock FROM
(
SELECT stock_date date,quantity in_stock,0 out_stock FROM article
UNION
SELECT selling_date,0,quantity FROM selling JOIN selling_detail ON selling_detail.selling_id = selling.id
) x
GROUP BY date;
As you are trying to combine similar data from two very different tables, you'll probably be staring down the barrel of a UNION ALL.
Something along these lines should get you started:
SELECT *
FROM (
SELECT a.stock_date `date`,
SUM(a.quantity) `in_stock (item in stock)`,
0 `out_stock (sum of item sold)`
FROM article a
WHERE a.id = :article_id
GROUP BY `date`
UNION ALL
SELECT s.selling_date,
0,
SUM(sd.quantity)
FROM selling s
JOIN selling_detail sd
ON sd.selling_id = s.id
AND sd.article_id = :article_id
/* WHERE s.selling_type = ??
AND s.selling_status = ?? /* If necessary */
GROUP BY `date`
) sr
ORDER BY `date`

Getting minimum value for three columns group by a single column in a single table

I have a single table and my table structure is like this:
unique_id vendor_name price1 price2 price3 code
1 Vendor 1 0.0012 0.0014 0.0054 125
2 Vendor 2 0.0015 0.0016 0.0050 125
3 Vendor 3 0.0011 0.0019 0.0088 125
4 Vendor 1 0.0025 0.0024 0.0034 126
5 Vendor 2 0.0043 0.0019 0.0065 126
6 Vendor 3 0.0019 0.0085 0.0082 126
I have to fetch the min price for each price column group by code. And my expected output is as follows:
Code price1 price2 price3 vendor for price1 vendor for price 2 vendor for price 3
125 0.0011 0.0014 0.0050 Vendor3 Vendor1 Vendor 2
126 0.0019 0.0019 0.0034 Vendor3 Vendor2 Vendor 1
So what would be the MySQL query to fetch records like this? And also I have to make a query to fetch maximum and second highest value from the table and there may be any number of rows with single code.
My data is in this SQL Fiddle.
In the second highest value case the output should be as:
Code price1 price2 price3 vendor for price1 vendor for price 2 vendor for price 3
125 0.0012 0.0016 0.0054 Vendor1 Vendor2 Vendor 1
126 0.0025 0.0024 0.0065 Vendor1 Vendor1 Vendor 2
Here is how you can do it
SELECT
vender_prices.code,
l.price1,
r.price2,
m.price3,
l.vendor_name `Vender1`,
r.vendor_name `Vender2`,
m.vendor_name `Vender3`
FROM vender_prices
LEFT JOIN (SELECT
code, vendor_name, vender_prices.price1
FROM vender_prices
INNER JOIN (SELECT MIN(price1) AS price1 FROM vender_prices GROUP BY vender_prices.code) AS l
ON l.price1 = vender_prices.price1
GROUP BY vender_prices.code
) as l ON vender_prices.code = l.code
LEFT JOIN (SELECT
code, vendor_name, vender_prices.price2
FROM vender_prices
INNER JOIN (SELECT MIN(price2) AS price2 FROM vender_prices GROUP BY vender_prices.code) AS l
ON l.price2 = vender_prices.price2
GROUP BY vender_prices.code
) as r ON vender_prices.code = r.code
LEFT JOIN (SELECT
code, vendor_name, vender_prices.price3
FROM vender_prices
INNER JOIN (SELECT MIN(price3) AS price3 FROM vender_prices GROUP BY vender_prices.code) AS l
ON l.price3 = vender_prices.price3
GROUP BY vender_prices.code
) as m ON vender_prices.code = m.code
GROUP BY vender_prices.code
SQL Fiddle Demo
OUTPUT
| CODE | PRICE1 | PRICE2 | PRICE3 | VENDER1 | VENDER2 | VENDER3 |
--------------------------------------------------------------------
| 125 | 0.0011 | 0.0014 | 0.0050 | Vendor 3 | Vendor 1 | Vendor 2 |
| 126 | 0.0019 | 0.0019 | 0.0034 | Vendor 3 | Vendor 2 | Vendor 1 |
SELECT
data.*,
v1.vendor_name 'vendor for price1',
v2.vendor_name 'vendor for price2',
v3.vendor_name 'vendor for price3'
FROM
(
SELECT
Code,
MIN(price1) price1,
MIN(price2) price2,
MIN(price3) price3
FROM
tbl
GROUP BY Code
) data
LEFT JOIN
(
SELECT
MIN(vendor_name) vendor_name,
Code
FROM
tbl
WHERE
price1 =
(
SELECT
MIN(price1)
FROM
tbl t
WHERE
t.Code = tbl.Code
)
GROUP BY Code
) v1 ON data.Code = v1.Code
LEFT JOIN
(
SELECT
MIN(vendor_name) vendor_name
Code
FROM
tbl
WHERE
price2 =
(
SELECT
MIN(price2)
FROM
tbl t
WHERE
t.Code = tbl.Code
)
GROUP BY Code
) v2 ON data.Code = v2.Code
LEFT JOIN
(
SELECT
MIN(vendor_name) vendor_name
Code
FROM
tbl
WHERE
price3 =
(
SELECT
MIN(price3)
FROM
tbl t
WHERE
t.Code = tbl.Code
)
GROUP BY Code
) v3 ON data.Code = v3.Code
Even though the query itself looks pretty big, but the joins are repeated 3 times.
UPDATE I updated the query removing LIMIT and adding MIN(vendor_name) vendor_name instead of vendor_name.
Demo Here
OUTPUT
| CODE | PRICE1 | PRICE2 | PRICE3 | VENDOR FOR PRICE1 | VENDOR FOR PRICE2 | VENDOR FOR PRICE3 |
-----------------------------------------------------------------------------------------------
| 125 | 0.0011 | 0.0014 | 0.0050 | Vendor 3 | Vendor 1 | Vendor 2 |
| 126 | 0.0019 | 0.0019 | 0.0034 | Vendor 3 | Vendor 2 | Vendor 1 |
Try this :
select table1.vendor_name as vendor_name_for_price1,table2.vendor_name as vendor_name_for_price2, table3.vendor_name as vendor_name_for_price3
from
table table1, table table2, table table3,
(Select code, min(price1),min(price2),min(price3) from table group by code ) TMP1
where
table1.price1=min(price1) and table1.code=TMP1.code
and
table2.price2=min(price2) and table2.code=TMP2.code
and
table3.price3=min(price3) and table3.code=TMP2.code ;
This is a good practice,and I try it a brute method # first.
select 1 as wh,code,min(price1) as price,vendor_name
from(
select code,price1,vendor_name
from ForgeRock
order by code,price1
)t1
group by code
union all
select 2 as wh,code,min(price2) as price,vendor_name
from(
select code,price2,vendor_name
from ForgeRock
order by code,price2
)t1
group by code
union all
select 3 as wh,code,min(price3) as price,vendor_name
from(
select code,price3,vendor_name
from ForgeRock
order by code,price3
)t1
group by code
This is very simple and easy method without join,
and I think this is enough for using.
I add a column wh,which you can pivot the result if you want.
But however,you can't get 2nd lowest result using the code above.
There are several method you can reach this goal,
and I suggest you try making partitions like:
CREATE TABLE ForgeRock
(`unique_id` tinyint, `vendor_name` varchar(8), `price1` float(5,4)
, `price2` float(5,4), `price3` float(5,4),`code` tinyint)
PARTITION BY KEY(code)
PARTITIONS 2;
INSERT INTO ForgeRock
(`unique_id`, `vendor_name`, `price1`, `price2`, `price3`, `code`)
VALUES
(1,'Vendor 1',0.0012,0.0014,0.0054,125),
(2,'Vendor 2',0.0015,0.0016,0.0050,125),
(3,'Vendor 3',0.0011,0.0019,0.0088,125),
(4,'Vendor 1',0.0025,0.0024,0.0034,126),
(5,'Vendor 2',0.0043,0.0019,0.0065,126),
(6,'Vendor 3',0.0019,0.0085,0.0082,126);
Well this is a little tricky,but very useful when you don't have so many various values in code.
You can get the result just like:
select * from
(select code,price1 as price,vendor_name
from ForgeRock partition (p0)
order by price1
Limit 1,1
)t1
union all
select * from
(select code,price1 as price,vendor_name
from ForgeRock partition (p1)
order by price1
Limit 1,1
)t1
union all
select * from
(select code,price2 as price,vendor_name
from ForgeRock partition (p0)
order by price2
Limit 1,1
)t1
union all
select * from
(select code,price2 as price,vendor_name
from ForgeRock partition (p1)
order by price2
Limit 1,1
)t1
union all
select * from
(select code,price3 as price,vendor_name
from ForgeRock partition (p0)
order by price3
Limit 1,1
)t1
union all
select * from
(select code,price3 as price,vendor_name
from ForgeRock partition (p1)
order by price3
Limit 1,1
)t1
While I think you can improve my answer obviously,
I'll leave that job to you.

Calculate Profit Based on First-In, First-Out Pricing

Say I have purchase and sales data for some SKUs:
po_id | sku | purchase_date | price | qty
----------------------------------------------
1 | 123 | 2013-01-01 12:25 | 20.15 | 5
2 | 123 | 2013-05-01 15:45 | 17.50 | 3
3 | 123 | 2013-05-02 12:00 | 15.00 | 1
4 | 456 | 2013-06-10 16:00 | 60.00 | 7
sale_id | sku | sale_date | price | qty
------------------------------------------------
1 | 123 | 2013-01-15 11:00 | 30.00 | 1
2 | 123 | 2013-01-20 14:00 | 28.00 | 3
3 | 123 | 2013-05-10 15:00 | 25.00 | 2
4 | 456 | 2013-06-11 12:00 | 80.00 | 1
How can I find the sales margin via SQL, assuming they are sold in the order they were purchased? E.g, the margin for sku 123 is
30*1 + 28*3 + 25*2 - 20.15*5 - 17.50*1
with 2 purchased at 17.50 and 1 purchased at 15.00 left unsold.
Good question. The approach that I'm taking is to calculate the total sales. Then calculate cumulative purchases, and combine them with special logic to get the right arithmetic for the combination:
select s.sku,
(MarginPos - SUM(case when s.totalqty < p.cumeqty - p.qty then p.price * p.qty
when s.totalqty between p.cumeqty - p.qty and p.qty
then s.price * (s.totalqty - (p.cumeqty - p.qty))
else 0
end)
) as Margin
from (select s.sku, SUM(price*qty) as MarginPos, SUM(qty) as totalqty
from sales s
) s left outer join
(select p.*,
(select SUM(p.qty) from purchase p2 where p2.sku = p.sku and p2.sale_id <= p.sale_id
) as cumeqty
from purchase s
)
on s.sku = p.sku
group by s.sku, MarginPos
Note: I haven't tested this query so it might have syntax errors.
setting ambient
declare #purchased table (id int,sku int,dt date,price money,qty int)
declare #sold table (id int,sku int,dt date,price money,qty int)
insert into #purchased
values( 1 , 123 , '2013-01-01 12:25' , 20.15 , 5)
,(2 , 123 , '2013-05-01 15:45' , 17.50 , 3)
,(3 , 123 , '2013-05-02 12:00' , 15.00 , 1)
,(4 , 456 , '2013-06-10 16:00' , 60.00 , 7)
insert into #sold
values(1 , 123 , '2013-01-15 11:00' , 30.00 , 1)
,(2 , 123 , '2013-01-20 14:00' , 28.00 , 3)
,(3 , 123 , '2013-05-10 15:00' , 25.00 , 2)
,(4 , 456 , '2013-06-11 12:00' , 80.00 , 1)
a sqlserver solution should be...
with cte_sold as (select sku,sum(qty) as qty, SUM(qty*price) as total_value
from #sold
group by sku
)
,cte_purchased as (select id,sku,price,qty
from #purchased
union all select id,sku,price,qty-1 as qty
from cte_purchased
where qty>1
)
,cte_purchased_ordened as(select ROW_NUMBER() over (partition by sku order by id,qty) as buy_order
,sku
,price
,1 as qty
from cte_purchased
)
select P.sku
,S.total_value - SUM(case when P.buy_order <= S.qty then P.price else 0 end) as margin
from cte_purchased_ordened P
left outer join cte_sold S
on S.sku = P.sku
group by P.sku,S.total_value,S.qty
resultset achieved
sku margin
123 45,75
456 20,00
same result for sku 123 example in the problem description...
30*1 + 28*3 + 25*2 - 20.15*5 - 17.50*1 = 45.75
This is really horrible since it changes a MySQL variable in the queries, but it kind of works (and takes 3 statements):
select
#income := sum(price*qty) as income,
#num_bought := cast(sum(qty) as unsigned) as units
from sale
where sku = 123
;
select
#expense := sum(expense) as expense,
sum(units) as units
from (select
price * least(#num_bought, qty) as expense,
least(#num_bought, qty) as units,
#num_bought := #num_bought - least(#num_bought, qty)
from purchase
where sku = 123 and #num_bought > 0
order by po_id
) as a
;
select round(#income - #expense, 2) as profit_margin;
This is Oracle query but should work in any SQL. It is simplified and does not include all necessary calculations. You can add them yourself. You will see slightly diff totals as 17.50*3 not 17.50*1:
SELECT po_sku AS sku, po_total, sale_total, (po_total-sale_total) Margin
FROM
(
SELECT SUM(price*qty) po_total, sku po_sku
FROM stack_test
GROUP BY sku
) a,
(
SELECT SUM(price*qty) sale_total, sku sale_sku
FROM stack_test_sale
GROUP BY sku
) b
WHERE po_sku = sale_sku
/
SKU PO_TOTAL SALE_TOTAL MARGIN
---------------------------------------------------
123 168.25 164 4.25
456 420 80 340
You can also add partition by SKU if required:
SUM(price*qty) OVER (PARTITION BY sku ORDER BY sku)