2012-08-23 64 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; 

只是通過簡單的查詢,你會得到這兩列。

相關問題