在MySQL中转换UNION查询

时间:2008-10-02 18:59:12

标签: sql mysql dynamic union

我有一个非常大的表(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上有索引,但这些子(?)查询中的每一个仍然需要大约10分钟,而且我必须做最少22分钟。

6 个答案:

答案 0 :(得分:2)

你在那张桌子上有什么样的索引?这个指数:

CREATE INDEX fs_search_idx ON fs_walk_scan(file_path,file_size desc)

会显着提高这个查询...如果你还没有这样的话。

更新

你说file_path和file_size上已有索引......它们是个别索引吗?或者是否有一个索引,两列都被索引在一起?这个查询的差异很大。即使有22个子查询,如果索引正确,这应该是快速的。

答案 1 :(得分:2)

你可以使用正则表达式:

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$')

您可以设置触发器以在插入记录时初始化字段,或者之后执行批量更新以填充两个额外的列。

答案 2 :(得分:1)

你可以这样做......假设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 ;

答案 3 :(得分:1)

试试这个。
您希望获得每个记录少于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不是很热,所以如果它不是那么适用。可以随意编辑/评论)

答案 4 :(得分:0)

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

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

通过这种方式,您可以对单个字段进行一对比较,这些比较通常与您描述的内容相匹配。也可以使用正则表达式,但我还没有这样做。

答案 5 :(得分:0)

您可以使用分组和自联接来实现此目的。

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;

我还没有尝试过这段代码,但变量的概念可以像这样用于你的目的。