Преобразование запроса UNION в MySQL

У меня очень большая таблица (8 ГБ) с информацией о файлах, и мне нужно создать для нее отчет, который будет выглядеть примерно так:

(select * from fs_walk_scan where file_path like '\\server1\groot$\%' order by file_size desc limit 0,30)
UNION ALL
(select * from fs_walk_scan where file_path like '\\server1\hroot$\%' order by file_size desc limit 0,30)
UNION ALL
(select * from fs_walk_scan where file_path like '\\server1\iroot$\%' order by file_size desc limit 0,30)
UNION ALL
(select * from fs_walk_scan where file_path like '\\server2\froot$\%' order by file_size desc limit 0,30)
UNION ALL
(select * from fs_walk_scan where file_path like '\\server2\groot$\%' order by file_size desc limit 0,30)
UNION ALL
(select * from fs_walk_scan where file_path like '\\server3\hroot$\%' order by file_size desc limit 0,30)
UNION ALL
(select * from fs_walk_scan where file_path like '\\server4\iroot$\%' order by file_size desc limit 0,30)
UNION ALL
(select * from fs_walk_scan where file_path like '\\server5\iroot$\%' order by file_size desc limit 0,30)
[...]
order by substring_index(file_path,'\',4), file_size desc

Этот метод выполняет то, что мне нужно: получить список из 30 самых больших файлов для каждого тома. Однако это смертельно медленно, и поисковые запросы «нравится» жестко запрограммированы, даже если они находятся в другом столе и могут быть получены таким образом.

Я ищу способ сделать это, не просматривая огромную таблицу несколько раз. У кого-нибудь есть идеи?

Спасибо.

P.S. Я никак не могу изменить структуру огромной исходной таблицы.

Обновление: есть индексы для file_path и file_size, но каждый из этих подзапросов (?) По-прежнему занимает около 10 минут, а мне нужно сделать минимум 22.

ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
1
0
1 928
6

Ответы 6

Какие у вас индексы на этой таблице? Этот индекс:

СОЗДАТЬ ИНДЕКС fs_search_idx ON fs_walk_scan (file_path, file_size desc)

значительно ускорит этот запрос ... если у вас его еще нет.

Обновлять:

Вы сказали, что уже есть индексы для file_path и file_size ... это отдельные индексы? Или существует единый индекс, в котором оба столбца индексируются вместе? Для этого запроса разница будет огромной. Даже с 22 подзапросами, если они правильно проиндексированы, это должно работать быстро.

Я думаю, это было бы невозможно при условии, что изменения в структуре БД запрещены.

warren 02.10.2008 23:07

они индивидуальны, но я не понимаю, как объединение их в один поможет

Zenshai 02.10.2008 23:17

По сути, вы выполняете поиск по file_path, а затем выбираете 30 самых больших файлов (используя LIMIT и ORDER BY). Таким образом, вы, по сути, ищете одновременно в двух столбцах. Если оба фрагмента данных, по которым вы выполняете поиск, доступны в индексе вместе, поиск выполняется быстро.

bobwienholt 02.10.2008 23:53

Как насчет чего-то вроде этого (не тестировал, но похоже):

select * from fs_walk_scan where file_path like '\\server' and file_path like 'root$\%' order by file_size desc 

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

Предлагаете ли вы делать это в каждом запросе UNIONed для повышения производительности (причина, которая может помочь), или я неправильно понял?

Zenshai 02.10.2008 23:22

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

select * from fs_walk_scan
  where file_path regexp '^\\server(1\[ghi]|2\[fg]|3\h|[45]\i)root$\'

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

select * from fs_walk_scan
  where server = 'server1' and base_path in ('groot$', 'hroot$', 'iroot$')
     or server = 'server2' and base_path in ('froot$', 'groot$')

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

Вы можете сделать что-то вроде этого ... при условии, что в fs_list есть список ваших поисковых запросов типа "LIKE":

DELIMITER $$

DROP PROCEDURE IF EXISTS `test`.`proc_fs_search` $$
CREATE PROCEDURE `test`.`proc_fs_search` ()
BEGIN

DECLARE cur_path VARCHAR(255);
DECLARE done INT DEFAULT 0;


DECLARE list_cursor CURSOR FOR select file_path from fs_list;

DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

SET @sql_query = '';

OPEN list_cursor;

REPEAT
  FETCH list_cursor INTO cur_path;

  IF NOT done THEN
    IF @sql_query <> '' THEN
      SET @sql_query = CONCAT(@sql_query, ' UNION ALL ');
    END IF;

    SET @sql_query = CONCAT(@sql_query, ' (select * from fs_walk_scan where file_path like ''', cur_path , ''' order by file_size desc limit 0,30)');
  END IF;

UNTIL done END REPEAT;

SET @sql_query = CONCAT(@sql_query, ' order by file_path, file_size desc');

PREPARE stmt FROM @sql_query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

END $$

DELIMITER ;

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

SELECT * 
FROM   fs_walk_scan a
WHERE  ( SELECT COUNT(*) 
         FROM   fs_walk_scan b 
         WHERE  b.file_size  > a.file_size 
         AND    b.file_path  = a.file_path
       ) < 30

Редактировать:

Очевидно, это работает как собака. Итак ... Как насчет синтаксиса цикла?

SELECT DISTINCT file_path
INTO tmp1
FROM   fs_walk_scan a

DECLARE path VARCHAR(255);

SELECT MIN(file_path)
INTO   path
FROM   tmp1 

WHILE  path IS NOT NULL DO
    SELECT * 
    FROM   fs_walk_scan
    WHERE  file_path = path
    ORDER BY file_size DESC
    LIMIT 0,30

    SELECT MIN(file_path)
    INTO   path
    FROM   tmp1
    WHERE  file_path > path 
END WHILE

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

(Я просмотрел синтаксис, но мне не очень нравится MySQL, поэтому извиняюсь, если он не совсем там. Не стесняйтесь редактировать / комментировать)

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

Zenshai 09.10.2008 19:36

ой. тогда позволь мне уйти и подумать об этом! Если что-то получше придумаю, отредактирую свой пост.

AJ. 09.10.2008 19:42

Спасибо за помощь, я очень ценю это. Однако я не думаю, что этот обновленный метод что-то изменит с точки зрения производительности. Проблема в том, что мы все еще выполняем «выбор * из fs_walk_scan» на каждой итерации цикла while, а это очень дорого, потому что таблица огромна.

Zenshai 09.10.2008 22:59

Что я могу сказать? Таблица является огромная. Это будет медленно. Другой стратегией было бы поддержание списка «30 лучших» с помощью триггера, но это не было бы более эффективным и замедлило бы вставки.

AJ. 10.10.2008 15:17

Вы правы, это будет медленно, несмотря ни на что. Однако я думаю, что есть способ сделать это правильно. То, о чем я думаю прямо сейчас, - это использовать perl-скрипт (но в конечном итоге нативный), чтобы пройти по таблице по одной строке за раз и проверить размер файла строки на самый нижний из 30 лучших списков для его пути loc.

Zenshai 13.10.2008 19:40

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

Zenshai 13.10.2008 19:43

Для этого вы можете использовать группировку и самостоятельное присоединение.

SELECT substring_index(file_path, '\', 4), file_path
from fs_walk_scan as ws1
WHERE 30<= (
select count(*) from fs_Walk_scan as ws2
where substring_index(ws2.file_path, '\', 4) = substring_index(ws1.file_path, '\', 4)
and ws2.file_size > ws1.file_size
and ws2.file_path <> ws1.file_path)
group by substring_index(file_path, '\', 4)

Это по-прежнему запрос O (n) (n - количество групп), но он более гибкий и короче.

Редактировать: Другой подход - использование переменных. Выполнимость для вашей цели будет зависеть от того, как вы собираетесь выполнять этот запрос.

set @idx=0; set @cur_vol=0;                                                                      
SELECT file_volume, file_path, file_size FROM (
    SELECT file_volume, file_path, file_size,
    IF(@cur_vol != a.file_volume, @idx:=1, @idx:=@idx+1) AS row_index,
    IF(@cur_vol != a.file_volume, @cur_vol:=a.file_volume, 0) AS discard
    FROM (SELECT substring_index(file_path, '\', 4) as file_volume, file_path, file_size 
        FROM fs_walk_scan
        ORDER BY substring_index(file_path,'\',4), file_size DESC) AS a
    HAVING row_index <= 30) AS b;

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

Другие вопросы по теме