Оптимизировать запрос для нескольких столбцов, извлеченных из одной таблицы - PullRequest
3 голосов
/ 09 мая 2011

Это продолжение другого вопроса здесь на SO .

У меня есть две таблицы базы данных (больше таблиц опущено):

acquisitions (acq)
    id {PK}
    id_cu {FK}
    datetime
    { Unique Constraint: id_cu - datetime }

data
    id {PK}
    id_acq {FK acquisitions}
    id_meas
    id_elab
    value

Все возможныеid и datetime все проиндексированы.

Конечно, я буду не изменить структуру БД. Мне нужно извлечь данные следующим образом:

  • строк, сгруппированных по дате и времени
  • каждому столбцу, соответствующему data.value для выбранной комбинации acq.id_cu - data.id_meas - data.id_elab.(см. примечание внизу поста)
  • разрешить пустые ячейки, если данные для какого-то столбца отсутствуют, но существуют для других в дату / время

Мой текущий запрос построен таким образом (см. ТАК вопрос ):

SELECT datetime, MAX(v1) AS v1, MAX(v2) AS v2, MAX(v3) AS v3 FROM (

SELECT acq.datetime AS datetime, data.value AS v1, NULL AS v2, NULL AS v3 
FROM acq INNER JOIN data ON acq.id = data.id_acq
WHERE acq.id_cu = 3 AND data.id_meas = 2 AND data.id_elab = 1

UNION

SELECT acq.datetime AS datetime, NULL AS v1, data.value AS v2, NULL AS v3 
FROM acq INNER JOIN data ON acq.id = data.id_acq
WHERE acq.id_cu = 5 AND data.id_meas = 4 AND data.id_elab = 6

UNION

SELECT acq.datetime AS datetime, NULL AS v1, NULL AS v2, data.value AS v3 
FROM acq INNER JOIN data ON acq.id = data.id_acq
WHERE acq.id_cu = 7 AND data.id_meas = 9 AND data.id_elab = 8

) AS T
WHERE datetime >= "2011-03-01 00:00:00" AND datetime <= "2011-04-30 23:59:59"
GROUP BY datetime

Здесь для получения всего 3 столбцов, но, как я уже сказал, столбцы часто больше 50.

Это работает безупречно, но я быхотелось бы узнать, можно ли оптимизировать его по скорости.

Это MySQL EXPLAIN EXTENDED для запроса выше:

+----+--------------+--------------+------+------------------------------------------------+-----------------------+---------+------------------------+-------+----------+----------------------------------------------+
| id | select_type  | table        | type | possible_keys                                  | key                   | key_len | ref                    | rows  | filtered | Extra                                        |
+----+--------------+--------------+------+------------------------------------------------+-----------------------+---------+------------------------+-------+----------+----------------------------------------------+
|  1 | PRIMARY      | <derived2>   | ALL  | NULL                                           | NULL                  | NULL    | NULL                   | 82466 |   100.00 | Using where; Using temporary; Using filesort |
|  2 | DERIVED      | acquisitions | ref  | PRIMARY,id_cu,ix_acquisitions_id_cu            | id_cu                 | 4       |                        | 18011 |   100.00 |                                              |
|  2 | DERIVED      | data         | ref  | ix_data_id_meas,ix_data_id_acq,ix_data_id_elab | ix_data_id_acq        | 4       | sensor.acquisitions.id |     9 |   100.00 | Using where                                  |
|  3 | UNION        | acquisitions | ref  | PRIMARY,id_cu,ix_acquisitions_id_cu            | ix_acquisitions_id_cu | 4       |                        | 20864 |   100.00 |                                              |
|  3 | UNION        | data         | ref  | ix_data_id_meas,ix_data_id_acq,ix_data_id_elab | ix_data_id_acq        | 4       | sensor.acquisitions.id |     9 |   100.00 | Using where                                  |
|  4 | UNION        | acquisitions | ref  | PRIMARY,id_cu,ix_acquisitions_id_cu            | id_cu                 | 4       |                        | 31848 |   100.00 |                                              |
|  4 | UNION        | data         | ref  | ix_data_id_meas,ix_data_id_acq,ix_data_id_elab | ix_data_id_acq        | 4       | sensor.acquisitions.id |     9 |   100.00 | Using where                                  |
| NULL | UNION RESULT | <union2,3,4> | ALL  | NULL                                           | NULL                  | NULL    | NULL                   |  NULL |     NULL |                                              |
+----+--------------+--------------+------+------------------------------------------------+-----------------------+---------+------------------------+-------+----------+----------------------------------------------+
8 rows in set, 1 warning (8.24 sec)

В настоящее время с ( edit : проверено сегодня) 390 тыс. Приобретений и 9,2 млн. Значений данных (и растут) для извлечения таблицы из 59 столбцов требуется около 10 минут .Я знаю, что предыдущему программному обеспечению потребовалось до 1 часа для извлечения данных.

Спасибо за ваше терпение, прочитавшее здесь:)


Обновление

После ответа ДенисаЯ попробовал его изменения 1. и 2., это результат нового запроса:

SELECT datetime, MAX(v1) AS v1, MAX(v2) AS v2, MAX(v3) AS v3 FROM (

SELECT acq.datetime AS datetime, data.value AS v1, NULL AS v2, NULL AS v3 
FROM acq INNER JOIN data ON acq.id = data.id_acq
WHERE acq.id_cu = 3 AND data.id_meas = 2 AND data.id_elab = 1
AND datetime >= "2011-03-01 00:00:00" AND datetime <= "2011-04-30 23:59:59"

UNION ALL

SELECT acq.datetime AS datetime, NULL AS v1, data.value AS v2, NULL AS v3 
FROM acq INNER JOIN data ON acq.id = data.id_acq
WHERE acq.id_cu = 5 AND data.id_meas = 4 AND data.id_elab = 6
AND datetime >= "2011-03-01 00:00:00" AND datetime <= "2011-04-30 23:59:59"

UNION ALL

SELECT acq.datetime AS datetime, NULL AS v1, NULL AS v2, data.value AS v3 
FROM acq INNER JOIN data ON acq.id = data.id_acq
WHERE acq.id_cu = 7 AND data.id_meas = 9 AND data.id_elab = 8
AND datetime >= "2011-03-01 00:00:00" AND datetime <= "2011-04-30 23:59:59"

) AS T GROUP BY datetime

и вот новый EXPLAIN EXTENDED:

+----+--------------+--------------+-------+--------------------------------------------------------------+----------------+---------+------------------------+-------+----------+---------------------------------+
| id | select_type  | table        | type  | possible_keys                                                | key            | key_len | ref                    | rows  | filtered | Extra                           |
+----+--------------+--------------+-------+--------------------------------------------------------------+----------------+---------+------------------------+-------+----------+---------------------------------+
|  1 | PRIMARY      | <derived2>   | ALL   | NULL                                                         | NULL           | NULL    | NULL                   | 51997 |   100.00 | Using temporary; Using filesort |
|  2 | DERIVED      | acquisitions | range | PRIMARY,id_cu,ix_acquisitions_datetime,ix_acquisitions_id_cu | id_cu          | 12      | NULL                   | 14827 |   100.00 | Using where                     |
|  2 | DERIVED      | data         | ref   | ix_data_id_meas,ix_data_id_acq,ix_data_id_elab               | ix_data_id_acq | 4       | sensor.acquisitions.id |     9 |   100.00 | Using where                     |
|  3 | UNION        | acquisitions | range | PRIMARY,id_cu,ix_acquisitions_datetime,ix_acquisitions_id_cu | id_cu          | 12      | NULL                   | 18663 |   100.00 | Using where                     |
|  3 | UNION        | data         | ref   | ix_data_id_meas,ix_data_id_acq,ix_data_id_elab               | ix_data_id_acq | 4       | sensor.acquisitions.id |     9 |   100.00 | Using where                     |
|  4 | UNION        | acquisitions | range | PRIMARY,id_cu,ix_acquisitions_datetime,ix_acquisitions_id_cu | id_cu          | 12      | NULL                   | 13260 |   100.00 | Using where                     |
|  4 | UNION        | data         | ref   | ix_data_id_meas,ix_data_id_acq,ix_data_id_elab               | ix_data_id_acq | 4       | sensor.acquisitions.id |     9 |   100.00 | Using where                     |
| NULL | UNION RESULT | <union2,3,4> | ALL   | NULL                                                         | NULL           | NULL    | NULL                   |  NULL |     NULL |                                 |
+----+--------------+--------------+-------+--------------------------------------------------------------+----------------+---------+------------------------+-------+----------+---------------------------------+
8 rows in set, 1 warning (3.01 sec)

хороший прирост производительностибез сомнения


Обновление (2)

Это прибавляет точку добавления 3.

EXPLAIN EXTENDED SELECT datetime, MAX(v1) AS v1, MAX(v2) AS v2, MAX(v3) AS v3 FROM (

SELECT acquisitions.datetime AS datetime, MAX(data.value) AS v1, NULL AS v2, NULL AS v3 
FROM acquisitions INNER JOIN data ON acquisitions.id = data.id_acq
WHERE acquisitions.id_cu = 1 AND data.id_meas = 1 AND data.id_elab = 2
AND datetime >= "2011-03-01 00:00:00" AND datetime <= "2011-04-30 23:59:59"
GROUP BY datetime

UNION ALL

SELECT acquisitions.datetime AS datetime, NULL AS v1, MAX(data.value) AS v2, NULL AS v3 
FROM acquisitions INNER JOIN data ON acquisitions.id = data.id_acq
WHERE acquisitions.id_cu = 4 AND data.id_meas = 1 AND data.id_elab = 2
AND datetime >= "2011-03-01 00:00:00" AND datetime <= "2011-04-30 23:59:59"
GROUP BY datetime

UNION ALL

SELECT acquisitions.datetime AS datetime, NULL AS v1, NULL AS v2, MAX(data.value) AS v3 
FROM acquisitions INNER JOIN data ON acquisitions.id = data.id_acq
WHERE acquisitions.id_cu = 8 AND data.id_meas = 1 AND data.id_elab = 2
AND datetime >= "2011-03-01 00:00:00" AND datetime <= "2011-04-30 23:59:59"
GROUP BY datetime

) AS T GROUP BY datetime;

, и это результат EXPLAIN EXTENDED

+----+--------------+--------------+-------+--------------------------------------------------------------+----------------+---------+------------------------+-------+----------+---------------------------------+
| id | select_type  | table        | type  | possible_keys                                                | key            | key_len | ref                    | rows  | filtered | Extra                           |
+----+--------------+--------------+-------+--------------------------------------------------------------+----------------+---------+------------------------+-------+----------+---------------------------------+
|  1 | PRIMARY      | <derived2>   | ALL   | NULL                                                         | NULL           | NULL    | NULL                   | 51997 |   100.00 | Using temporary; Using filesort |
|  2 | DERIVED      | acquisitions | range | PRIMARY,id_cu,ix_acquisitions_datetime,ix_acquisitions_id_cu | id_cu          | 12      | NULL                   | 14827 |   100.00 | Using where                     |
|  2 | DERIVED      | data         | ref   | ix_data_id_meas,ix_data_id_acq,ix_data_id_elab               | ix_data_id_acq | 4       | sensor.acquisitions.id |     9 |   100.00 | Using where                     |
|  3 | UNION        | acquisitions | range | PRIMARY,id_cu,ix_acquisitions_datetime,ix_acquisitions_id_cu | id_cu          | 12      | NULL                   | 18663 |   100.00 | Using where                     |
|  3 | UNION        | data         | ref   | ix_data_id_meas,ix_data_id_acq,ix_data_id_elab               | ix_data_id_acq | 4       | sensor.acquisitions.id |     9 |   100.00 | Using where                     |
|  4 | UNION        | acquisitions | range | PRIMARY,id_cu,ix_acquisitions_datetime,ix_acquisitions_id_cu | id_cu          | 12      | NULL                   | 13260 |   100.00 | Using where                     |
|  4 | UNION        | data         | ref   | ix_data_id_meas,ix_data_id_acq,ix_data_id_elab               | ix_data_id_acq | 4       | sensor.acquisitions.id |     9 |   100.00 | Using where                     |
| NULL | UNION RESULT | <union2,3,4> | ALL   | NULL                                                         | NULL           | NULL    | NULL                   |  NULL |     NULL |                                 |
+----+--------------+--------------+-------+--------------------------------------------------------------+----------------+---------+------------------------+-------+----------+---------------------------------+
8 rows in set, 1 warning (3.06 sec)

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


Обновление (3)

Я пробовал с MAX(data.value)... GROUP BY datetime и без него, и при запросе в 60 столбцов я получаю лучшие результаты с.Результаты варьируются от попытки попробовать, это один из них.

  • оригинальный запрос 9m12.144s
  • с Денисом '1. и 2. 4m6.597s
  • с Денисом * 1., 2. и 3. 4m0.210s

Это примерно на 57% меньше необходимого времени.


Обновление (4)

Я пробовал решение Andiry, но оно намного медленнее, чем оптимизация Дениса.

Проверено на 3 комбинациях / столбцах:

  • неоптимизировано: 1 м3
  • Оптимизация Дениса: 1,7 с
  • CASE Эндири: 9,3 с

Я также тестировал комбинации по 12 / столбцы:

  • неоптимизировано: не проверено
  • Оптимизация Дениса: 3,6 с
  • У Андири CASE: 13,7 с

Более того, решение Andiry также использует даты получения, когда нет данных для какой-либо из выбранных комбинаций, но существует для других.

Блок управления Immagine 1 получает данные каждые 30 минут в: 00 и: 30, а блок управления 2в: 15 и: 45: Я удвою количество строк с пустыми пустыми заполненными NULL.


Примечание:

Это все о системе датчиков: имеется несколько блоков управления (по одному на каждый id_cu) со множеством датчиков каждый.

Один датчик идентифицируется парой id_cu / id_meas и отправляетразличные подробности для каждой меры, скажем, MIN (id_elab=1), MAX (id_elab=2), AVERAGE (id_elab=3), INSTANT (id_elab=...) и т. д., по одному на каждую id_elab.

Пользователь может выбрать любое количество разработок, например:

  • СРЕДНЕЕ значение (3) датчика № 3 блока управления № 1 для столбца результатов, поэтому id_cu=1 / id_meas=3 / id_elab=3
  • СРЕДНЕЕ значение (3) датчика № 5 блока управления № 1 для столбца результатов, поэтому id_cu=1 / id_meas=5 / id_elab=3
  • МИН. Значение (1) датчика № 2 блока управления № 4 для другогостолбец так id_cu=4 / id_meas=2 / id_elab=1
  • (укажите любую действительную комбинацию id_cu, id_meas, id_elab)
  • ...

и так далее, до десятков вариантов ...

Вот частичный DDL (не имеет значенияисключено):

CREATE TABLE acquisitions (
    id INTEGER NOT NULL AUTO_INCREMENT, 
    id_cu INTEGER NOT NULL, 
    datetime DATETIME NOT NULL, 
    PRIMARY KEY (id), 
    UNIQUE (id_cu, datetime), 
    FOREIGN KEY(id_cu) REFERENCES ctrl_units (id) ON DELETE CASCADE
)

CREATE TABLE data (
    id INTEGER NOT NULL AUTO_INCREMENT, 
    id_acq INTEGER NOT NULL, 
    id_meas INTEGER NOT NULL, 
    id_elab INTEGER NOT NULL, 
    value FLOAT, 
    PRIMARY KEY (id), 
    FOREIGN KEY(id_acq) REFERENCES acquisitions (id) ON DELETE CASCADE
)

CREATE TABLE ctrl_units (
    id INTEGER NOT NULL, 
    name VARCHAR(40) NOT NULL, 
    PRIMARY KEY (id)
)

CREATE TABLE sensors (
    id_cu INTEGER NOT NULL, 
    id_meas INTEGER NOT NULL, 
    id_elab INTEGER NOT NULL, 
    name VARCHAR(40) NOT NULL, 
    `desc` VARCHAR(80), 
    PRIMARY KEY (id_cu, id_meas), 
    FOREIGN KEY(id_cu) REFERENCES ctrl_units (id) ON DELETE CASCADE
)

Ответы [ 3 ]

3 голосов
/ 09 мая 2011

Существует три основных вопроса:

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

  2. Предложение where может быть помещено в каждое из подстановок объединения:

    select ...
    from (
    select ... from ...  where ...
    union all
    select ... from ...  where ...
    union all
    ...
    )
    group by ...
    

    То, как вы это написали, начинается с извлечения всех строк, добавления их всех и, наконец, фильтрации тех, которые вам нужны.Внедрение предложения where в объединение подстановок заставит его извлечь только те строки, которые вам нужны, и, наконец, добавить их все.

  3. В тех же строках предварительно агрегировать агрегаты:

    select ..., max(foo) as foo
    from (
    select ..., max(foo) as foo from ...  where ... group by ...
    union all
    select ..., max(foo) as foo from ...  where ... group by ...
    union all
    ...
    )
    group by ...
    

    Оптимизатор будет лучше использовать существующие индексы, и в итоге вы добавите только несколько строк, а не миллионы.

1 голос
/ 09 мая 2011
SELECT
  acq.datetime,
  MAX(CASE WHEN acq.id_cu = 2 AND data.id_meas = 2 AND data.id_elab = 1 THEN data.value END) AS v1,
  MAX(CASE WHEN acq.id_cu = 5 AND data.id_meas = 4 AND data.id_elab = 6 THEN data.value END) AS v2,
  MAX(CASE WHEN acq.id_cu = 7 AND data.id_meas = 9 AND data.id_elab = 8 THEN data.value END) AS v3
FROM acq
  INNER JOIN data acq.id = data.id_acq
WHERE datetime >= 2011-03-01 00:00:00 AND datetime <= 2011-04-30 23:59:59
GROUP BY acq.datetime

Это может выглядеть примерно так же, как ваш исходный запрос, но главное отличие в том, что логически он сканирует таблицы только один раз вместо трех или сколько угодно раз с UNION.

0 голосов
/ 09 мая 2011

В принципе, я думаю, что вы получите лучшие результаты с одним SELECT и CASE, работающим с условиями. В любом случае вы можете захотеть сравнить и сравнить ...

SELECT acq.datetime AS datetime, 
       MAX(
           CASE acq.id_cu
           WHEN 1 THEN data.value
           END 
       ) as v1,
       MAX(
           CASE acq.id_cu
           WHEN 4 THEN data.value
           END 
       ) as v2,
       MAX(
           CASE acq.id_cu
           WHEN 8 THEN data.value
           END 
       ) as v3
FROM 
       acq INNER JOIN data ON acq.id = data.id_acq
WHERE 
       data.id_meas = 1 AND data.id_elab = 2 AND
       datetime BETWEEN "2011-03-01 00:00:00" AND "2011-04-30 23:59:59"

Это должно сделать чистое сканирование диапазона. Кроме того, есть еще что-то, что можно сделать с помощью составных индексов.

Наконец, что-то не так с использованием GROUP BY, например

SELECT data.id_means, acq.datetime AS datetime, MAX(data.value)
FROM 
       acq INNER JOIN data ON acq.id = data.id_acq
WHERE 
       data.id_elab = 2 AND
       datetime BETWEEN "2011-03-01 00:00:00" AND "2011-04-30 23:59:59" AND
       data.id_means IN (1,4,8)
GROUP BY
       data.id_means

Это самая простая форма (и наиболее гибкая) - даже если строки не были перенесены для вас в столбцы (для разных значений data.id_meas). Но это даст вам лучшее представление о том, какую производительность ожидать и какие индексы должны быть наиболее полезными для запроса.

EDIT: Чтобы получить максимальное значение data.value для * acq.id_cu - data.id_meas - data.id_elab *, вы можете просто использовать

SELECT 
       acq.id_cu, data.id_meas, data.id_elab, acq.datetime AS datetime, MAX(data.value)
FROM 
       acq INNER JOIN data ON acq.id = data.id_acq
WHERE 
       data.id_elab = 2 AND
       datetime BETWEEN "2011-03-01 00:00:00" AND "2011-04-30 23:59:59" AND
       data.id_means IN (1,4,8)
GROUP BY
       acq.id_cu, data.id_meas, data.id_elab, acq.datetime

даст максимум (data.value) для всех комбинаций acq.id_cu, data.id_meas, data.id_elab, acq.datetime ( после , отфильтровав его со значениями откуда - корректируя, где влияет на результаты). Это не будет показывать NULL для комбинаций, у которых нет строк, но для этого есть обходной путь, если это правильное направление для вас. GROUP BY также определяет порядок, поэтому порядок столбцов в группе меняется на.

Если в моем ответе все еще отсутствует точка, некоторые данные / тестовый пример будут полезны.

Запутанная часть в вашем примере, когда вы говорите

каждый столбец соответствует data.value для выбранного acq.id_cu - data.id_meas - комбинация data.id_elab.

но когда вы выбираете данные в примерных запросах, вы выбираете их прямо в столбцы с только группировкой по времени и дате, поэтому, если это действительно комбинация значений, невозможно определить, какая строка соответствует какой комбинации (может быть несколько строк на определенную дату). Если на самом деле это не комбинация всех значений, которые вы хотите фильтровать / группировать, но условия группировки, определяющие значение max, напрямую зависят от даты и времени.

...