Select Sum from two joined tables - mysql

There are structures:
CREATE TABLE `invoices` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`date` date NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO `invoices` VALUES (1,'2018-09-22');
CREATE TABLE `products` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`invoice_id` int(10) unsigned NOT NULL,
`amount` decimal(10,2) unsigned NOT NULL,
`quantity` smallint(5) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO `products` VALUES (1,1,150.00,2),(2,1,60.00,3),(3,1,50.00,1);
CREATE TABLE `payments` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`invoice_id` int(10) unsigned NOT NULL,
`amount` decimal(10,2) unsigned NOT NULL,
`date` date NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO `payments` VALUES (1,1,400.00,'2018-09-23'),(2,1,80.00,'2018-09-23');
I have this query:
select i.id, sum(pr.amount * pr.quantity) as productAmount,
sum(pm.amount) as paymentAmount
from invoices as i
left join products as pr on pr.invoice_id=i.id
left join payments as pm on pm.invoice_id=i.id
group by i.id
and have this result:
+----+---------------+---------------+
| id | productAmount | paymentAmount |
+----+---------------+---------------+
| 1 | 1060.00 | 1440.00 |
+----+---------------+---------------+
1 row in set (0,00 sec)
However, I want to get the following result:
+----+---------------+---------------+
| id | productAmount | paymentAmount |
+----+---------------+---------------+
| 1 | 530.00 | 480.00 |
+----+---------------+---------------+
1 row in set (0,00 sec)
I want sum amount of products and sum amount of payments grouped by invoice.id.
What should be the query in this case?

I do face this kind of queries at times. Due to multiple joins, values from a particular table get duplicated, triplicated etc. To fix this, I normally do a small hack by dividing the sum (on a particular table) by the count of distinct Id(s) from the other table. This negates the effect of multiple duplicates happening.
Try the following query:
select i.id,
(sum(pr.amount * pr.quantity) / IF(count(distinct pm.id) > 0, count(distinct pm.id), 1) as productAmount,
(sum(pm.amount) / IF(count(distinct pr.id) > 0, count(distinct pr.id), 1) as paymentAmount
from invoices as i
left join products as pr on pr.invoice_id=i.id
left join payments as pm on pm.invoice_id=i.id
group by i.id

Use the below sub query to get your expect result
SELECT id,
(select sum(pr.amount * pr.quantity) from products as pr where pr.invoice_id=i.id ) as productAmt,
(select sum(amount) from payments where invoice_id=i.id ) as PaymentAmt
FROM `invoices` i order by id asc

Try this: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=3b496214bf0ffb517dcaa9be7f0b6bb7
select a.id,pramount,sum(amount) as paymentamount from
(select i.id,sum(pr.amount *pr.quantity) as pramount
from invoices as i
join products as pr on pr.invoice_id=i.id
group by i.id)a
join payments pm on a.id=pm.invoice_id
group by a.id

I can imagine a situation where an invoice is created a products is created but no payment, similarly an invoice is created a payment is created but no products
.So you could create sub queries for the products and payments
drop table if exists i,pr,pm;
CREATE TABLE `i` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`date` date NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO `i` VALUES (1,'2018-09-22'),(2,'2018-09-22'),(3,'2018-09-22'),(4,'2018-09-22');
CREATE TABLE `pr` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`invoice_id` int(10) unsigned NOT NULL,
`amount` decimal(10,2) unsigned NOT NULL,
`quantity` smallint(5) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO `pr` VALUES (1,1,150.00,2),(2,1,60.00,3),(3,1,50.00,1),(4,3,50.00,1);
CREATE TABLE `pm` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`invoice_id` int(10) unsigned NOT NULL,
`amount` decimal(10,2) unsigned NOT NULL,
`date` date NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO `pm` VALUES (1,1,400.00,'2018-09-23'),(2,1,80.00,'2018-09-23'),(3,4,80.00,'2018-09-23');
select i.id, (select sum(pr.amount * pr.quantity) from pr where pr.invoice_id = i.id) as productAmount,
(select sum(pm.amount) from pm where pm.invoice_id = i.id) as paymentAmount
from i
having productAmount > 0 or paymentAmount > 0;
You may (or may not want the having clause)
+----+---------------+---------------+
| id | productAmount | paymentAmount |
+----+---------------+---------------+
| 1 | 530.00 | 480.00 |
| 3 | 50.00 | NULL |
| 4 | NULL | 80.00 |
+----+---------------+---------------+
3 rows in set (0.00 sec)

Related

How can i optimize this mysql query in social table structure?

Scheme
CREATE TABLE IF NOT EXISTS `content` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`entity_uid` int(11) NOT NULL,
....
PRIMARY KEY (`uid`),
UNIQUE KEY `insert_at` (`insert_at`),
KEY `fk_entity` (`entity_uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `entity` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `entity_comment` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`entity_uid` int(11) NOT NULL,
`user_uid` int(11) NOT NULL,
....
PRIMARY KEY (`uid`),
KEY `fk_entity` (`entity_uid`),
KEY `fk_user` (`user_uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `entity_like` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`entity_uid` int(11) NOT NULL,
`user_uid` int(11) NOT NULL,
....
PRIMARY KEY (`uid`),
KEY `fk_entity` (`entity_uid`),
KEY `fk_user` (`user_uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `entity_share` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`entity_uid` int(11) NOT NULL,
`user_uid` int(11) NOT NULL,
`share_type` int(2) NOT NULL,
....
PRIMARY KEY (`uid`),
KEY `fk_entity` (`entity_uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `entity_view` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`entity_uid` int(11) NOT NULL,
`user_uid` int(11) NOT NULL,
....
PRIMARY KEY (`uid`),
KEY `fk_entity` (`entity_uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `user` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(30) NOT NULL,
....
PRIMARY KEY (`uid`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query 1 - using Left Join [Query took 16.3032 sec]
SELECT c . * , COUNT(DISTINCT ev.uid) AS view_count, COUNT( DISTINCT el.uid ) AS like_count, COUNT( DISTINCT ec.uid ) AS reply_count, COUNT( DISTINCT es.uid ) AS share_count
FROM content AS c
LEFT JOIN entity_view AS ev ON ev.entity_uid = c.entity_uid
LEFT JOIN entity_like AS el ON el.entity_uid = c.entity_uid
LEFT JOIN entity_share AS es ON es.entity_uid = c.entity_uid
LEFT JOIN entity_comment AS ec ON ec.entity_uid = c.entity_uid
GROUP BY c.uid
EDIT - Explain
Query 2 - using Sub query [Query took 0.0069 sec]
SELECT c.*,
(SELECT COUNT(*) FROM entity_view WHERE entity_uid = c.entity_uid) AS view_count ,
(SELECT COUNT(*) FROM entity_like WHERE entity_uid = c.entity_uid) AS like_count ,
(SELECT COUNT(*) FROM entity_comment WHERE entity_uid = c.entity_uid) AS reply_count ,
(SELECT COUNT(*) FROM entity_share WHERE entity_uid = c.entity_uid) AS share_count
FROM content AS c
EDIT - Explain
Result
uid | data of content | view_count | like_count | reply_count | share_count |
-----------------------------------------------------------------------------
1 | ..... | 100 | 10 | 5 | 6 |
-----------------------------------------------------------------------------
2 | ..... | 200 | 20 | 20 | 3 |
-----------------------------------------------------------------------------
3 | ..... | 300 | 10 | 10 | 2 |
-----------------------------------------------------------------------------
Explain
Storage Engine : InnoDB
entity_{action} : Insert occurs when user {action} occurs.(e.g) entity_view is insertion occurs when user sees the content.
Question
How can I optimize more in the above mysql query?
I run the query in two ways and got the results above.
This proved that subquery is much better.
Is there a way to a get better performance than subquery like this table structure? And why is left join so bad?

Enhancing performance when applying multiple joins in mysql

I have 3 tables:
Nutritions_facts table which is static table that have all nutrition nutrition_id and nutrition_ename
Products_info table which has products info and the columns are product_id, product_ename, brand
product_nutrition_facts
which is mediator table that links the product with its nutrition facts with their values . The structure is product_id, nutrition_id, nutrition_value .. Each product can have 1 row or more depending on number of nutrition facts it has.
Here is a real testing example
Nutrition_facts table
nutrition_id |nutrition_ename
1 | caloreis
2 | fat
3 | sugar
4 | salt
Products_info table
product_id| product_ename | brand
1 | Nutella Hazelnut Cocoa | Nutella
2 | Nutella Jar | Nutella
product_nutrition_facts table
product_id | nutrition_id | nutrition_value
1 | 1 | 200
1 | 2 | 15
1 | 3 | 2
1 | 4 | 11
2 | 1 | 200
2 | 2 | 15
2 | 3 | 12
2 | 4 | 11
I need to make query that returns me the products' name with value of sugar is less than or equla 15 and salt less than or equal 140
I build a query that return correct values but it takes long time to process. Can someone suggest edits to enhance the performance please..
SELECT DISTINCT p.product_id, p.brand, p.e_name, p.image_low
FROM products_info p
JOIN product_nutrition_facts pn ON p.product_id = pn.product_id
WHERE p.brand = 'AL MARAI'
AND (
(
p.product_id
IN (
SELECT product_id
FROM product_nutrition_facts pn
WHERE pn.nutrition_id =3
AND pn.nutrition_value <=15
)
)
AND (
p.product_id
IN (
SELECT product_id
FROM product_nutrition_facts pn
WHERE pn.nutrition_id =4
AND pn.nutrition_value <=140
)
)
)
AND (
pn.nutrition_id =3
OR pn.nutrition_id =4
)
EDITS
CREATE TABLE `products_info` (
`product_id` int(11) NOT NULL AUTO_INCREMENT,
`image_low` varchar(400) DEFAULT NULL,
`e_name` varchar(200) DEFAULT NULL,
PRIMARY KEY (`product_id`),
UNIQUE KEY `product_id_UNIQUE` (`product_id`)
) ENGINE=InnoDB AUTO_INCREMENT=249292 DEFAULT CHARSET=utf8
CREATE TABLE `product_nutrition_facts` (
`prod_nut_id` int(11) NOT NULL AUTO_INCREMENT,
`product_id` int(11) DEFAULT NULL,
`nutrition_id` int(11) DEFAULT NULL,
`nutrition_value` varchar(25) DEFAULT NULL,
`unit_id` int(11) DEFAULT NULL,
`parent` int(11) DEFAULT NULL,
`serving_size` varchar(145) DEFAULT NULL,
`serving_size_unit` int(11) DEFAULT NULL,
`no_nutrition_facts` int(11) NOT NULL,
`added_by` varchar(145) DEFAULT NULL,
`last_update` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`inserted_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_by` varchar(150) NOT NULL,
PRIMARY KEY (`prod_nut_id`),
UNIQUE KEY `prod_nut_id_UNIQUE` (`prod_nut_id`),
KEY `nutrition_id_fk_idx` (`nutrition_id`),
KEY `unit_Fk_idx` (`unit_id`),
KEY `unit_fk1_idx` (`serving_size_unit`),
KEY `product_idFK` (`product_id`)
) ENGINE=InnoDB AUTO_INCREMENT=580809 DEFAULT CHARSET=utf8
CREATE TABLE `nutrition_facts` (
`nutrition_id` int(11) NOT NULL AUTO_INCREMENT,
`nutrition_aname` varchar(145) DEFAULT NULL,
`nutrition_ename` varchar(145) DEFAULT NULL,
`alternative_name` varchar(145) DEFAULT NULL,
`unit` varchar(8) NOT NULL,
`daily_value` int(11) NOT NULL,
`nut_order` int(2) NOT NULL,
`is_child` int(1) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`nutrition_id`)
) ENGINE=InnoDB AUTO_INCREMENT=53 DEFAULT CHARSET=utf8
Try to add indexes product_nutrition_facts (nutrition_id,nutrition_value,product_id), product_nutrition_facts (product_id,nutrition_id,nutrition_value), products_info (brand) and perfom query
SELECT p.*
FROM products_info p
join product_nutrition_facts pn1 on
pn1.product_id=p.product_id
AND pn1.nutrition_id=3
AND pn1.nutrition_value<=15
join product_nutrition_facts pn2 on
pn2.product_id=p.product_id
AND pn2.nutrition_id=4
AND pn2.nutrition_value<=140
where
p.brand = 'AL MARAI'
IN ( SELECT ... ) -- Does not optimize well; turn into JOIN (as Max suggests)
"Overnormalization" -- Nutrition_facts can be eliminated; simply use the nutrition_names in place of nutrition_id.

unique records in mysql one-to-many join without DISTINCT or GROUP BY

Here's the basic query:
SELECT
some_columns
FROM
d
JOIN
m ON d.id = m.d_id
JOIN
s ON s.id = m.s_id
JOIN
p ON p.id = s.p_id
WHERE
some_criteria
ORDER BY
d.date DESC
LIMIT 25
Table m can contain multiple s_ids per each d_id. Here's a super simple example:
+--------+--------+------+
| id | d_id | s_id |
+--------+--------+------+
| 317354 | 291220 | 642 |
| 317355 | 291220 | 32 |
+--------+--------+------+
2 rows in set (0.00 sec)
Which we want. But, obviously, it's producing duplicate d records in this particular query.
These tables have lots of columns, and I need to edit these down due to the sensitive nature of the data, but here's the basic structure as it pertains to this query:
| d | CREATE TABLE `d` (
`id` bigint(22) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
KEY `date` (`date`)
) ENGINE=InnoDB |
| m | CREATE TABLE `m` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`d_id` bigint(20) NOT NULL,
`s_id` bigint(20) NOT NULL,
`is_king` binary(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `d_id` (`d_id`),
KEY `is_king` (`is_king`),
KEY `s_id` (`s_id`)
) ENGINE=InnoDB |
| s | CREATE TABLE `s` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`p_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `p_id` (`p_id`)
) ENGINE=InnoDB |
| p | CREATE TABLE `p` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB |
Now, previously, we had a GROUP BY d.id in place to grab uniques. The data here are now huge, so we can no longer realistically do that. SELECT DISTINCT d.id is even slower.
Any ideas? Everything I come up with creates a problem elsewhere.
Does changing "JOIN m ON d.id = m.d_id" to "LEFT JOIN m ON d.id = m.d_id" accomplish what you're looking for here?
I'm not sure I understand your goal clearly, but "table m contains many rows per each d" immediately has me wondering if you should be using some other type of join to accomplish your ends.

Complex issue with MySQL queries

OK, so I'm facing this extremely complicated issue and since I'm not a guru with MySQL I'd definitely need your input on that.
Let's say we've got a database, created using the code below (I'm pasting the creation code - of just the absolutely-necessary tables - to avoid pasting all the tables) :
DROP TABLE IF EXISTS `Jeweller`.`Orders`;
CREATE TABLE `Jeweller`.`Orders` (
`id` int(11) unsigned NOT NULL,
`date` date DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `Jeweller`.`Product_categories`;
CREATE TABLE `Jeweller`.`Product_categories` (
`id` int(11) unsigned NOT NULL,
`name` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `Jeweller`.`Product_orders`;
CREATE TABLE `Jeweller`.`Product_orders` (
`order_id` int(11) unsigned NOT NULL,
`product_id` int(11) unsigned NOT NULL,
`quantity` int(11),
`value` float,
FOREIGN KEY (`order_id`) REFERENCES `Jeweller`.`Orders`(`id`),
FOREIGN KEY (`product_id`) REFERENCES `Jeweller`.`Products`(`id`),
CHECK (`quantity`>0),
CHECK (`value`>0)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `Jeweller`.`Product_returns`;
CREATE TABLE `Jeweller`.`Product_returns` (
`sale_id` int(11) unsigned NOT NULL,
`product_id` int(11) NOT NULL,
`date` date DEFAULT NULL,
`quantity` int(11),
`value` float,
FOREIGN KEY (`sale_id`) REFERENCES `Jeweller`.`Sales`(`id`),
FOREIGN KEY (`product_id`) REFERENCES `Jeweller`.`Products`(`id`),
CHECK (`quantity`>0),
CHECK (`value`>0)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `Jeweller`.`Product_sales`;
CREATE TABLE `Jeweller`.`Product_sales` (
`sale_id` int(11) unsigned NOT NULL,
`product_id` int(11) NOT NULL,
`quantity` int(11),
`value` float,
FOREIGN KEY (`sale_id`) REFERENCES `Jeweller`.`Sales`(`id`),
FOREIGN KEY (`product_id`) REFERENCES `Jeweller`.`Products`(`id`),
CHECK (`quantity`>0),
CHECK (`value`>0)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `Jeweller`.`Products`;
CREATE TABLE `Jeweller`.`Products` (
`id` int(11) unsigned NOT NULL,
`product_category_id` int(11) NOT NULL,
`seller_id` int(11) NOT NULL,
`name` varchar(100) NOT NULL,
`description` text,
PRIMARY KEY (`id`),
FOREIGN KEY (`product_category_id`) REFERENCES `Jeweller`.`Product_categories`(`id`),
FOREIGN KEY (`seller_id`) REFERENCES `Jeweller`.`Sellers`(`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `Jeweller`.`Sales`;
CREATE TABLE `Jeweller`.`Sales` (
`id` int(11) unsigned NOT NULL,
`date` date DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Now, given that we define Profit as :
Sales - Returns - Orders
How would you go about a query to fetch :
Profit by month AND product_category, only for the year 2013.
For testing purposes, here's the full DB Creation code as well as the DB Population code (with some demo data). (SQLFiddle link)
P.S.
The actual code is kinda different (the above is just an example - though a 100% loyal one)
After several attempts I've managed to just filter 2013 sales/orders/etc... I've even managed to get Profit by product (though it took some endless joins, left outer joins, etc... lol)... However this looks much more complicated. Any ideas?
Here's an approximation of your schema...
DROP TABLE IF EXISTS orders;
CREATE TABLE orders
( order_id int(11) unsigned NOT NULL auto_increment
, date date DEFAULT NULL
, PRIMARY KEY (order_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO orders VALUES
(NULL,'2013-01-01'),
(NULL,'2013-01-01'),
(NULL,'2013-02-02'),
(NULL,'2013-02-03'),
(NULL,'2013-03-05'),
(NULL,'2013-06-07');
DROP TABLE IF EXISTS product_orders;
CREATE TABLE product_orders
( order_id int unsigned NOT NULL
, product_id int unsigned NOT NULL
, quantity int NOT NULL DEFAULT 1
, value DECIMAL(5,2)
, PRIMARY KEY(order_id,product_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO product_orders VALUES
(1,101,1,100),
(1,102,1,50),
(2,101,2,200),
(3,101,1,100),
(4,102,2,100),
(4,103,3,150),
(5,104,1,300),
(6,102,1,50),
(6,103,2,100),
(6,104,1,300);
DROP TABLE IF EXISTS product_returns;
CREATE TABLE product_returns
( sale_id int unsigned NOT NULL
, product_id int NOT NULL
, date date DEFAULT NULL
, quantity int NOT NULL DEFAULT 1
, value DECIMAL(5,2)
, PRIMARY KEY(sale_id,product_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO product_returns VALUES
(21,101,'2013-01-04',2,200),
(22,102,'2013-03-06',1,50),
(22,103,'2013-05-08',1,50),
(23,104,'2013-06-09',1,300);
DROP TABLE IF EXISTS product_sales;
CREATE TABLE product_sales
( sale_id int unsigned NOT NULL
, product_id int NOT NULL
, quantity int NOT NULL
, value DECIMAL(5,2)
, PRIMARY KEY(sale_id,product_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO product_sales VALUES
(20,101,1,100),
(20,102,1,50),
(21,101,3,300),
(22,101,1,100),
(22,102,2,100),
(22,103,1,50),
(23,103,2,100),
(23,104,2,600);
DROP TABLE IF EXISTS products;
CREATE TABLE products
( product_id int unsigned NOT NULL AUTO_INCREMENT
, product_category_id int NOT NULL
, name varchar(100) NOT NULL
, description text NULL
, PRIMARY KEY (product_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO products VALUES
(101,1,'donuts','Mmm, donuts'),
(102,2,'buzz Cola','Mmm, donuts'),
(103,2,'duff beer','Can\'t get enough'),
(104,1,'Krusty-O\'s','Yum, yum');
DROP TABLE IF EXISTS sales;
CREATE TABLE sales
( sale_id int NOT NULL
, date date DEFAULT NULL
, PRIMARY KEY (sale_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO sales VALUES
(20,'2013-01-12'),
(21,'2013-02-15'),
(22,'2013-03-17'),
(23,'2013-05-18');
...and a possible query...
SELECT p.product_category_id
, MONTH(date) month
, SUM(value) profit
FROM
( SELECT product_id,value, date
FROM product_sales ps
JOIN sales s
ON s.sale_id = ps.sale_id
UNION ALL
SELECT product_id,value*-1,date FROM product_returns
UNION ALL
SELECT product_id,value*-1,date
FROM product_orders po
JOIN orders o
ON o.order_id = po.order_id
) x
JOIN products p
ON p.product_id = x.product_id
WHERE YEAR(date) = 2013
GROUP
BY p.product_category_id
, MONTH(date);
+---------------------+-------+---------+
| product_category_id | month | profit |
+---------------------+-------+---------+
| 1 | 1 | -400.00 |
| 1 | 2 | 200.00 |
| 1 | 3 | -200.00 |
| 1 | 5 | 600.00 |
| 1 | 6 | -600.00 |
| 2 | 1 | 0.00 |
| 2 | 2 | -250.00 |
| 2 | 3 | 100.00 |
| 2 | 5 | 50.00 |
| 2 | 6 | -150.00 |
+---------------------+-------+---------+
...and an sqlfiddle of same :http://www.sqlfiddle.com/#!2/22a1d/1
In simplest terms, if restructuring is undesirable, then I would do a simple query to determine the value of orders, returns, and sales seperately and then join those together. This could be done using UNION and subqueries as in the following example : SQLFiddle
I've also taken the liberty of swapping the FLOATs for DECIMALs. There is probably room for improvement on indexes and the like but this should put you on a good track for determining the sums. If you look at the subquery you'll see that ORDER and RETURN selects are selecting a negative value as per your requirement.
One potential pitfall is that any records for which the record from Product has been deleted would not be included. This could be avoided by changing the Product joins into LEFT JOINs and handling the NULL value for product_category_id appropriately. Decided to add this into the latest example, though if the rows from Product are NEVER deleted, then INNER JOIN will suffice
SELECT
d.thisMonth,
d.product_category_id,
SUM(d.sumValue)
FROM (
(
-- Get the order value
SELECT
'order' AS valueType,
MONTH(o.date) AS thisMonth,
p.product_category_id,
SUM(-po.value * po.quantity) AS sumValue
FROM Orders o
INNER JOIN Product_orders po
ON po.order_id = o.id
LEFT JOIN Products p
ON p.id = po.product_id
WHERE o.date BETWEEN '2013-01-01' AND '2013-12-31'
GROUP BY
thisMonth,
product_category_id
) UNION ALL (
-- Get the sales value
SELECT
'sale' AS valueType,
MONTH(s.date) AS thisMonth,
p.product_category_id,
SUM(ps.value * ps.quantity) AS sumValue
FROM Sales s
INNER JOIN Product_sales ps
ON ps.sale_id = s.id
INNER JOIN Products p
ON p.id = ps.product_id
WHERE s.date BETWEEN '2013-01-01' AND '2013-12-31'
GROUP BY
thisMonth,
product_category_id
) UNION ALL (
-- Get the return value
SELECT
'return' AS valueType,
p.product_category_id,
MONTH(pr.date) AS thisMonth,
SUM(-pr.value * pr.quantity) AS sumValue
FROM Product_returns pr
INNER JOIN Products p
ON p.id = pr.product_id
WHERE pr.date BETWEEN '2013-01-01' AND '2013-12-31'
GROUP BY
thisMonth,
product_category_id
)
) d
GROUP BY
d.thisMonth,
d.product_category_id;

SQL 'merging' columns from result sets into one result set

I've been pulling my hair about how to write a particular view within the constraints of MySQL.
The following tables and columns are of importance:
CREATE TABLE `invoices` (
`id` int(10) unsigned NOT NULL AUTO INCREMENT,
PRIMARY KEY (`id`)
)
-- Joins payments to invoices. The sum of all `invoice_currency_value`s is the balance paid towards an invoice.
CREATE TABLE `financial_transactions_invoices` (
`id` int(10) unsigned NOT NULL AUTO INCREMENT,
`invoice` int(10) unsigned NOT NULL,
`invoice_currency_value` decimal(8,2) unsigned NOT NULL,
PRIMARY KEY (`id`)
)
-- Lists items (services) available to purchase.
CREATE TABLE `items` (
`id` int(10) unsigned NOT NULL AUTO INCREMENT,
`value` decimal(8,2) unsigned NOT NULL
PRIMARY KEY (`id`)
)
-- Each instance represents that the `item` has been purchased.
CREATE TABLE `item_instances` (
`id` int(10) unsigned NOT NULL AUTO INCREMENT,
`invoice` int(10) unsigned NOT NULL,
`item` int(10) unsigned NOT NULL,
`invoice_currency_rate` decimal(11,5) unsigned NOT NULL,
PRIMARY KEY (`id`)
)
-- Any number of tax instances can exist for an item instance and indicate this tax has been applied to the associated item instance.
CREATE TABLE `tax_instances` (
`id` int(10) unsigned NOT NULL AUTO INCREMENT,
`item_instance` int(10) unsigned NOT NULL,
`value` decimal(8,2) unsigned NOT NULL,
PRIMARY KEY (`id`)
)
Now, I need a view that lists for each row,
the invoice number
the total value of the invoice
the total tax on the invoice
and the total value of payments made towards the invoice
However, I can't figure out how to get these three separate queries into the same result set of one row per invoice, e.g.
inv_no total_value total_tax payments
1 150 5 120
2 120 10 20
3 10 0 10
4 1000 150 1150
I have written the following query which produces the desired result, but due to the 'no subquery' rule in MySQL views, it is not acceptable.
SELECT `invoice_id`, SUM(`total_value`) AS `total_value`, SUM(`total_tax`) AS `total_tax`,
SUM(`paid_balance`) AS `paid_balance`
FROM
(SELECT `invoices`.`id` AS `invoice_id`, SUM(`items`.`value` * `item_instances`.`invoice_currency_rate`) AS `total_value`,
NULL AS `total_tax`, NULL AS `paid_balance`
FROM `items`
JOIN `item_instances` ON `items`.`id` = `item_instances`.`item`
JOIN `invoices` ON `item_instances`.`invoice` = `invoices`.`id`
GROUP BY `invoices`.`id`
UNION
SELECT `invoices`.`id`, NULL, SUM(`tax_instances`.`value`), NULL
FROM `tax_instances`
JOIN `item_instances` ON `tax_instances`.`item_instance` = `item_instances`.`id`
JOIN `invoices` ON `item_instances`.`invoice` = `invoices`.`id`
GROUP BY `invoices`.`id`
UNION
SELECT `invoices`.`id`, NULL, NULL, SUM(`financial_transactions_invoices`.`invoice_currency_value`)
FROM `financial_transactions_invoices`
JOIN `invoices` ON `financial_transactions_invoices`.`invoice` = `invoices`.`id`
GROUP BY `invoices`.`id`) AS `components`
GROUP by `invoice_id`;
Without tackling the problem in the way I have, I can't think of any other way I can do it within MySQL.
Any ideas? Appreciate any help.
You could create two views. One with the UNION Subquery, and one with the outer query.