MySQL sum() on different group bys - mysql

Ok, I have a query over two tables. I need to get two sums. I do a group by so the sum() works correctly.
SELECT sum(a.x), sum(b.y) FROM a,b GROUP BY a.n where a.n=b.m
So far this works well, but the problem is i need to group them differently for the second sum (sum(b.y)), than for the first sum (sum(a.x)).
The real query is somewhat more complex but this is my main problem.
This is what i actually try to select sum(stock.amount) - if( sold.amount IS NULL , 0, sum( sold.amount ) )
How can I solve that in one query?

since you are not writing down the tables I am gonna make a wild guess and assume the tables are like :
stock : id, item_id, amount
sold : id, item_id, amount
then again I assume that you need the stock_in_total, sold_total, left_total counts
SELECT
stock_sums.item_id,
stock_sums.st_sum as stock_in_total,
COALESCE(sold_sums.so_sum,0) as sold_total,
(stock_sums.st_sum - COALESCE(sold_sums.so_sum,0)) as left_total
FROM (
SELECT stock.item_id as item_id, SUM(stock.amount) as st_sum
FROM stock
GROUP BY item_id
) as stock_sums
LEFT JOIN (
SELECT sold.item_id as item_id, SUM(sold.amount) as so_sum
FROM sold
GROUP by item_id
) as sold_sums ON stock_sums.item_id = sold_sums.item_id
I hope this would help.

Here is how I would do it. I assume that Stock is the main table, with an ID and an amount, and that Sold maps to Stock via an ID value, and has zero to many records for each Stock item.
SELECT Q1.id, Q1.Total1, Q2.Total2
, Q1.Total1 - COALESCE(Q2.Total2,0) as Outstanding
FROM (
SELECT id, SUM(amount) as Total1
FROM Stock GROUP BY id
) as Q1
LEFT OUTER JOIN (
SELECT id, SUM(Amount) as Total2
FROM Sold GROUP BY id
) as Q2
ON Q2.id = Q1.id
Note that simply formatting your SQL into a clean way forces you to break it into logical parts and will often reveal exactly what is wrong with the query.
The example above also handles correctly the cases where there is not match in the Sold table.
Cheers,
Daniel
(Code Assumptions)
DROP TABLE Stock
CREATE TABLE Stock (
id integer
, amount decimal(10,2)
)
INSERT INTO Stock (id, amount ) VALUES ( 1, 10.1);
INSERT INTO Stock (id, amount ) VALUES ( 2, 20.2);
INSERT INTO Stock (id, amount ) VALUES ( 3, 30.3);
SELECT * FROM STOCK
DROP TABLE Sold
CREATE TABLE Sold (
id integer
, amount decimal(10,2)
)
INSERT INTO Sold (id, amount ) VALUES ( 1, 1.1);
INSERT INTO Sold (id, amount ) VALUES ( 1, 2.2);
INSERT INTO Sold (id, amount ) VALUES ( 1, 3.3);
INSERT INTO Sold (id, amount ) VALUES ( 2, 2.22);
SELECT * FROM Sold
SELECT Q1.id, Q1.Total1, Q2.Total2
, Q1.Total1 - COALESCE(Q2.Total2,0) as Outstanding
FROM (
SELECT id, SUM(amount) as Total1
FROM Stock GROUP BY id
) as Q1
LEFT OUTER JOIN (
SELECT id, SUM(Amount) as Total2
FROM Sold GROUP BY id
) as Q2
ON Q2.id = Q1.id
Results:
id Total1 Total2 Outstanding
1 10.10 6.60 3.50
2 20.20 2.22 17.98
3 30.30 30.30

REVISION
It sounds like you want the total amount of stock you have as one count for each different stock. Then you want how much stock you have left for each stock based on what has been sold. Correct?
If so check this out:
select stock, sum(a.x) as sharesBeforeSale, (sum(a.x) - sum(b.y)) as sharesAfterSale
FROM db.table1 a, db.table2 b
WHERE a.UNIQUEID = b.UNIQUEID AND b.y IS NOT NULL
GROUP BY a.UNIQUEID;
Does that accomplish what you are looking to do?
stock sharesBeforeSale sharesAfterSale
duk 100 25
orc 101 101
yrc 54 41
Enjoy!
Sample tables
db.table1 (stock owned):
UNIQUEID x stock
1 100 duk
2 101 orc
3 54 yrc
db.table2 (stock sold):
UNIQUEID y
1 75
2 0
3 13

Related

how to select the lower value product of matching SKU

I have two suppliers where I am trying to filter the products to achieve the following:
Select Products from Table1(supplier 1) that are not already present in Table3 and some further filtering on categories (Working)
Select Products from Table2 (Supplier 2) that are not already present in Table3 (Working)
There are duplicate products from Table1 and Table2. I want to filter these so the resulting table does not have duplicate products but I want to do this by selecting the cheaper supplier rate as the product that ends up in the table. (Stuck on this).
My current SQL query at the moment is the below which does the first two items but I am unsure how to modify to get the third item above working. Any suggestions greatly appreciated.
SELECT Vendor,
VendorStockCode AS SKU,
StockDescription AS Description,
StockAvailable AS Stock
FROM Table1
WHERE NOT EXISTS (SELECT sku
FROM Table3_product_meta_lookup
WHERE Table1.VendorStockCode = Table3_product_meta_lookup.sku)
AND PrimaryCategory != 'SERVICES'
AND PrimaryCategory != 'WARRANTY'
AND cast(DealerEx as decimal(10,2)) <= cast('15000.00' as decimal(10,2))
UNION
SELECT Manufacture_Name,
Manufacture_Code,
Short_Description,
Stock_Qty
FROM Table2
WHERE NOT EXISTS (SELECT sku
FROM Table3_product_meta_lookup
WHERE Manufacture_Code = Table3_product_meta_lookup.sku)
OK, after some experiment, I followed a similar line to what I had and seems to be working. Not sure if it is the most efficient manner but is getting the data I am after. Thank you for those who responded. I have pasted the final query below for reference:
SELECT Vendor, VendorStockCode AS SKU,
StockDescription AS Description,
StockAvailable AS Stock
FROM Table1
WHERE NOT EXISTS (
SELECT sku
FROM Table3_product_meta_lookup
WHERE Table1.VendorStockCode = Table3_product_meta_lookup.sku )
AND PrimaryCategory != 'SERVICES'
AND PrimaryCategory != 'WARRANTY'
AND CAST(DealerEx AS DECIMAL(10,2)) <= CAST('15000.00' AS DECIMAL(10,2))
AND NOT EXISTS (
SELECT Manufacture_Code
FROM Table2
WHERE VendorStockCode = Manufacture_Code
AND CAST(DealerEx AS DECIMAL(10,2)) >= CAST(ExTax AS DECIMAL(10,2)))
UNION
SELECT Manufacture_Name, Manufacture_Code, Short_Description,
Stock_Qty
FROM Table2
WHERE NOT EXISTS (
SELECT sku
FROM Table3_product_meta_lookup
WHERE Manufacture_Code = Table3_product_meta_lookup.sku )
AND NOT EXISTS (
SELECT VendorStockCode
FROM Table1
WHERE VendorStockCode = Manufacture_Code
AND CAST(DealerEx AS DECIMAL(10,2)) < CAST(ExTax AS DECIMAL(10,2)));
How to calculate supplier rate?
You want to remove the duplicate products and leave one which is lower in supplier rate?
Assuming that you've already collected all data from table1 and table2 with supplier_rate column and union them into a table 'table_a'.
It will be easier to filter the duplicated SKUs with different suppliers and leave on with one supplier with a lower rate.
The tested query below.
select vendor,sku,stock from (
select vendor,sku,stock,supplier_rate,rank() over(PARTITION by sku order by supplier_rate) as rk from table_a) pd
where rk = 1;
The input table data:
vendorname sku stock supplier_rate
vendor1 100 1000 30
Manufacture1 100 2000 40
vendor3 200 1500 50
Manufacture2 300 2000 60
Manufacture3 200 1200 25
The output table data:
vendorname sku stock
vendor1 100 1000
Manufacture3 200 1200
Manufacture2 300 2000

Mysql: How to join a query to find results from another table

I have two tables:
TABLE A
Unique_id
id
price
1
1
10.50
2
3
14.70
3
1
12.44
TABLE B
Unique_id
Date
Category
Store
Cost
1
2022/03/12
Shoes
A
13.24
2
2022/04/15
Hats
A
15.24
3
2021/11/03
Shoes
B
22.31
4
2000/12/14
Shoes
A
15.33
I need to filter TABLE A on a known id to get the Unique_id and average price to join to Table B.
Using this information I need to know which stores this item was sold in.
I then need to create a results table displaying the stores and the amount of days sales were recorded in the stores - regardless of whether the sales are associated with the id and the average cost.
To put it more simply I can break down the task into 2 separate commands:
SELECT AVG(price)
FROM table_a
WHERE id = 1
GROUP BY unique_id;
SELECT store, COUNT(date), AVG(cost)
FROM table_b
WHERE category = 'Shoes'
GROUP BY store;
The unique_id should inform the join but when I join the tables it messes up my COUNT function and only counts the days in which the id is connected - not the total store sales days.
The results should look something like this:
Store
AVG price
COUNT days
AVG cost
A
10.50.
3
14.60.
B
12.44
1.
22.31.
I wwas hard to grasp, what you wanted, but after some thinking and your clarification, it can be solved as the code shows
CREATE TABLE TableA
(`Unique_id` int, `id` int, `price` DECIMAL(10,2))
;
INSERT INTO TableA
(`Unique_id`, `id`, `price`)
VALUES
(1, 1, 10.50),
(2, 3, 14.70),
(3, 1, 12.44)
;
CREATE TABLE TableB
(`Unique_id` int, `Date` datetime, `Category` varchar(5), `Store` varchar(1), `Cost` DECIMAL(10,2))
;
INSERT INTO TableB
(`Unique_id`, `Date`, `Category`, `Store`, `Cost`)
VALUES
(1, '2022-03-12 01:00:00', 'Shoes', 'A', 13.24),
(2, '2022-04-15 02:00:00', 'Hats', 'A', 15.24),
(3, '2021-11-03 01:00:00', 'Shoes', 'B', 22.31),
(4, '2000-12-14 01:00:00', 'Shoes', 'A', 15.33)
SELECT
B.`Store`
, AVG(A.`price`) price
, (SELECT COUNT(*) FROM TableB WHERE `Store` = B.`Store` ) count_
, (SELECT AVG(
`cost`) FROM TableB WHERE `Store` = B.`Store` ) price
FROM TableA A
JOIN TableB B ON A.`Unique_id` = B.`Unique_id`
WHERE B.`Category` = 'Shoes'
GROUP BY B.`Store`
Store | price | count_ | price
:---- | --------: | -----: | --------:
A | 10.500000 | 3 | 14.603333
B | 12.440000 | 1 | 22.310000
db<>fiddle here
This should be the query you are after. Mainly you simply join the rows using an outer join, because not every table_b row has a match in table_a.
Then, the only hindrance is that you only want to consider shoes in your average price. For this to happen you use conditional aggregation (a CASE expression inside the aggregation function).
select
b.store,
avg(case when b.category = 'Shoes' then a.price end) as avg_shoe_price,
count(b.unique_id) as count_b_rows,
avg(b.cost) as avg_cost
from table_b b
left outer join table_a a on a.unique_id = b.unique_id
group by b.store
order by b.store;
I must admit, it took me ages to understand what you want and where these numbers result from. The main reason for this is that you have WHERE table_a.id = 1 in your query, but this must not be applied to get the result you are showing. Next time please look to it that your description, queries and sample data match.
(And then, I think that names like table_a, table_b and unique_id don't help understanding this. If table_a were called prices instead and table_b costs and unique_id were called cost_id then, I wouldn't have had to wonder how the tables are related (by id? by unique id?) and wouldn't have had to look again and again which table the cost resides in, which table has a price and which table is the outer joined one while looking at the problem, the requested result and while writing my query.)

get max, min, count and mode (occurrence)

I have an items table in my database that i want my query to process the values and give me the data of the max price, min price, most recurrent max price in that specific item category and no of items (and ignore the ones that are null), so here is my items table:
id
category
min_price
max_price
1
kids
10
100
2
adult
20
200
3
both
null
null
4
adult
20
100
5
adult
50
100
6
adult
50
200
7
kids
20
100
8
both
20
100
9
kids
null
null
10
adult
10
500
11
misc
null
null
I want the query to return this result:
category
min_price
max_price
price_mode
no_items
kids
10
100
100
3
adult
20
500
200
5
both
20
100
100
2
misc
null
null
null
1
so just to further explain the adult lowest price in 20 and highest is 500 and the 100 and 200 max_price has 2 occurrences both i want to take the highest as the price_mode which is 200 in this case and the no_items is just the count of how many times adult is shown in the table.
am struggling to get the mode honestly and grouping it correctly to get the output I want.
Below is the commands to create table and feed it with data. Tried to put it in SqlFiddle but that's not working for me i don't know why.
CREATE TABLE IF NOT EXISTS `items` (
`id` int(6) unsigned NOT NULL,
`category` TEXT NOT NULL,
`min_price` FLOAT DEFAULT NULL,
`max_price` FLOAT DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `items` (`id`, `category`, `min_price`, `max_price`) VALUES
('kids', 10, 100),
('adult', 20, 200),
('both', null, null),
('adult', 20, 100),
('adult', 50, 100),
('adult', 50, 200),
('kids', 20, 100),
('both', 20, 100),
('kids', null, null),
('adult', 10, 500),
('misc', null, null);
Your create table + insert data syntax doesn't work in fiddle because your data VALUES are for just 3 columns whereby you define 4 columns in the INSERT:
INSERT INTO `items` (`id`, `category`, `min_price`, `max_price`) VALUES
('kids' , 10 , 100),
/*where's the value for `id`?*/
...
If you remove id from the INSERT syntax, it won't work as well because you've set it as PRIMARY KEY so it can't be empty. What you can do in addition to removing id from INSERT is to define AUTO_INCREMENT on the id column:
CREATE TABLE IF NOT EXISTS `items` (
`id` int(6) unsigned NOT NULL AUTO_INCREMENT,
....
Now, to get the expected result on your price_mode, you may want to try using GROUP_CONCAT() with ORDER and define which of the data in there that you want to return. Let's say you do GROUP_CONCAT(max_price ORDER BY max_price DESC) to return the set with max_price in descending order like this:
SELECT category,
MIN(min_price),
MAX(max_price),
GROUP_CONCAT(max_price ORDER BY max_price DESC),
COUNT(*)
FROM items
GROUP BY category;
Then you'll get a result like this:
category
MIN(min_price)
MAX(max_price)
GROUP_CONCAT(max_price ORDER BY max_price DESC)
COUNT(*)
adult
10
500
500,200,200,100,100
5
both
20
100
100
2
kids
10
100
100,100
3
misc
NULL
NULL
NULL
1
So, there's a consistent pattern in the GROUP_CONCAT() result that you probably can work out with. Assuming that you want the second largest value in the set, you can apply SUBSTRING_INDEX() twice to get it like this:
SELECT category,
MIN(min_price) AS min_price,
MAX(max_price) AS max_price,
SUBSTRING_INDEX(
SUBSTRING_INDEX(
GROUP_CONCAT(max_price ORDER BY max_price DESC),',',2),',',-1)
AS price_mode,
COUNT(*) AS no_items
FROM items
GROUP BY category;
This return the following result:
category
min_price
max_price
price_mode
no_items
adult
10
500
200
5
both
20
100
100
2
kids
10
100
100
3
misc
NULL
NULL
NULL
1
Demo fiddle
The following is an updated suggestion after getting further clarification:
SELECT i.category,
MIN(i.min_price),
MAX(i.max_price),
v2.mp AS price_mode,
COUNT(DISTINCT i.id)
FROM items i
LEFT JOIN
(SELECT cat,
mp,
cnt,
CASE WHEN cat = #cat
THEN #rownum := #rownum + 1
ELSE #rownum:=1 END AS rownum,
#cat := cat
FROM
(SELECT category cat,
max_price mp,
COUNT(*) cnt
FROM items
GROUP BY category,
max_price) v1
CROSS JOIN (SELECT #rownum := 1,
#cat := NULL) seq
WHERE mp IS NOT NULL
ORDER BY cat, cnt DESC, mp DESC) v2
ON i.category=v2.cat
AND v2.rownum=1
GROUP BY i.category, v2.mp;
The query starts with getting the COUNT(*) value of category and max_price combination. Then generating a custom row numbering on it with a WHERE condition that doesn't return max_price with NULL after the first operation. Probably the crucial part here is the ORDER BY cat, cnt DESC, mp DESC since the row numberings are assigned based on it. Otherwise, the row numbering will mess up. Finally, LEFT JOIN the items table with it with ON i.category=v2.cat AND v2.rownum=1 condition. It's important to make sure the v2.rownum=1 is placed at ON condition instead of WHERE in order to return the last row value of misc; since the subqueries will not have the value with the present sample data.
Here's an updated fiddle for reference, including the sample of 3 adult=NULL.
Maybe this query will help
with maximumvaluecounts
as ( select
count(max_price) as c, category, max_price
from yourtable
group by category
),
maximumcountpercategory
as ( select category,max(c) as c
from maximumvaluecounts
group by category
),
modes as ( select category, max_price as modevalue
from maximumcountpercategory m1
join maximumvaluecounts m2
on m1.category=m2.category
and m1.c=m2.c
)
, others as (
select
category,
min(min_price) as min_price,
max(max_price) as max_price,
count(max_price) as no_items
from yourtable
group by category
)
select o.*, m.modevalue as price_mode
from others o join
modes m on o.category=m.category

MySQL - COUNT scatter classified by multiple records

I have SQL table:
CREATE TABLE `test_results` (
`id` int AUTO_INCREMENT,
`date_time` datetime,
`altpn` varchar(60),
`vsp` decimal(10,4),
PRIMARY KEY (`id`)
);
VALUES
(null, 2016-07-22 13:30:01, pn1, 14.55),
(null, 2016-07-22 13:30:02, pn1, 14.45),
(null, 2016-07-22 13:30:03, pn1, 14.55),
(null, 2016-07-22 13:30:04, pn2, 14.45),
(null, 2016-07-22 13:30:05, pn2, 14.65),
(null, 2016-07-22 13:30:06, pn2, 14.45);
And i need result like this:
vsp - altpn - COUNT
14.45 - pn1 - 1
14.45 - pn2 - 2
14.55 - pn1 - 2
14.55 - pn2 - 0
14.65 - pn1 - 0
14.65 - pn2 - 1
It can also be altpn - vsp - COUNT, it does not matter. But every vsp value must contain all pns, which are in table. Even that with zero value.
Is this even possible to do? I can do normal SQL like:
SELECT test_results.vsp,
test_results.altpn,
COUNT(*)
FROM test_results
GROUP BY test_results.vsp,
test_results.altpn
and re-compute this result in php to table what i need (For chart), but it will by easier do it in SQL.
SELECT d.*,
c.*,
(SELECT count(*)
FROM test_results e
WHERE e.altpn=d.idaltpn
AND e.vsp=c.idvsp)
FROM
(SELECT distinct(a.altpn) idaltpn
FROM test_results a) d,
(SELECT distinct(a.vsp) idvsp
FROM test_results a) c
You are close. You'll want to assemble together a list of all the unique vsp/altpn and then left join that to a count subquery. I'm going to use a crossjoin of distinct values of vps to the distinct values of altpn to create that list for you...you might want to use business logic if there is something that determines when a vsp/altpn combination is valid in your results.
select a.vsp, a.altpn, ifnull(counts.totalcount,0)
from (select vsp.vsp, altpn.altpn
from (select vsp
from test_results
group by vsp)vsp
join
(select altpn
from test_results
group by altpn)altpn
on 1=1) a
left join
(SELECT test_results.vsp, test_results.altpn, COUNT(*) as totalcount
FROM test_results
GROUP BY test_results.vsp, test_results.altpn) counts
on a.vsp = counts.vsp and a.altpn = counts.altpn

MYSQL - Group By / Order By not working

I have the following data inside a table:
id person_id item_id price
1 1 1 10
2 1 1 20
3 1 3 50
Now what I want to do is group by the item ID, select the id that has the highest value and take the price.
E.g. the sum would be: (20 + 50) and ignore the 10.
I am using the following:
SELECT SUM(`price`)
FROM
(SELECT id, person_id, item_id, price
FROM `table` tbl
INNER JOIN person p USING (person_id)
WHERE p.person_id = 1
ORDER BY id DESC) x
GROUP BY item_id
However, this query is still adding (10 + 20 + 50), which is obviously not what I need to have.
Any ideas to where I am going wrong?
Here is what you are trying to achieve. First you need grouping in a subquery and not in outer query. In outer query you need only sum:
SELECT SUM(`price`)
FROM
(SELECT MAX(price) as price
FROM `table` tbl
INNER JOIN person p USING (person_id)
WHERE p.person_id = 1
GROUP BY item_id) x
http://sqlfiddle.com/#!9/40803/5
SELECT SUM(t1.price)
FROM tbl t1
LEFT JOIN tbl t2
ON t1.person_id= t2.person_id
AND t1.item_id = t2.item_id
AND t1.id<t2.id
WHERE t1.person_id = 1
AND t2.id IS NULL;
I'm not sure if this is the only requirement you have. If so, try this.
SELECT SUM(price)
FROM
(SELECT MAX(price)
FROM table
WHERE person_id = 1
GROUP BY item_id)
First of all - you don't need the person table, because the other table already contains the person_id. So i removed it from the examples.
Your query returns a sum of prices for each item.
If you replace SELECT SUM(price) with SELECT item_id, SUM(price) you wil get
item_id SUM(`price`)
1 30
3 50
But that is not what you want. Neither is it what you wrote in the question " (10 + 20 + 50)".
Now replacing the first line with SELECT id, item_id, SUM(price) you will get one row for each item with the highest id.
id item_id price
2 1 20
3 3 50
This works because of the "undocumented feature" of MySQL, wich allows you to select columns that are not listed in the GROUP BY clause and get the first row from the subselect each group (each item in this case).
Now you only need to sum the price column in an additional outer select
SELECT SUM(price)
FROM (
SELECT id, item_id ,price
FROM (
SELECT id, person_id, item_id, price
FROM `table` tbl
WHERE tbl.person_id = 1
ORDER BY id DESC ) x
GROUP BY item_id
) y
However i do not recomend to use that "feature". While it still works on MySQL 5.6, you never know if that will work with newer versions. It already doesn't work on MariaDB.
Instead you can determite the MAX(id) for each item in an subselect, select only the rows with the determined ids and get the summed price of them.
SELECT SUM(`price`)
FROM `table` tbl
WHERE tbl.id IN (
SELECT MAX(tbl2.id)
FROM `table` tbl2
WHERE tbl2.person_id = 1
GROUP BY tbl2.item_id
)
Another solution (wich internaly does the same) is
SELECT SUM(`price`)
FROM `table` tbl
JOIN (
SELECT MAX(tbl2.id) as id
FROM `table` tbl2
WHERE tbl2.person_id = 1
GROUP BY tbl2.item_id
) x ON x.id = tbl.id
Alex's solution also works fine, if the groups (number of rows per person and item) are rather small.
You have used group by in main query, but it is on subquery like
SELECT id, person_id, item_id, SUM(`price`) FROM ( SELECT MAX(price) FROM `table` tbl WHERE p.person_id = 1 GROUP BY item_id ) AS x