Respecting GROUP BY Clause in a MySQL SUB-QUERY - mysql

I have a query which returns some figures from a table of sales data. The table contains data for a number of different sales managers. I need to return data for the individual managers and also some calculated figures.
One of the figures I'm trying to get at involves a subquery. Getting the figures for each individual manager is fine and works well. The problem occurs when I am trying to get a figure which involves the use of a subquery. It seems that, though the outer query uses a group by clause to separate out individual salespeople, the subquery operates on the entire set.
Sample Data
name | Amount | Sell_at | Profit
--------------------------------
Fred | 1 | 3.99 | 0.99
Joe | 2 | 10.50 | 5.00
Fred | 5 | 20.00 | 15.00
Joe | 10 | 10.00 | 6.00
Desired result:
name | Total Profit | < 50% | > 50%
------------------------------------
Fred | 75.99 | 0.99 | 75.00
Joe | 71.00 | 60 | 10
SELECT
Account_Manager,
SUM(Profit * Amount) AS 'Total Profit'
(SELECT sum(Profit * Amount) from sales WHERE Profit * Amount / (Sell_at * Amount) < 0.5) AS '< 50%',
(SELECT sum(Profit * Amount) from sales WHERE Profit * Amount / (Sell_at * Amount) > 0.5) AS '> 50%'
FROM sales WHERE Invoice_Date = 'some date' GROUP BY Account_Manager
This gives me a row for each salesperson and their profit for that day, but the sub queries return figures totaled from the entire table. I could add a clause to the subquery WHERE in order to limit the result to the same date as the outer query, but ideally what I need to do really is to get the results for each individual salesperson.
Am I on the right track or is there another way I should be approaching this?

From your sample data and desired results it appears you only need a conditional aggregate:
select Name, sum(amount * profit) as TotalProfit,
Sum(case when Profit * Amount / (Sell_at * Amount) < 0.5 then Profit * Amount end) as '<50%',
Sum(case when Profit * Amount / (Sell_at * Amount) > 0.5 then Profit * Amount end) as '>50%'
from t
group by name

The expression:
Profit * Amount / (Sell_at * Amount)
is equivalent to just:
Profit / Sell_at
Use it in a CASE expression to perform conditional aggregation:
SELECT Account_Manager,
SUM(Amount * Profit) as TotalProfit,
SUM(CASE WHEN Profit / Sell_at < 0.5 THEN Profit * Amount END) `< 50%`,
SUM(CASE WHEN Profit / Sell_at > 0.5 THEN Profit * Amount END) `> 50%`
FROM Sales
WHERE Invoice_Date = 'some date'
GROUP BY Account_Manager;
You should also check for the case that Profit / Sell_at is equal to 0.5.
See the demo.

If you want to classify the rows based on their percentile, then use window functions. Let me assume that you want to know who is above and below average:
SELECT Account_Manager,
SUM(Profit * Amount) AS Total_Profit,
(CASE WHEN SUM(Profit * Amount) > AVG(SUM(Profit * Amount)) OVER ()
THEN 'Above average'
WHEN SUM(Profit * Amount) < AVG(SUM(Profit * Amount)) OVER ()
THEN 'Below average'
ELSE 'Average'
END) as relative_position
FROM sales
WHERE Invoice_Date = 'some date'
GROUP BY Account_Manager;

Related

Refine SQL Query given list of ids

I am trying to improve this query given that it takes a while to run. The difficulty is that the data is coming from one large table and I need to aggregate a few things. First I need to define the ids that I want to get data for. Then I need to aggregate total sales. Then I need to find metrics for some individual sales. This is what the final table should look like:
ID | Product Type | % of Call Sales | % of In Person Sales | Avg Price | Avg Cost | Avg Discount
A | prod 1 | 50 | 25 | 10 | 7 | 1
A | prod 2 | 50 | 75 | 11 | 4 | 2
So % of Call Sales for each product and ID adds up to 100. The column sums to 100, not the row. Likewise for % of In Person Sales. I need to define the IDs separately because I need it to be Region Independent. Someone could make sales in Region A or Region B, but it does not matter. We want aggregate across Regions. By aggregating the subqueries and using a where clause to get the right ids, it should cut down on memory required.
IDs Query
select distinct ids from tableA as t where year>=2021 and team = 'Sales'
This should be a unique list of ids
Aggregate Call Sales and Person Sales
select ids
,sum(case when sale = 'call' then 1 else 0 end) as call_sales
,sum(case when sale = 'person' then 1 else 0 end) as person_sales
from tableA
where
ids in t.ids
group by ids
This will be as follows with the unique ids, but the total sales are from everything in that table, essentially ignoring the where clause from the first query.
ids| call_sales | person_sales
A | 100 | 50
B | 60 | 80
C | 100 | 200
Main Table as shown above
select ids
,prod_type
,cast(sum(case when sale = 'call' then 1 else 0 end)/CAST(call_sales AS DECIMAL(10, 2)) * 100 as DECIMAL(10,2)) as call_sales_percentage
,cast(sum(case when sale = 'person' then 1 else 0 end)/CAST(person_sales AS DECIMAL(10, 2)) * 100 as DECIMAL(10,2)) as person_sales_percentage
,mean(price) as price
,mean(cost) as cost
,mean(discount) as discount
from tableA as A
where
...conditions...
group by
...conditions...
You can combine the first two queries as:
select ids, sum( sale = 'call') as call_sales,
sum(sale = 'person') as person_sales
from tableA
where
ids in t.ids
group by ids
having sum(year >= 2021 and team = 'Sales') > 0;
I'm not exactly sure what the third is doing, but you can use the above as a CTE and just plug it in.

Is it possible to run calculations while aggregating in Google BigQuery

In StandardSQL, is it possible to run operations on each row during the grouping process? I'm not sure if I'm even asking the right question. Here's an example.
Let's say I have 3 rows like this:
| move_id | item_id | quantity | value |
|---------|---------|----------|-------|
| 1 | 1 | 10 | 100 |
| 1 | 2 | 20 | 150 |
| 1 | 3 | 30 | 200 |
I now want to group the table by move_id, summing values based on the proportion of each row's quantity to the minimum quantity.
For example the minimum quantity is 10, and row 2 has a quantity of 20, which means it's value should be cut in half before summing. Row 3 has a quantity of 30, which means it's value should be cut to a third before summing.
So my final value column should be 100 + (150 / 2) + (200 / 3) = 241.67.
My result should be:
| move_id | quantity | value |
|---------|----------|--------|
| 1 | 10 | 241.67 |
The query should be something like:
SELECT ANY_VALUE(move_id) AS move_id, MIN(quantity) AS quantity, SUM([THIS IS MY QUESTION, WHAT GOES HERE?]) as value FROM table GROUP BY move_id;
Is this possible?
Below is for BigQuery Standard SQL and does all in one shot
#standardSQL
SELECT move_id,
MIN(quantity) AS quantity,
SUM(value/quantity) * MIN(quantity) AS value
FROM `project.dataset.table`
GROUP BY move_id
If to apply to sample data from your question - result is
Row move_id quantity value
1 1 10 241.66666666666669
As you can see here - instead of splitting calculation/aggregation inside query, you can rather transform your formula such like below
100 + (150 / 2) + (200 / 3)
(100 * 10 / 10 + (150 * 10 / 20) + (200 * 10 / 30)
((100 / 10 + (150 / 20) + (200 / 30)) * 10
SUM(value / quantity) * MIN(quantity)
so, you ended up with just simple aggregation "in one shot"
The somewhat difficult part of your query is that you want to aggregate, but the sum you have in mind itself requires the result of an aggregation - the minimum quantity per each move_id group. One option here would be to first generate the min quantity in a CTE, then aggregate that CTE using your logic.
WITH cte AS (
SELECT *, MIN(quantity) OVER (PARTITION BY move_id) min_quantity
FROM yourTable
)
SELECT
move_id,
MIN(quantity) AS quantity,
SUM(value * min_quantity / quantity) AS value
FROM cte
GROUP BY
move_id;
Demo
Note: The above demo uses SQL Server, but the SQL used is ANSI compliant, and should also run on BigQuery without any issues.
Also, if your version of BigQuery does not support cte, then you may just inline the code contained inside the CTE as as a subquery.
In absence of CTEs, you can use Derived Table (subquery) to fetch minimum quantity for every move_id separately. And, then utilize them in the main query, to compute the sum:
SELECT t.move_id,
dt.min_quantity,
Sum(t.value / ( t.quantity / dt.min_quantity )) AS value
FROM your_table AS t
JOIN (SELECT move_id,
Min(quantity) AS min_quantity
FROM your_table
GROUP BY move_id) AS dt
ON dt.move_id = t.move_id
GROUP BY t.move_id
SQL Fiddle Demo

Mysql FIFO consumption

I have 2 tables
article_receive , which records the the receiving via following columns;
item | title |trans_id |qty|price|current_items | current_value
ADN231 | 12" Valve |jvn2333 |24 | 175 | 24 | 4200
ADN231 | 12" Valve |jvn2388 |12 | 185 | 36 | 6420
Current Items is always total of all items
Current Value is value of all transaction combined (4200 + 2220)
For Issuance , i have article_issue with following columns;
item | title | trans_id | qty
ADN231 | 12" Valve | ISU2333 | 6
ADN231 | 12" Valve | ISU2401 | 24
My requirement is , that i want to create a consumption report, which basically calculates the exact amount of items on every issuance using FIFO method.
2nd row in article_issue has items from 2 transactions and has 2 different prices. How to calculate it in MYSQL 8.0.15 Community Version.
Currently, i am using this SQL Statement;
SELECT
article_receives.id as RCVID, article_receives.item_id, article_receives.item_title, article_receives.quantity as rcv_qty,article_receives.transaction_id, article_receives.price as rate,
article_issues.id as ISUID,article_issues.quantity as isu_qty, article_issues.quantity * article_receives.price as value
FROM article_receives
LEFT JOIN article_issues ON article_receives.item_id = article_issues.item_id
ORDER BY article_receives.item_id
/** Some Column names are changed */
This gives me data in following state;
Please help me out in creating a proper consumption report in mysql.
On side note, This is an app developed in laravel 5.8 , so eloquent is also available.
Thanks
Thanks to P.Salmon provided link FIFO i was able to solve the problem.
MySql Query is :
WITH
running_purchase AS
( SELECT transaction_id, created_at, quantity, item_id, price,
SUM(quantity) OVER (PARTITION BY item_id
ORDER BY created_at, transaction_id
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW)
AS running_total
FROM article_receives
),
running_stock AS
( SELECT demand_id, created_at, quantity, item_id,
SUM(quantity) OVER (PARTITION BY item_id
ORDER BY created_at, demand_id
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW)
AS running_total
FROM article_issues
)
SELECT
s.demand_id, p.transaction_id,p.created_at as purchase_date, p.item_id, p.price, s.created_at as issue_date,
LEAST(p.running_total, s.running_total)
- GREATEST(s.running_total - s.quantity, p.running_total - p.quantity)
AS quantity
FROM running_purchase AS p
JOIN running_stock AS s
ON p.item_id = s.item_id
AND s.running_total - s.quantity < p.running_total
AND p.running_total - p.quantity < s.running_total
WHERE s.created_at BETWEEN '2019-06-01' AND DATE_ADD('2019-06-30', INTERVAL 1 DAY)
ORDER BY
p.item_id, p.created_at, p.transaction_id ;

SQL AVG of a SUM

I want to get the average of a calculated sum. I have tried the syntax from this stackoverflow answer So my SQL query looks like this:
SELECT AVG(iq.stockvalue_sum), iq.date
FROM(
SELECT CONCAT(DATE_FORMAT(s.date, '%Y'), '-01-01') as date,
SUM(GREATEST(s.stockvalue,0)) as stockvalue_sum
FROM stockvalues s
GROUP BY CONCAT(DATE_FORMAT(s.date, '%Y'), '-01-01')
) iq
However this is not giving me a correct average. I want to get the average stockvalue for each year. The idea behind the table is to save every day the stock and stockvalue for each product. So this specifiq query is to show the average stockvalue for each year it has data for.
Edit: Sample output data
Stockvalue | Year
_________________
- 205 | 2015
- 300 | 2014
Input data:
pid | val | date
______________________
- 1 | 100 | 28-04-2015
- 2 | 150 | 28-04-2015
- 1 | 80 | 27-04-2015
- 2 | 80 | 27-04-2015
....
- 1 | 100 | 29-01-2014
- 2 | 100 | 29-01-2014
- 1 | 200 | 30-01-2014
- 2 | 200 | 30-01-2014
So I need to calculate know the average of the total stockvalue. So the sum of all stockvalues for day X and the average of X
At minimum you are missing a group by in your outer query:
SELECT AVG(iq.stockvalue_sum), iq.date
FROM(
SELECT CONCAT(DATE_FORMAT(s.date, '%Y'), '-01-01') as date,
SUM(GREATEST(s.stockvalue,0)) as stockvalue_sum
FROM stockvalues s
GROUP BY CONCAT(DATE_FORMAT(s.date, '%Y'), '-01-01')
) iq
GROUP BY iq.date
However, given your inner query is returning a single year with a summed value, the average of that value would be the same. Perhaps you can clarify your intentions. Are you sure you need the inner query at all? Perhaps this is all you need?
select avg(GREATEST(stockvalue,0)), CONCAT(DATE_FORMAT(s.date, '%Y'), '-01-01') as date
from stockvalues
group by CONCAT(DATE_FORMAT(s.date, '%Y'), '-01-01')
I think you need to group your inner query by date, and your outer query by year to get the results you are after:
SELECT AVG(s.Stockvalue) AS Stockvalue
YEAR(s.Date) AS Date
FROM (
SELECT DATE(s.Date) AS Date,
SUM(GREATEST(s.stockvalue,0)) AS Stockvalue
FROM stockvalues AS s
GROUP BY DATE(s.Date)
) AS s
GROUP BY YEAR(s.Date);

MySQL query for total sales per month and payment type

Hello i have problem in Mysql query to display monthly report per payment type.
I have database table like this.
uid | invoice_num | pay_type | trans_date | total
in pay_type i use enum type, where 0=cash, 1=debit and 2=credit.
I want to display the data like this.
Trans_date | Cash | Debit | Credit | Total
2013-10-01 1000 0 500 1500
etc.
This is my current query but, it return error.
SELECT
(SELECT tgl_transaksi FROM tb_detail_transjual GROUP BY tgl_transaksi) as tanggal,
(SELECT SUM(total) FROM tb_detail_transjual
WHERE jenis_trans=0) as cash,
(SELECT SUM(total) FROM tb_detail_transjual
WHERE jenis_trans=1) as debit,
(SELECT SUM(total) FROM tb_detail_transjual
WHERE jenis_trans=2) as credit
Thank you.
You could use a CASE WHEN statement. I haven't tested this but it should work. You can add a ORDER BY trans_date to the end if you want to sort it.
SELECT trans_date,
CASE WHEN pay_type = 0 THEN sum(total) ELSE 0 END AS Cash,
CASE WHEN pay_type = 1 THEN sum(total) ELSE 0 END AS Debit,
CASE WHEN pay_type = 2 THEN sum(total) ELSE 0 END AS Credit,
sum(total) AS Total
FROM tb_detail_transjual
GROUP BY trans_date