如何刪除數據庫中的冗餘數據(翻譯)

做爲數據庫的開發者,咱們常常面臨着要找出及刪除數據庫中冗餘數據的任務,若是數據庫中有大量的冗餘數據(佔總數的百分比太多),數據的精確性和可靠性將受到影響,同時也影響着數據庫的性能,那麼如何解決這個問題呢?下面我將探討關於這個問題的這個解決方案,oracle也爲咱們提供了一個解決方案,可是Oracle提供的解決方案不夠完美,遇到大批量數據那個解決方案工做起來很慢
應該怎麼刪除冗餘數據呢?
在這裏咱們應用一個PL/SQl方案(一個自定義的存儲過程)或者一個SQL語句的解決方案(使用一個分析的函數RANK()和一個嵌套的子查詢)來消除冗餘數據而後控制應該保留的記錄

什麼是冗餘數據?
冗餘數據就是一個數據表中,這個表中的行包含了一些相同的值,這些值理論上來講應該是惟一的(這些值通常來講能肯定一條記錄)例如,像社會保險號,姓與名的集合.那麼咱們把這麼含有相同信息的行中包含的數據叫作冗餘數據,如今全部的數據庫表中都有主鍵約束,主鍵中記錄了一行記錄中的惟一值,從數據庫的角度來看,每一行都是惟一的,可是從咱們用戶角度看來,這些記錄都是相同的記錄,由於它們都包含相同的鍵值(First Name + Last Name),即便他們有不一樣的主鍵
ID   Last Name       First Name City            Phone
---- --------------- ---------- --------------- ----------
1005 Krieger         Jeff       San Ramon       9252997100
1012 Krieger         Jeff       San Ramon       9252997100
1017 Krieger         Jeff       San Ramon       9252997100
那麼這些冗餘數據是怎麼出現的那?一般有兩種狀況:1.從不一樣的表中加載或者合併數據
經過圖形化的用戶接口來輸入數據,而後由計算機來生成一個惟一的鍵,並作爲這一條記錄的主鍵
那麼怎樣找到冗餘數據呢?讓咱們來建立一個叫做Customer 的表並向其中加入冗餘數據,看錶1,正如你所看到的,咱們並無在這個表上作什麼限制來防止冗餘數據,下面這麼代碼建立了一個惟一約束,來防止冗餘數據的生成
SQL
Listing 1. 建立Customer表
這個表中咱們故意加入了冗餘數據
DROP TABLE Customers CASCADE CONSTRAINTS;
CREATE TABLE Customers(
   Id INTEGER NOT NULL,
   LastName VARCHAR2(15) NOT NULL,
   FirstName VARCHAR2(10),
   Address VARCHAR2(20),
   City VARCHAR2(15),
   State CHAR(2),
   Zip VARCHAR2(10),
   Phone VARCHAR2(10)
   CONSTRAINT Customers_PK
   PRIMARY KEY (ID))
   TABLESPACE TALLYDATA;
COMMIT;
 看下面的代碼我在姓,和名這兩個字段上加上惟一約束,(固然你能夠在建立表的時候加上
這一約束,來防止冗餘數據)
ALTER TABLE Customers
   ADD CONSTRAINT Customers_LastFirst
   UNIQUE (LastName, FirstName);
Customer表中的冗餘鍵是LastName和FirstName的集合,咱們把含
有冗餘鍵的數據進行分組並進行統計.
SELECT LastName, FirstName, COUNT(*)   FROM Customers
   GROUP BY LastName, FirstName
   ORDER BY LastName, FirstName;
Listing 2顯示了這條語句的輸出,咱們能夠看到有三行的輸出大於1,這也就意味
着表中含有3組冗餘數據.
Listing 2. 找出冗餘
LASTNAME        FIRSTNAME    COUNT(*)
--------------- ---------- ----------
Blake           Becky               1
Blue            Don                 1
Bradley         Tom                 1
Chang           Jim                 1
Griffith        David               1
Hill            Larry               1
King            Chuck               1
Krieger         Jeff                3
Loney           Julie               1
Lord            Don                 1
Mason           Paul                1
Monroe          John                1
Simon           Michael             2
Stone           Tony                5
14 rows selected.
咱們在語句中加入Having()語句來過濾出非冗餘數據.
SELECT LastName, FirstName, COUNT(*)
   FROM Customers
   GROUP BY LastName, FirstName
   HAVING COUNT(*) > 1;
SQL
Listing 3. 過濾冗餘
加入Having()語句來過濾出非冗餘數據.
LASTNAME        FIRSTNAME    COUNT(*)
--------------- ---------- ----------
Krieger         Jeff                3
Simon           Michael             2
Stone           Tony                5
3 rows selected.
Listing 3顯示了以上代碼的輸入,儘管如此,這些查詢結果並無顯示出能標識每
一行的字段,咱們將上一語句作爲一個嵌套查詢來顯示標識這些記錄的ID
SELECT ID, LastName, FirstName
   FROM Customers
   WHERE (LastName, FirstName) IN
   (SELECT LastName, FirstName
       FROM Customers
       GROUP BY LastName, FirstName
       HAVING COUNT(*) > 1)
   ORDER BY LastName, FirstName;
Listing 4顯示出了以上代碼的結果,這些查詢顯示了有三組冗餘,共有十行,咱們
應該保留這些組中的1005,1009,1001這些記錄而後刪除1012,1017,1010,1011,1016,
1019,1014這些冗餘的條目.
SQL
Listing 4. 找出惟一的鍵
語句的輸出
ID LASTNAME        FIRSTNAME
----- --------------- ----------
 1005 Krieger         Jeff
 1012 Krieger         Jeff
 1017 Krieger         Jeff
 1009 Simon           Michael
 1010 Simon           Michael
 1001 Stone           Tony
 1011 Stone           Tony
 1016 Stone           Tony
 1019 Stone           Tony
 1014 Stone           Tony
10 rows selected.
Oracle公司給出的一個解決方案
Oracle 公司給咱們提供一個見刪除冗餘數據的一個方案,這個方案使用了Oracl
e公司本身的一個集合函數MIN()或者MAX()來解決這一問題MIN()函數能夠得
到每一組中(冗餘的非冗餘的),應保留的全部值.(正如咱們所見,輸入出不包含那些大I
D的冗餘值
SELECT MIN(ID) AS ID, LastName, FirstName
   FROM Customers
   GROUP BY LastName, FirstName;
這一條命令的輸出
Listing 5. Output of MIN() query
這一條命令顯示了全部的非冗餘的數據,其它的行則應該被刪除
ID LASTNAME        FIRSTNAME
----- --------------- ----------
 1018 Blake           Becky
 1013 Blue            Don
 1000 Bradley         Tom
 1002 Chang           Jim
 1008 Griffith        David
 1020 Hill            Larry
 1004 King            Chuck
 1005 Krieger         Jeff
 1003 Loney           Julie
 1007 Lord            Don
 1015 Mason           Paul
 1006 Monroe          John
 1009 Simon           Michael
 1001 Stone           Tony
14 rows selected.

這樣你就能夠刪除那些不在這個表中的全部的行,一樣將上一條語句做爲一個子查詢,構造一
個語句
DELETE FROM Customers
   WHERE ID NOT IN
   (SELECT MIN(ID)
       FROM Customers
    GROUP BY LastName, FirstName);
儘管如此,理論是可行的,可是這個方案並非那麼有效,由於這樣一來,DBMS要完成兩
個表的掃描來完成這項任務,對於大量的數據來講,這簡直是不可行的,爲了測試他的性能,
我建立了Customer表,大約有5000,000行,45,000冗餘行,(9%)以上這個命
令運行了一個小時,沒有輸出結果,它耗盡了個人耐心,因此我殺死了這個進程
這個方案的令外這個方案還有一個缺點,你不能控制每個組中你要保留的行

一種PL/SQl解決方案:使用存儲過程刪除冗餘數據,叫作DeleDuplicate
的存儲過程,這個過程的結構很清晰的.
SQL
Listing 6. The DeleteDuplicate stored procedure
它將這些冗餘行選擇一到一個遊標中,而後從表中取出每個冗餘行來進行與遊標中的行進行
比對,而後決定是否刪除
CREATE OR REPLACE PROCEDURE DeleteDuplicates(
   pCommitBatchSize IN INTEGER := 5000) IS
CURSOR csr_Duplicates IS
SELECT ID, LastName, FirstName
   FROM Customers
   WHERE (LastName, FirstName) IN
   (SELECT LastName, FirstName
       FROM Customers
       GROUP BY LastName, FirstName
       HAVING COUNT(*) > 1)
   ORDER BY LastName, FirstName;
/*保存上一次的姓和名*/
vLastName Customers.LastName%TYPE := NULL;
vFirstName Customers.FirstName%TYPE := NULL;
vCounter INTEGER := 0;
BEGIN
   FOR vDuplicates IN csr_Duplicates
   LOOP
      IF vLastName IS NULL OR
      (vDuplicates.LastName != vLastName
       OR NVL(vDuplicates.FirstName, ' ') != NVL(vFirstName, ' '))
 THEN
 /*第一次取出行或者是一個新行
  保存它的姓和名的值*/
    vLastName := vDuplicates.LastName;
    vFirstName := vDuplicates.FirstName;
 ELSE
       /*冗餘數據,刪除它*/
    DELETE
            FROM Customers
       WHERE ID = vDuplicates.ID;
    vCounter := vCounter + 1;
/*提交結果*/
    /* Commit every pCommitBatchSize rows */
    IF MOD(vCounter, pCommitBatchSize) = 0
    THEN
       COMMIT;
    END IF;
      END IF;
   END LOOP;
   IF vCounter > 0
   THEN
      COMMIT;
   END IF;

   DBMS_OUTPUT.PUT_LINE(TO_CHAR(vCounter)
                        ' duplicates have been deleted.');
   EXCEPTION
    WHEN OTHERS
   THEN
      DBMS_OUTPUT.PUT_LINE('Error '
                                TO_CHAR(SQLCODE) ': ' SQLERRM);
         ROLLBACK;
END DeleteDuplicates;
 
它將冗餘數據選擇到一個遊標中,並根據(LastName,FirstName)來分組
(在咱們這個方案中),而後打開遊標而後循環地取出每一行,而後用與先前的取出的鍵值進
行比較,若是這是第一次取出這個值,或者這個值不是冗餘鍵,那麼跳過這個記錄而後取下一
個,否則的話,這就是這個組中的冗餘記錄,因此刪掉它.
讓咱們運行一下這個存儲過程
BEGIN
   DeleteDuplicates;
END;
/
SELECT LastName, FirstName, COUNT(*)
   FROM Customers
   GROUP BY LastName, FirstName
   HAVING COUNT(*) > 1;
最後一個查詢語句沒有返回值,因此冗餘數據沒有了從表中取冗餘數據的過程徹底是由定義在
csr_Duplicates 這個遊標中的SQL語句來實現的,PL/SQl只是用來實現刪除冗餘數
,那麼能不能徹底用SQL語句來實現呢?
二.SQL解決方案,使用RANK()刪除冗餘數據
Oracle8i分析函數RANK()來枚舉每個組中的元素,在咱們的方案中, 咱們應
用這個方案,咱們使用這個函數動態的把冗餘數據連續的排列起來加上編號,組由Parti
ntion by 這個語句來分開,而後用Order by 進行分組
SELECT ID, LastName, FirstName,
   RANK() OVER (PARTITION BY LastName,
      FirstName ORDER BY ID) SeqNumber
   FROM Customers
   ORDER BY LastName, FirstName;
SQL
Listing 7. Output of single SQL statement that uses RANK()
顯示的是根據記錄的條數的個數來顯示尤爲對於冗餘數據
ID LASTNAME        FIRSTNAME   SEQNUMBER
----- --------------- ---------- ----------
 1018 Blake           Becky               1
 1013 Blue            Don                 1
 1000 Bradley         Tom                 1
 1002 Chang           Jim                 1
 1008 Griffith        David               1
 1020 Hill            Larry               1
 1004 King            Chuck               1
 1005 Krieger         Jeff                1
 1012 Krieger         Jeff                2
 1017 Krieger         Jeff                3
 1003 Loney           Julie               1
 1007 Lord            Don                 1
 1015 Mason           Paul                1
 1006 Monroe          John                1
 1009 Simon           Michael             1
1010 Simon           Michael             2
 1001 Stone           Tony                1
 1011 Stone           Tony                2
 1014 Stone           Tony                3
 1016 Stone           Tony                4
 1019 Stone           Tony                5
咱們能夠看一到,SeqNumber這一列中的數值,冗餘數據是根據ID號由小到大進行
的排序,全部的冗餘數據的SqlNumber都大於一,全部的非冗餘數據都等於一,因此
咱們取本身所需,刪除那麼沒用的
SELECT ID, LastName, FirstName
   FROM
   (SELECT ID, LastName, FirstName,
      RANK() OVER (PARTITION BY LastName,
         FirstName ORDER BY ID) AS SeqNumber
      FROM Customers)
   WHERE SeqNumber > 1;

SQL
Listing 8. 冗餘鍵的鍵值
有七行必須被刪除
ID LASTNAME        FIRSTNAME
----- --------------- ----------
 1012 Krieger         Jeff
 1017 Krieger         Jeff
 1010 Simon           Michael
 1011 Stone           Tony
 1014 Stone           Tony
 1016 Stone           Tony
 1019 Stone           Tony
7 rows selected.
這顯示有七行須要刪除,仍是用上一個表我測試了一下這個代碼,它用了77秒種就刪除了所
有的數據準備好了用Sql語句來刪除冗餘數據,版本一它執行了135秒
DELETE
  FROM CUSTOMERS
  WHERE ID IN
   (SELECT ID
      FROM
      (SELECT ID, LastName, FirstName,
         RANK() OVER (PARTITION BY LastName,
            FirstName ORDER BY ID) AS SeqNumber
         FROM Customers)
      WHERE SeqNumber > 1);
咱們能夠看到最後的兩行語句對錶中的數據進行了排序,這不是有效的,因此咱們來優化一下
最後一個查詢語句,把Rank()函數應用到只含有冗餘數據的組,而不是全部的列
下面這個語句是比較有效率的,雖然它不像上一個查詢那樣精簡
SELECT ID, LastName, FirstName
   FROM
   (SELECT ID, LastName, FirstName,
      RANK() OVER (PARTITION BY LastName,
         FirstName ORDER BY ID) AS SeqNumber
      FROM
     (SELECT ID, LastName, FirstName
         FROM Customers
         WHERE (LastName, FirstName) IN
         (SELECT LastName, FirstName
            FROM Customers
            GROUP BY LastName, FirstName
            HAVING COUNT(*) > 1)))
     WHERE SeqNumber > 1;
選擇冗餘數據只用了26秒鐘,這樣就提升了67%的性能,這樣就提升
了將這個做爲子查詢的刪除查詢的效率,
DELETE
  FROM Customers
  WHERE ID IN
  (SELECT ID
      FROM
      (SELECT ID, LastName, FirstName,
         RANK() OVER (PARTITION BY LastName,
            FirstName ORDER BY ID) AS SeqNumber
         FROM
        (SELECT ID, LastName, FirstName
            FROM Customers
            WHERE (LastName, FirstName) IN
            (SELECT LastName, FirstName
               FROM Customers
               GROUP BY LastName, FirstName
               HAVING COUNT(*) > 1)))
        WHERE SeqNumber > 1);
如今只用了47秒鐘的就完成的上面的任務,比起上一個136秒,這是一個很大的進步,相比之
下,存儲過程用了56秒,這樣存儲過程有些慢了使用PL/SQL語句咱們和咱們以上的代碼
,會獲得更好的更精確的代碼,和提升你代碼的執行效率,雖然對於從數據庫中枚舉數據PL
/SQL對於Sql二者沒有什麼差異,可是對於數據的比較上,PL/SQL就比SQL要
快不少,可是若是冗餘數據量比較小的話,咱們儘可能使用SQL而不使用PL/SQL
若是你的數據表沒有主鍵的話,那麼你能夠參考其它技術
Rank()其它的方法
使用Rank()函數你能夠對選擇你所保留的數據,(或者是小ID的或者是大ID 的,
就由RECDate這個列來決定這種狀況下,你能夠把REcdate加入到(Order
 by )子句中,倒序或者正序

這是一種保留最大Id的一種解決方案
DELETE
  FROM Customers
  WHERE ID IN
  (SELECT ID
      FROM
      (SELECT ID, LastName, FirstName,
         RANK() OVER (PARTITION BY LastName,
            FirstName ORDER BY RecDate DESC, ID) AS SeqNumber
         FROM
        (SELECT ID, LastName, FirstName, RecDate
           FROM Customers
            WHERE (LastName, FirstName) IN
            (SELECT LastName, FirstName
               FROM Customers
               GROUP BY LastName, FirstName
               HAVING COUNT(*) > 1)))
        WHERE SeqNumber > 1);
這種技術保證了你能夠控制每個表中的保留的組,假設你有一個數據庫,有一個促銷或者有
一個折扣信息,好比一個團體可使用這種促銷5次,或者我的可使用這個折扣三次,爲了
指出要保留的組的個數,你能夠在where 和having子句中進行設置,那麼你將刪除全部大於你
設置有數的冗餘組
DELETE
  FROM Customers
  WHERE ID IN
  (SELECT ID
      FROM
      (SELECT ID, LastName, FirstName,
         RANK() OVER (PARTITION BY LastName,
            FirstName ORDER BY ID) AS SeqNumber
         FROM
        (SELECT ID, LastName, FirstName
            FROM Customers
            WHERE (LastName, FirstName) IN
            (SELECT LastName, FirstName
               FROM Customers
               GROUP BY LastName, FirstName
               HAVING COUNT(*) > 3)))
        WHERE SeqNumber > 3);
As you can see, using the RANK() function allows you to eliminate duplicates in a
single SQL statement and gives you more capabilities by extending the power of
your
queries.
正如你所見使用Rank()能夠消除冗餘數據並且能給你很大的可伸展性
[http://www.abcdown.net/InfoView/Article_47389.html]
相關文章
相關標籤/搜索