Complex SQL Query for Inventory System - mysql

I have 5 tables:
Items
Inventory
ConsumedItemsMonitoring
DamagedItemsMonitoring
UnaccountedItems
I'm new to Complex SQL queries did some research and asked for help and this is what I my code looks like so far.
SELECT Items.ItemID, Items.Item,
SUM(CASE WHEN DATE(Inventory.ItemTransactionDate) < CURDATE() THEN Inventory.Quantity ELSE 0 END) -
SUM(CASE WHEN DATE(consumeditemmonitoring.TransactionDate) <CURDATE() THEN consumeditemmonitoring.Quantity ELSE 0 end) -
SUM(CASE WHEN DATE(damagedinventory.ItemTransactionDate)<CURDATE() THEN damagedinventory.Quantity ELSE 0 end) -
SUM(CASE WHEN DATE(unaccounteditems.ItemTransactionDate)<CURDATE() THEN unaccounteditems.Quantity ELSE 0 end) AS 'PrevBalance',
SUM(CASE WHEN DATE(Inventory.ItemTransactionDate)=CURDATE() THEN Inventory.Quantity else 0 END) AS 'DeliveredToday',
SUM(CASE WHEN DATE(damagedinventory.ItemTransactionDate)=CURDATE() THEN damagedinventory.Quantity ELSE 0 END) AS 'DamagedToday',
SUM(CASE WHEN DATE(consumeditemmonitoring.TransactionDate)=CURDATE() THEN consumeditemmonitoring.Quantity ELSE 0 END) AS 'ConsumedToday',
SUM(CASE WHEN DATE(unaccounteditems.ItemTransactionDate)=CURDATE() THEN unaccounteditems.Quantity ELSE 0 END) AS 'UnAccountedToday',
SUM(CASE WHEN DATE(Inventory.ItemTransactionDate) < CURDATE() THEN Inventory.Quantity else 0 end)-
SUM(CASE WHEN DATE(consumeditemmonitoring.TransactionDate) < CURDATE() THEN consumeditemmonitoring.Quantity ELSE 0 END)-
SUM(CASE WHEN DATE(damagedinventory.ItemTransactionDate) < CURDATE() THEN damagedinventory.Quantity ELSE 0 END)-
SUM(CASE WHEN DATE(unaccounteditems.ItemTransactionDate) < CURDATE() THEN unaccounteditems.Quantity ELSE 0 END)-
SUM(CASE WHEN DATE(consumeditemmonitoring.TransactionDate) = CURDATE() THEN consumeditemmonitoring.Quantity ELSE 0 END)-
SUM(CASE WHEN DATE(damagedinventory.ItemTransactionDate) = CURDATE() THEN damagedinventory.Quantity ELSE 0 END)-
SUM(CASE WHEN DATE(unaccounteditems.ItemTransactionDate) = CURDATE() THEN unaccounteditems.Quantity ELSE 0 END) +
SUM(CASE WHEN DATE(Inventory.ItemTransactionDate) = CURDATE() then Inventory.Quantity ELSE 0 end) AS 'Total Balance'
FROM Items
LEFT OUTER JOIN consumeditemmonitoring ON consumeditemmonitoring.ItemID = Items.ItemID
LEFT OUTER JOIN damagedinventory ON damagedinventory.ItemID = Items.ItemID
LEFT OUTER JOIN unaccounteditems ON unaccounteditems.ItemID = Items.ItemID
LEFT OUTER JOIN inventory ON inventory.ItemID= Items.ItemID
GROUP BY Items.ItemID
The output looks like some of the table are multiplied.

What you are seeing is a result of how joins work and the fact that the joins are executed before the group by. I can illustrate this with a simplified version of your data.
drop table if exists
items,
items_inventory,
items_consumed,
items_damaged,
items_unaccounted;
create table items (id int);
create table items_inventory(id int,itemid int,qty int);
create table items_consumed(id int,itemid int,qty int);
create table items_damaged(id int,itemid int,qty int);
create table items_unaccounted(id int,itemid int,qty int);
insert into items values(1),(2);
insert into items_inventory values (1,1,10),(2,1,10),(2,2,20);
insert into items_consumed values(1,1,5),(2,2,15);
insert into items_damaged values(1,1,25);
If we run a simple select
select i.id,
ii.id,ii.qty,
ic.id,ic.qty,
id.id,id.qty,
iu.id,iu.qty
from items i
left join items_inventory ii on ii.itemid = i.id
left join items_consumed ic on ic.itemid = i.id
left join items_damaged id on id.itemid = i.id
left join items_unaccounted iu on iu.itemid = i.id
;
we get 2 rows for item 1 even though there is only 1 row for items_consumed
+------+------+------+------+------+------+------+------+------+
| id | id | qty | id | qty | id | qty | id | qty |
+------+------+------+------+------+------+------+------+------+
| 1 | 1 | 10 | 1 | 5 | 1 | 25 | NULL | NULL |
| 1 | 2 | 10 | 1 | 5 | 1 | 25 | NULL | NULL |
| 2 | 2 | 20 | 2 | 15 | NULL | NULL | NULL | NULL |
+------+------+------+------+------+------+------+------+------+
3 rows in set (0.00 sec)
When we aggregate
select i.id,
count(*) as rows,
sum(ii.qty) as inventory,
sum(ic.qty) as consumed,
sum(id.qty) as damaged,
sum(iu.qty) as unaccounted
from items i
left join items_inventory ii on ii.itemid = i.id
left join items_consumed ic on ic.itemid = i.id
left join items_damaged id on id.itemid = i.id
left join items_unaccounted iu on iu.itemid = i.id
group by i.id;
we get 'doubling' up of consumed and damaged.
+------+------+-----------+----------+---------+-------------+
| id | rows | inventory | consumed | damaged | unaccounted |
+------+------+-----------+----------+---------+-------------+
| 1 | 2 | 20 | 10 | 50 | NULL |
| 2 | 1 | 20 | 15 | NULL | NULL |
+------+------+-----------+----------+---------+-------------+
2 rows in set (0.00 sec)
One way to deal with this is to aggregate BEFORE you join by pushing the aggregations into sub queries which you would then join. For example
select i.id, ii.inventory,ic.consumed,id.damaged,iu.unaccounted,
coalesce(ii.inventory,0)+coalesce(ic.consumed,0)+coalesce(id.damaged,0)+coalesce(iu.unaccounted,0) total
from items i
left join (select ii.itemid,sum(ii.qty) as inventory from items_inventory ii group by itemid) ii on ii.itemid = i.id
left join (select ic.itemid,sum(ic.qty) as consumed from items_consumed ic group by itemid) ic on ic.itemid = i.id
left join (select id.itemid,sum(id.qty) as damaged from items_damaged id group by itemid) id on id.itemid = i.id
left join (select iu.itemid,sum(iu.qty) as unaccounted from items_unaccounted iu group by itemid) iu on iu.itemid = i.id
;
+------+-----------+----------+---------+-------------+-------+
| id | inventory | consumed | damaged | unaccounted | total |
+------+-----------+----------+---------+-------------+-------+
| 1 | 20 | 5 | 25 | NULL | 50 |
| 2 | 20 | 15 | NULL | NULL | 35 |
+------+-----------+----------+---------+-------------+-------+
2 rows in set (0.00 sec)

Working Query thanks to sir #P.Salmon
SELECT I.ItemID,
I.Item,
COALESCE(II.InventoryPrevBal,0) - COALESCE(ICP.ConsumedPrevBal,0) - COALESCE(IDP.DamagedPrevBal,0) - COALESCE(IUP.UnaccountedPrevBal,0) PrevBalance,
COALESCE(II.InventoryBal,0) CurrentDelivered,
COALESCE(IC.Consumed,0) CurrentConsumed,
COALESCE(ID.Damaged,0) CurrentDamaged,
COALESCE(IU.Unaccounted,0) CurrentUnaccounted,
COALESCE(II.InventoryPrevBal,0) + COALESCE(II.InventoryBal,0) - COALESCE(ICP.ConsumedPrevBal,0) - COALESCE(IDP.DamagedPrevBal,0) - COALESCE(IUP.UnaccountedPrevBal,0) - COALESCE(IC.Consumed,0) - COALESCE(ID.Damaged,0) - COALESCE(IU.Unaccounted,0) CurrentTotal
FROM items I
LEFT JOIN (SELECT II.ItemID, SUM(CASE WHEN DATE(II.ItemTransactionDate) < CURDATE() THEN II.Quantity ELSE 0 END) as InventoryPrevBal, SUM(CASE WHEN DATE(II.ItemTransactionDate) = CURDATE() THEN II.Quantity ELSE 0 END) as InventoryBal FROM inventory II GROUP BY ItemID) II ON II.ItemID = I.ItemID
LEFT JOIN (SELECT ICP.ItemID, ICP.TransactionDate, SUM(ICP.Quantity) as ConsumedPrevBal FROM consumeditemmonitoring ICP WHERE DATE(ICP.TransactionDate) < CURDATE() GROUP BY ItemID) ICP ON ICP.ItemID = I.ItemID
LEFT JOIN (SELECT IDP.ItemID, IDP.ItemTransactionDate, SUM(IDP.Quantity) as DamagedPrevBal FROM damagedinventory IDP WHERE DATE(IDP.ItemTransactionDate) < CURDATE() GROUP BY ItemID) IDP ON IDP.ItemID = I.ItemID
LEFT JOIN (SELECT IUP.ItemID, IUP.ItemTransactionDate, SUM(IUP.Quantity) as UnaccountedPrevBal FROM unaccounteditems IUP WHERE DATE(IUP.ItemTransactionDate) < CURDATE() GROUP BY ItemID) IUP ON IUP.ItemID = I.ItemID
LEFT JOIN (SELECT IC.ItemID, IC.TransactionDate, SUM(IC.Quantity) as Consumed FROM consumeditemmonitoring IC WHERE DATE(IC.TransactionDate) = CURDATE() GROUP BY ItemID) IC ON IC.ItemID = I.ItemID
LEFT JOIN (SELECT ID.ItemID, ID.ItemTransactionDate, SUM(ID.Quantity) as Damaged FROM damagedinventory ID WHERE DATE(ID.ItemTransactionDate) = CURDATE() GROUP BY ItemID) ID ON ID.ItemID = I.ItemID
LEFT JOIN (SELECT IU.ItemID, IU.ItemTransactionDate, SUM(IU.Quantity) as Unaccounted FROM unaccounteditems IU WHERE DATE(IU.ItemTransactionDate) = CURDATE() GROUP BY ItemID) IU ON IU.ItemID = I.ItemID
ORDER BY I.Item ASC

Related

SQL get aggregate breakdown by joined table

I have the following entities and relationships in a MySQL database:
Each Post has N Review
Each Review has 1 Comment and a state (is_accepted)
Each Comment has 1 User
Desired result:
I'm trying to get an aggregate report of reviews on a specific post, grouped by user:
+---------+--------------+-----------------------+---------------------+
| user_id | review_count | review_accepted_count | review_denied_count |
+---------+--------------+-----------------------+---------------------+
| 1 | 3 | 2 | 1 |
| 2 | 5 | 1 | 4 |
| 3 | 1 | 1 | 0 |
+---------+--------------+-----------------------+---------------------+
What I've tried:
SELECT
C.user_id,
COUNT(C.user_id) AS review_count,
(SELECT COUNT(*) FROM reviews WHERE `post_id` = R.post_id AND `user_id` = C.user_id AND `is_accepted` = 1) review_accepted_count,
(SELECT COUNT(*) FROM reviews WHERE `post_id` = R.post_id AND `user_id` = C.user_id AND `is_accepted` = 0) review_denied_count
FROM reviews R
INNER JOIN comments C ON C.id = R.comment_id
WHERE post_id = 1234
GROUP BY C.user_id
Actual result:
The returned review_accepted_count and review_denied_count columns are the total across all reviews, not grouped per user
Try this:
SELECT
C.user_id,
COUNT(C.user_id) AS review_count,
SUM(CASE WHEN `is_accepted` = 1 THEN 1 ELSE 0 END) AS review_accepted_count,
SUM(CASE WHEN `is_accepted` = 0 THEN 1 ELSE 0 END) AS review_denied_count
FROM reviews R
INNER JOIN comments C ON C.id = R.comment_id
WHERE post_id = 1234
GROUP BY C.user_id
In your subqueries review_accepted_count and review_denied_count you should join (in WHERE clause) by the review primary key. You don't need to make subqueries to get the result. This way is faster.
If you only have 1s and 0s in column is_accepted you can do:
SUM(`is_accepted`) AS review_accepted_count

Show query result differently

I have this query:
SELECT pr_products.product AS PRODUCT, pr_varieties.variety AS VARIETY, pr_grades.GRADE, SUM(pf_harvest.quantity) AS QUANTITY
FROM pf_harvest
INNER JOIN pf_performance ON pf_performance.id = pf_harvest.id_performance
INNER JOIN pr_products ON pr_products.id = pf_harvest.id_product
INNER JOIN pr_varieties ON pr_varieties.id = pf_harvest.id_variety
INNER JOIN pr_grades ON pr_grades.id = pf_harvest.id_grade
WHERE pf_performance.status = 100
AND pf_harvest.id_tenant = 1
AND pf_harvest.date = '2017-03-22'
GROUP BY pf_harvest.id_product, pf_harvest.id_variety, pf_harvest.id_grade
ORDER BY pf_harvest.id_product, pr_varieties.variety, pf_harvest.id_grade;
Which shows me the following result:
-------------------------------------------------------------------
PRODUCT | VARIETY | GRADE | QUANTITY |
-------------------------------------------------------------------
ROSE | ROSEV1 | GRADE1 | 1000 |
-------------------------------------------------------------------
ROSE | ROSEV1 | GRADE2 | 5000 |
-------------------------------------------------------------------
ROSE | ROSEV2 | GRADE1 | 2000 |
-------------------------------------------------------------------
ROSE1 | ROSE1V1 | GRADE1 | 3500 |
-------------------------------------------------------------------
Is it possible to display the query result as follows?
-------------------------------------------------------------------
PRODUCT | VARIETY | GRADE1 | GRADE2 | TOTAL |
-------------------------------------------------------------------
ROSE | ROSEV1 | 1000 | 5000 | 6000 |
-------------------------------------------------------------------
ROSE | ROSEV2 | 2000 | 0 | 2000 |
-------------------------------------------------------------------
ROSE1 | ROSE1V1 | 3500 | 0 | 3500 |
-------------------------------------------------------------------
I tried to change the query but I could not and I would like to know if it is possible, I hope someone can help me.
UPDATED
NOTE: There may be more GRADES in the query result (GRADE1, GRADE2, GRADE3...).
Thanks!
Modify your current query by not grouping on the GRADE column, but instead pivot on that column. Then, use conditional aggregation to compute the GRADE1 and GRADE2 columns.
SELECT t3.product AS PRODUCT,
t4.variety AS VARIETY,
SUM(CASE WHEN t5.GRADE = 'GRADE1' THEN t1.quantity ELSE 0 END) AS GRADE1,
SUM(CASE WHEN t5.GRADE = 'GRADE2' THEN t1.quantity ELSE 0 END) AS GRADE2,
SUM(CASE WHEN t5.GRADE = 'GRADE3' THEN t1.quantity ELSE 0 END) AS GRADE3,
-- hopefully it is clear how to add more grades
SUM(t1.quantity) AS TOTAL
FROM pf_harvest t1
INNER JOIN pf_performance t2
ON t2.id = t1.id_performance
INNER JOIN pr_products t3
ON t3.id = pf_harvest.id_product
INNER JOIN pr_varieties t4
ON t4.id = t1.id_variety
INNER JOIN pr_grades t5
ON t5.id = t1.id_grade
WHERE t2.status = 100 AND
t1.id_tenant = 1 AND
t1.date = '2017-03-22'
GROUP BY t1.id_product,
t1.id_variety
ORDER BY t1.id_product,
t4.variety,
t1.id_grade;
try this
added this code
sum(case when pr_grades.Grade='Grade1' then pf_harvest.quantity else 0 end)) [Grade1],
sum(case when pr_grades.Grade='Grade2' then pf_harvest.quantity else 0 end)) [Grade2]
SELECT
pr_products.product AS PRODUCT,
pr_varieties.variety AS VARIETY,
sum(case when pr_grades.Grade='Grade1' then pf_harvest.quantity else 0 end)) [Grade1],
sum(case when pr_grades.Grade='Grade2' then pf_harvest.quantity else 0 end)) [Grade2],
SUM(pf_harvest.quantity) AS TOTAL
FROM pf_harvest
INNER JOIN pf_performance
ON pf_performance.id = pf_harvest.id_performance
INNER JOIN pr_products
ON pr_products.id = pf_harvest.id_product
INNER JOIN pr_varieties
ON pr_varieties.id = pf_harvest.id_variety
INNER JOIN pr_grades
ON pr_grades.id = pf_harvest.id_grade
WHERE pf_performance.status = 100
AND pf_harvest.id_tenant = 1
AND pf_harvest.date = '2017-03-22'
GROUP BY pf_harvest.id_product,
pf_harvest.id_variety
ORDER BY pf_harvest.id_product, pr_varieties.variety, pf_harvest.id_grade;

MySQL: Multiple Running Totals from Different Subqueries

When I run a single query using the following formula to have the first column give back the month/year, the second give back the number of people signing per month, and the third give back the running total of signers, it works great:
SET #runtot1:=0;
SELECT
1rt.MONTH,
1rt.1signed,
(#runtot1 := #runtot1 + 1rt.1signed) AS 1rt
FROM
(SELECT
DATE_FORMAT(STR_TO_DATE(s.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
IFNULL(COUNT(DISTINCT CASE WHEN s.surveyid = 791796 THEN s.id ELSE NULL END),0) AS 1signed
FROM table1 s
JOIN table2 m ON s.id = m.id AND m.current = "Yes"
WHERE STR_TO_DATE(s.datecontacted,'%m/%d/%Y') > '2015-03-01'
GROUP BY MONTH
ORDER BY MONTH) AS 1rt
With the query above, I get the following results table, which would be exactly what I want if I only needed to count one thing:
MONTH 1signed 1rt
2015-03 0 0
2015-04 1 1
2015-05 0 1
2015-08 1 2
2015-10 1 3
2015-11 1 4
2016-01 0 4
2016-02 0 4
But I can't figure out how to do that with multiple subqueries since I need this to happen for multiple columns at the same time. For example, I was attempting things like this (which doesn't work):
SET #runtot1:=0;
SET #runtot2:=0;
select
DATE_FORMAT(STR_TO_DATE(s1.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
t1.1signed,
(#runtot1 := #runtot1 + t1.1signed) AS 1rt,
t2.2signed,
(#runtot2 := #runtot2 + t2.2signed) AS 2rt
from
(select
DATE_FORMAT(STR_TO_DATE(s.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
IFNULL(COUNT(DISTINCT CASE WHEN s.surveyid = 791796 THEN s.id ELSE NULL END),0) AS 1signed
from table1 s
left join table2 m ON m.id = s.id
where m.current = "Yes"
GROUP BY MONTH
ORDER BY MONTH) as T1,
(select
DATE_FORMAT(STR_TO_DATE(s.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
IFNULL(COUNT(DISTINCT CASE WHEN s.surveyid = 846346 THEN s.id ELSE NULL END),0) AS 2signed
from table1 s
left join table2 m ON m.id = s.id
where m.current = "Yes"
GROUP BY MONTH
ORDER BY MONTH) as T2,
table1 s1
LEFT JOIN table2 m1 ON m1.id = s1.id AND m1.current = "Yes"
WHERE STR_TO_DATE(s1.datecontacted,'%m/%d/%Y') > '2015-03-01'
GROUP BY DATE_FORMAT(STR_TO_DATE(s1.datecontacted,'%m/%d/%Y'),'%Y-%m')
ORDER BY DATE_FORMAT(STR_TO_DATE(s1.datecontacted,'%m/%d/%Y'),'%Y-%m')
That blew up my results badly -- I also tried LEFT JOINs to get those two next each other, but that didn't work either.
Here's a SQL Fiddle with a few values with the query at the top that works, but not the query needed to look like the idea below.
If the multiple subquery version of the code worked, below would be the ideal end-result:
MONTH 1signed 1rt 2signed 2rt
2015-03 0 0 1 1
2015-04 1 1 0 1
2015-05 0 1 1 2
2015-08 1 2 0 2
2015-10 1 3 0 2
2015-11 1 4 0 2
2016-01 0 4 0 2
2016-02 0 4 1 3
Just trying to figure out a way to get counts by month and rolling totals since March 2015 for two different survey questions using the same query. Any help would be greatly appreciated!
Your attempt was actually pretty close. I just got rid of S1 and joined the two subqueries together on their MONTH columns:
SET #runtot1:=0;
SET #runtot2:=0;
select
T1.MONTH,
t1.1signed,
(#runtot1 := #runtot1 + t1.1signed) AS 1rt,
t2.2signed,
(#runtot2 := #runtot2 + t2.2signed) AS 2rt
from
(select
DATE_FORMAT(STR_TO_DATE(s.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
IFNULL(COUNT(DISTINCT CASE WHEN s.surveyid = 791796 THEN s.id ELSE NULL END),0) AS 1signed
from table1 s
left join table2 m ON m.id = s.id
where m.current = "Yes" and STR_TO_DATE(s.datecontacted,'%m/%d/%Y') > '2015-03-01'
GROUP BY MONTH
ORDER BY MONTH) as T1,
(select
DATE_FORMAT(STR_TO_DATE(s.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
IFNULL(COUNT(DISTINCT CASE WHEN s.surveyid = 846346 THEN s.id ELSE NULL END),0) AS 2signed
from table1 s
left join table2 m ON m.id = s.id
where m.current = "Yes" and STR_TO_DATE(s.datecontacted,'%m/%d/%Y') > '2015-03-01'
GROUP BY MONTH
ORDER BY MONTH) as T2
WHERE
T1.MONTH=T2.MONTH
GROUP BY T1.MONTH
ORDER BY T1.MONTH
I haven't tested Strawberry's solution, which looks more elegant. But I thought you'd like to know that your approach (solving the running totals individually, then joining the results together) would have worked too.
It seems that you're after something like this...
The data set:
DROP TABLE IF EXISTS table1;
CREATE TABLE table1
( id INT NOT NULL
, date_contacted DATE NOT NULL
, survey_id INT NOT NULL
, PRIMARY KEY(id,survey_id)
);
DROP TABLE IF EXISTS table2;
CREATE TABLE table2
(id INT NOT NULL PRIMARY KEY
,is_current TINYINT NOT NULL DEFAULT 0
);
INSERT INTO table1 VALUES
(1,"2015-03-05",846346),
(2,"2015-04-15",791796),
(2,"2015-05-04",846346),
(3,"2015-06-07",791796),
(3,"2015-06-08",846346),
(4,"2015-08-02",791796),
(5,"2015-10-15",791796),
(6,"2015-11-25",791796),
(6,"2016-01-02", 11235),
(6,"2016-02-06",846346);
INSERT INTO table2 (id,is_current) VALUES
(1,1),
(2,1),
(3,0),
(4,1),
(5,1),
(6,1);
The query:
SELECT x.*
, #a:=#a+a rt_a
, #b:=#b+b rt_b
FROM
( SELECT DATE_FORMAT(date_contacted,'%Y-%m') month
, SUM(survey_id = 791796) a
, SUM(survey_id = 846346) b
FROM table1 x
JOIN table2 y
ON y.id = x.id
WHERE y.is_current = 1
GROUP
BY month
) x
JOIN (SELECT #a:=0,#b:=0) vars
ORDER
BY month;
+---------+------+------+------+------+
| month | a | b | rt_a | rt_b |
+---------+------+------+------+------+
| 2015-03 | 0 | 1 | 0 | 1 |
| 2015-04 | 1 | 0 | 1 | 1 |
| 2015-05 | 0 | 1 | 1 | 2 |
| 2015-08 | 1 | 0 | 2 | 2 |
| 2015-10 | 1 | 0 | 3 | 2 |
| 2015-11 | 1 | 0 | 4 | 2 |
| 2016-01 | 0 | 0 | 4 | 2 |
| 2016-02 | 0 | 1 | 4 | 3 |
+---------+------+------+------+------+

How can I simplify the query without using UNION ALL?

SELECT i.id invn_id, IF(ss.serial_id IS NOT NULL, ss.serial_id, NULL) serial_number,
IF(ss.serial_id IS NOT NULL, 1.000000, -(i.qty)) qty,
'auto' flag, i.sid site_id, i.prod_id, i.fifo_total_amount, i.trans_date
FROM inventories i
INNER JOIN prod p ON p.id = i.prod_id
LEFT JOIN sale_products sp ON sp.inventory_id = i.id
LEFT JOIN sale_serial ss ON ss.prod_id = i.prod_id AND ss.sale_id = sp.sale_id
WHERE i.qty < 0
AND ('2015-03-25 00:00:00' IS NULL OR i.trans_date >= '2015-03-25 00:00:00')
AND ('2015-03-27 08:27:36' IS NULL OR i.trans_date <= '2015-03-27 08:27:36')
AND p.name NOT IN ('Starting Balance' , 'Opening Balance', 'Equity')
AND i.prod_id = 7655 AND (0 = 0 OR i.sid = 0)
UNION ALL
(
SELECT i.id invn_id,
IF(ss.serial_id IS NOT NULL,ss.serial_id, NULL) serial_number,
-(SUM(IF(ss.serial_id IS NOT NULL, 1.000000, -(i.qty))) - -(i.qty)) qty, -- difference
'auto' flag, i.sid site_id, i.prod_id, i.fifo_total_amount, i.trans_date
FROM inventories i
INNER JOIN prod p ON p.id = i.prod_id
LEFT JOIN sale_products sp ON sp.inventory_id = i.id
LEFT JOIN sale_serial ss ON ss.prod_id = i.prod_id AND ss.sale_id = sp.sale_id
WHERE i.qty < 0
AND ('2015-03-25 00:00:00' IS NULL OR i.trans_date >= '2015-03-25 00:00:00')
AND ('2015-03-27 08:27:36' IS NULL OR i.trans_date <= '2015-03-27 08:27:36')
AND p.name NOT IN ('Starting Balance' , 'Opening Balance', 'Equity')
AND i.prod_id = 7655 AND (0 = 0 OR i.sid = 0)
GROUP BY i.id -- difference
HAVING qty > 0 -- difference
)
ORDER BY site_id , prod_id , trans_date , qty ASC
Observe that the second statement (union table) is almost the same as the first statement except for the lines in comment. I'm satisfied with the outcome, but not satisfied with the query because of its redundancy. Is there a possibility to make this brief?
The output I wanna get is something like this, for instance:
I have a total quantity of 5 with an ID of 95514:
+-------+-----------+---------+
| id | qty | prod_id |
+-------+-----------+---------+
| 95514 | 5.000000 | 7655 |
+-------+-----------+---------+
If I'll execute the query above, the result will be like this:
+---------+---------------+----------+------+---------+---------+-------------------+---------------------+
| invn_id | serial_number | qty | flag | site_id | prod_id | fifo_total_amount | trans_date |
+---------+---------------+----------+------+---------+---------+-------------------+---------------------+
| 95514 | 237658 | 1.000000 | auto | 1 | 7655 | 2763.0194 | 2010-07-22 09:48:24 |
| 95514 | 237671 | 1.000000 | auto | 1 | 7655 | 2763.0194 | 2010-07-22 09:48:24 |
| 95514 | 237699 | 1.000000 | auto | 1 | 7655 | 2763.0194 | 2010-07-22 09:48:24 |
| 95514 | 237658 | 2.000000 | auto | 1 | 7655 | 2763.0194 | 2010-07-22 09:48:24 |
+---------+---------------+----------+------+---------+---------+-------------------+---------------------+
The first three rows returned the first statement of the query in UNION ALL, while the fourth row returned the second statement. If we'll sum up the qty column, we can get the value of 5.
I suspect you want group by with rollup:
SELECT i.id invn_id,
IF(ss.serial_id IS NOT NULL,ss.serial_id, NULL) serial_number,
-(SUM(IF(ss.serial_id IS NOT NULL, 1.000000, -(i.qty))) - -(i.qty)) qty, -- difference
'auto' flag, i.sid site_id, i.prod_id, i.fifo_total_amount, i.trans_date
FROM inventories i
INNER JOIN prod p ON p.id = i.prod_id
LEFT JOIN sale_products sp ON sp.inventory_id = i.id
LEFT JOIN sale_serial ss ON ss.prod_id = i.prod_id AND ss.sale_id = sp.sale_id
WHERE i.qty < 0
AND ('2015-03-25 00:00:00' IS NULL OR i.trans_date >= '2015-03-25 00:00:00')
AND ('2015-03-27 08:27:36' IS NULL OR i.trans_date <= '2015-03-27 08:27:36')
AND p.name NOT IN ('Starting Balance' , 'Opening Balance', 'Equity')
AND i.prod_id = 7655 AND (0 = 0 OR i.sid = 0)
GROUP BY i.id WITH ROLLUP
HAVING qty > 0 -- difference
However, I'm not 100% sure how this fits into the SELECT logic.

Getting sum of multiple columns and calculate each row's ratio

Below, is the schema of my brand_of_items table. For simplicity, shown here with two columns: id (primary and AI), symbol (varchar 50, unique)
Table - brand_of_items
id symbol
0 a
1 b
2 c
.. ..
10 j
Below, is the schema of my items_of_brand.
Table - mainIndexQuantity
id brand_of_items_id vol item_type salefinalizeddate
0 1 5 0 2005-5-11
1 1 6 0 2004-5-11
2 1 7 0 2011-5-11
3 1 8 0 2011-5-12
4 1 9 0 2011-5-12
5 1 10 0 2011-5-11
6 1 5 1 2012-5-11
7 1 6 1 2012-5-11
8 1 7 1 2011-5-11
9 1 8 1 2010-5-12
10 1 9 1 2012-5-12
11 1 10 1 2005-5-12
The mainIndexQuantity table brand_of_items_id columns is a foreign key which points to brand_of_items (id).
The mainIndexQuantity table item_type column is not a foreign key, which it should be.
The two item types are: 0 = retail and 1 = wholesale
I want to calculate the ratio of the types of items (retail vs wholesale) per each_brand_of_items table entry. The goal is to see if the a brands item is selling more in retail or wholesale.
**
Adding Complexity:
I want to add a date column to mainIndexQuantity table and want to find out the difference in sum of RetailVolume and WholesaleVolume and group the results by salefinalizeddate field.
This is to help determine what items in what seasons sold more and the (delta) difference in sum of RetailVolume & WholeSaleVolume will help to select items to pay most attention to.
Try this:
SELECT
b.id,
b.symbol,
IFNULL(SUM(m.item_type = 1), 0) / (COUNT(*) * 1.0) AS wholesaleRatio,
IFNULL(SUM(m.item_type = 0), 0) / (COUNT(*) * 1.0) AS RetailRatio
FROM brand_of_items b
LEFT JOIN mainIndexQuantity m ON b.id = m.brand_of_items_id
GROUP BY b.id,
b.symbol;
SQL Fiddle Demo.
This will give you:
| ID | SYMBOL | WHOLESALERATIO | RETAILRATIO |
----------------------------------------------
| 0 | a | 0 | 0 |
| 1 | b | 0.5 | 0.5 |
| 2 | c | 0 | 0 |
| 10 | j | 0 | 0 |
Assuming that:
wholesaleRatio is the count of the items of type Whole sale to the count of all items.
RetailRatio is the count of the items of type retail to the count of all items.
If this ration is for the total sum of the vol column to the total vol you can do this instead:
SELECT
b.id,
b.symbol,
SUM(CASE WHEN m.item_type = 1 THEN m.vol ELSE 0 END) / SUM(m.vol) AS wholesaleRatio,
SUM(CASE WHEN m.item_type = 0 THEN m.vol ELSE 0 END) / SUM(m.vol) AS RetailRatio
FROM brand_of_items b
LEFT JOIN mainIndexQuantity m ON b.id = m.brand_of_items_id
GROUP BY b.id,
b.symbol;
Note that:
I used LEFT JOIN, so that you got the unmatched rows in the result set, i.e, those brand items that has no entries the MainIndexQuantity table. If you don't want to include them, use INNER JOIN instead.
The multiply with 1.0 to get the count with decimal places, as noted by #JW.
Update 1
To include the Total Volume, Retail Volume Sum and Wholesale Volume sum try this:
SELECT
b.id,
b.symbol,
IFNULL(SUM(m.item_type = 1), 0) * 1.0 / COUNT(*) AS wholesaleRatio,
IFNULL(SUM(m.item_type = 0), 0) * 1.0 / COUNT(*) AS RetailRatio,
IFNULL(SUM(m.vol), 0) AS 'Total Volume',
SUM(CASE WHEN m.item_type = 1 THEN m.vol ELSE 0 END) AS 'Retail Volume sum',
SUM(CASE WHEN m.item_type = 1 THEN m.vol ELSE 0 END) AS 'Wholesale Volume sum'
FROM brand_of_items b
LEFT JOIN mainIndexQuantity m ON b.id = m.brand_of_items_id
GROUP BY b.id,
b.symbol;
Updated SQL Fiddle Demo.
This will give you:
| ID | SYMBOL | WHOLESALERATIO | RETAILRATIO | TOTAL VOLUME | RETAIL VOLUME SUM | WHOLESALE VOLUME SUM |
--------------------------------------------------------------------------------------------------------
| 0 | a | 0 | 0 | 0 | 0 | 0 |
| 1 | b | 0.5 | 0.5 | 90 | 45 | 45 |
| 2 | c | 0 | 0 | 0 | 0 | 0 |
| 10 | j | 0 | 0 | 0 | 0 | 0 |
If you want to sort the result set by these total and sums, put this query in a subquery, then you can do this:
SELECT *
FROM
(
SELECT
b.id,
b.symbol,
IFNULL(SUM(m.item_type = 1), 0) * 1.0 / COUNT(*) AS wholesaleRatio,
IFNULL(SUM(m.item_type = 0), 0) * 1.0 / COUNT(*) AS RetailRatio,
IFNULL(SUM(m.vol), 0) AS TotalVolume,
SUM(CASE WHEN m.item_type = 1 THEN m.vol ELSE 0 END) AS RetailVolumeSum,
SUM(CASE WHEN m.item_type = 1 THEN m.vol ELSE 0 END) AS WholesaleVolumeSum
FROM brand_of_items b
LEFT JOIN mainIndexQuantity m ON b.id = m.brand_of_items_id
GROUP BY b.id,
b.symbol
) AS sub
ORDER BY RetailVolumeSum DESC,
WholesaleVolumeSum DESC;
But your last requirement is not clear, are you looking for those brand of items that has the highest of retio/wholesale ratis and volumns or select the highest values of them?
For the later one:
SELECT *
FROM
(
SELECT
b.id,
b.symbol,
IFNULL(SUM(m.item_type = 1), 0) * 1.0 / COUNT(*) AS wholesaleRatio,
IFNULL(SUM(m.item_type = 0), 0) * 1.0 / COUNT(*) AS RetailRatio,
IFNULL(SUM(m.vol), 0) AS TotalVolume,
SUM(CASE WHEN m.item_type = 1 THEN m.vol ELSE 0 END) AS RetailVolumeSum,
SUM(CASE WHEN m.item_type = 1 THEN m.vol ELSE 0 END) AS WholesaleVolumeSum
FROM brand_of_items b
LEFT JOIN mainIndexQuantity m ON b.id = m.brand_of_items_id
GROUP BY b.id,
b.symbol
) AS sub
ORDER BY RetailVolumeSum DESC,
WholesaleVolumeSum DESC,
TotalVolume DESC
LIMIT 1;
Update 2
To get those brands that has the highest total volume, you can do this:
SELECT
b.id,
b.symbol,
IFNULL(SUM(m.item_type = 1), 0) * 1.0 / COUNT(*) AS wholesaleRatio,
IFNULL(SUM(m.item_type = 0), 0) * 1.0 / COUNT(*) AS RetailRatio,
IFNULL(SUM(m.vol), 0) AS TotalVolume,
SUM(CASE WHEN m.item_type = 1 THEN m.vol ELSE 0 END) AS RetailVolumeSum,
SUM(CASE WHEN m.item_type = 1 THEN m.vol ELSE 0 END) AS WholesaleVolumeSum
FROM brand_of_items b
LEFT JOIN mainIndexQuantity m ON b.id = m.brand_of_items_id
GROUP BY b.id,
b.symbol
HAVING SUM(m.vol) = (SELECT MAX(TotalVolume)
FROM
(
SELECT brand_of_items_id, SUM(vol) AS TotalVolume
FROM mainIndexQuantity
GROUP BY brand_of_items_id
) t);
Like this.
Note that:
This will give you the brands that has the highest total volume, if you are looking for those that has the highest ratio, you have to replace the having clause to get the max of the ratio rather than the max of total volume.
This will give you the items that have the highest total volume, so you might expect to have more than item, in case there was multiple items having the highest total volume, like in this updated fiddle demo. In this case, to get only one, you have to use LIMIT to return only one.