2012-08-23 69 views
19

情况如下:在MySQL表中查找所有那些只有空值的列

我有大量的表,每个表都有大量的列。我需要处理这个旧的和将被弃用的数据库以用于新系统,并且我正在寻找一种方法来消除显然没有被使用过的所有列。

我想要通过过滤掉所有列上有任何给定行上的值的列,让我有一组列,其中所有列中的值都为NULL。当然,我可以手动对每列降序进行排序,但这会花费太长的时间,因为我正在处理大量的表和列。我估计它是400张桌子,每桌最多50列(!)。

有没有什么办法可以从information_schema获取这些信息?

编辑:

下面是一个例子:

column_a column_b column_c column_d 
NULL  NULL  NULL  1 
NULL  1   NULL  1 
NULL  1   NULL  NULL 
NULL  NULL  NULL  NULL 

输出应该是 'column_a' 和 'column_c',对是唯一的列没有任何填充的值。

+0

我觉得它很难被任何单个查询解决。你需要一个程序。通过程序或仅查询来完成此操作是否可以接受? – Sami

+0

没问题,可能会更好,因为我可以轻松地传递另一个表名。 – Sherlock

回答

17

您可以通过动态创建(从INFORMATION_SCHEMA.COLUMNS表)包含您希望执行的SQL的字符串,然后从该字符串中执行preparing a statement并执行它来避免使用过程。

我们要建立的SQL的样子:

SELECT * FROM (
    SELECT 'tableA' AS `table`, 
     IF(COUNT(`column_a`), NULL, 'column_a') AS `column` 
    FROM tableA 
UNION ALL 
    SELECT 'tableB' AS `table`, 
     IF(COUNT(`column_b`), NULL, 'column_b') AS `column` 
    FROM tableB 
UNION ALL 
    -- etc. 
) t WHERE `column` IS NOT NULL 

这可以使用下列内容:

SET group_concat_max_len = 4294967295; -- to overcome default 1KB limitation 

SELECT CONCAT(
     'SELECT * FROM (' 
     , GROUP_CONCAT(
      'SELECT ', QUOTE(TABLE_NAME), ' AS `table`,' 
      , 'IF(' 
      , 'COUNT(`', REPLACE(COLUMN_NAME, '`', '``'), '`),' 
      , 'NULL,' 
      , QUOTE(COLUMN_NAME) 
      , ') AS `column` ' 
      , 'FROM `', REPLACE(TABLE_NAME, '`', '``'), '`' 
      SEPARATOR ' UNION ALL ' 
     ) 
     , ') t WHERE `column` IS NOT NULL' 
     ) 
INTO @sql 
FROM INFORMATION_SCHEMA.COLUMNS 
WHERE TABLE_SCHEMA = DATABASE(); 

PREPARE stmt FROM @sql; 
EXECUTE stmt; 
DEALLOCATE PREPARE stmt; 

看到它的sqlfiddle

+0

尽管您的示例正在工作(在SQLFiddle上),但它对于我的数据库来说太重了。它说:内存马上消耗殆尽......我认为你提出的方法并不存在。 – Sherlock

+0

@ Robinv.G .:除了可能调整MySQL的配置参数和/或向服务器添加更多内存......但是,您正在调查〜400 * 50 = 20k列,这将创建一个非常大的查询。如果单个查询太大,您可以一次关注一部分表格,例如通过将'WHERE'子句更改为'TABLE_SCHEMA = DATABASE()AND TABLE_NAME BETWEEN'A'和'C''。否则,你将不得不使用循环构造,例如在存储过程中 - 没有其他办法。 – eggyal

+0

谢谢你的效果,我会稍微玩一下! – Sherlock

0

我认为你可以GROUP_CONCAT和GROUP BY做到这一点:

select length(replace(GROUP_CONCAT(my_col), ',', '')) 
from my_table 
group by my_col 

未经测试

编辑:该文档似乎不声明GROUP_CONCAT需要一个相应的组BY,所以试试这个:

select 
    length(replace(GROUP_CONCAT(col_a), ',', '')) as len_a 
    , length(replace(GROUP_CONCAT(col_b), ',', '')) as len_b 
    , length(replace(GROUP_CONCAT(col_c), ',', '')) as Len_c 
from my_table 
+0

这是每列,我需要它表宽_every_列,留给我一组没有值的列。我会用一个例子展开我原来的帖子。 – Sherlock

+0

对编辑的回应:这仍然需要手动插入所有列。几乎没有选择,但谢谢。 – Sherlock

+0

这似乎有用吗? 'SELECT w''non empty cols',LENGTH(REPLACE(w,',',''))FROM(SELECT column_name as w FROM information_schema.columns WHERE table_schema =“my_database”AND table_name =“my_table”ORDER BY table_name, ordinal_position)t;' –

10

我不是SQL程序的专家,因此给出使用SQL查询和PHP/python脚本的总体思路。

  • 使用SHOW TABLESINFORMATION_SCHEMA数据库中的一些其他查询来获取所有的表在数据库MY_DATABASE

  • 做一个查询生成一个语句获取特定表中的所有列名,这将是使用在下一个查询中。

SELECT Group_concat(Concat("MAX(", column_name, ")")) 
     FROM information_schema.columns 
     WHERE table_schema = 'MY_DATABSE' 
       AND table_name = 'MY_TABLE' 
     ORDER BY table_name,ordinal_position 
  • 你会得到像MAX(column_a),MAX(column_b),MAX(column_c),MAX(column_d)

  • 使用此输出的输出,以产生最终的查询:

SELECT Max(column_a),Max(column_b),Max(column_c),Max(column_d)FROM MY_DATABASE。MY_TABLE

输出将是:

MAX(column_a) MAX(column_b) MAX(column_c) MAX(column_d) 
    NULL   1   NULL    1 
  • 所有与最大值的列NULL是具有所有值的那些NULL
+1

这是一个有趣的方法(其中:+1),但感觉有点不尽人意。它确实需要一种语言来将它们粘合在一起。它可能在纯SQL中(将此方法封装在SP中),但它可能最终会变得冗长而相当难看。在接受这个之前,我会等待其他答案。感谢这种方法寿。 :) – Sherlock

+0

@ Robinv.G。它在一个存储过程当然是可能的,但是脚本语言可以提供更多的控制和更清晰的流程。 – DhruvPathak

4

您可以采取的行为优势COUNT关于NULL的聚合函数。通过传递该字段作为参数,COUNT函数返回非NULL值的数量,而 COUNT(*)返回总行数。因此,您可以计算NULL与“可接受”值的比率。

我举一个例子用下面的表结构:

CREATE TABLE `t1` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `col_1` int(10) unsigned DEFAULT NULL, 
    `col_2` int(10) unsigned DEFAULT NULL, 
    PRIMARY KEY (`id`) 
) ; 

-- let's fill the table with random values 
INSERT INTO t1(col_1,col_2) VALUES(1,2); 
INSERT INTO t1(col_1,col_2) 
SELECT 
IF(RAND() > 0.5, NULL ,FLOOR(RAND()*1000), 
IF(RAND() > 0.5, NULL ,FLOOR(RAND()*1000) FROM t1; 

-- run the last INSERT-SELECT statement a few times 
SELECT COUNT(col_1)/COUNT(*) AS col_1_ratio, 
COUNT(col_2)/COUNT(*) AS col_2_ratio FROM t1; 

可以编写通过将表名作为输入变量自动构建从 INFORMATION_SCHEMA数据库的查询功能。 下面是如何直接从INFORMATION_SCHEMA表能获得结构数据:

SET @query:=CONCAT("SELECT @column_list:=GROUP_CONCAT(col) FROM (
SELECT CONCAT('COUNT(',c.COLUMN_NAME,')/COUNT(*)') AS col 
FROM INFORMATION_SCHEMA.COLUMNS c 
WHERE NOT COLUMN_KEY IN('PRI') AND TABLE_SCHEMA=DATABASE() 
AND TABLE_NAME='t1' ORDER BY ORDINAL_POSITION) q"); 
PREPARE COLUMN_SELECT FROM @query; 
EXECUTE COLUMN_SELECT; 
SET @null_counters_sql := CONCAT('SELECT ',@column_list, ' FROM t1'); 
PREPARE NULL_COUNTERS FROM @null_counters_sql; 
EXECUTE NULL_COUNTERS; 
+0

这项工作看起来不错,尽管我无法在MySQL上使用它。我明天会着手解决这个问题。倒数第二行给出语法错误。 – Sherlock

+0

我检查了我的答案,请再检查一次。 – wisefish

+0

我一直在玩它,并且它连接了所有COUNT,但它不作为查询运行。我得到的输出是: 'COUNT(列)/ COUNT(*)'为每列。它并不实际执行。你知道在哪里看? – Sherlock

5

SQL Fiddle Demo Link

我创建了4桌。三个演示和一个nullcolumns是解决方案的必修部分。在三张表中,只有salarydept的列的所有值都为空(您可以查看其脚本)。

强制性表和程序,在结尾处给出

您可以复制粘贴和运行(必修部分或全部)作为SQL(只是你必须改变的分隔符//)在您所需的数据库在本地主机,然后--- call get();并查看结果

CREATE TABLE IF NOT EXISTS `dept` (
    `did` int(11) NOT NULL, 
    `dname` varchar(50) DEFAULT NULL, 
    PRIMARY KEY (`did`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1; 


INSERT INTO `dept` (`did`, `dname`) VALUES 
(1, NULL), 
(2, NULL), 
(3, NULL), 
(4, NULL), 
(5, NULL); 

CREATE TABLE IF NOT EXISTS `emp` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `ename` varchar(50) NOT NULL, 
    `did` int(11) NOT NULL, 
    PRIMARY KEY (`ename`), 
    KEY `deptid` (`did`), 
    KEY `id` (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ; 


INSERT INTO `emp` (`id`, `ename`, `did`) VALUES 
(1, 'e1', 4), 
(2, 'e2', 4), 
(3, 'e3', 2), 
(4, 'e4', 4), 
(5, 'e5', 3); 


CREATE TABLE IF NOT EXISTS `salary` (
    `EmpCode` varchar(50) NOT NULL, 
    `Amount` int(11) DEFAULT NULL, 
    `Date` int(11) DEFAULT NULL 
) ENGINE=InnoDB DEFAULT CHARSET=latin1; 

INSERT INTO `salary` (`EmpCode`, `Amount`, `Date`) VALUES 
('1', 344, NULL), 
('2', NULL, NULL); 

------------------------------------------------------------------------ 
------------------------------------------------------------------------ 

CREATE TABLE IF NOT EXISTS `nullcolumns` (
    `Table_Name` varchar(100) NOT NULL, 
    `Column_Name` varchar(100) NOT NULL 
) ENGINE=InnoDB DEFAULT CHARSET=latin1; 

--Only one procedure Now 
CREATE PROCEDURE get(dn varchar(100)) 
BEGIN 
declare c1 int; declare b1 int default 0; declare tn varchar(30); 
declare c2 int; declare b2 int; declare cn varchar(30); 

select count(*) into c1 from information_schema.tables where table_schema=dn; 
delete from nullcolumns; 
while b1<c1 do 
select table_name into tn from information_schema.tables where 
table_schema=dn limit b1,1;   

select count(*) into c2 from information_schema.columns where 
table_schema=dn and table_name=tn; 
set b2=0; 
while b2<c2 do 
select column_name into cn from information_schema.columns where 
table_schema=dn and table_name=tn limit b2,1; 

set @nor := 0; 
set @query := concat("select count(*) into @nor from ", dn,".",tn); 
prepare s1 from @query; 
execute s1;deallocate prepare s1; 

if @nor>0 then set @res := 0; 
set @query := concat("select ((select max(",cn,") from ", dn,".",tn,") 
is NULL) into @res"); 
prepare s1 from @query; 
execute s1;deallocate prepare s1; 

if @res=1 then 
insert into nullcolumns values(tn,cn); 
end if; end if; 

set b2=b2+1; 
end while; 

set b1=b1+1; 
end while; 
select * from nullcolumns; 
END; 

您可以轻松地在phpmyadin轻松执行存储过程作为SQL“因为它是”只是改变了分隔符(在SQL quesry框的底部)到//然后

call get(); 

而且享受 :)

你可以看到现在的表nullcolumns示意具有表名

在程序代码if @nor>0沿100/100空值的列限制,没有空表应包括在结果中,您可以删除该限制。

+0

如果您在存储过程或其他任何问题上遇到任何困难,我将很乐意进一步指导 – Sami

+0

哇,这真是一个干净的SQL!我正在努力让它在MySQL上工作(叹息......)(限制B,1不起作用),但这是一个很好的工作! – Sherlock

+0

我编辑了一个程序而不是三个程序。一个程序也允许我在sqlfiddle @ Robinv.G上传演示。 – Sami

-2
select column_name 
from user_tab_columns 
where table_name='Table_name' and num_nulls>=1; 

只是通过简单的查询,你会得到这两列。

相关问题