Optimize MySQL query used to find matches between two tables

On a project I'm working on, I have two tables:

  1. consumption: Contains historical orders from customers with fields specifying the features of the product they have bought (one product per row)
  2. product: Contains current product stock

The database engine is InnoDB.

Goals:

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:

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:

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.

Upvotes: 4

Views: 143

Answers (2)

Rick James
Rick James

Reputation: 142208

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.

Upvotes: 1

Evgeniy Belov
Evgeniy Belov

Reputation: 358

  1. why need LEFT JOIN client_has_group cli_gr ON cli_gr.client_id=cons.client_id it never used
  2. why need GROUP BY cons.id, prod.id if you select all fields maybe select only what you need
  3. 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
    
  4. maybe better do calculation count in background and add this field in product and consumption table.

Upvotes: 0

Related Questions