Mysql запрос подстроки - PullRequest
0 голосов
/ 13 мая 2019

У меня есть таблица ограничений по регионам для кодов товаров. Код продукта не имеет фиксированной длины, но может варьироваться от 10 до 25 номеров. Ограничение может содержать префикс продукта, так что все продукты из этого диапазона будут запрещены. Используемая БД - это MariaDB / Mysql, и вот определение таблицы:

CREATE TABLE product_restrict (
    `id` VARCHAR(25) NOT NULL,
    `region` VARCHAR(3) NOT NULL,
    `from_dttm` DATETIME NOT NULL,
    `to_dttm` DATETIME NULL DEFAULT NULL,
    PRIMARY KEY (`region`, `id`, `from_dttm`))
ENGINE = InnoDB;

В данный момент я использую 15 запросов, определяемых длиной кода целевого продукта, поэтому я могу найти все префиксы, поэтому в моем коде у меня 15 запросов, подобных этому:

SELECT * 
FROM product_restrict
WHERE
 region='XXX' AND
(
    id = "9" OR 
    id = "98" OR 
    id = "987" OR 
    id = "9876" OR 
    id = "98765" OR 
    id = "987654" OR 
    id = "9876543" OR 
    id = "98765432" OR 
    id = "987654321" OR 
    id = "9876543210" OR 
    id = "98765432109" OR 
    id = "987654321098" OR 
    id = "9876543210987" OR 
    id = "98765432109876" OR 
    id = "987654321098765" OR 
    id = "9876543210987654" 
) AND (
 now() >= from_dttm AND
 ( now() < to_dttm OR to_dttm is null)
);


SELECT * 
FROM product_restrict
WHERE
 region='XXX' AND
(
    id = "9" OR 
    id = "98" OR 
    id = "987" OR 
    id = "9876" OR 
    id = "98765" OR 
    id = "987654" OR 
    id = "9876543" OR 
    id = "98765432" OR 
    id = "987654321" OR 
    id = "9876543210" OR 
    id = "98765432109" OR 
    id = "987654321098" OR 
    id = "9876543210987" OR 
    id = "98765432109876" OR 
    id = "987654321098765" OR 
    id = "9876543210987654" OR 
    id = "98765432109876543" 
) AND (
 now() >= from_dttm AND
 ( now() < to_dttm OR to_dttm is null)
);

В этой таблице содержится около 100 миллионов записей. У меня вопрос, есть ли способ свести это к одному запросу с той же производительностью выбора? Изменение структуры таблицы, к сожалению, вне моей власти.


Отредактировано после подсказки INSTR () от @Pham X. Бах:

Я провел несколько тестов на моей локальной базе данных, где у меня только 670 000 записей выборок, и INSTR () работает, но с точки зрения производительности это выглядит намного хуже. Мне придется подождать до завтра, чтобы провести этот тест на промышленном образце.

Вот анализ (объяснение) для моего исходного запроса:

Id  select_type table               type    posible_keys    key     key_len ref     rows    r_rows      filtered    r_filtered  Extra
1   SIMPLE      product_restrict    range   PRIMARY         PRIMARY 201             17      2.00        76.47       100.00      Using where

А вот для ИНСТР:

Id  select_type table               type    posible_keys    key     key_len ref     rows    r_rows      filtered    r_filtered  Extra
1   SIMPLE      product_restrict    ref     PRIMARY         PRIMARY 98      const   335022  671732.00   100.00      0.00        Using where

Запрос INSTR, например:

SELECT * 
FROM product_restrict
WHERE
 region='XXX' AND
 INSTR('98765432109876543', id) = 1 AND (
 now() >= from_dttm AND
 ( now() < to_dttm OR to_dttm is null)
);

Ответы [ 3 ]

1 голос
/ 13 мая 2019

Во-первых, нет причины, по которой вы не можете просто настроить текущий запрос:

SELECT pr.* 
FROM product_restrict pr
WHERE pr.region = 'XXX' AND
      now() >= pr.from_dttm AND
      ( now() < pr.to_dttm OR pr.to_dttm is null) AND
      pr.id in ('9', '98', . . ., '98765432109876544',
                '9', '98', . . ., '98765432109876543'
               . . .
              )

Не должно быть проблем с наличием списка IN с несколькими сотнями записей.Дублированные записи в порядке, но вы также можете удалить их.

Вы можете написать это проще, используя like или регулярные выражения.Например:

WHERE pr.region = 'XXX' AND
      now() >= pr.from_dttm AND
      ( now() < pr.to_dttm OR pr.to_dttm is null) AND
      ('98765432109876544' LIKE concat(pr.id, '%') OR
       '98765432109876543' LIKE concat(pr.id, '%') OR
       . . .
      )

Однако IN, вероятно, работает лучше.

0 голосов
/ 24 мая 2019

Другой способ проверить префикс.

AND LEFT('9876543210987654', LENGTH(id)) = id

Вероятно, невозможно разработать способ, позволяющий избежать сканирования таблицы и проверки всех идентификаторов.

0 голосов
/ 13 мая 2019

Вы можете использовать простое условие типа

SELECT * 
FROM product_restrict
WHERE  region='XXX' 
AND '9876543210987654' like concat(id, '%')
AND (
      now() >= from_dttm 
      AND ( 
         now() < to_dttm OR to_dttm is null
      )
) ;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...