Оптимизировать запрос MySQL, используемый для поиска совпадений между двумя таблицами - PullRequest
0 голосов
/ 08 октября 2018

В проекте, над которым я работаю, у меня есть две таблицы:

  1. consumption: содержит исторические заказы клиентов с полями, в которых указаны характеристики купленного ими продукта (один продукт настрока)
  2. product: содержит текущий товарный запас

Ядром базы данных является InnoDB.

Цели:

  • Приложениедолжен отображать совпадения с обеих сторон, я имею в виду:
  • Когда я перечисляю текущий запас товаров, я хочу показать столбец, который отображает, сколько исторических заказов соответствуют определенному товару
  • Когда я перечисляюисторические заказы, я хочу увидеть, сколько продуктов соответствует определенному историческому заказу

Структура базы данных для таблиц consumption и product плюс другие связанные таблицы:

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
;

Что я сделал, чтобы найти совпадения между двумя таблицами:

Я создал запрос LEFT JOIN между таблицами consumption и product (который также объединяется с дополнительными таблицами, если требуется).

выглядит примерно так:

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  

Когда я хочу перечислить продукты и показать совпадения потребления для каждого продукта, у меня есть основной запрос, который просто перечисляет продукты, затем я объединяю этот запрос с предыдущимзапрос сверху и подсчет совпадений по номеру товара.

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

Другие примечания / соображения:

  • Приложение использует несколько полей с одинаковыми именами в обеих таблицах, чтобы найти совпадения с помощью ON вJOIN
  • Приложение также использует более сложную бизнес-логику, например, материал продукта может быть равным или может быть внутри таблицы или группы эквивалентности
  • Пользователь может сохранять личные фильтры, которыепочему используется таблица user_filter, поэтому как пользователь я могу сохранить несколько «поисков» и быстро переключаться с одного на другой
  • Соответствия должны отображаться в реальном времени, я имею в виду, рассчитанные налетать, а не с помощью какого-либо хамства, потому что пользовательский фильтр всегда будет меняться
  • Объем данных, с которыми приложение будет работать прямо сейчас, составит около 35 тыс. строк в таблице потребления и около 1,5 тыс. строк в таблице продуктов
  • Сервер, на котором размещено приложение, - это выделенный сервер (64 ГБ ОЗУ) под управлением MySQL

У меня была хорошая производительность с 3k строкпотребления и 100 продуктов, теперь с потреблением 10 000+ и 600 продуктами, начинают получать время ожидания шлюза от nginx.Предположим, что запросы занимают слишком много времени.

Я уже знаю, что если причина ON имеет много условий, она будет работать быстрее, потому что наборы результатов меньше, но если условие очень широкое, это дасттайм-аут, я думаю, что в результате строки слишком много.Может быть, объединение приведет к миллионам строк.

Я хотел бы спросить:

  • Я на правильном пути, чтобы выполнять "живые совпадения" данных междуобе таблицы?Является ли использование JOIN хорошим решением?Я не могу придумать другого способа сделать это.
  • Кроме попыток оптимизировать запросы и индексы, есть ли какие-либо настройки сервера, которые я мог бы сделать, чтобы в полной мере использовать преимущества серверного оборудования?
  • Любые другие советыили методы от кого-то, кто сделал нечто подобное в другом проекте?

Обновление 1: добавление здесь полного запроса для перечисления продуктов с соответствиями потребления:

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

Время запроса: 00:02:41 (+ 0,078 с. Сеть).

Примечание. Подзапрос JOIN, запускаемый отдельно, создает 600 тыс. Строк.Я собираюсь как-то сгруппировать его, чтобы уменьшить его.

Обновление 2: Значительное улучшение достигается за счет подсчета внутри подзапроса и уменьшения набора результатов, используемого для JOIN

По сути, подзапрос вместо возврата 600k + строк возвращает только столько строк, сколько продуктов или потреблений, в зависимости от того, что вы ищете.Для этого matchCount был перемещен внутри подзапроса, а не снаружи, и группа была изменена в зависимости от того, какой список вы хотите отобразить.

Вот так выглядят окончательные запросы прямо сейчас:

Список продуктов потребления и подсчет, которые соответствуют каждому потреблению:

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

Список продуктов и подсчет потребления, которые соответствуют каждому продукту:

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

Оба запроса выполняются менее чем за 1 секунду (каждый).

Примечание. Я по-прежнему использую предыдущие запросы в своем приложении, например, когда я хочу разбитьсписок продуктов, которые соответствуют одному потреблению, или наоборот.В этом случае я уже добавляю фильтр для идентификатора потребления или идентификатора продукта, который значительно уменьшает размер набора результатов.

Ответы [ 2 ]

0 голосов
/ 10 октября 2018

Если client_has_group - это «много: 1», это неправильный способ сделать это.Вам не нужна дополнительная таблица.

INT всегда 4 байта.Рассмотрим меньшие типы данных.В конечном итоге размер базы данных может добавить к вашим проблемам.

Вам действительно нужно date_add и date_upd.Они кажутся беспорядком, который вы никогда не будете использовать.

Избегайте IN ( SELECT ... ) там, где это возможно.Переключитесь на JOIN или EXISTS.

Почему так много таблиц с кодом + группа + эквивалентность?Могут ли они быть одной группой?Вам нужны все 3 колонки?Вам нужен id, поскольку code равен UNIQUE?Наступает момент, когда схема «чрезмерно нормализуется» и производительность страдает, не занимая много места.

OR в некоторых случаях снижает производительность.

«Коррелированные подзапросы» полезныв некоторых ситуациях, но это, вероятно, лучше сделать с помощью JOIN:

AND `group` = ( SELECT `group` FROM quality WHERE id=cons.quality_id )

Остерегайтесь агрегатов (например, COUNT) с JOIN;Вы можете получить завышенную стоимость.Это потому, что JOIN происходит первым.

0 голосов
/ 08 октября 2018
  1. зачем нужно ВЛЕВО ПРИСОЕДИНЯТЬСЯ client_has_group cli_gr ON cli_gr.client_id = cons.client_id никогда не используется
  2. зачем нужен GROUP BY cons.id, prod.id, если вы выберете все поля, возможно, выберите только то, что вынужно
  3. попробуйте этот выбор, я думаю, что это будет быстрее

    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. возможно, лучше сделать подсчет в фоновом режиме и добавить это поле в продукт иТаблица потребления.

...