在MySQL中转换UNION查询

时间:2020-03-06 15:02:37  来源:igfitidea点击:

我有一个很大的表(8gb),其中包含有关文件的信息,并且我需要针对该表运行一个报告,该报告看起来像这样:

(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上有索引,但是每个这些sub(?)查询仍然需要大约10分钟,而且我必须最少执行22分钟。

解决方案

我们在那张桌子上有什么样的索引?该指标:

在fs_walk_scan上创建索引fs_search_idx(file_path,file_size desc)

如果我们还没有这样的查询,它将大大加快此查询的速度。

更新:

我们说过file_path和file_size上已经有索引...它们是单独的索引吗?还是只有一个索引,两个列都被索引在一起?对于此查询,差异将是巨大的。即使有22个子查询,如果索引正确,这也应该很快。

这样的事情怎么样(还没有测试过,但是看起来很接近):

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

这样,我们可以在单个字段上进行一对比较,这些比较通常会与我们所描述的相匹配。也可以使用正则表达式,但是我还没有这样做。

我们可以使用正则表达式:

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不太热衷,因此如果应用程序还不很完善,请继续使用。请随时进行编辑/注释)