MySQL netting of rows based on available stock - mysql

I have a MySQL database that includes a table of orders and table of part numbers with available stock.
For example
==Orders Table==
part_number
order_date
qty
==Stock Table==
part_number
stock_qty
Assuming that available stock will be assigned to the most recent orders, how would I select rows from the orders table that don't have stock to cover them.
The orders table could have between 1.5 and 2 million records so reading the whole data set into memory and processing in PHP first is not ideal.
===Update and Final Answer===
Gordon's answer gave me enough information to get the exact result that worked for me. which I have included below.
select * from (
select o.*,s.stock_qty,(#netted_stock := if(#pn = o.part_number,#netted_stock-sumqty,s.stock_qty-sumqty)) as netted_stock
from (select o.*,
(#sumqty := if(#pt = part_number, #sumqty + qty,
if(#pt := part_number, qty, qty)
)
) as sumqty
from orders o cross join
(select #pn := -1, #sumqty := 0, #netted_stock:= 0) params
order by part_number, order_date desc
) o join
stock s
on o.part_number = s.part_number
) s
Here is the result it gives me.

You need a cumulative sum of stock per order. You can get that with variables. The rest is just simple logic:
select o.*
from (select o.*,
(#sumqty := if(#pt = part_number, #sumqty + qty,
if(#pt := part_number, qty, qty)
)
) as sumqty
from orders o cross join
(select #pn := -1, #sumqty := 0) params
order by part_number, order_date desc
) o join
stock s
on o.part_number = s.part_number
where s.stock_qty < o.sumqty;

Related

How to get top selling item every month

I want to get top selling item/product for every month. I tried to using GROUP BY function, but my problem is how to get just 1 product in every month.
SELECT MONTHNAME(date), product, SUM(quantity)
FROM mytable
GROUP BY MONTHNAME(date), product
Also, how to use row_number() over function in mysql? I think by using that I can get just 1 product per month?
This is what I want to get:
You can try below -
select MONTHNAME(`date`),product,sum(quantity) qty
from tablename a
group by MONTHNAME(`date`),product
having max(qty) in (select sum(quantity) from tablename b where MONTHNAME(a.`date`)=MONTHNAME(b.`date`) and a.product=b.product)
SELECT
month, n, product, quantity
FROM
( SELECT #prev := '', #n := 0 ) init
JOIN
( SELECT #n := if(MONTHNAME(date) != #prev, 1, #n + 1) AS n,
#prev := MONTHNAME(date),
MONTHNAME(date) AS month, product, SUM(quantity) AS quantity
FROM mytable
GROUP BY
MONTHNAME(date), product
ORDER BY
MONTHNAME(date) ASC,
SUM(quantity) DESC
) x
WHERE n <= 1
ORDER BY month, n

Aggregate Functions in MySQL

I have been running into some issues with where/when I can use aggregate functions in MySQL. If I have the following two simple tables:
Campaign Table (campaign_id, campagin_name, account, country)
Revenue Table (campaign_id, revenue, date)
I want to write a query to find the top account by revenue for each week:
I tried the following
SELECT account, SUM(revenue) as sum_rev
FROM campaign
JOIN revenue
ON c.campaign_id = r.campaign_id
WHERE revenue =
( SELECT revenue
FROM campaign
JOIN revenue
ON c.campaign_id = r.campaign_id
WHERE revenue = MAX(SUM(revenue))
)
GROUP BY week(date)
I was told this isn't correct, is the issue just the nesting of the aggregate function max and sum?
In MySQL, I think variables are the simplest way:
SELECT cr.*
FROM (SELECT cr.*,
(#rn := if(#w = concat_ws('-', yyyy, wk), #rn + 1,
if(#rn := concat_ws('-', yyyy, wk), 1, 1)
)
) as rn
FROM (SELECT c.account, year(r.date) as yyyy, week(r.date) as wk, SUM(r.revenue) as sum_rev
FROM campaign c JOIN
revenue r
ON c.campaign_id = r.campaign_id
GROUP BY c.account, year(r.date), week(r.date)
ORDER BY yyyy, wk, sum_rev DESC
) cr CROSS JOIN
(SELECT #wy := '', #rn := 0) params
) cr
WHERE rn = 1;

Finding the most rented car of each year in SQL

I have tables as follows:
Car(id, make, ....)
Deal(id,datetime,car_id,....)
I want to write a query that would return a year, and a car make for the cars that have the most deals (ie the most deal ids) and the number of deals for that car make.
I started out,
SELECT YEAR(D.datetime) AS the_year, C.make, COUNT(D.id) AS num
FROM Deal D, Car C
WHERE D.car_id=C.id
GROUP BY the_year
Unfortunately, this has returned the year and the total number of deals.
So I am thinking to create this within another table and then call MAX(tbl.num), but I am confused on the syntax.
Can somebody help me out please?
This is an interesting problem. What you are looking for is specifically called the "mode" in statistics. In MySQL, you would get this by using variables or the group_conat()/substring_index()` trick. I'll show the latter:
SELECT the_year,
substring_index(group_concat(cd.make order by num desc), ',', 1) as the_mark
FROM (SELECT YEAR(D.datetime) AS the_year, C.make, COUNT(D.id) AS num
FROM Deal D JOIN
Car C
ON D.car_id = C.id
GROUP BY the_year, c.make
) cd
GROUP BY the_year;
EDIT:
The version using variables:
SELECT the_year,
substring_index(group_concat(cd.make order by num desc), ',', 1) as the_mark
FROM (SELECT YEAR(D.datetime) AS the_year, C.make, COUNT(D.id) AS num,
#rn := if(#year = YEAR(D.datetime), #rn + 1, 1) as rn,
#year := YEAR(D.datetime)
FROM Deal D JOIN
Car C
ON D.car_id = C.id CROSS JOIN
(SELECT #year := 0, #rn := 0) vars
GROUP BY the_year, c.make
ORDER BY the_year, num DESC
) cd
WHERE rn = 1;

Complicated Query

I'm not sure if the following can be done using a mere select statement, but I have two tables (truncated with the data necessary to the problem).
Inventory Item
id int (PRIMARY)
quantity int
Stock - Contains changes in the stock of the inventory item (stock history)
id int (PRIMARY)
inventory_item_id int (FOREIGN KEY)
quantity int
created datetime
The quantity in stock is the change in stock, while the quantity in inventory item is the current quantity of that item
EVERYTHING IN THE running COLUMN WILL RETURN 0
SELECT
inventory_item.id,
(inventory_item.quantity - SUM(stock.quantity)) AS running
FROM
stock
JOIN
inventory_item ON stock.inventory_item_id = inventory_item.id
GROUP BY inventory_item.id
THE QUESTION
Now, what I would like to know is: Is it possible to select all of the dates in the stock table where the running quantity of the inventory_item ever becomes zero using a SELECT?
I know this can be done programmatically by simply selecting all of the stock data in one item, and subtracting the stock quantity individually from the current inventory item quantity, which will get the quantity before the change in stock happened. Can I do this with a SELECT?
(Updated) Assuming there will never be more than one record for a given combination of inventory_item_id and created, try:
SELECT i.id,
s.created,
i.quantity - COALESCE(SUM(s2.quantity),0) AS running
FROM inventory_item i
JOIN stock s ON s.inventory_item_id = i.id
LEFT JOIN stock s2 ON s2.inventory_item_id = i.id and s.created < s2.created
GROUP BY i.id, s.created
HAVING running=0
My take on it:
select
inventory_item_id `item`,
created `when`
from
(select
#total := CASE WHEN #curr <> inventory_item_id
THEN quantity
ELSE #total+quantity END as running_total,
inventory_item_id,
created,
#curr := inventory_item_id
from
(select #total := 0) a
(select #curr := -1) b
(select inventory_item_id, created, quantity from stock order by inventory_item_id, created asc) c
) running_total
where running_total.running_total = 0;
This one has the relative advantage of having to give only one pass to the stock table. Depending on the size and the indexes on it that may or may not be a good thing.
The most logical way to do this is with a cumulative sum. But, MySQL doesn't support that.
The clearest approach, in my opinion, is to use a correlated subquery to get the running quantity. Then it is a simple matter of a where clause to select where it is 0:
select i.*
from (select i.*,
(select SUM(i2.inventory)
from inventory i2
where i2.inventory_item_id = i.inventory_item_id and
i2.created <= i.created
) as RunningQuantity
from inventory i
) i
where RunningQuantity = 0;
I had a response similar based on a running total to be flagged found here...
You can do with MySQL #variables, but the data needs to be pre-queried and ordered by the data of activity... then set a flag on each row that causes the negative and keep only those. Something like
select
PreQuery.*
from
( select
s.id,
s.created,
#runBal := if( s.id = #lastID, #runBal - quantity, #i.quantity ) as CurBal,
#lastID := s.id as IDToCompareNextEntry
from
stock s
join inventory_item ii
on s.inventory_item_id = ii.id,
(select #lastID := -1,
#runBal := 0 ) sqlvars
order by
s.id,
s.created DESC ) PreQuery
where
PreQuery.CurBal < 0
This way, for each inventory item, it works backwards by created date (order by the created descending per ID). So, when the inventory ID changes, look to the inventory table "Quantity" field to START the tally of used stock down. If same ID as the last record processed, just use the running balance and subtract out the quantity of that stock entry.
I believe this is a simple approach to this.
SELECT inventory_item.id, stock.created
FROM inventory_item
JOIN stock ON stock.inventory_item_id = inventory_item.id
WHERE (SELECT SUM(quantity) FROM stock WHERE created <= stock.created) = 0

Partition data in Percentile range and assign different value for different Range

I have table structure as shown in below
Temp
Customer_id | sum
Now I have to create view with extra column customer_type and assign value 1 if customer lies in top 10% customer (with descending order of sum,also total number of customer may vary) and 2 if customer lies between 10%-20%, 3 if customer lies between 20%-60% and 4 if customer lies between 60%-100%. How can I do this?
I just able to extract top 10% and between 10% - 20% data but couldn't able to assign value as (source)
SELECT * FROM temp WHERE sum >= (SELECT sum FROM temp t1
WHERE(SELECT count(*) FROM temp t2 WHERE t2.sum >= t1.sum) <=
(SELECT 0.1 * count(*) FROM temp));
and (not efficient just enhance above code)
select * from temp t1
where (select count(*) from temp t2 where t2.sum>=t2.sum)
>= (select 0.1 * count(*) from temp) and (select count(*) from temp t2 where t2.sum>=t1.sum)
<= (select 0.2 * count(*) from temp);
Sample data are available at sqlfiddle.com
This should help you. You need to get row number for sum and total number of rows. I'm sure you can figure out the rest easily.
SELECT
*,
#curRow := #curRow + 1 AS row_number,
(#curRow2 := #curRow2 + 1) / c as pct_row
FROM
temp t
JOIN (SELECT #curRow := 0) r
JOIN (SELECT #curRow2 := 0) r2
join (select count(*) c from temp) s
order by
sum desc
This is based on this answer
I had solve this as like this. Thanks for #twn08 for his answer which guide me upto this.
select customer_id,sum,case
when pct_row<=0.10 then 1
when pct_row>0.10 and pct_row<=0.20 then 2
when pct_row>0.20 and pct_row<=0.60 then 3
when pct_row>0.60 then 4
end as customer_label from (
select customer_id,sum,(#curRow := #curRow+1)/c as pct_row
from temp t
jOIN (SELECT #curRow := 0) r
JOIN (SELECT #curRow2 := 0) r2
join (select count(*) c from temp) s
order by sum desc) p;
I don't know whether this is efficient method or not but work fine for small data set.