2016-09-20 5 views
0

MYSQL/MariaDB схемы и выборки данных:Получить автомобили, которые прошли определенных камер

CREATE DATABASE IF NOT EXISTS `puzzle` DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci; 

USE `puzzle`; 

DROP TABLE IF EXISTS `event`; 

CREATE TABLE `event` (
    `eventId` bigint(20) NOT NULL AUTO_INCREMENT, 
    `sourceId` bigint(20) NOT NULL COMMENT 'think of source as camera', 
    `carNumber` varchar(40) NOT NULL COMMENT 'ex: 5849', 
    `createdOn` datetime DEFAULT NULL, 
    PRIMARY KEY (`eventId`) 
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 


INSERT INTO `event` (`eventId`, `sourceId`, `carNumber`, `createdOn`) VALUES 
    (1, 44, '4456', '2016-09-20 20:24:05'), 
    (2, 26, '26484', '2016-09-20 20:24:05'), 
    (3, 5, '4456', '2016-09-20 20:24:06'), 
    (4, 3, '72704', '2016-09-20 20:24:15'), 
    (5, 3, '399606', '2016-09-20 20:26:15'), 
    (6, 5, '4456', '2016-09-20 20:27:25'), 
    (7, 44, '72704', '2016-09-20 20:29:25'), 
    (8, 3, '4456', '2016-09-20 20:30:55'), 
    (9, 44, '26484', '2016-09-20 20:34:55'), 
    (10, 26, '4456', '2016-09-20 20:35:15'), 
    (11, 3, '72704', '2016-09-20 20:35:15'), 
    (12, 3, '399606', '2016-09-20 20:44:35'), 
    (13, 26, '4456', '2016-09-20 20:49:45'); 

Я хочу, чтобы получить CarNumber (ы), которые имеют SourceId = 3 И (26 ИЛИ 44) во время 20:24 до 20:45. запрос должен быть быстрым, так как реальная таблица содержит более 300 миллионов записей.

до сих пор ниже максимального я мог бы пойти с запросом (его даже не производя действительных результатов)

select * from event e where 
e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00' 
and e.sourceId IN(3,26,44) group by e.carNumber; 

правильные результаты за предоставленные данные:

carNumber 
4456 
72704 

Я действительно озадачен и застрял. я попробовал EXISTS, Joins, sub-query без везения, поэтому я задаюсь вопросом, способен ли SQL решить этот вопрос или мне нужно использовать кодирование с использованием бэкэнд?

версия MySQL/MariaDB в использовании:

MariaDB-5.5.50

MySQL-5.5.51

ответ

1

Если вам это нужно, чтобы быть быстрым, то следующий может работы, если у вас есть индекс по event(createdOn, carNumber, SourceId):

select e.carNumber 
from event e 
where e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00' 
group by e.carNumber 
having sum(e.sourceId = 3) > 0 and 
     sum(e.sourceId IN (26, 44)) > 0; 

я был бы склонен изменить это:

select e.carNumber 
from event e 
where e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00' and 
     e.sourceId in (3, 26, 44) 
group by e.carNumber 
having sum(e.sourceId = 3) > 0 and 
     sum(e.sourceId IN (26, 44)) > 0; 

А потом для исполнения, даже это:

select carNumber 
from ((select carNumber, sourceId 
     from event e 
     where e.sourceId = 3 and 
      e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00' 
    ) union all 
     (select carNumber, sourceId 
     from event e 
     where e.sourceId = 26 and 
      e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00' 
    ) union all 
     (select carNumber, sourceId 
     from event e 
     where e.sourceId = 44 and 
      e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00' 
    ) 
    ) e 
group by e.carNumber 
having sum(e.sourceId = 3) > 0 and 
     sum(e.sourceId IN (26, 44)) > 0; 

Эта версия может воспользоваться индексом на event(sourceId, createdOn, carNumber). Каждый подзапрос должен использовать этот индекс очень эффективно, объединяя небольшое количество данных для окончательной агрегации.

+0

Я не могу использовать индексацию, так как Insertion получил более высокий приоритет в проекте, около 200 записей, вставленных в секунду, а реальная таблица содержит гораздо больше столбцов. btw, второй запрос работал быстрее ~ 20%, чем 1-й запрос, который идентичен –

+0

@ juergend относительно третьего запроса, так как я не использую индексы, он занял более двух часов по сравнению со вторым запросом :( –

+1

@JawadAlShaikh ... Третий запрос * специально * сформулированы для использования индексов, как описано в ответе. –

1

-то вроде следующего должен сделать трюк для вас:

SELECT carNumber 
FROM event 
WHERE sourceID = 3 
    AND carNumber IN (SELECT carNumber FROM event WHERE sourceID IN(26,44)) 
GROUP BY carNumber 

Это предложение WHERE ищет записи с sourceID3, а затем также гарантирует, что у carnumber есть хотя бы один другой запись в таблице, где sourceid является либо 26 или 44

Ничего не закодировать вне SQL для этого, так как это, безусловно, проблема, которую SQL построен, чтобы решить как можно быстрее.

+0

Ваш запрос занял около 25% дополнительного времени по запросу @juergen d. но он дал достоверные результаты. Благодарю. –

+1

Отлично. Мое предположение заключалось в том, что @ juergend будет быстрее. Это была гонка между несколькими просмотрами таблиц и односкатной разверткой с агрегацией. Интересно, изменились бы результаты, если бы вы проиндексировали как исходный, так и цифровой номер даты. – JNevill

+0

еще один момент, на реальных данных ваш запрос возвратил разные результаты, чем juergend's и @Gordon, я не знаю, почему, ваш запрос вернулся 209 автомобилей, в то время как другие ответы вернули 59 автомобилей. Я не могу делиться реальными данными с момента его огромного и против сроков. –

1

Вы можете использовать предложение having для фильтрации по группам. Используйте sum(), чтобы подсчитать, сколько раз certains условий присутствуют в группе данных

select e.carNumber 
from event e 
where e.createdOn > '2016-09-20 20:24:00' 
    and e.createdOn < '2016-09-20 20:45:00' 
group by e.carNumber 
having sum(e.sourceId = 3) > 0 
    and sum(e.sourceId IN (26,44)) > 0 
+0

, если @Gordon не придумал более быстрый запрос, я бы назвал ваш ответ, отличную работу. –

1

Уменьшить размер таблицы

С 300M строк, вы действительно должны использовать самые маленькие типы данных, которые практичны.

  • BIGINT занимает 8 байт; INT UNSIGNED (всего 4 байта) обычно достаточно (максимум 4 миллиарда). Если используется менее 65K камер, используйте 2 байта SMALLINT UNSIGNED.

  • carNumber выглядит как номер, поэтому зачем использовать VARCHAR? Примеры, которые у вас есть, занимают 5-7 байтов в VARCHAR, будут помещаться в 4 байта с INT UNSIGNED или 3 байтами с MEDIUMINT UNSIGNED (макс. 16M).

Сокращение таблицы поможет любому решению.

Покрытие индекс

Это уже было предложено в других ответах, но я хочу, чтобы понять, почему это помогает. Если все столбцы существуют в одном запросе, запрос может быть выполнен в BTree индекса, не касаясь данных. Это обычно быстрее из-за меньшего размера. Индекс «покрытия» для этого запроса имеет source_id, car_number, createdOn в любом порядке.

Порядок столбцов в индексе

Поскольку индекс может быть использован только слева-направо порядок важен. (Это не относится к Гордона первого выбора, который должен createdOn в первую очередь.)

  1. sourceId обрабатывается с = или IN, поэтому он должен прийти первым. В случае IN вам, вероятно, понадобится 5.6 или новее, чтобы получить оптимизацию IN.
  2. createdOn - это диапазон, поэтому поиск остановится.
  3. Для «покрытия» теперь можно добавить дополнительные столбцы. В этом случае carNumber.

Итак, большинство (не все) предложения хотят получить этот заказ: INDEX(sourceId, createdOn, carNumber).

Избавьтесь от auto_increment

Используете ли вы eventID в других таблицах? Если это так, то вам, вероятно, следует сохранить его. Если нет, то является ли комбо (sourceId, createdOn, carNumber) уникальным? Если да, то сделайте это PRIMARY KEY. Surrogate PK хорош для некоторых ситуаций, но это мешает производительности в других. Я предполагаю, что это может быть помехой здесь.

Избегайте медленные операции

UNION обычно включает в себя временную таблицу; это добавляет накладные расходы. В то время как UNION выгоден для лучшего использования индексов, и, избегая OR, накладные расходы таблицы tmp могут перевесить преимущества для того, что кажется небольшим набором результатов.

Gordon был прав использования UNION ALL вместо UNION DISTINCT; последний нуждается в пропуске, который не нужен для его запроса.

Bottom Line

  1. термоусадочную таблицу.
  2. Измените PK, если это возможно; если нет, добавьте предлагаемый индекс.
  3. Обновить не менее 5.6
  4. Используйте второй запрос Гордона.

Другое решение

(я не знаю, если это лучше, но это может быть стоит попробовать.)

SELECT carNumber 
    FROM (SELECT DISTINCT carNumber 
      FROM event 
      WHERE sourceId = 3 
      AND createdOn >= '2016-09-20 20:24:00' 
      AND createdOn < '2016-09-20 20:45:00' 
     ) AS x 
    WHERE EXISTS (SELECT * FROM event 
      WHERE carNumber = x.carNumber 
       AND sourceId IN (26,44) 
       AND createdOn >= '2016-09-20 20:24:00' 
       AND createdOn < '2016-09-20 20:45:00' 
       ); 

Было бы необходимо два индекса:

(sourceId, createdOn, carNumber) -- as before 
(carNumber, sourceId, createdOn) -- to optimize the EXISTS 
Смежные вопросы