Optimize MySQL query used to find matches between two tables - mysql
On a project I'm working on, I have two tables:
consumption: Contains historical orders from customers with fields specifying the features of the product they have bought (one product per row)
product: Contains current product stock
The database engine is InnoDB.
Goals:
The application must show matches from both sides, I mean:
When I list current products stock, I want to show a column that displays how many historical orders match with a particular product
When I list the historical orders, I want to see how many products match with a particular historical order
Database structure for consumption and product tables plus other related tables:
CREATE TABLE `consumption` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`created_by_id` INT(11) NULL DEFAULT NULL,
`client_id` INT(11) NOT NULL,
`data_import_id` INT(11) NULL DEFAULT NULL,
`tmp_consumption_id` INT(11) NULL DEFAULT NULL,
`material_id` INT(11) NULL DEFAULT NULL,
`quality_id` INT(11) NULL DEFAULT NULL,
`thick` DECIMAL(10,3) NULL DEFAULT NULL,
`thick_max` DECIMAL(10,3) NULL DEFAULT NULL,
`width` DECIMAL(10,2) NULL DEFAULT NULL,
`width_max` DECIMAL(10,2) NULL DEFAULT NULL,
`long` INT(11) NULL DEFAULT NULL,
`long_max` INT(11) NULL DEFAULT NULL,
`purchase_price` DECIMAL(10,2) NULL DEFAULT NULL,
`sale_price` DECIMAL(10,2) NULL DEFAULT NULL,
`comments` VARCHAR(255) NULL DEFAULT NULL,
`annual_consumption` DECIMAL(10,3) NULL DEFAULT NULL,
`type` ENUM('consumption','request') NULL DEFAULT 'consumption',
`date_add` DATETIME NOT NULL,
`date_upd` DATETIME NOT NULL,
`covering_grammage` VARCHAR(64) NULL DEFAULT NULL,
`asp_sup_acab` VARCHAR(64) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `fk_consumption_client1` (`client_id`),
INDEX `created_by_id` (`created_by_id`),
INDEX `material_id` (`material_id`),
INDEX `quality_id` (`quality_id`),
CONSTRAINT `consumption_ibfk_1` FOREIGN KEY (`material_id`) REFERENCES `material` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT `consumption_ibfk_2` FOREIGN KEY (`quality_id`) REFERENCES `quality` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT `fk_consumption_client1` FOREIGN KEY (`client_id`) REFERENCES `client` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=30673
;
CREATE TABLE `product` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`warehouse_id` INT(11) NULL DEFAULT NULL,
`created_by_id` INT(11) NULL DEFAULT NULL,
`data_import_id` INT(11) NULL DEFAULT NULL,
`tmp_product_id` INT(11) NULL DEFAULT NULL,
`code` VARCHAR(32) NOT NULL,
`material_id` INT(11) NULL DEFAULT NULL,
`quality_id` INT(11) NULL DEFAULT NULL,
`covering_id` INT(11) NULL DEFAULT NULL,
`finish_id` INT(11) NULL DEFAULT NULL,
`source` VARCHAR(128) NULL DEFAULT NULL,
`thickness` DECIMAL(10,3) NULL DEFAULT NULL,
`width` INT(11) NULL DEFAULT NULL,
`tons` DECIMAL(10,3) NULL DEFAULT NULL,
`re` INT(11) NULL DEFAULT NULL,
`rm` INT(11) NULL DEFAULT NULL,
`a_percent` INT(11) NULL DEFAULT NULL,
`comments` VARCHAR(255) NULL DEFAULT NULL,
`price` DECIMAL(10,2) NULL DEFAULT NULL,
`deleted` TINYINT(1) NOT NULL DEFAULT '0',
`date_add` DATETIME NOT NULL,
`date_upd` DATETIME NOT NULL,
PRIMARY KEY (`id`),
INDEX `warehouse_id` (`warehouse_id`),
INDEX `material_id` (`material_id`),
INDEX `quality_id` (`quality_id`),
INDEX `covering_id` (`covering_id`),
INDEX `finish_id` (`finish_id`),
CONSTRAINT `product_ibfk_1` FOREIGN KEY (`material_id`) REFERENCES `material` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT `product_ibfk_2` FOREIGN KEY (`quality_id`) REFERENCES `quality` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT `product_ibfk_3` FOREIGN KEY (`covering_id`) REFERENCES `covering` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT `product_ibfk_4` FOREIGN KEY (`finish_id`) REFERENCES `finish` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT `product_ibfk_5` FOREIGN KEY (`warehouse_id`) REFERENCES `warehouse` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=740
;
CREATE TABLE `client` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`zone_id` INT(11) NULL DEFAULT NULL,
`zone2_id` INT(11) NULL DEFAULT NULL,
`code` VARCHAR(64) NOT NULL,
`business_name` VARCHAR(255) NULL DEFAULT NULL,
`fiscal_name` VARCHAR(255) NULL DEFAULT NULL,
`nif` VARCHAR(15) NULL DEFAULT NULL,
`contact_short_name` VARCHAR(128) NULL DEFAULT NULL,
`contact_full_name` VARCHAR(128) NULL DEFAULT NULL,
`email` VARCHAR(255) NULL DEFAULT NULL,
`group` VARCHAR(255) NULL DEFAULT NULL,
`status` TINYINT(1) NOT NULL DEFAULT '1',
`date_add` DATETIME NOT NULL,
`date_upd` DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `code_UNIQUE` (`code`),
INDEX `zone_id` (`zone_id`),
INDEX `zone2_id` (`zone2_id`),
CONSTRAINT `client_ibfk_1` FOREIGN KEY (`zone_id`) REFERENCES `zone` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=443
;
CREATE TABLE `client_group` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`code` VARCHAR(15) NOT NULL,
`date_add` DATETIME NOT NULL,
`date_upd` DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `code` (`code`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=49
;
CREATE TABLE `client_has_group` (
`client_id` INT(11) NOT NULL,
`group_id` INT(11) NOT NULL,
INDEX `client_id` (`client_id`),
INDEX `group_id` (`group_id`),
CONSTRAINT `client_has_group_ibfk_1` FOREIGN KEY (`client_id`) REFERENCES `client` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT `client_has_group_ibfk_2` FOREIGN KEY (`group_id`) REFERENCES `client_group` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;
CREATE TABLE `covering` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`code` VARCHAR(128) NOT NULL,
`group` VARCHAR(128) NULL DEFAULT NULL,
`equivalence` VARCHAR(128) NULL DEFAULT NULL,
`date_add` DATETIME NOT NULL,
`date_upd` DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `code_UNIQUE` (`code`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=55
;
CREATE TABLE `finish` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`code` VARCHAR(128) NOT NULL,
`group` VARCHAR(128) NULL DEFAULT NULL,
`equivalence` VARCHAR(128) NULL DEFAULT NULL,
`date_add` DATETIME NOT NULL,
`date_upd` DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `code_UNIQUE` (`code`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=42
;
CREATE TABLE `material` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`code` VARCHAR(128) NOT NULL,
`group` VARCHAR(128) NULL DEFAULT NULL,
`equivalence` VARCHAR(128) NULL DEFAULT NULL,
`date_add` DATETIME NOT NULL,
`date_upd` DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `code_UNIQUE` (`code`),
INDEX `group` (`group`),
INDEX `equivalence` (`equivalence`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=46
;
CREATE TABLE `quality` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`code` VARCHAR(128) NOT NULL,
`group` VARCHAR(128) NULL DEFAULT NULL,
`equivalence` VARCHAR(128) NULL DEFAULT NULL,
`date_add` DATETIME NOT NULL,
`date_upd` DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `code_UNIQUE` (`code`),
INDEX `group` (`group`),
INDEX `equivalence` (`equivalence`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=980
;
CREATE TABLE `user_filter` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`filter_type` ENUM('consumption','product') NOT NULL DEFAULT 'consumption',
`name` VARCHAR(255) NOT NULL,
`is_default` TINYINT(1) NOT NULL DEFAULT '0',
`client_status` TINYINT(1) NULL DEFAULT NULL,
`client_group` VARCHAR(45) NULL DEFAULT NULL,
`material` VARCHAR(15) NULL DEFAULT NULL,
`quality` VARCHAR(64) NULL DEFAULT NULL,
`thickness` VARCHAR(45) NULL DEFAULT NULL,
`width` VARCHAR(45) NULL DEFAULT NULL,
`tons` VARCHAR(45) NULL DEFAULT NULL,
`covering` VARCHAR(45) NULL DEFAULT NULL,
`finish` VARCHAR(45) NULL DEFAULT NULL,
`re` VARCHAR(45) NULL DEFAULT NULL,
`rm` VARCHAR(45) NULL DEFAULT NULL,
`a_percent` VARCHAR(45) NULL DEFAULT NULL,
`comments` VARCHAR(255) NULL DEFAULT NULL,
`price` VARCHAR(45) NULL DEFAULT NULL,
`warehouse` VARCHAR(45) NULL DEFAULT NULL,
`date` VARCHAR(45) NULL DEFAULT NULL,
`type` ENUM('consumption','request') NULL DEFAULT NULL,
`date_add` DATETIME NOT NULL,
`date_upd` DATETIME NOT NULL,
PRIMARY KEY (`id`),
INDEX `fk_user_filter_user1` (`user_id`),
INDEX `filter_type` (`filter_type`),
CONSTRAINT `fk_user_filter_user1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=5
;
CREATE TABLE `warehouse` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(128) NOT NULL,
`zone_id` INT(11) NULL DEFAULT NULL,
`zone2_id` INT(11) NULL DEFAULT NULL,
`date_add` DATETIME NOT NULL,
`date_upd` DATETIME NOT NULL,
PRIMARY KEY (`id`),
INDEX `zone_id` (`zone_id`),
INDEX `zone2_id` (`zone2_id`),
CONSTRAINT `warehouse_ibfk_1` FOREIGN KEY (`zone_id`) REFERENCES `zone` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=37
;
CREATE TABLE `zone` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`zone2_id` INT(11) NULL DEFAULT NULL,
`name` VARCHAR(128) NOT NULL,
`date_add` DATETIME NOT NULL,
`date_upd` DATETIME NOT NULL,
PRIMARY KEY (`id`),
INDEX `zone2_id` (`zone2_id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=49
;
What I have done to be able to find matches between the two tables:
I have created a LEFT JOIN query between consumption and product table (that is also joining with additional tables if required).
Looks something like this:
SELECT cons.`id` as `consumption_id`, cons.`client_id` as `consumption_client_id`, cons.`material_id` as `consumption_material_id`, cons.`quality_id` as `consumption_quality_id`, cons.`thick` as `consumption_thick`, cons.`thick_max` as `consumption_thick_max`, cons.`width` as `consumption_width`, cons.`width_max` as `consumption_width_max`, cons.`long` as `consumption_long`, cons.`long_max` as `consumption_long_max`, cons.`type` as `consumption_type`, cons.`date_add` as `consumption_date_add`, prod.`id` as `product_id`, prod.`warehouse_id` as `product_warehouse_id`, prod.`code` as `product_code`, prod.`material_id` as `product_material_id`, prod.`quality_id` as `product_quality_id`, prod.`covering_id` as `product_covering_id`, prod.`finish_id` as `product_finish_id`, prod.`thickness` as `product_thickness`, prod.`width` as `product_width`, prod.`tons` as `product_tons`
FROM consumption cons
INNER JOIN client cli
ON cli.id=cons.client_id
LEFT JOIN client_has_group cli_gr
ON cli_gr.client_id=cons.client_id
LEFT JOIN product prod
ON
(
(cons.material_id=prod.material_id)
OR
prod.material_id IN (
SELECT id FROM material WHERE `equivalence`=(
SELECT `equivalence` FROM material WHERE id=cons.material_id
)
AND `group`=(
SELECT `group` FROM material WHERE id=cons.material_id
)
)
)
AND
(
(cons.quality_id=prod.quality_id)
OR
prod.quality_id IN (
SELECT id FROM quality WHERE `equivalence`=(
SELECT `equivalence` FROM quality WHERE id=cons.quality_id
)
AND `group`=(
SELECT `group` FROM quality WHERE id=cons.quality_id
)
)
)
AND (prod.thickness >= (cons.thick - 0.1) AND prod.thickness <= (cons.thick_max + 0.1))
AND (prod.width >= (cons.width - 1000) AND prod.width <= (cons.width_max + 1000))
WHERE 1 > 0 AND prod.deleted=0 AND cli.status=1 AND cons.date_add >= '2017-10-08 00:00:00'
GROUP BY cons.id, prod.id
When I want to list products and show matches of consumptions per every product, I have a main query that simply lists the products, then I join that query with the previous query from above and count the matches grouping by product id.
SELECT t.*,
count(f.consumption_id) AS matchesCount
FROM `product` t
LEFT JOIN (...previous query here...) f ON f.product_id=t.id
GROUP BY t.id
Other notes/considerations:
The application uses a couple of fields that have the same name in both tables, in order to find matches using ON in the JOIN
The application also uses more complex business logic, for example, product material can be equal or can be inside of an equivalence table or group
The user can save personal filters, that why it is used the user_filter table, so as a user I can have multiple "searches" saved and quickly switch from one to the other
The matches have to be displayed LIVE, I mean, calculated on the fly, not by any cronjob because user filter will always change
The amount of data the application will be working with right now will be about 35k rows in consumption table and about 1.5k rows in the product table
The server where the application is hosted is a dedicated server (64GB RAM) running MySQL
I had good performance with 3k rows of consumptions and 100 products, now with 10k+ consumption and 600 products, starting to get gateway timeout from nginx. Guess queries take too long.
I already know that if the ON cause has a lot of conditions it will work faster because the results sets are smaller, but if the condition is very wide, it will give a timeout, I guess the resulting rows are too many. Maybe the join will produce millions of rows.
What I'd like to ask is:
Am I on the right path in order to do "live matches" of data between both tables? is using the JOIN a good solution? I cannot think of another way to do it.
Apart from trying to optimize queries and indexes, are there any server tweaking I could do to take full advantage of server hardware?
Any other tips or techniques from someone who has done something similar in another project?
Update 1: Adding here full query for listing products with consumption matches:
SELECT t.*,
count(f.consumption_id) AS matchesCount
FROM `product` t
LEFT JOIN (
SELECT cons.`id` as `consumption_id`, cons.`client_id` as `consumption_client_id`, cons.`material_id` as `consumption_material_id`, cons.`quality_id` as `consumption_quality_id`, cons.`thick` as `consumption_thick`, cons.`thick_max` as `consumption_thick_max`, cons.`width` as `consumption_width`, cons.`width_max` as `consumption_width_max`, cons.`long` as `consumption_long`, cons.`long_max` as `consumption_long_max`, cons.`type` as `consumption_type`, cons.`date_add` as `consumption_date_add`, prod.`id` as `product_id`, prod.`warehouse_id` as `product_warehouse_id`, prod.`code` as `product_code`, prod.`material_id` as `product_material_id`, prod.`quality_id` as `product_quality_id`, prod.`covering_id` as `product_covering_id`, prod.`finish_id` as `product_finish_id`, prod.`thickness` as `product_thickness`, prod.`width` as `product_width`, prod.`tons` as `product_tons`
FROM consumption cons
INNER JOIN client cli
ON cli.id=cons.client_id
LEFT JOIN client_has_group cli_gr
ON cli_gr.client_id=cons.client_id
LEFT JOIN product prod
ON
(
(cons.material_id=prod.material_id)
OR
prod.material_id IN (
SELECT id FROM material WHERE `equivalence`=(
SELECT `equivalence` FROM material WHERE id=cons.material_id
)
AND `group`=(
SELECT `group` FROM material WHERE id=cons.material_id
)
)
)
WHERE 1 > 0 AND prod.deleted=0 AND cli.status=1 AND cons.date_add >= '2017-10-08 00:00:00'
GROUP BY cons.id, prod.id
) f ON f.product_id=t.id
GROUP BY t.id
Query time: 00:02:41 (+ 0,078 sec. network).
Note: The subquery JOIN run separately produces 600k rows. I'm thinking to try to group it somehow in order to make it smaller.
Update 2: Major improvement achieved by making the count inside the subquery and so reducing the result set used for the JOIN
Basically the subquery instead of returning 600k+ rows, it only returns as much rows as products or consumptions, depending what you're looking for. For that, the matchesCount has been moved inside the subquery instead of outside, and the group by has been changed, depending what list you want to display.
This is how the final queries look like right now:
List consumption and count products that match each consumption:
SELECT SQL_NO_CACHE `t`.*,
IFNULL(f.matchesCount, 0) AS matchesCount
FROM `consumption` `t`
LEFT JOIN
(SELECT cons.`id` AS `consumption_id`,
cons.`client_id` AS `consumption_client_id`,
cons.`material_id` AS `consumption_material_id`,
cons.`quality_id` AS `consumption_quality_id`,
cons.`thick` AS `consumption_thick`,
cons.`thick_max` AS `consumption_thick_max`,
cons.`width` AS `consumption_width`,
cons.`width_max` AS `consumption_width_max`,
cons.`long` AS `consumption_long`,
cons.`long_max` AS `consumption_long_max`,
cons.`type` AS `consumption_type`,
cons.`date_add` AS `consumption_date_add`,
prod.`id` AS `product_id`,
prod.`warehouse_id` AS `product_warehouse_id`,
prod.`code` AS `product_code`,
prod.`material_id` AS `product_material_id`,
prod.`quality_id` AS `product_quality_id`,
prod.`covering_id` AS `product_covering_id`,
prod.`finish_id` AS `product_finish_id`,
prod.`thickness` AS `product_thickness`,
prod.`width` AS `product_width`,
prod.`tons` AS `product_tons`,
count(prod.`id`) AS matchesCount
FROM consumption cons
INNER JOIN client cli ON cli.id=cons.client_id
LEFT JOIN product prod ON ((cons.material_id=prod.material_id)
OR prod.material_id IN
(SELECT id
FROM material
WHERE `equivalence`=
(SELECT `equivalence`
FROM material
WHERE id=cons.material_id )
AND `group`=
(SELECT `group`
FROM material
WHERE id=cons.material_id ) ))
AND ((cons.quality_id=prod.quality_id)
OR prod.quality_id IN
(SELECT id
FROM quality
WHERE `equivalence`=
(SELECT `equivalence`
FROM quality
WHERE id=cons.quality_id )
AND `group`=
(SELECT `group`
FROM quality
WHERE id=cons.quality_id ) ))
AND (prod.thickness >= (cons.thick - 0.1)
AND prod.thickness <= (cons.thick_max + 0.1))
AND (prod.width >= (cons.width - 1000)
AND prod.width <= (cons.width_max + 1000))
WHERE 1 > 0
AND prod.deleted=0
AND cli.status=1
AND cons.date_add >= '2017-10-08 00:00:00'
GROUP BY cons.id) f ON f.consumption_id=t.id
GROUP BY t.id
List products and count consumptions that match each product:
SELECT SQL_NO_CACHE t.*,
IFNULL(f.matchesCount, 0) AS matchesCount
FROM `product` `t`
LEFT JOIN
(SELECT cons.`id` AS `consumption_id`,
cons.`client_id` AS `consumption_client_id`,
cons.`material_id` AS `consumption_material_id`,
cons.`quality_id` AS `consumption_quality_id`,
cons.`thick` AS `consumption_thick`,
cons.`thick_max` AS `consumption_thick_max`,
cons.`width` AS `consumption_width`,
cons.`width_max` AS `consumption_width_max`,
cons.`long` AS `consumption_long`,
cons.`long_max` AS `consumption_long_max`,
cons.`type` AS `consumption_type`,
cons.`date_add` AS `consumption_date_add`,
prod.`id` AS `product_id`,
prod.`warehouse_id` AS `product_warehouse_id`,
prod.`code` AS `product_code`,
prod.`material_id` AS `product_material_id`,
prod.`quality_id` AS `product_quality_id`,
prod.`covering_id` AS `product_covering_id`,
prod.`finish_id` AS `product_finish_id`,
prod.`thickness` AS `product_thickness`,
prod.`width` AS `product_width`,
prod.`tons` AS `product_tons`,
count(cons.`id`) AS matchesCount
FROM consumption cons
INNER JOIN client cli ON cli.id=cons.client_id
LEFT JOIN product prod ON cons.material_id=prod.material_id
AND cons.quality_id=prod.quality_id
WHERE 1 > 0
AND prod.deleted=0
AND cli.status=1
GROUP BY prod.id) f ON f.product_id=t.id
WHERE deleted=0
GROUP BY t.id
Both queries take less than 1 second to execute (each).
Note: I still use the previous queries in my application, for example, when I want a break down of the list of products that match a single consumption, or the other way around. In that case I already add a filter per consumption id or product id that reduces the size of the result set a lot.
If client_has_group is "many:1", that is the wrong way to do it. You don't need the extra table.
INT is always 4 bytes. Consider smaller datatypes. Eventually the size of the database may add to your problems.
Do you really need date_add and date_upd. They seem like clutter that you will never use.
Avoid IN ( SELECT ... ) where practical. Switch to JOIN or EXISTS.
Why so many tables with code + group + equivalence? Could they be a single group? Do you need all 3 columns? Do you need id since code is UNIQUE? There comes a point where a schema is "over-normalized" and performance suffers without helping space much.
OR is a performance killer in some contexts.
"Correlated subqueries" are useful in some situations, but this one is probably better done via a JOIN:
AND `group` = ( SELECT `group` FROM quality WHERE id=cons.quality_id )
Beware of aggregates (eg, COUNT) with JOIN; you may be getting an inflated value. This is because the JOIN happens first.
why need
LEFT JOIN client_has_group cli_gr ON cli_gr.client_id=cons.client_id
it never used
why need GROUP BY cons.id, prod.id if you select all fields maybe select only what you need
try this select, i think it will be more faster
SELECT count(*), prod.*
FROM consumption cons
INNER JOIN client cli ON cli.id=cons.client_id
INNER JOIN material m ON m.id=cons.material_id
INNER JOIN quality q ON q.id=cons.quality_id
LEFT JOIN product prod
ON
(
(cons.material_id=prod.material_id)
OR
prod.material_id IN (
SELECT id FROM material WHERE `equivalence`=m.equivalence
AND `group`=m.group
)
)
AND
(
(cons.quality_id=prod.quality_id)
OR
prod.quality_id IN (
SELECT id FROM quality WHERE `equivalence`=q.equivalence
AND `group`=q.group
)
)
AND (prod.thickness >= (cons.thick - 0.1) AND prod.thickness <= (cons.thick_max + 0.1))
AND (prod.width >= (cons.width - 1000) AND prod.width <= (cons.width_max + 1000))
WHERE 1 > 0 AND prod.deleted=0 AND cli.status=1 AND cons.date_add >= '2017-10-08 00:00:00'
group by prod.id
maybe better do calculation count in background and add this field in product and consumption table.
Related
MySql Using filesort when i using a group by
I have a little problem with optimizing a query, I have 2 tables, one which records the participation (participation) in a quiz, and the other which records the answer to each question (participation_rep), participation is linked to the campaign table. SELECT count(DISTINCT p.id) as number_of_participation FROM participation_rep prep INNER JOIN participation p ON p.id = prep.id_participation AND p.trash <> 1 WHERE prep.id_question IN (780,787,794,801,809) AND prep.trash <> 1 GROUP BY pp.id_campaign Explain of the query And the problem is that this request is very heavy to execute when there is a lot of data which is concerned by the request and I do not know how to optimize it. This query take 30-50ms to execute. Structure of table participation : CREATE TABLE IF NOT EXISTS `participation` ( `id` int(11) NOT NULL AUTO_INCREMENT, `id_campagne` int(11) NOT NULL, `id_identifiant` int(11) DEFAULT NULL, `firstname` varchar(255) DEFAULT NULL, `surname` varchar(255) DEFAULT NULL, `email` varchar(255) DEFAULT NULL, `date_p` date NOT NULL, `hour_p` time NOT NULL, `comment` text, `trash` tinyint(1) DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; Structure of table participation_rep : CREATE TABLE IF NOT EXISTS `participation_rep` ( `id` int(11) NOT NULL AUTO_INCREMENT, `id_participation` int(11) NOT NULL, `id_question` int(11) NOT NULL, `id_rep` int(11) NOT NULL, `trash` tinyint(1) DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `id_participation` (`id_participation`,`id_question`,`id_reponse`), KEY `id_question` (`id_question`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
sum function with join between two date ranges
hi friends i have two tables with name order and product order CREATE TABLE `order` ( `order_num` int(11) NOT NULL AUTO_INCREMENT, `date` date DEFAULT NULL, `time` time DEFAULT NULL, `cutomer_name` varchar(45) DEFAULT NULL, PRIMARY KEY (`order_num`) ) ENGINE=InnoDB AUTO_INCREMENT=235 DEFAULT CHARSET=latin1 And CREATE TABLE `product_order` ( `order_num` int(11) NOT NULL, `idproduct` int(11) NOT NULL, `quantity` tinyint(4) DEFAULT NULL, `sub_price` int(11) DEFAULT NULL, `price` int(11) DEFAULT NULL, `actual_price` int(11) DEFAULT NULL, PRIMARY KEY (`order_num`,`idproduct`), KEY `fk_product_order_order1_idx` (`order_num`), KEY `fk_product_order_product1_idx` (`idproduct`), CONSTRAINT `fk_product_order_order1` FOREIGN KEY (`order_num`) REFERENCES `order` (`order_num`) ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT `fk_product_order_product1` FOREIGN KEY (`idproduct`) REFERENCES `product` (`idproduct`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB DEFAULT CHARSET=latin1; i want to join both table and want to retrieve the sum of product_order.actual_price,product_order.sub_price and product_order.quantity between specific dates my query return zero rows and i don't know how to solve this problem here is my query Select `order`.date, Sum(product_order.actual_price) as actual_price_sum, Sum(product_order.sub_price) as sub_price_sum, Sum(product_order.quantity) as sold_quantity, (Sum(product_order.sub_price)-Sum(product_order.actual_price)) as profit From `order` Inner Join product_order On `order`.order_num = product_order.order_num WHERE CAST(`date` AS date) BETWEEN '2014-11-11' and '2015-11-11' Group By `order`.date
Big query on a big mysql database
I have a big query to count 3 distinct things on same data table: SELECT leagues.feed_league_id AS mleague, leagues.league_id AS mleague_id, leagues.league_name, countries.feed_name, (SELECT COUNT(DISTINCT m2.match_id) FROM `matches` AS m2 LEFT JOIN stats ON stats.match_id=m2.match_id LEFT JOIN leagues AS l1 ON m2.feed_league_id=l1.feed_league_id WHERE stats.tipo='Golos1ª2ª' AND m2.status='FT' AND (m2.home_ht_score*1+ m2.away_ht_score*1) >1 AND m2.home_ht_score REGEXP '[0-9]+' AND m2.away_ht_score REGEXP '[0-9]+' AND m2.feed_league_id=mleague ) AS Total, (SELECT COUNT(DISTINCT m3.match_id) FROM `matches` AS m3 LEFT JOIN stats ON stats.match_id=m3.match_id LEFT JOIN leagues AS l1 ON m3.feed_league_id=l1.feed_league_id WHERE stats.tipo='Golos1ª2ª' AND m3.status='FT' AND (m3.home_ht_score*1+ m3.away_ht_score*1) >1 AND (m3.home_ft_score*1+m3.away_ft_score*1)-(m3.home_ht_score*1+ m3.away_ht_score*1)>0 AND m3.home_ht_score REGEXP '[0-9]+' AND m3.away_ht_score REGEXP '[0-9]+' AND m3.feed_league_id=mleague ) AS Golos, (SELECT COUNT(DISTINCT m2.match_id) FROM `matches` AS m2 LEFT JOIN stats ON stats.match_id=m2.match_id LEFT JOIN leagues AS l1 ON m2.feed_league_id=l1.feed_league_id WHERE DATEDIFF(DATE(NOW()), date)<=4 AND stats.tipo='Golos1ª2ª' AND m2.status='FT' AND (m2.home_ht_score*1+ m2.away_ht_score*1) >1 AND m2.home_ht_score REGEXP '[0-9]+' AND m2.away_ht_score REGEXP '[0-9]+' AND m2.feed_league_id=mleague ) AS LastDays FROM leagues LEFT JOIN countries ON countries.country_id=leagues.country_id GROUP BY leagues.feed_league_id HAVING Total>20 ORDER BY Golos/Total DESC` Table "leagues" - 828 records CREATE TABLE IF NOT EXISTS `leagues` ( `league_id` int(11) NOT NULL AUTO_INCREMENT, `league_name` varchar(255) NOT NULL, `alt_league_name` varchar(255) NOT NULL, `league_season` varchar(255) NOT NULL, `feed_league_id` varchar(8) NOT NULL, `feed_sub_id` varchar(10) NOT NULL, `country_id` int(11) NOT NULL, PRIMARY KEY (`league_id`), KEY `league_name` (`league_name`), KEY `league_season` (`league_season`), KEY `feed_league_id` (`feed_league_id`), KEY `country_id` (`country_id`), KEY `feed_sub_id` (`feed_sub_id`), KEY `alt_league_name` (`alt_league_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=837 Table "matches" - 125K records CREATE TABLE IF NOT EXISTS `matches` ( `match_id` int(11) NOT NULL AUTO_INCREMENT, `date` date NOT NULL, `feed_match_id` varchar(255) NOT NULL, `status` varchar(10) NOT NULL, `time` varchar(10) NOT NULL, `updated` datetime NOT NULL, `home_team_id` int(11) NOT NULL, `away_team_id` int(11) NOT NULL, `home_ft_score` varchar(11) NOT NULL, `away_ft_score` varchar(11) NOT NULL, `home_ht_score` varchar(11) NOT NULL, `away_ht_score` varchar(11) NOT NULL, `country_id` int(11) NOT NULL, `league_id` int(11) NOT NULL, `feed_league_id` varchar(8) NOT NULL, `feed_sub_id` varchar(10) NOT NULL, `stage_id` int(11) NOT NULL, `week_number` int(11) NOT NULL, `n_updates` int(11) NOT NULL, PRIMARY KEY (`match_id`), KEY `date` (`date`), KEY `feed_match_id` (`feed_match_id`), KEY `status` (`status`), KEY `time` (`time`), KEY `updated` (`updated`), KEY `home_ft_score` (`home_ft_score`), KEY `away_ft_score` (`away_ft_score`), KEY `home_ht_score` (`home_ht_score`), KEY `away_ht_score` (`away_ht_score`), KEY `country_id` (`country_id`), KEY `league_id` (`league_id`), KEY `stage_id` (`stage_id`), KEY `week_number` (`week_number`), KEY `home_team_id` (`home_team_id`), KEY `away_team_id` (`away_team_id`), KEY `feed_league_id` (`feed_league_id`), KEY `feed_sub_id` (`feed_sub_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=125231 ; Table "stats" - 250K records CREATE TABLE IF NOT EXISTS `stats` ( `id` int(11) NOT NULL AUTO_INCREMENT, `match_id` int(11) NOT NULL, `stat` varchar(255) NOT NULL, `equipa` varchar(255) NOT NULL, `tipo_id` smallint(6) NOT NULL, PRIMARY KEY (`id`), KEY `match_id` (`match_id`), KEY `stat` (`stat`), KEY `equipa` (`equipa`), KEY `tipo_id` (`tipo_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=246459 ; Can someone help me simplifying this query. It takes too long to be executed. If you need more information, please ask. Thanks.
I don't even know where to begin. The basic problem is that you are counting in your SELECT clause. This means that for each record in leagues (main query), you are executing 3 other big queries (not to mention all the regex). You're not giving your server a chance. Move your counting from SELECT to FROM (make them join tables). UPDATE: Here's what I mean: SELECT leagues.feed_league_id, Total.cnt FROM leagues JOIN countries ON countries.country_id=leagues.country_id JOIN (SELECT l1.feed_league_id, COUNT(DISTINCT m2.match_id) as cnt FROM `matches` AS m2 LEFT JOIN stats ON stats.match_id=m2.match_id LEFT JOIN leagues AS l1 ON m2.feed_league_id=l1.feed_league_id WHERE stats.tipo='Golos1ª2ª' AND m2.status='FT' AND (m2.home_ht_score*1+ m2.away_ht_score*1) >1 AND m2.home_ht_score REGEXP '[0-9]+' AND m2.away_ht_score REGEXP '[0-9]+' GROUP BY l1.feed_league_id ) AS Total on Total.feed_league_id = leagues.feed_league_id GROUP BY leagues.feed_league_id As you can see this is only for Total, but it's the same for the other two big queries.
join count per chatroom
i have tried different join examples from w3c site, but could not solve my problem. All i could get working was: SELECT * FROM chatters_online WHERE TIME_TO_SEC(TIMEDIFF(NOW(),datumtijd )) < 300 But i want is a count of chatters per chatroom: room1(12),room12(2) etc. CREATE TABLE IF NOT EXISTS `chatters_online` ( `room` int(10) unsigned NOT NULL, `datumtijd` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `chatterID` int(10) unsigned NOT NULL DEFAULT '0', `nick` varchar(20) NOT NULL, UNIQUE KEY `unique_index` (`room`,`nick`), KEY `room` (`room`), KEY `nick` (`nick`), KEY `datumtijd` (`datumtijd`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1; CREATE TABLE IF NOT EXISTS `chatrooms` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `roomname` varchar(20) NOT NULL, `moderator` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `roomname` (`roomname`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=18 ; What i am looking for is the needed SQL for a count per chatroom.
JOIN and GROUP BY should give you the required results select CR.roomname, count(CO.chatterID) as ChattersCount FROM chatrooms CR JOIN chatters_online CO on CO.room = CR.id GROUP BY CR.roomname
SQL query; inner join on 4 tables
Is this the most efficient way of joining these 4 tables? Also is it possible to only have some rows of each tables selected? I tried changing * to a name of a column but only the columns from studentlist are allowed. SELECT c.classID, c.instrument, c.grade, u.ID, u.firstname, u.lastname, u.lastsongplayed, u.title FROM studentlist s INNER JOIN classlist c ON s.listID = c.classID INNER JOIN ( SELECT * FROM users u INNER JOIN library l ON u.lastsongplayed = l.fileID ) u ON s.studentID = u.ID WHERE teacherID =3 ORDER BY classID LIMIT 0 , 30 Database structure: CREATE TABLE IF NOT EXISTS `classlist` ( `classID` int(11) NOT NULL AUTO_INCREMENT, `teacherID` int(11) NOT NULL, `instrument` text, `grade` int(11) DEFAULT NULL, PRIMARY KEY (`classID`), KEY `teacherID_2` (`teacherID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=27 ; CREATE TABLE IF NOT EXISTS `studentlist` ( `listID` int(11) NOT NULL, `studentID` int(11) NOT NULL, KEY `teacherID` (`studentID`), KEY `studentID` (`studentID`), KEY `listID` (`listID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `users` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `email` varchar(60) NOT NULL, `password` varchar(60) NOT NULL, `firstname` text NOT NULL, `lastname` text NOT NULL, `sessionID` varchar(60) DEFAULT NULL, `lastlogin` time DEFAULT NULL, `registerdate` date NOT NULL, `isteacher` tinyint(1) DEFAULT NULL, `isstudent` tinyint(1) DEFAULT NULL, `iscomposer` tinyint(1) DEFAULT NULL, `lastsongplayed` int(11) NOT NULL, PRIMARY KEY (`ID`), UNIQUE KEY `ID` (`ID`), UNIQUE KEY `email` (`email`,`sessionID`), KEY `ID_2` (`ID`), KEY `ID_3` (`ID`), KEY `lastsongplayed` (`lastsongplayed`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=63 ; CREATE TABLE IF NOT EXISTS `library` ( `fileID` int(11) NOT NULL AUTO_INCREMENT, `userID` int(11) NOT NULL, `uploaddate` datetime NOT NULL, `title` varchar(60) NOT NULL, `OrigComposer` varchar(60) NOT NULL, `composer` varchar(60) NOT NULL, `genre` varchar(60) DEFAULT NULL, `year` year(4) DEFAULT NULL, `arrangement` varchar(60) DEFAULT NULL, PRIMARY KEY (`fileID`), KEY `userID` (`userID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=77 ;
Is this the most efficient way of joining these 3 tables? Your JOIN looks correct and you are joining on your keys. So this should be efficient. However, I would encourage you to analyze your query with EXPLAIN to determine additional optimizations. Is it possible to only have some rows of each tables selected? Yes. Change * to be the columns from each table you want. I encourage you to explicitly prefix them with the originating table. Depending on the columns you select, this could also make your query more performant. SELECT studentlist.studentID, users.email FROM ...