Dynamic Average Calculations Against Partial Amounts of Distinct Groups - mysql

We have a table representing purchases of a particular SKU. Let's say it has 4 columns, PurchaseNo, SKU, QTY and PRICE.
The QTY column represents the number of an SKU involved in each particular purchase. The PRICE column represents the dollar value paid for each unit on that particular purchase.
We need a query to determine the average PRICE of all currently “in stock” AG1TBA.
Let us assume that the QTY currently in stock is known to be 200. Let's also assume the top 3 rows of this table have the following QTY and PRICE information: 50, 22 | 120, 21 | 96, 25 |.
Since we only have 200 in stock, we somehow need the query to calculate the average from all 50 (at $22), all 120 (at $21) and 30 of the 96 (at $25). Obviously, the average price here is $21.85, but how could the query dynamically modify itself to limit the calculation of the average values to a specific amount of the the previous transactions (in this case, 200).
Here is an image of this hypothetical table:

Assuming the name of your table was endurion, you could solve it with following statements:
SET #stock := 200.0;
SET #amount := 0;
SELECT
SUM(e1.costs)/#stock as average_price_per_unit
FROM
(
SELECT
e.PurchaseNo,
e.SKU,
e.QTY,
#amount := #amount + e.QTY,
CASE
WHEN #amount <= #stock THEN e.QTY
ELSE e.QTY + #stock - #amount
END as part,
e.PRICE,
CASE
WHEN #amount <= #stock THEN e.QTY * e.PRICE
ELSE (e.QTY + #stock - #amount) * e.PRICE
END as costs
FROM (
SELECT
*
FROM
endurion e1 -- replace your table name here
WHERE
SKU = 'AG1TBA'
ORDER BY PurchaseNo DESC
) AS e
WHERE
#amount <= #stock
) as e1;
Explanation:
The innermost Subselect
SELECT
*
FROM
endurion e1
WHERE
SKU = 'AG1TBA'
ORDER BY
PurchaseNo DESC
ensures we match the latest purchases, because as you stated those will give us the current stock.
The outermost query will give us the average price per unit in stock. That should be obvious.
The outer subquery calculates the total of units by purchases. If this is greater than the stock, then only a part contributes to the stock. This part is calculated in the case statement and the second one calculates the total price for this units.
Hint
The complete query works for my installation of MySQL v5.6.16 but not in the v5.5.32 of the current sqlfiddle. I would consider to insert the result of
SELECT
e.PurchaseNo,
e.SKU,
e.QTY,
#amount := #amount + e.QTY,
CASE
WHEN #amount <= #stock THEN e.QTY
ELSE e.QTY + #stock - #amount
END AS part,
e.PRICE,
CASE
WHEN #amount <= #stock THEN e.QTY * e.PRICE
ELSE (e.QTY + #stock - #amount) * e.PRICE
END AS costs
FROM (
SELECT
*
FROM
endurion e1 -- replace your table name here
WHERE
SKU = 'AG1TBA'
ORDER BY PurchaseNo DESC
) AS e
WHERE
#amount <= #stock
into a temporary table and calculate the average by this temporary table.

Related

SQL - Percentiles

I have one table:
country(ID, city, freg, counts, date)
I want to calculate the 90th percentile of counts in a specific interval of dates ($min and $max).
I've already did the same but with the average (code below):
SELECT
AVG(counts)
FROM country
WHERE date>= #min AND date < #max
;
How can I calculate the 90th percentile instead of the average?
Finally, something GROUP_CONCAT is good for...
SELECT SUBSTRING_INDEX(
SUBSTRING_INDEX(
GROUP_CONCAT(ct.ctdivol ORDER BY ct.ctdivol SEPARATOR ','),',',90/100 * COUNT(*) + 1
),',',-1
) `90th Percentile`
FROM ct
JOIN exam e
ON e.examid = ct.examid
AND e.date BETWEEN #min AND #max
WHERE e.modality = 'ct';
It appears doing it with a single query is not possible. At least not in MySQL.
You can do it in multiple queries:
1) Select how many rows satisfy your condition.
SELECT
COUNT(*)
FROM exam
INNER JOIN ct on exam.examID = ct.examID AND ct.ctdivol_mGy > 0
WHERE exam.modality = 'CT'
AND exam.date >= #min AND exam.date < #max
2) Check the percentile threshold by multiplying the number of rows by percentile/100. For example:
Number of rows in previous count: 200
Percentile: 90%
Number of rows to threshold: 200 * (90/100) = 180
3) Repeat the query, order by the value you want the percentile from and LIMIT the result to the only row number you found in the 2nd point. Like so:
SELECT
ct.ctdivol_mGy
FROM exam
INNER JOIN ct on exam.examID = ct.examID AND ct.ctdivol_mGy > 0
WHERE exam.modality = 'CT'
AND exam.date >= #min AND exam.date < #max
ORDER BY ct.ctdivol_mGy
LIMIT 1 OFFSET 179 --> Take 1 row after 179 rows, so our 180th we need
You'll get the 180th value of the selected rows, so the 90th percentile you need.
Hope this helps!

Limit query by sum of values

I have a mysql table, transactions, with fields id, transactionType, quantity, price etc. I want to limit records by the cumulative sum of one column. So I want to pull out all the transactions until the cumulative quantity reaches my variable (here <=50).
What am I doing wrong?
SET #qsum := 0;
SELECT *
FROM (
SELECT *, (#qsum := #qsum + quantity) AS cumulative_quantity
FROM transactions ORDER BY id DESC
) transactions
WHERE
transactionType = 'buy'
AND typeID = 10
AND cumulative_quantity <= 50
Try this way
SET #qsum := 0;
SELECT *
FROM (
SELECT *, (#qsum := #qsum + quantity) AS cumulative_quantity
FROM transactions
WHERE transactionType = 'buy'
AND typeID = 10
AND cumulative_quantity <= 50
ORDER BY id DESC
) transaction

calculate the differences between two rows in SQL

I have a SQL table, one row is the revenue in the specific day, and I want to add a new column in the table, the value is the incremental (could be positive or negative) revenue between a specific day and the previous day, and wondering how to implement by SQL?
Here is an example,
original table,
...
Day1 100
Day2 200
Day3 150
...
new table (add incremental column at the end, and for first column, could assign zero),
Day1 100 0
Day2 200 100
Day3 150 -50
I am using MySQL/MySQL Workbench.
thanks in advance,
Lin
SELECT a.day, a.revenue , a.revenue-COALESCE(b.revenue,0) as previous_day_rev
FROM DailyRevenue a
LEFT JOIN DailyRevenue b on a.day=b.day-1
the query assume that each day has one record in the table. If there could be more than 1 row for each day you need to create a view that sums up all days grouping by day.
If you're okay with re-ordering the columns slightly, something like this is pretty simple to understand:
SET #prev := 0;
SELECT day, revenue - #prev AS diff, #prev := revenue AS revenue
FROM revenue ORDER BY day ASC;
The trick is that we calculate the difference to the previous first, then set the previous to the current and display it as the current in one step.
Note, this depends on the order being correct since the calculations are done during the returning of the rows, so you need to make sure you have an ORDER BY clause that returns the days in the correct order.
Try;
select
t.date_col, t.val_col,
case when t1.val_col is null then 0
else t.val_col - t1.val_col end diff
from (
select t.* , #r := #r + 1 lev
from tbl t,
(select #r := 0) r
order by t.date_col
) t
left join (
select t.* , #r1 := #r1 + 1 lev
from tbl t,
(select #r1 := 1) r
order by t.date_col
) t1
on t.lev = t1.lev
This will calculate value diff even if there is a missing date

MySQL query to get count of current month and last month to get percentage of growth

What im trying to achieve is to get the current count and last month count so i can do a formula to get the growth percentage
(CountCurrent - CountLastMonth) / CountLastMonth
My table has the following fields
activity, upload_date
This is the query im trying now.
SELECT
Y.CurrentMonth, Y.CountCurrent,
Z.LastMonth, Z.CountLastMonth
FROM
(SELECT
upload_date,
activity,
DATE_FORMAT(upload_date,'%M %Y') AS CurrentMonth,
COUNT(activity) AS CountCurrent
FROM appmaster
WHERE activity = 'com.google.test'
GROUP BY DATE_FORMAT(upload_date,'%m%y'))
Y INNER JOIN
(SELECT
activity,
DATE_FORMAT(upload_date,'%M %Y') AS CurrentMonth2,
DATE_FORMAT(upload_date - INTERVAL 1 MONTH,'%M %Y') AS LastMonth,
COUNT(activity) AS CountLastMonth
FROM appmaster
WHERE activity = 'com.google.test'
GROUP BY DATE_FORMAT(upload_date - INTERVAL 1 MONTH,'%m%y'))
Z ON Z.CurrentMonth2 = Y.CurrentMonth
GROUP BY DATE_FORMAT(upload_date,'%Y%m')
ORDER BY DATE_FORMAT(upload_date,'%Y%m')
My CurrentMonth, CountCurrent and LastMonth work fine. But the CountLastMonth is coming out the same as CountCurrent.
I was trying this before and it would give me everything but the CountLastMonth
SELECT b.CurrentMonth, sum(b.CountCurrent), b.LastMonth
FROM (SELECT DATE(a.upload_date - INTERVAL 1 MONTH) AS LastMonth, DATE(a.upload_date) AS CurrentMonth, COUNT(a.activity) AS CountCurrent
FROM appmaster a WHERE a.activity = 'com.google.android.googlequicksearchbox'
group BY MONTH(a.upload_date)) AS b
group BY MONTH(b.CurrentMonth)
No temporary table required:
SELECT
a.ym,
CASE #totals
WHEN 0 THEN 0
ELSE (a.totals - #totals) / #totals
END increment,
#totals := a.totals totals
FROM
(
SELECT
EXTRACT(YEAR_MONTH FROM upload_date) ym,
COUNT(1) AS totals
FROM
appmaster
WHERE
activity = 'com.google.test'
GROUP BY ym -- no ORDER BY required
) a
CROSS JOIN (SELECT #totals := 0) x
Maybe there's a simpler way to do this, using a little trick with user variables.
First, you need to write a query that groups your data by month; I'll store it in a temp table to ease things a bit:
drop table if exists temp_count;
create temporary table temp_count
select last_day(upload_date) as month -- A little trick to get
-- the last day of the month
, count(activity) as count_current
-- Add any other fields or expressions you need
from app_master
-- Add the needed joins
-- Include any WHERE conditions here
group by last_day(upload_date);
-- Let's add an index to this temp table... add any indexes you may need
alter table temp_count
add index idx_month(month);
And now, let's use this temp table to get what you need:
select a.month
, #count_last as count_last -- This is the value of the user variable
-- before reassigning it
, (a.count_current - #count_last) / #count_last as increment
, #count_last := a.count_current -- Here the variable is updated with the
-- current value
from
( -- This subquery is used to initialize the user variable
select #count_last := 0
) as init
, temp_count as a
-- It's important to order the data, otherwise God knows what may happen ;)
order by a.month;
Hope this helps
#Rental number Growth per month -- This is the example for monthly growth where "Total count of activity per month" is concerned -- This answer was developed based on Barranka answer and credit goes to him.
SELECT
a.YM,
CASE #Num_of_Rentals
WHEN 0 THEN 0
ELSE (a.Num_of_Rentals - #Num_of_Rentals) / #Num_of_Rentals
END increment,
#Num_of_Rentals := a.Num_of_Rentals Num_of_Rentals
FROM
(
SELECT
LEFT(rental_date,7) YM,
COUNT(rental_id) AS Num_of_Rentals
FROM
rental
GROUP BY ym -- no ORDER BY required
) a
CROSS JOIN (SELECT #Num_of_Rentals := 0) x;
-- OR
SELECT
a.YM,
CASE #Num_of_Rentals
WHEN 0 THEN 0
ELSE (a.Num_of_Rentals - #Num_of_Rentals) / #Num_of_Rentals
END increment,
#Num_of_Rentals := a.Num_of_Rentals Num_of_Rentals
FROM
(
SELECT
LEFT(rental_date,7) YM,
COUNT(1) AS Num_of_Rentals
FROM
rental
GROUP BY ym -- no ORDER BY required
) a
CROSS JOIN (SELECT #Num_of_Rentals := 0) x;
Revenue growth over each month - This is based on the answer provided by Barranka but it is suitable for Monthly revenue growth:
SELECT
a.YM,
CASE #Revenue
WHEN 0 THEN 0
ELSE (a.Revenue - #Revenue) / #Revenue
END Increment,
#Revenue := a.Revenue Revenue
FROM
(
SELECT
LEFT(payment_date, 7) YM,
SUM(amount) AS Revenue -- Toatl is SUM of Amount
FROM
payment
GROUP BY YM -- no ORDER BY required
) a
CROSS JOIN (SELECT #Revenue := 0) x
;
SELECT
a.YM,
CASE #Revenue
WHEN 0 THEN 0
ELSE (a.Revenue - #Revenue) / #Revenue
END Increment,
#Revenue := a.Revenue Revenue
FROM
(
SELECT
EXTRACT(YEAR_MONTH FROM payment_date) YM,
SUM(amount) AS Revenue
FROM
payment
GROUP BY YM -- no ORDER BY required
) a
CROSS JOIN (SELECT #Revenue := 0) x
;

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